#navi_header|C言語系| "__declspec(naked)"を関数に指定すると、コンパイラはその関数のprolog, epilogを省略する。これが ''Naked Function'' あるいは ''Naked Function Call'' と呼ばれる呼び出し規約である。 "naked"属性は関数の「型」とは異なり、関数本体にのみ作用する。関数プロトタイプや変数に"__declspec(naked)"を指定するとコンパイルエラーになる。"naked"属性はx86アーキテクチャでのみ有効で、x86_64(x64)やItaniumアーキテクチャでは無効。 "naked"属性を指定しない場合の典型的なprolog/epilogとは以下のような機械語のことである。 prolog: #pre||> push ebp ; Save ebp mov ebp, esp ; Set stack frame pointer sub esp, localbytes ; Allocate space for locals push ; Save registers ||< EBPをスタックに保存した後、ESPに合わせる(スタックフレームの生成)。ローカル変数用の領域をESPをずらすことで確保し、必要であればレジスタをスタックに保存する。これが、コンパイラが自動的に生成して関数本体の前に追加するprologコードになる。 epilog: #pre||> pop ; 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++ のリファレンス以下) - Naked Functions -- http://msdn.microsoft.com/ja-jp/library/21d5kd3a.aspx --- "C/C++ Language" -> "C Language Reference" -> "Functions (C)" -> "C Function Definitions" -> "Naked Functions" - Naked Function Calls -- http://msdn.microsoft.com/ja-jp/library/5ekezyy2.aspx --- "C/C++ Language" -> "C++ Language Reference" -> "Microsoft-Specific Modifiers" -> "Calling Conventions" -> "Naked Function Calls" #more|| #outline|| ---- * VC++2008 Express Edtion "naked"属性はVisual C++で対応されている。 ** アセンブラコードの確認 "naked"属性を指定した関数とそうでない通常の関数とで、prolog/epilogの違いを実際に確認してみる。 naked_unnaked.c: #code|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)を確認してみる: #pre||> 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"属性を指定した関数のアセンブラコードを確認してみる: #pre||> 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: #code|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: #pre||> 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を手動実装してみる。また、値を返せるようにしてみる。 まず通常の関数のアセンブラコードを確認する。 #pre||> int normal(int i, int j) { i = i + j; return i; } ||< → #pre||> ; 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"属性の関数に組み込む。 #pre||> __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 } } ||< → #pre||> 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()"関数を呼び出してみる。 #code|c|> #include 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のそれと同様で、エピローグ・プロローグを省くらしい。 - Function Attributes - Using the GNU Compiler Collection (GCC) -- http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html -- http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Function-Attributes.html #navi_footer|C言語系|