1_程序运行栈的基本操作

程序运行栈的基本操作

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 也可以理解为跳转, 它跳转到当前的栈顶所存放的地址 (类似起始地址嘛).