程序在运行的时候通常会使用堆栈来存储数据,所以了解堆栈对逆向是很有帮助的.我们常说的堆栈,实际是栈.栈和堆是两个不同的概念.在绝大多数主流架构(包括 ARM、x86 和 x86-64)中,堆栈(stack)默认都是从高地址向低地址生长的,也就是说,每次压栈(push)操作会使栈指针(stack pointer)减小,而弹栈(pop)操作会使栈指针增大。
这是由操作系统和 ABI(Application Binary Interface)约定的,而不是 CPU 架构强制规定的。不过,这些架构的设计通常都支持这种向下增长的堆栈,并且主流操作系统(如 Linux、Windows、Android 等)都采用这种约定。
具体说明如下:
-
x86 / x86-64:
栈指针寄存器是ESP(32 位)或RSP(64 位)。执行push指令时,先减小栈指针,再写入数据;pop则相反。堆栈从高地址向低地址增长。 -
ARM(包括 AArch32 和 AArch64):
在标准的 AAPCS(ARM Architecture Procedure Call Standard)和 AAPCS64 中,栈也是满递减(Full Descending)的,即栈指针指向最后一个有效数据,并且向低地址增长。这是默认且广泛采用的模式。
注意:理论上,堆栈也可以设计为从低地址向高地址增长(比如某些嵌入式系统或特殊用途的运行时环境),但这在通用操作系统和主流 ABI 中极为罕见。
下面我用文字“画图”来直观地说明 堆栈从高地址向低地址生长 的过程,并配合简要解释。
🧠 内存地址布局(简化版)
在典型的程序内存空间中(以 Linux 或 Android 为例):
高地址(例如 0xFFFF0000)
┌───────────────────────┐
│ 内核空间 │
├───────────────────────┤
│ 栈(Stack) │ ← 栈顶(初始时靠近高地址)
│ ... │
├───────────────────────┤
│ 空闲内存 │
├───────────────────────┤
│ 堆(Heap) │ → 堆向高地址增长
├───────────────────────┤
│ 数据段(.data) │
│ 代码段(.text) │
└───────────────────────┘
低地址(例如 0x00000000)
注意:栈在高地址区域,堆在低地址区域,两者相向增长。
📌 堆栈生长方向详解(以 x86-64 / ARM 为例)
假设初始栈指针 SP 指向 0x7FFFFFF0(高地址)。
初始状态:
地址 内容
0x7FFFFFFC [未使用]
0x7FFFFFF8 [未使用]
0x7FFFFFF4 [未使用]
0x7FFFFFF0 ← SP(栈顶,空栈)
0x7FFFFFEC [已用/不可用]
...
执行 push A(压入一个 4 字节数据 A):
- SP 先减 4 → 变成
0x7FFFFFEC - 把 A 存到
0x7FFFFFEC
地址 内容
0x7FFFFFFC [未使用]
0x7FFFFFF8 [未使用]
0x7FFFFFF4 [未使用]
0x7FFFFFF0 [未使用]
0x7FFFFFEC ← SP → A
0x7FFFFFE8 [...]
再执行 push B:
- SP 减 4 →
0x7FFFFFE8 - 存入 B
地址 内容
0x7FFFFFF0 [未使用]
0x7FFFFFEC A
0x7FFFFFE8 ← SP → B
0x7FFFFFE4 [...]
✅ 可见:每次压栈,地址变小 → 堆栈向低地址生长。
🔄 对比:堆(Heap)的生长方向
堆通常通过 malloc() 或 new 分配,由低地址向高地址扩展:
堆起始:0x10000000
malloc(16) → 分配到 0x10000000~0x1000000F
malloc(8) → 分配到 0x10000010~0x10000017
→ 堆指针 向上增长(低 → 高)
📝 总结图(方向对比)
高地址
↑
│ 栈(Stack) ↓ 向下生长(高 → 低)
│ ...
│ 堆(Heap) ↑ 向上生长(低 → 高)
↓
低地址
所有主流架构(ARM、x86、x86-64)在标准操作系统(如 Android、Linux、Windows)中都采用这种约定。
683

被折叠的 条评论
为什么被折叠?



