函数调用和汇编
文章目录
1 函数调用和汇编
1.1 函数的调用约定
- C函数的默认调用约定是__cdecl
- C++全局函数和静态成员函数的默认调用约定是__stdcall
- 类的成员函数的调用约定是__thiscall
函数生成符号不同
函数参数入栈顺序不同
void __stdcall swap(int a,int b)
{
int tmp = a;
a = b;
b = tmp;
}
1.2 堆栈平衡
- 因为函数调用过程中,参数需要压栈,所以在函数调用结束后,用于函数调用的压栈参数也需要退栈。
| 调用约定 | 堆栈平衡方式 |
|---|---|
| __stdcall | 函数自己平衡 |
| __cdecl | 调用者负责平衡 |
| __thiscall | 调用者负责平衡 |
| __fastcall | 调用者负责平衡 |
| __naked | 编译器不负责平衡,由编写者自己负责 |
1.3 汇编码
1.3.1 寄存器
- 寄存器ebp(base pointer ):帧指针或基址指针,栈底指针
- 寄存器esp(stack pointer):栈指针,栈顶指针
1.3.2 指令
- lea eax,dword ptr [b]:将b地址的偏移量保存到寄存器eax中
- mov ebp,esp:将esp指向内存中的数据保存到目的寄存器ebp中
- call swap():将程序跳转到目标函数处执行。
首先将call指令的下一条指令的地址(当前指令地址寄存器EIP中的地址+偏移量4个字节)压栈;
然后执行jump指令跳转到调用函数的入口,即函数的开始地址。 - ret:将栈顶保存的地址弹出到EIP中
#include<iostream>
using namespace std;
int swap(int a,int b)
{
int tmp = a;
a = b;
b = tmp;
return a;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
return 0;
}



1.4 栈帧
- 存储在用户栈上的,ebp栈底指针和esp栈顶指针之间的所有函数调用涉及的相关信息的记录单元;也叫做,过程活动记录。
1.4.1 开辟栈帧

1.4.2 栈帧回退

