目录 Table of Contents
程序运行栈的基本操作
x86-32 的程序栈
栈就是所谓 "先进后出, 后进先出" 的一种数据结构. 所谓程序栈就是符合栈工作原理的这么一块内存区域.
注意, 它是从高地址向低地址增长.
我们画图的时候, 一般把栈底画在上面, 因为这是高地址.
|---| -------> 栈底
| |
|---|
| |
|---|
| | --------> esp - 4
|---|
| | --------> 栈顶指针 %esp
|---|
%esp 存储栈顶地址
这个图是从下面往上面存东西, 和下一节的图方向相反, 相反的原因是经过了不同的操作
压栈操作
pushl Src
顾名思义, 就是把一个数据压栈
这个指令只有一个操作数 Src.
这条指令意思是, 我从 Src 取出来一个操作数, 然后把 %esp - 4. 这个减去 4 就是压栈, esp 是栈顶嘛, 减去 4 就是往下走, 栈顶往下走就是压栈的意思. 然后再把取入的那个操作数写入栈顶地址, 就是写入 esp 所指向的内存地址(简单理解就是一个类似起始地址的东西
esp - 4 就是腾出位置, 然后我在这个腾出来的位置写数据, 这个位置起始地址就是最开始的 esp.
出栈操作
popl Dest
这个就跟上面的操作是相反的.
它就是读取栈顶数据, 读取 esp 指向的那个地址里面的数据(类似起始地址), 然后 esp + 4 出栈把数据弹出来, 然后把这个数据写入 Destination 目标操作数
过程调用
我们可以利用栈的这个规律来支持过程调用与返回.
一级套一级的过程调用, 先被调用的过程肯定是后返回, 后被调用的过程肯定是先返回, 这个工作属性和栈的工作原理很像, 所以我们可以利用栈来支持过程调用返回.
- 过程调用指令 :
call label
我要跳到你的目标地址, 在跳之前我要把返回地址压栈
- 返回地址 :
返回地址就是 call
指令后面的那条指令的地址. 我们把它压到栈里面, 以便返回.
实例 :
804854e: call 8048b90 <main>
8048553: pushl %eax
返回地址 = 0x8048553
只要有 call
, 就肯定有 return ret
- 过程返回指令 :
ret
也可以理解为跳转, 它跳转到当前的栈顶所存放的地址 (类似起始地址嘛).