默认读者有一定编程经验
栈的特点:
先入后出
栈的相关寄存器:
ESP 栈顶指针寄存器
EBP 栈底指针寄存器
栈的相关指令
PUSH 将数据压入栈中
POP 将数据弹出栈
那么什么是栈帧?
栈帧在程序中用于声明局部变量,调用函数。利用EBP(栈顶指针)寄存器,访问栈内局部变量,参数,函数的返回地址等等。
那么为什么是利用EBP而不是ESP呢?
因为栈的特点是先进后出,所以栈底指针不会改变只有栈顶指针会改变
所以在一个函数中以栈底指针为基址就不会找错值
下面我们以一个例子研究栈帧的生成
使用编译器为vc6++
我们先记录一下没有开始执行的寄存器和内存以及栈的值
非常地道
我们先执行到0x401063的sub esp,48
sub esp,48这条指令就是给栈开辟一个新的空间用于存放这个函数可能使用到的局部变量
接着执行下面的三条push指令
我们发现这三个寄存器的值被存储到栈中
lea edi, dword ptr [ebp-48]
这个指令是将ebp-48的地址存放到edi寄存器中
这个mov ecx 12 是用来为下面的循环做准备的
但是下面这个mov就是用来填充eax寄存器的值
00401076 |. F3:AB rep stos dword ptr es:[edi]
现在我们马上要执行这个指令了
Rep指令是重复字符串操作的指令
在这里ECX寄存器用来计数
EDI为目标寄存器指针
当我们执行之后发现
这段代码就是填充原来栈中可能存在的垃圾数据
下面执行两条mov指令
这四条指令就是将刚才填入的1和2再放入eax和ecx寄存器中
下一条指令就是进入add函数
这都是一一对应的关系
其中可以看到push和pop的寄存器的顺序变了,这就是因为栈的特点
我们直接执行到
0040D4C8 |. C745 FC 00000>mov dword ptr [ebp-4], 0
调用printf函数
在调用完函数之后
我们可以看见栈顶指针加上了8
因为上面push进了两个参数
这两个参数的字节都是4
这样正好清除栈里面的垃圾数据 这个属于是cdecl调用约定 由调用者清理栈
将最后的结果存入eax
(x86寄存器都是将函数的返回值传入EAX寄存器)
然后我们一路来到主函数
这个也是cdecl