Visual C++中的几种函数调用方式
(本文中所有汇编代码均采用Intel语法,即dest在左边)
C++中的函数被编译成汇编代码的时候,必须遵循一定的规范,如参数怎么传递,栈指针怎么增减。Visual C++中,一共有5种情况:
默认情况下,是__cdecl。__cdecl 和__stdcall的区别是:__cdecl是调用者清理栈,而__stdcall是被调用者清理栈。所以,理论来说,__cdecl生成的代码体积会更大。但是,对于varargs函数,由于被调用者并不知道参数的具体长度,所以这样的函数只能采用__cdecl。
所有这四种方式,生成的函数都有固定的边界特征,
__cdecl 和__stdcall以这样的模式开始:
push ebp ;保存ebp
mov ebp,esp ;设置栈指针
sub esp,0C0h ;为局部变量保留栈空间
push ebx ;保存在这个函数中可能用到的寄存器
push esi ;保存在这个函数中可能用到的寄存器
push edi ;保存在这个函数中可能用到的寄存器
以这样的模式结束:
pop edi ;恢复寄存器原先的值
pop esi ;恢复寄存器原先的值
pop ebx ;恢复寄存器原先的值
add esp,0C0h ;与前面的sub esp,0C0h对应
mov esp,ebp ;恢复栈指针
pop ebp ;恢复ebp
ret
其中 “mov esp,ebp; pop ebp;”也可替换成一句“leave”指令。
如果是__stdcall,最后一行的ret会变成
ret 0Ch
这样。最后的那个数字代表从栈上弹出多少个字节,它应当等于函数参数的总大小。
类的成员函数默认是采用__thiscall。它与__stdcall非常相似,区别是this指针会通过ecx传递。所以,如果在一个函数中发现ecx未被赋值就开始读它,那么多半是__thiscall。如
push ebp ;保存ebp
mov ebp, esp ;设置栈指针
push ecx ;保存在这个函数中可能用到的寄存器
push ebx ;保存在这个函数中可能用到的寄存器
push esi ;保存在这个函数中可能用到的寄存器
mov esi, ecx ; 注意!!!ecx尚未被赋值就开始读了
mov ebx, [esi+344h] ; this指针一般存放在esi中,并且在整个函数体内,esi尽量保持不变。
另外再次强调,__thiscall末尾的ret语句要跟一个数字,来清理栈上的函数参数。
另外就是,如果类成员函数采用了变长参数列表,那么就不能用__thiscall,而必须用__cdecl,把this指针作为最后一个参数弹入。
__fastcall和__thiscall有时候很难区分开。__fastcall是把前2个DWORD类型的参数用ecx,edx传递,其它的继续push进栈里。
假设函数声明为:
void __fastcall testfunc(int x,int y);
那么调用这个函数的代码testfunc(3,4)就会被编译成
mov edx,4
mov ecx,3
call testfunc
"/GS" : Buffer Security Check
如果编译的时候加了"/GS"参数,并且开启了优化(如Release版本),那么对于某些可能会遭受缓存区溢出攻击的函数,编译器在生成代码的时候,会在函数局部变量的最末尾加一个4字节的cookie。如:
push ebp
mov ebp, esp
sub esp, 214h
mov eax, ___security_cookie ; random value, initialized at module startup
xor eax, ebp ; XOR it with the current base pointer
mov [ebp+var_4], eax ; store the cookie
___security_cookie是一个随机数,在程序启动的时候由CRT初始化,此处把当前的栈指针(ebp)和这个cookie作XOR运算,然后在return之前再检查一遍:
mov ecx, [ebp+var_4] ; get the cookie from the stack
xor ecx, ebp ; XOR the cookie with the current base pointer
call __security_check_cookie ; check the cookie
leave
retn 0Ch
__security_check_cookie是一个__fastcall,代码如下:
void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie) { /* x86 version written in asm to preserve all regs */ __asm { cmp ecx, __security_cookie jne failure rep ret /* REP to avoid AMD branch prediction penalty */ failure: jmp __report_gsfailure } }
就是简单的做下比较,不正确则终止。
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:changming 来源: changming的blog
- 标签: Visual
- 发布时间:2012-06-14 13:56:48
- [66] Go Reflect 性能
- [65] Oracle MTS模式下 进程地址与会话信
- [64] 如何拿下简短的域名
- [59] android 开发入门
- [59] IOS安全–浅谈关于IOS加固的几种方法
- [58] 图书馆的世界纪录
- [58] 【社会化设计】自我(self)部分――欢迎区
- [53] 视觉调整-设计师 vs. 逻辑
- [47] 界面设计速成
- [46] 读书笔记-壹百度:百度十年千倍的29条法则