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);

}

对应的汇编代码

clip_image001

操作方式

· 用Visual Studio建立一个Visual C++的Win32 Console工程,在主文件中,删除所有代码,把上述C++代码粘贴上。

· 设置端点在main下面的左括号上,如下图

clip_image002

· 按F5运行程序,当断点触发时,如下图

clip_image003

· 点击Visual Studio的主菜单“Debug”,点击Windows子菜单,然后分别选择Memory1、Disassembly和Registers。如下图红色部分。

clip_image005

· 修改你的Visual Studio窗口布局,如下图(这是我喜好的风格)

clip_image007

首先修改右上角的Columns列,这里我修改为4,以便于每次stack的操作,我们能更清楚地看到。

其次在Address对应的那个输入框上,输入@esp,然后按回车。

clip_image009

上面的窗口为Disassembly窗口,当前断点所在处为push ebp

clip_image011

上面的窗口为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,一直走到下面这行:

clip_image012

此时,ESP = 002AF954

在Memory窗口,输入@esp,然后回车

clip_image013

在灰色光标闪烁的地方,就是当前的栈顶,里面的值为0。按F10,此时代码进行到下一行:

clip_image014

观察此时的栈情况,首先ESP = 002AF950

然后看memory窗口

clip_image015

最下面一行显示0x200已经入栈了,表示为一个DWORD类型的00 02 00 00。

继续按F10,ESP = 002AF94C

然后看Memory窗口

clip_image016

第二个参数0x100也已经入栈了,表示为一个DWORD类型的00 01 00 00。

这时要注意,代码运行到了func call的地方。

clip_image017

在这里,我们要按F11,同时记住当前的ESP是002AF94C,该call下一行要执行的代码是add esp, 8,地址是0103146D。

按F11进入到call中后,首先观察寄存器的情况:ESP = 002AF948

ESP又减少了4,为什么?我们要观察Memory窗口

clip_image018

当前ESP指向的地址是什么?就是上面的add esp,8的地址。

所以在当前环境下,我们可以总结出:

· 右面参数入栈

· 左面参数入栈

· 返回地址入栈

(这个与call方式有关,我们在下一篇文章中再谈)

上面的add esp,8 代表什么?因为我们push了两个参数,共占用了栈的8个字节,所以我们需要恢复栈的原始状态。加8即代表回到了原来的地址。如果是1个参数,则就是加4了。

Prologue的说明

clip_image019

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)

clip_image021

重新debug,我们会看到汇编代码如下:

clip_image022

上面的填充CC以及sub esp的操作已经没有了。如果修改代码如下:

clip_image023

则汇编代码变化为:

clip_image024

注意962BC3地址的sub esp,8,因为我们有两个local变量,所以栈预留了8个字节。那么如果我们有很多个变量呢?大家可以自己试验一下。

 

作者:鞠强

posted on 2010-11-03 19:57  虹桥路3号  阅读(5165)  评论(5编辑  收藏  举报

导航