declspec(naked) 编写干净函数

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;
  }


   下面是用 VC6 在 Release 方式下编译后的的反汇编代码:
  
    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 并返回


   这段代码虽然很精干 (都能自动使用寄存器来保存变量了),但是有的时候我们并不需要编译器提供这些自作主张的代码 (比如写驱动程序的时候,不过我还没遇到过这种情况,呵呵~~),我们希望整个全部函数都是自己亲手写进去的 (BT 呀^o^)。好,请出今天的主角 —— “naked”(怎么是裸体呀?),欢迎!Visual C++ 的扩展关键字 naked 允许我们完全定制一个函数,废话不说了,看例子 (熬夜写的 Zzzzzz~~):
  
     __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
   }
  }


   上面的代码是使用的 VC 的内联汇编,和 VC 编译后生成的代码完全是一样的 (很有完全控制的成就感吧^_^)。上面我们并没有又节省什么 (节省的 PUSH ECX 并不是 naked 的功劳),但是有的时候确实需要的 (举不出例子来了,倒!)。最后随便说说注意事项: 
   
   1.使用 naked 关键字需要自己构建 EBP 参数指针 (如果用到了 EBP 作为参数指针的话);
   2.必须自己使用 RET 或 RET n 指令返回 (除非你不返回)。

你可能感兴趣的:(c/c++)