home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/呼び出し規約/x86/naked

C言語系/呼び出し規約/x86/naked

C言語系 / 呼び出し規約 / x86 / naked
id: 622 所有者: msakamoto-sf    作成日: 2010-03-17 12:27:16
カテゴリ: Assembler C言語 Windows 

"__declspec(naked)"を関数に指定すると、コンパイラはその関数のprolog, epilogを省略する。これがNaked FunctionあるいはNaked Function Callと呼ばれる呼び出し規約である。

"naked"属性は関数の「型」とは異なり、関数本体にのみ作用する。関数プロトタイプや変数に"__declspec(naked)"を指定するとコンパイルエラーになる。"naked"属性はx86アーキテクチャでのみ有効で、x86_64(x64)やItaniumアーキテクチャでは無効。

"naked"属性を指定しない場合の典型的なprolog/epilogとは以下のような機械語のことである。
prolog:

push        ebp                ; Save ebp
mov         ebp, esp           ; Set stack frame pointer
sub         esp, localbytes    ; Allocate space for locals
push        <registers>        ; Save registers

EBPをスタックに保存した後、ESPに合わせる(スタックフレームの生成)。ローカル変数用の領域をESPをずらすことで確保し、必要であればレジスタをスタックに保存する。これが、コンパイラが自動的に生成して関数本体の前に追加するprologコードになる。

epilog:

pop         <registers>   ; Restore registers
mov         esp, ebp      ; Restore stack pointer
pop         ebp           ; Restore ebp
ret                       ; Return from function

必要であればレジスタをスタックから復元し、ESPとEBPを復元し(スタックフレームの破棄)、RET命令を呼ぶ。これが、コンパイラが自動的に生成して関数本体の後ろに追加するepilogコードになる。

"naked"属性を指定された関数では、これらprolog/epilogコードが追加されない。代わりに、インラインアセンブラ(inline assembler)を用いてprolog/epilog相当のコードを手動で実装する。仮想デバイスドライバや割り込みハンドラなど、CPUに近い処理を実装する場合に活用できる。

"naked"属性の関数内ではスタックフレームの生成が開発者に委ねられるため、コンパイラの生成するスタックフレームに依存する機能が使えなくなる。

  • returnステートメントが使えなくなる。
  • _alloca 関数, setjmp 関数が使えなくなる。
  • スタックフレームをunwindする構造化例外処理およびC++の例外処理が使えなくなる。
  • フレームポインタ最適化オプション("/Oy")は推奨されなくなる(開発者自身の手動調整が前提になるため)。
  • C++のオブジェクト生成でいくつか制限が発生する(下記MSDN参照)。
  • ローカル変数については"__LOCAL_SIZE"シンボルをprologのインラインアセンブラで使う(下記MSDN参照)。

参考MSDN:(Visual C++ のリファレンス以下)


VC++2008 Express Edtion

"naked"属性はVisual C++で対応されている。

アセンブラコードの確認

"naked"属性を指定した関数とそうでない通常の関数とで、prolog/epilogの違いを実際に確認してみる。

naked_unnaked.c:

void normal(int i, int j) {
    i = i + j;
}
 
__declspec(naked) void naked(int i, int j) {
    i = i + j;
}

コンパイル&アセンブラコード生成:

> cl /FAs /c naked_unnaked.c

生成されたアセンブラコード(naked_unnaked.asm)で、まず通常の関数(normal)を確認してみる:

PUBLIC	_normal
; Function compile flags: /Odtp
_TEXT	SEGMENT
_i$ = 8							; size = 4
_j$ = 12						; size = 4
_normal	PROC

## ここがprologコード
; 1    : void normal(int i, int j) {

	push	ebp
	mov	ebp, esp

## ここから関数本体
; 2    : 	i = i + j;

	mov	eax, DWORD PTR _i$[ebp]
	add	eax, DWORD PTR _j$[ebp]
	mov	DWORD PTR _i$[ebp], eax

## ここがepilogコード
; 3    : }

	pop	ebp
	ret	0
_normal	ENDP
_TEXT	ENDS

レジスタ保存無し、ローカル変数も使っていないのでESPの調整も無いシンプルなprolog/epilogが生成されていることが確認できた。

続いて、"naked"属性を指定した関数のアセンブラコードを確認してみる:

PUBLIC	_naked
; Function compile flags: /Odtp
_TEXT	SEGMENT
_i$ = 8							; size = 4
_j$ = 12						; size = 4
_naked	PROC

; 6    : 	i = i + j;

	mov	eax, DWORD PTR _i$[ebp]
	add	eax, DWORD PTR _j$[ebp]
	mov	DWORD PTR _i$[ebp], eax
_naked	ENDP
_TEXT	ENDS
END

prolog/epilogに相当するアセンブラコードが一切生成されていないことが確認できた。

なお、試しにこのnaked()関数内で"return"を使ってみたところ、次のようなコンパイルエラーが発生した:

