栈(Stack),一种为了优化内存运用,分配效率而产生的单程序数据大锅饭。

这个字的本义是仓储建筑,对于理解其含义没有任何帮助,而英文的 Stack 个人还是认为更加形象。无论如何,其标志性特征为”先进先出,后进后出“的规则。

除此之外,还需要了解栈的生长方向。栈由高地址向低地址生长。如 push rbp后,64位下rsp- 8。

栈帧

栈是一种叠叠乐。 为了让每一个函数拥有相对独立的运行环境,大锅饭升级了“鸳鸯锅”,为每一个函数调用安排了一层供它们自己折腾,运行结束返回时收回。为此,程序做了以下工作:

在函数调用前,

call fun

调用该函数,等价于

push rip
jmp  fun

先将此时程序运行的位置压入栈中暂存,以备之后还原,然后跳转到call的函数的起始位置,此时的栈状态如下:

返回地址(旧rip)   < rsp
...

初始化时,

push rbp
mov  rbp, rsp
sub  rsp, 30h

其先会将旧的栈底入栈,储存起来等待函数运行结束后还原。然后将旧的栈顶作为新的栈底,然后将栈顶减去特定数值为该战阵分配空间。这些步骤完成后,此时的栈状态如下:

栈顶(低地址)   < rsp

[30字节的位置以供数据使用]

旧栈底的地址   < rbp
返回地址(旧rip)
...

在返回时,

leave
retn

也就是

mov  rsp, rbp
pop  rbp
 
pop  rip

程序会将栈顶移回栈底,然后弹出先前储存的就栈底,最后在弹出call命令压入的地址,继续运行先前的函数。此时的栈结构:

*返回地址* (数据并没有被删除,只是框定的rsp和rbp离开了,其成为了废数据)
...   < rsp

风险

不受限制的输入非常危险。未加限制的输入可能会越过数据分配的边界,污染到其他的数据;更有甚者,数据会横冲直撞,覆盖返回地址,导致栈溢出。这就让别有用心的人可以控制程序流,构造ROP链