void __declspec(naked) MyNakedFunction()
{
strcmp(...);
// __cdecl 函数是调用者清除参数堆栈,对于非内联汇编调用这类函数,编译器将自动平衡堆栈,加入 ADD ESP, 8
}
void __declspec(naked) MyNakedFunction()
{
//...
__asm CALL strcmp;
__asm ADD ESP, 8; // 内联汇编需要自己平衡堆栈
}
__declspec(naked) 是且仅是不产生 prolog 和 epilog 代码 {保存并恢复使用过的寄存器和分配局部变量、平衡堆栈、返回值。
Microsoft Specific
All arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures. Parameters are pushed onto the stack from right to left. Structures that are not PODs will not be returned in registers.
The compiler generates prolog and epilog code to save and restore the ESI, EDI, EBX, and EBP registers, if they are used in the function.
Note
When a struct, union, or class is returned from a function by value, all definitions of the type need to be the same, else the program may fail at runtime.
For information on how to define your own function prolog and epilog code, see Naked Function Calls.
The following calling conventions are supported by the Visual C/C++ compiler.
Keyword Stack cleanup Parameter passing
__cdecl Caller Pushes parameters on the stack, in reverse order (right to left)
__clrcall n/a Load parameters onto CLR expression stack in order (left to right).
__stdcall Callee Pushes parameters on the stack, in reverse order (right to left)
__fastcall Callee Stored in registers, then pushed on stack
__thiscall Callee Pushed on stack; this pointer stored in ECX
微软细节
所有参数在传递时都被扩展为 32 位。除了 8 字节结构体返回在 EDX:EAX 寄存器一对中,其它返回值同样被扩展为 32 位并返回在 EAX 寄存器中。大的结构体返回在 EAX 寄存器中的是指针来间接返回结构体。参数是自右向左压入堆栈。结构体是非 POD(Plain Old Data)类型将不会返回在寄存器中。
如果 ESI、EDI、EBX 和 EBP 寄存器在函数中使用,编译器将产生 prolog 和 epilog 代码保存并恢复它们。
注释
当一个函数用值返回结构体(struct)、联合体(union)或者类(class)时,所有类型的定义必须相同,否则程序可能在运行过程中失败。
关于如何定义你自身函数 prolog 和 epilog 代码的更多信息,请查阅 Naked Function Calls。
Visual C/C++ 编译器支持以下调用转换:
关键字 堆栈清除 参数传递
__cdecl 调用者 将参数倒序压入堆栈(自右向左)
__clrcall 不适用 将参数顺序加载到 CLR expression 堆栈中(自左向右)
__stdcall 被调用者 将参数倒序压入堆栈(自右向左)
__fastcall 被调用者 保存在寄存器然后压入堆栈
__thiscall 被调用者 压入堆栈,指针保存在 ECX 寄存器中
※ __fastcall:两个以内不大于 4 字节(DWORD)的参数将自左向右分别保存在 ECX、EDX 寄存器内,由于没有将参数压入堆栈,函数返回前无需将参数出栈(这就是它FASTCALL由来);其它参数仍按自右向左压入堆栈,函数返回时需要把这些参数出栈。
///
正常的情况下,我们写一个 C/C++ 函数,即使是一个空函数,编译器也为我们做了不少的工作,生成了一些“必要”的代码。请看下面的函数 (为了说明问题随便写的):
int Test()
{
int iReturn;
char szTemp[33];
szTemp[0] = 'A';
szTemp[1] = '\0';
iReturn = MessageBox(NULL, szTemp, szTemp, MB_OK);
MessageBeep(iReturn);
return iReturn;
}
00401000 sub esp,24h // 增加堆栈空间存放局部变量 (24H = 36D,4 字节对齐,注意这里没有为 iReturn 分配空间)
00401003 push esi // 保存要使用的重要寄存器
00401004 lea eax,[esp+4] // 下面是传递 MessageBox() 要使用的参数
00401008 push 0
0040100A lea ecx,[esp+8] // 编译器愚蠢,根本不用 ECX,两个都是 szTemp,两次 PUSH EAX 不得了
0040100E push eax
0040100F push ecx
00401010 push 0
00401012 mov byte ptr [esp+14h],41h
00401017 mov byte ptr [esp+15h],0
0040101C call dword ptr ds:[40509Ch] // 调用 MessageBox()
00401022 mov esi,eax // 保存返回值到变量 iReturn 。靠!变量 iReturn 自动使用 ESI,编译器太聪明了:)
00401024 push esi
00401025 call dword ptr ds:[4050A0h] // 调用 MessageBeep()
0040102B mov eax,esi // 把变量 iReturn 交给 EAX 作为返回值
0040102D pop esi // 恢复要使用的重要寄存器
0040102E add esp,24h // 减少堆栈空间
00401031 ret // 堆栈长度减 4 并返回
__declspec(naked) int Test()
{
__asm
{
SUB ESP,24H
PUSH ESI
LEA EAX,[ESP+4]
PUSH 0
PUSH EAX
PUSH EAX
PUSH 0
MOV BYTE PTR [ESP+14H],41H
MOV BYTE PTR [ESP+15H],0
CALL DWORD PTR [MessageBoxA]
MOV ESI,EAX
PUSH ESI
CALL DWORD PTR [MessageBeep]
MOV EAX,ESI
POP ESI
ADD ESP,24H
RET
}
}