函数调用栈
1、函数参数带入(入调用方函数的栈,从右向左入栈)
int fun(int a);
int fun(int a, int b);
int fun(int a, int b, int c);
形参字节 参数带入方式
byte<=4 push a 入栈
byte>=4 && byte<=8 push b push a 入栈
byte>=12 先让栈顶偏移12字节(参数大小) sub esp,0ch
再将实参的值写入的偏移的内存中,从栈顶方向向栈底方向写入 c、b、a 如果是数组入栈,则对应顺序应是a[0],a[1],a[2]
2、函数栈帧开辟
int fun(int a, int b)//主函数中调用该函数
{
int c = a + b;
return c;
}
int main()
{
int a = 10;
int b = 20;
int rt = 0;
rt = fun(a, b);
return 0;
}
1.实参入栈
2.将main(调用方)栈底地址入栈
3.让 ebp=esp //ebp(栈底指针)指向esp(栈顶指针)位置,充当新函数(调用的函数fun())的栈底
4.指令:"sub esp,栈帧大小" //向低地址(因为栈是从高地址向低地址生长的)开辟空间,esp栈顶指针向低地址偏移
5.将栈帧内存初始化为0xcccccccc //这就是为初始化变量显示"燙燙燙"的原因,部分编译器会初始化成随机值
3、函数返回值的带出
4字节 eax寄存器
4<=8字节的返回值带出 两个寄存器,eax,edx
>8字节返回值带出
在main(调用方)的栈帧上预留一部分空间
将返回值写入到main预留的空间内
将该部分的地址写入到eax寄存器,由eax寄存器带出
使用时返回值时,从eax寄存器中存储的地址找到存储的位置取出返回值
4、函数栈帧的回退
将多个寄存器pop //pop edi esi ebx
esp=edp //回收fun函数的栈帧,esp指向当前栈底
pop main栈底地址 //edp=pop edp回退至调用前的状态(调用方的栈底)
ret //返回调用方
esp+8 //清栈,清理开辟的形参空间,esp所加大小视形参所占空间而定
若返回值字节数小于4,则只需一个寄存器eax即可带出
007B1733 mov eax,dword ptr [c]
若返回值字节数大于4小于8,则需要两个寄存器eax、edx带出
007B1733 mov eax,dword ptr [c]
007B1736 mov edx,dword ptr [ebp-2Ch]
若返回值字节数大于8,则需要提前开辟内存空间,将返回值存放与此空间中,由eax带出该空间的地址
struct test //测试大于8个字节的返回值
{
int a;
int b;
int c;
};
001441C8 mov eax,dword ptr [ebp+8]
001441CB mov ecx,dword ptr [test]
001441CE mov dword ptr [eax],ecx
001441D0 mov edx,dword ptr [ebp-40h]
001441D3 mov dword ptr [eax+4],edx
001441D6 mov ecx,dword ptr [ebp-3Ch]
001441D9 mov dword ptr [eax+8],ecx
001441DC mov eax,dword ptr [ebp+8]
函数调用过程分析与汇编指令解析
int fun(int a, int b)
{
/*
push ebp //保存旧栈底
mov ebp,esp //ebp = esp //ebp从旧栈底(mian)跑到新栈底(fun)
sub esp,0cch //开辟栈帧
push …… //push寄存器 //pop ebx esi edi
* lea edi,[ebp-0CCh] //edi保存未把寄存器入栈前的栈顶
* mov ecx,33h
mov 0xcccccccc //初始化前 初始化范围为寄存器与栈底之间的内容
* rep stos dword ptr es:[edi] //循环初始化内存为0xcccc cccc
*/
int c = a + b; // eax,dword ptr [a] \ eax,dword ptr [b] \ dword ptr [c],eax
return c;
/*
mov eax,dword, ptr[c]
*/
}
/*
pop …… //pop 多个寄存器
mov esp,ebp //回收栈,将esp指向(fun)栈底
pop ebp //ebp=pop //ebp回到原栈底
ret //近返回的指令,将栈顶字单元保存的偏移地址作为下一条指令的偏移地址
*/
int main()
{
int c = fun(10, 20);
/*
push 14h
push 0ah
call fun(xxxxxxx) //压如下一行指令 \ 跳转到fun()
add esp,8 //清栈
mov dword ptr[c],eax
*/
return 0;
}
5、三种调用约定的比较
函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。常见的的函数调用约定有以下几种:_cdecl (c标准调用约定)、_stdcall(windows下标准调用约定) 、_fastcall(快速调用约定) …
入参顺序 入参方式(size) 返回值带出 参数清除
__cdecl 从右向左 <=8 push <=8 寄存器 调用方(main) -> add esp,4
>8 提前开辟内存 >8 提前开辟内存
__stdcall 从右向左 <=8 push <=8 寄存器 被调用方(fun) -> ret 8 //返回到原函数且清除形参(pop + add)
>8 提前开辟内存 >8 提前开辟内存
__fastcall 从右向左 <=8 直接寄存器带入 <=8 寄存器 被调用方(fun),没有入栈,栈帧回退直接清除
>8 超过部分开辟内存 >8 提前开辟内存
函数符号生成规则:
-
_cdecl调用约定:
“?”+函数名+ “@@YA” + 返回值类型 + 参数类型 + “@Z”或“Z”(无参数)。如:
int __cdecl bom(void) -> (?bom@@YAHXZ)
int __cdecl Sum(int,int) -> (? Sum@@YAHHH@Z)
-
_stdcall调用约定:
“?”+ 函数名 + “@@YG” + 返回值类型 + 参数类型 + “@Z”或“Z”(无参数)。如:
int __stdcall bom(void) -> (? bom@@YGHXZ)
int __stdcall Sum(int,int) -> (? Sum@@YGHHH@Z)
-
_fastcal调用约定:
“?”+ 函数名 + “@@YI”+ 返回值类型 + 参数类型 + “@Z”或“Z”(无参数)。
int __fastcall bom(void) -> (? bom@@YIHXZ)
int __fastcall Sum(int,int) -> (? Sum@@YIHHH@Z)
X | D | E | F | H | I | J | K | M | N | _N | U | PA+ | PB+ |
void | char | unsigned char | short | int | unsigned int | long | unsigned long | float | double | bool | struct | 指针+其类型 | const指针+其类型 |
三种调用约定实例:
- __cdecl 调用约定
int __cdecl Sum(int a, int b); // 函数符号 (?Sum@@YAHHH@Z)
int main()
{
int ret = Sum(10,20);
/* 反汇编
004F1848 push 14h // 将形参20入栈
004F184A push 0Ah // 将形参10入栈
004F184C call Sum (04F115Eh) // 调用Sum()函数
004F1851 add esp,8 // 栈顶指针回退8字节 ————清理形参
004F1854 mov dword ptr [ret],eax // 将eax内的数据(返回值),赋值到ret中,赋值double word(4字节)大小
*/
return 0;
}
int __cdecl Sum(int a, int b)
{
return a + b;
/* 反汇编
004F1738 mov eax,dword ptr [a]
004F173B add eax,dword ptr [b] // 将a+b的结果保存在eax寄存器中
*/
}
/* 反汇编
...
004F174E mov esp,ebp // ...
004F1750 pop ebp // 栈帧回退
004F1751 ret // 回调至调用方(main)
*/
- __stdcall 调用约定
int __stdcall Sum(int a, int b);
int main()
{
int ret = Sum(10,20);
/*
00861848 push 14h // 将形参20入栈
0086184A push 0Ah // 将形参10入栈
0086184C call Sum (08611B3h) // ..
00861851 mov dword ptr [ret],eax // ..
*/
return 0;
}
int __stdcall Sum(int a, int b)
{
return a + b;
/*
00861738 mov eax,dword ptr [a]
0086173B add eax,dword ptr [b] // ..
*/
}
/*
...
0086174E mov esp,ebp // ..
00861750 pop ebp // ..
00861751 ret 8 //①ret 弹出偏移地址.. ②esp+8 回收栈空间(清理形参)
*/
- __fastcall 调用约定
int __fastcall Sum(int a, int b);
int main()
{
int ret = Sum(10,20);
/*
009C1848 mov edx,14h // edx 带入参数20
009C184D mov ecx,0Ah // ecx 带入参数10
009C1852 call Sum (09C1384h) // ..
009C1857 mov dword ptr [ret],eax // ..
*/
return 0;
}
int __fastcall Sum(int a, int b)
{
return a + b;
/*
009C1740 mov eax,dword ptr [a]
009C1743 add eax,dword ptr [b] // ..
*/
}
/*
...
009C1756 mov esp,ebp // ..
009C1758 pop ebp // ..
009C1759 ret // ..
*/
测试环境为Microsoft Visual Studio 2019