"__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"属性の関数内ではスタックフレームの生成が開発者に委ねられるため、コンパイラの生成するスタックフレームに依存する機能が使えなくなる。
参考MSDN:(Visual C++ のリファレンス以下)
"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' は許されません。
前掲の"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を手動実装してみる。また、値を返せるようにしてみる。
まず通常の関数のアセンブラコードを確認する。
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
x86ではないが、ARM, AVRなどのCPU用に "naked" という関数属性がサポートされている。機能としてはVC++2008のそれと同様で、エピローグ・プロローグを省くらしい。