Call Stack简介
假设和说明
本文以32Bit的X86 Windows为原型进行说明。栈的增长方向为高地址向低地址。
开发环境为Visual Studio,语言是C++。没有涉及到Delphi、Borland C++、Visual Basic等。
文中的任何栈地址、寄存器值以及内存址,都会与大家的环境不同。这些值是基于笔者当前环境的当前镜像。
另外一篇文章会介绍C call、Standard call、Fast call和This call。本文默认基于C call。
示例代码
C++代码
int FuncOne(int a, int b)
{
int sum;
sum = a + b;
return sum;
}
void main()
{
FuncOne(0x100, 0x200);
}
对应的汇编代码
操作方式
· 用Visual Studio建立一个Visual C++的Win32 Console工程,在主文件中,删除所有代码,把上述C++代码粘贴上。
· 设置端点在main下面的左括号上,如下图
· 按F5运行程序,当断点触发时,如下图
· 点击Visual Studio的主菜单“Debug”,点击Windows子菜单,然后分别选择Memory1、Disassembly和Registers。如下图红色部分。
· 修改你的Visual Studio窗口布局,如下图(这是我喜好的风格)
首先修改右上角的Columns列,这里我修改为4,以便于每次stack的操作,我们能更清楚地看到。
其次在Address对应的那个输入框上,输入@esp,然后按回车。
上面的窗口为Disassembly窗口,当前断点所在处为push ebp
上面的窗口为Registers窗口,显示几个重要的寄存器。
代码说明
代码由Prologue、代码call和Epilogue组成。
Prologue
PUSH EBP |
保存old Frame Pointer地址到栈上 |
MOV EBP,ESP |
保存当前栈地址,因为下面要修改栈地址 |
SUB ESP, 0C0H |
预留出C0个字节的空间,供本地变量及其他使用。如编译器的对栈的检查,如Edit & Continue。后面我们修改这些设置,就会发现预留空间的变化。 |
PUSH EBX |
这三个寄存器经常用,所以不管下面代码是否实际使用,都保留上。这是编译器的行为。 |
PUSH ESI |
|
PUSH EDI |
Epilogue
POP EDI |
恢复寄存器的原址 |
POP ESI |
|
POP EBX |
|
ADD ESP, 0CH |
清理栈 |
MOV ESP,EBP |
恢复栈地址 |
POP EBP |
恢复Frame Pointer地址 |
栈的变化
01031440 push ebp
执行前:ESP = 003FFD50。执行后:EIP = 01031441 ESP = 003FFD4C
我们可以看到,在入栈操作后,ESP减少了4,即我们开始提到的,栈的空间是高地址向低地址增长的。EIP的始终指向当前要执行的代码的地址。
继续按F10,一直走到下面这行:
此时,ESP = 002AF954
在Memory窗口,输入@esp,然后回车
在灰色光标闪烁的地方,就是当前的栈顶,里面的值为0。按F10,此时代码进行到下一行:
观察此时的栈情况,首先ESP = 002AF950
然后看memory窗口
最下面一行显示0x200已经入栈了,表示为一个DWORD类型的00 02 00 00。
继续按F10,ESP = 002AF94C
然后看Memory窗口
第二个参数0x100也已经入栈了,表示为一个DWORD类型的00 01 00 00。
这时要注意,代码运行到了func call的地方。
在这里,我们要按F11,同时记住当前的ESP是002AF94C,该call下一行要执行的代码是add esp, 8,地址是0103146D。
按F11进入到call中后,首先观察寄存器的情况:ESP = 002AF948
ESP又减少了4,为什么?我们要观察Memory窗口
当前ESP指向的地址是什么?就是上面的add esp,8的地址。
所以在当前环境下,我们可以总结出:
· 右面参数入栈
· 左面参数入栈
· 返回地址入栈
(这个与call方式有关,我们在下一篇文章中再谈)
上面的add esp,8 代表什么?因为我们push了两个参数,共占用了栈的8个字节,所以我们需要恢复栈的原始状态。加8即代表回到了原来的地址。如果是1个参数,则就是加4了。
Prologue的说明
Q: 为什么要ebp-0C0h,而不是其他的数字?
A: 看下面一行的30h,乘以sizeof(DWORD)之后,即是0C0h。这段空间包含了参数使用的栈空间、Edit&Continue的空间和编译器对于栈保护的空间。
Q: move ax, 0CCCCCCCCh什么意思?
A: CC代表了int 3。所以上述代码实际上是填充了0C0h个int3在该栈空间内。如果代码“不小心”运行到了这里,则会立刻中断。
Edit & Continue
在Visual Studio中右击project,选择Property,修改General中的Debug Information Format为下图(默认为:Program Database for Edit And Continue (/ZI))
重新debug,我们会看到汇编代码如下:
上面的填充CC以及sub esp的操作已经没有了。如果修改代码如下:
则汇编代码变化为:
注意962BC3地址的sub esp,8,因为我们有两个local变量,所以栈预留了8个字节。那么如果我们有很多个变量呢?大家可以自己试验一下。
作者:鞠强