一.C函数调用与堆栈的关系
C语言通过堆栈将参数传入函数内部
push和pop的时候esp用于指向栈顶——栈顶总是栈中地址最小的位置。push则esp减少,pop则esp增加。
二.函数调用规则
定义:函数调用规则是指调用者和被调用函数之间传递参数及返回参数的方法。
Windows上常用的有Pascal方式,WINAPI(_stdcall)方式以及C方式(_cdecl)。
1._cdecl方式
参数入栈:参数从右到左依次入栈
清理方式:函数返回后,调用者负责清理堆栈。这种调用会生成较大的可执行程序。
2._stdcall方式(WINAPI方式)
参数入栈:参数从右到左依次入栈
清理方式:被调用函数在返回前自行清理堆栈。生成代码比_cdecl方式小。
3.Pascal方式
入栈方式:参数从左到右依次入栈
清理方式:被调用函数在返回前自行清理堆栈
不支持可变参数的函数调用
此外Windows内核中还有常见的_fastcall快速调用;c++中有_thiscall方式。
任何调用方式,最终返回值都是写入eax,然后返回。外部从eax中取得返回值。
一.C函数调用与堆栈的关系
C语言通过堆栈将参数传入函数内部
push和pop的时候esp用于指向栈顶——栈顶总是栈中地址最小的位置。push则esp减少,pop则esp增加。
二.函数调用规则
定义:函数调用规则是指调用者和被调用函数之间传递参数及返回参数的方法。
Windows上常用的有Pascal方式,WINAPI(_stdcall)方式以及C方式(_cdecl)。
1._cdecl方式
参数入栈:参数从右到左依次入栈
清理方式:函数返回后,调用者负责清理堆栈。这种调用会生成较大的可执行程序。
2._stdcall方式(WINAPI方式)
参数入栈:参数从右到左依次入栈
清理方式:被调用函数在返回前自行清理堆栈。生成代码比_cdecl方式小。
3.Pascal方式
入栈方式:参数从左到右依次入栈
清理方式:被调用函数在返回前自行清理堆栈
不支持可变参数的函数调用
此外Windows内核中还有常见的_fastcall快速调用;c++中有_thiscall方式。
任何调用方式,最终返回值都是写入eax,然后返回。外部从eax中取得返回值。
三.技术细节
1. 标准C的_cdecl调用方式
调用者把参数反序压入堆栈中
调用函数
调用者把堆栈清理复原
2._cdecl被调用函数
a.保存ebp。ebp总是被用来保存当前函数执行前的esp,执行完毕后我们用ebp回复esp。上层函数也用ebp作同样的事。所以先把ebp压入堆栈,返回之前弹出,避免ebp被我们改动。
b.保存esp到ebp
push ebp ;保存ebp
move ebp, esp ;把esp放入ebp 此时esp=ebp 都是这次函数调用时的栈顶
c.在堆栈中腾出一个区域用来保存局部变量。esp减去一个值,这样就等于压入了一堆变量。回复的时候只要把esp恢复成ebp中保存的值即可。
d.保存ebx,esi,edi到堆栈中,函数调用完了以后恢复。
sub esp, 0cch ;esp向下移动一个范围,等同于在堆栈中放入一片新空间来存放局部变量
push ebx ;保存 ebx,esi,edi 三个寄存器
push esi
push edi
e.局部变量区域初始化成全0xCCCCCCCC。0cch实际上是int 3指令的机器码,这是一个断点中断指令。因为局部变量不可能被执行,执行了必然有错,这时发生中断来提示开发者。这是VC编译debug版本的特有操作。
lea edi, [ebp-0cch] ; 即 edi = ebp-0xcc
mov ecx, 33h ;总长cch,每次stos操作时4h,所以操作次数=CCh/4h=33h
mov eax, 0cccccccch ;将被写入的内容
rep stos dword ptr [edi] ;串写入。stos将eax数据写入edi同时edi加4,rep则是重复执行ecx次
f.做函数该做的事情ebp+12字节为第二个参数ebp+8为第一个参数,ebp+4是返回地址
&nbs
p;g.恢复ebx,esi,edi,esp,ebp
pop edi ;回复 edi, esi, ebx
pop esi
pop ebx
mov esp, ebp ;恢复上层函数原有的ebp和esp
pop ebp
ret
四.实例
void myfunction(int a, int b)
{
int c = a+b;
}
我自己用VS2005反汇编了一下
void function(int a, int b)
{
00411390 push ebp ;保存上一级函数的ebp,作为返回地址
00411391 mov ebp,esp ;保存当前栈顶
00411393 sub esp,0CCh ;esp = esp – 0cch
00411399 push ebx ;压栈ebx, esi, edi
0041139A push esi
0041139B push edi
0041139C lea edi,[ebp-0CCh] ;edi = ebp-0cch
004113A2 mov ecx,33h ;串指令执行次数
004113A7 mov eax,0CCCCCCCCh ; 串指令填充的内容
004113AC rep stos dword ptr es:[edi] ;重复执行33h次,将eax写入edi并且edi加4 (ebp-0cch到ebp)
int c = a+b;
004113AE mov eax,dword ptr [a] ; eax = a
004113B1 add eax,dword ptr [b] ;eax += b
004113B4 mov dword ptr [c],eax ; c = eax
}
004113B7 pop edi ; 恢复 edi,esi,ebx
004113B8 pop esi
004113B9 pop ebx
;当前esp位于ebp-0cch
004113BA mov esp,ebp ;恢复esp到当前函数的栈顶ebp,即可跳过局部变量区
004113BC pop ebp ;恢复ebp为上一级函数ebp
004113BD ret
>

Really good work about this website was done. Keep trying more – thanks!