首先说明,本文的分析对象是运行在IA32平台上的程序,试验用的编译器是Visual C++ 6.0中的cl.exe(Microsoft 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86)。
IA32程序利用程序堆栈来支持过程(procedure)调用,比如创建局部数据、传递参数、保存返回值信息、保存待今后恢复的寄存器等。为了一个过程调用而分配的堆栈空间称为一个stack frame。最顶层的stack frame由两个寄存器标识:ebp保存stack frame的基址,esp保存栈顶地址,因为在过程执行的时候栈顶寄存器的值会经常变化,所以绝大多数的信息都是通过ebp来相对寻址的。图1描绘了stack frame的基本结构。注意在IA32机器上,栈是向低地址方向增长,所以栈顶的地址值小于等于栈底的地址值。
stack "bottom"
________________ ______
| | |
| . | |
| | |
| . | |
| | |--->Earlier frames
| . | |
| | |
| . | |
| | | |
| |________________|__|___
| | . | |
| | . | |
| | . | |
Decreasing |________________| |
address | Argument n | |
| 4m-->|________________| |
| (m为整数) | . | |-->Caller's frame
| | . | |
| |________________| |
| | Argument 1 | |
| +8-->|________________| |
V | Return address | |
+4-->|________________|__|___
| Saved ebp | |
Frame Pointer-->|________________| |
ebp |Saved registers| |
|local variables| |
| and | |-->Current frame
| temporaries | |
|________________| |
| Argument | |
| build area | |
Stack pointer-->|________________|__|___
esp stack "top"
图1
调用C函数时,不管参数类型如何(包括浮点和struct类型)调用者(caller)负责从右至左将参数依次压栈,最后压入返回地址并跳转到被调函数入口处执行,其中每个参数都要按4字节地址对齐。按照地址来说被传递的参数和返回地址都是位于调用者stack frame中的。如果函数的返回值类型是整型(包括char,short,int,long及它们的无符号型)或指针类型的话,那么就利用EAX寄存器来传回返回值。请看下面的函数:
long foo_long(long offset)
{
long val = 2006 + offset;
return val;
}
用 /c /Od /FAsc 的编译选项(下文均同)编译出如下的汇编码:
PUBLIC _foo_long
_TEXT SEGMENT
_offset$ = 8
_val$ = -4
_foo_long PROC NEAR
; 38 : {
push ebp ;保存调用者的stack frame基址
mov ebp, esp
push ecx
; 39 : long val = 2006