2 汇编解析
int main()
//当前栈帧:ESP = 00B5FD54 EBP = 00B5FD70
002D1920 push ebp//ebp压栈,保存栈顶指针
//ESP = 00B5FD50 EBP = 00B5FD70
002D1921 mov ebp,esp//将esp栈顶指针指向的地址保存到ebp中
//ESP = 00B5FD50 EBP = 00B5FD50
002D1923 sub esp,0D8h//将esp的地址 - D8,预留字节给函数临时变量
//ESP = 00B5FC78 EBP = 00B5FD50
002D1929 push ebx//基址(base)寄存器,压栈
//ESP = 00B5FC74 EBP = 00B5FD50
002D192A push esi//源索引寄存器,压栈
//ESP = 00B5FC70 EBP = 00B5FD50
002D192B push edi//目标索引寄存器,压栈
//ESP = 00B5FC6C EBP = 00B5FD50
002D192C lea edi,[ebp-18h]//将 ebp-18 的地址保存到edi中
002D192F mov ecx,6//计数器,用于重复rep指令
002D1934 mov eax,0CCCCCCCCh//给eax寄存器赋予随机值
002D1939 rep stos dword ptr es:[edi]//从edi的地址开始向上每4个字节,重复执行上述指令
002D193B mov ecx,offset _12508171_源文件名@cpp (02DC072h)//文件的地址
002D1940 call @__CheckForDebuggerJustMyCode@4 (02D1339h)//检查错误
int a = 10;
002D1945 mov dword ptr [a],0Ah//将10写入4个字节到a的地址中
int b = 20;
002D194C mov dword ptr [b],14h//将20写入4个字节到b的地址中
swap(a, b);
002D1953 mov eax,dword ptr [b]//从b的地址取4个字节到eax寄存器中
002D1956 push eax//eax寄存器,压栈
//ESP = 00B5FC68 EBP = 00B5FD50
002D1957 mov ecx,dword ptr [a]//从a的地址取4个字节到eax寄存器中
002D195A push ecx//ecx寄存器,压栈
//ESP = 00B5FC64 EBP = 00B5FD50
002D195B call swap (02D13E3h)//进入swap函数
//push EIP//将当前指令的下一条指令的地址压栈,(EIP = 002D195B + 4)
//ESP = 00B5FC60 EBP = 00B5FD50
---------------------------------------------------------------------------
//当前栈帧:ESP = 00B5FC60 EBP = 00B5FD50
//EIP = 002D13E3
002D13E3 jmp swap (02D18D0h)//跳转到swap函数的入口,即开始地址
//EIP = 002D18D0
//进入swap函数体
int swap(int a, int b)
002D18D0 push ebp//ebp压栈,保存栈顶指针
//ESP = 00B5FC5C EBP = 00B5FD50
002D18D1 mov ebp,esp//将esp栈顶指针指向的地址保存到ebp中
//ESP = 00B5FC5C EBP = 00B5FC5C
002D18D3 sub esp,0CCh//将esp的地址 - CC,预留字节给函数临时变量
//ESP = 00B5FB90 EBP = 00B5FC5C EFL = 00000206(标志寄存器),执行地址运算指令时,标记位变化
002D18D9 push ebx//ebx寄存器压栈
//ESP = 00B5FB8C EBP = 00B5FC5C
002D18DA push esi//esi寄存器压栈
//ESP = 00B5FB88 EBP = 00B5FC5C
002D18DB push edi//edi寄存器压栈
//ESP = 00B5FB84 EBP = 00B5FC5C
002D18DC lea edi,[ebp-0Ch]
002D18DF mov ecx,3
002D18E4 mov eax,0CCCCCCCCh
002D18E9 rep stos dword ptr es:[edi]
002D18EB mov ecx,offset _12508171_源文件名@cpp (02DC072h)
002D18F0 call @__CheckForDebuggerJustMyCode@4 (02D1339h)
int tmp = a;
002D18F5 mov eax,dword ptr [a]//从a的地址取4个字节到eax寄存器中
002D18F8 mov dword ptr [tmp],eax//将eax寄存器中的数据写入4个字节到tmp的地址中
a = b;
002D18FB mov eax,dword ptr [b]//从b的地址取4个字节到eax寄存器中
002D18FE mov dword ptr [a],eax//将eax寄存器中的数据写入4个字节到a的地址中
b = tmp;
002D1901 mov eax,dword ptr [tmp]//从tmp的地址取4个字节到eax寄存器中
002D1904 mov dword ptr [b],eax//将eax寄存器中的数据写入4个字节到b的地址中
return a;
002D1907 mov eax,dword ptr [a]//从a的地址取4个字节到eax寄存器中
002D190A pop edi//edi寄存器出栈
//ESP = 00B5FB88 EBP = 00B5FC5C
002D190B pop esi//esi寄存器出栈
//ESP = 00B5FB8C EBP = 00B5FC5C
002D190C pop ebx//ebx寄存器出栈
//ESP = 00B5FB90 EBP = 00B5FC5C
002D190D add esp,0CCh//将esp的地址 + CC,回退释放临时变量的内存空间,平衡堆栈
//ESP = 00B5FC5C EBP = 00B5FC5C
002D1913 cmp ebp,esp//检查堆栈平衡
002D1915 call __RTC_CheckEsp (02D1253h)//判断esp和ebp是否相等
002D191A mov esp,ebp
002D191C pop ebp//ebp出栈,ebp返回到进入函数之前的地址
//ESP = 00B5FC60 EBP = 00B5FD50
002D191D ret//复位,EIP出栈,栈帧回退到main函数
//pop EIP
//ESP = 00B5FC64 EBP = 00B5FD50
---------------------------------------------------------------------------
//退出函数
//当前栈帧:ESP = 00B5FC64 EBP = 00B5FD50
//EIP = 002D1960
002D1960 add esp,8//esp栈顶指针+8,回退释放两个参数占用的内存空间
//ESP = 00B5FC6C EBP = 00B5FD50
return 0;
002D1963 xor eax,eax//eax异或,寄存器清零
002D1965 pop edi//edi寄存器出栈
//ESP = 00B5FC70 EBP = 00B5FD50
002D1966 pop esi//esi寄存器出栈
//ESP = 00B5FC74 EBP = 00B5FD50
002D1967 pop ebx//ebx寄存器出栈
//ESP = 00B5FC78 EBP = 00B5FD50
002D1968 add esp,0D8h//将esp的地址 + D8,回退释放临时变量的内存空间,平衡堆栈
//ESP = 00B5FD50 EBP = 00B5FD50
002D196E cmp ebp,esp//检查堆栈平衡
002D1970 call __RTC_CheckEsp (02D1253h)
002D1975 mov esp,ebp
002D1977 pop ebp//ebp出栈,ebp返回到main函数之前的地址
//ESP = 00B5FD54 EBP = 00B5FD70
002D1978 ret//复位,栈帧回退
//调用main函数,mainCRTStartup()
2.1 函数调用流程
| 进入main函数 | |
|---|---|
| push ebp | 保存栈底指针 |
| push 寄存器 | 保存现场 |
| push 参数 | 压参 |
| call 函数 | 调用函数 |
| 进入被调用函数 | |
| push ebp | 保存栈底指针 |
| push 寄存器 | 保存现场 |
| 执行函数运算 | |
| pop 寄存器 | 恢复现场 |
| add esp | 释放临时变量占用空间,平衡堆栈 |
| cmp ebp,esp | 检查堆栈平衡 |
| pop ebp | 回退栈底指针 |
| ret | 复位,栈帧回退 |
| 退出被调用函数 | |
| add esp | 释放参数占用空间 |
| xor eax | 寄存器清零 |
| pop 寄存器 | 恢复现场 |
| add esp | 释放临时变量占用空间,平衡堆栈 |
| cmp ebp,esp | 检查堆栈平衡 |
| pop ebp | 回退栈底指针 |
| ret | 复位,栈帧回退 |