error C2490: 'naked' 属性の関数内で 'return' は許されません。

"naked"属性の関数を呼ぶ側はどうなるか

前掲の"normal()", "naked()"関数を呼ぶ側がどうなるか確認してみる。

naked_unnaked_main.c:

void normal(int i, int j);
void naked(int i, int j);
 
int main(int argc, char *argv[]) {
    normal(1, 2);
    naked(3, 4);
    return 0;
}

コンパイル:

cl /FAs /c naked_unnaked_main.c

naked_unnaked_main.asm:

PUBLIC	_main
EXTRN	_naked:PROC
EXTRN	_normal:PROC
; Function compile flags: /Odtp
; File c:\in_vitro\c\cc_naked\naked_unnaked_main.c
_TEXT	SEGMENT
_argc$ = 8						; size = 4
_argv$ = 12						; size = 4
_main	PROC

; 4    : int main(int argc, char *argv[]) {

	push	ebp
	mov	ebp, esp

; 5    : 	normal(1, 2);

	push	2
	push	1
	call	_normal
	add	esp, 8

; 6    : 	naked(3, 4);

	push	4
	push	3
	call	_naked
	add	esp, 8

; 7    : 	return 0;

	xor	eax, eax

; 8    : }

	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS

"naked"属性を指定していないので、当然、通常の関数と同じ呼び出し方法になっている。

この状態でリンクしてみると、"runtime error"が発生する。

> cl naked_unnaked_main.obj naked_unnaked.obj
> naked_unnaked_main.exe

runtime error

"naked"属性が関数本体に作用し、関数の「型」ではないためプロトタイプ宣言では使えないことを確認してみる:

__declspec(naked) void naked(int i, int j);

→次のコンパイルエラーが発生する:

error C2488: 'naked' : 'naked' はメンバではない関数定義にのみ適用されます。

インラインアセンブラでprolog/epilogを手動実装してみる

インラインアセンブラでprolog/epilogを手動実装してみる。また、値を返せるようにしてみる。

まず通常の関数のアセンブラコードを確認する。

int normal(int i, int j) {
	i = i + j;
	return i;
}


; 1    : int normal(int i, int j) {

	push	ebp
	mov	ebp, esp

; 2    : 	i = i + j;

	mov	eax, DWORD PTR _i$[ebp]
	add	eax, DWORD PTR _j$[ebp]
	mov	DWORD PTR _i$[ebp], eax

; 3    : 	return i;

	mov	eax, DWORD PTR _i$[ebp]

; 4    : }

	pop	ebp
	ret	0

このprolog/epilogをそっくり"naked"属性の関数に組み込む。

__declspec(naked) int naked(int i, int j) {
	/* prolog */
	__asm {
		push  ebp
		mov   ebp, esp
	}

	i = i + j;

	/* epilog */
	__asm {
		mov   eax, i
		pop   ebp
		ret   0
	}
}


PUBLIC	_naked
; Function compile flags: /Odtp
_TEXT	SEGMENT
_i$ = 8							; size = 4
_j$ = 12						; size = 4
_naked	PROC

; 6    : 	/* prolog */
; 7    : 	__asm {
; 8    : 		push  ebp

	push	ebp

; 9    : 		mov   ebp, esp

	mov	ebp, esp

; 10   : 	}
; 11   : 
; 12   : 	i = i + j;

	mov	eax, DWORD PTR _i$[ebp]
	add	eax, DWORD PTR _j$[ebp]
	mov	DWORD PTR _i$[ebp], eax

; 13   : 
; 14   : 	/* epilog */
; 15   : 	__asm {
; 16   : 		mov   eax, i

	mov	eax, DWORD PTR _i$[ebp]

; 17   : 		pop   ebp

	pop	ebp

; 18   : 		ret   0

	ret	0
_naked	ENDP
_TEXT	ENDS

"normal()", "naked()"関数を呼び出してみる。

#include <stdio.h>
 
int normal(int i, int j);
int naked(int i, int j);
 
int main(int argc, char *argv[]) {
    printf("normal() = %d\n", normal(1, 2));
    printf("naked() = %d\n", naked(3, 4));
    return 0;
}

コンパイル&リンク&実行:

> cl /c naked_unnaked_main2.c
> cl naked_unnaked_main2.obj naked_unnaked2.obj
> naked_unnaked_main2.exe
normal() = 3
naked() = 7

GCC

x86ではないが、ARM, AVRなどのCPU用に "naked" という関数属性がサポートされている。機能としてはVC++2008のそれと同様で、エピローグ・プロローグを省くらしい。



プレーンテキスト形式でダウンロード
現在のバージョン : 3
更新者: msakamoto-sf
更新日: 2010-03-25 10:36:23
md5:f4facb9b199b11db44113cc2b0c883f1
sha1:16dedad7b0f7a3694e0b11ba5f4cb32da14bea58
コメント
コメントを投稿するにはログインして下さい。