堆栈平衡指的是在调用子程序时,保证子程序执行前后 esp(栈顶指针寄存器)和 ebp(存取堆栈指针)的数据保持一致,把栈恢复到调用前的状态,以保证程序后续能够持续正确运行。
在调用约定里,push 用于保护现场(记录当前栈指向的位置),而 pop 则用来恢复现场。
示例:源代码与反汇编
下面是一个最小的 C++ 示例,以及它在 Debug 模式下的反汇编输出,重点观察函数调用前后 esp / ebp 的变化。
源代码
/*源代码.cpp*/
int a(int aa,int v1)
{
return 5;
}
int main()
{
int x = 5;
int t=a(5, 4);
return 0;
}
main 函数的反汇编
/* debug 反汇编 */
--- c:\users\hekun\desktop\cppp\cppp\源.cpp -------------------------------------
int main()
{
00213AB0 push ebp
00213AB1 mov ebp,esp
00213AB3 sub esp,0D8h
00213AB9 push ebx
00213ABA push esi
00213ABB push edi
00213ABC lea edi,[ebp-0D8h]
00213AC2 mov ecx,36h
00213AC7 mov eax,0CCCCCCCCh
00213ACC rep stos dword ptr es:[edi]
int x = 5;
00213ACE mov dword ptr [x],5
int t=a(5, 4);
00213AD5 push 4 /*第二参数入栈*/
00213AD7 push 5 /*第一参数入栈*/
00213AD9 call a (021142Eh) /*调用a*/
00213ADE add esp,8 /* esp 恢复正确位置,参数占用8字节, 为__cdecl 调用 (调用者 堆栈平衡) */
00213AE1 mov dword ptr [t],eax /*函数返回值位于eax寄存器内*/
return 0;
00213AE4 xor eax,eax /*清空eax*/
}
00213AE6 pop edi
00213AE7 pop esi
00213AE8 pop ebx
00213AE9 add esp,0D8h
00213AEF cmp ebp,esp
00213AF1 call __RTC_CheckEsp (02112DFh)
00213AF6 mov esp,ebp
00213AF8 pop ebp
00213AF9 ret
函数 a 内的反汇编
/*函数a内 反汇编*/
--- c:\users\hekun\desktop\cppp\cppp\源.cpp -------------------------------------
#include "iostream"
using namespace std;
int a(int aa,int v1)
{
013241A0 push ebp /*保护缺点ebp 状态*/
013241A1 mov ebp,esp /*ebp指向栈顶(esp 指向 变量aa )*/
013241A3 sub esp,0C0h /*预留 192 字节空间*/
013241A9 push ebx
013241AA push esi
013241AB push edi
013241AC lea edi,[ebp-0C0h]
013241B2 mov ecx,30h
013241B7 mov eax,0CCCCCCCCh
013241BC rep stos dword ptr es:[edi]
return 5;
013241BE mov eax,5 /*返回值保存eax寄存器中*/
}
013241C3 pop edi
013241C4 pop esi
013241C5 pop ebx
013241C6 mov esp,ebp /恢复现场 恢复函数a执行前的 栈顶 (esp)*/
013241C8 pop ebp
013241C9 ret
要点小结
- 调用方通过连续的
push把参数从右向左压栈,再call进入子程序。 - 子程序入口会
push ebp/mov ebp, esp建立新的栈帧,并用sub esp, XXh为局部变量预留空间。 - 子程序退出前通过
mov esp, ebp/pop ebp恢复到进入时的栈顶状态,返回值通过eax带出。 - 对于
__cdecl调用约定,参数占用的栈空间由调用者负责清理,也就是示例中call a之后的add esp,8,对应两个 4 字节参数一共 8 字节。这就是"调用者堆栈平衡"的由来。