我正在尝试学习 x86。(IA-32 架构)今天我学习了 Stack。我认为我理解的是:
StackPointer (SP) 指向堆栈的“顶部”(最小地址)并存储在 ESP 寄存器中。帧指针 (FP) 可以访问参数和局部变量,因为它是“弹出地址”,因此即使 SP 在堆栈内进一步移动也是静态的。
int addSome(int arg1, int arg2);
int main(void){
int answer = addSome(1,2);
}
int addSome(int arg1, int arg2){
return arg1 + arg2;
}
堆栈看起来应该是这样的:
如果有嵌套函数,FP 会发生什么情况?它不能上移,因为如果弹出子函数,将需要它的位置。
为了实现可视化,添加此函数,它将在 addSome() 内部调用:
int subSome(int arg3, int arg4);
int subSome(int arg3, int arg4){
return arg3 - arg4;
}
FP 在此处如何处理?如何知道新的“弹出地址”以及新参数的位置? 堆栈中的帧指针显示第一个函数的“弹出地址”,第二个函数未知
我的猜测是 addSome 的堆栈大小将被保存在某个地方,以获取相对于 FP 的新“弹出地址”的位置:
但是堆栈大小必须保存在某个地方,如果有更多的嵌套函数,就必须有更多的地方来存储这些大小,我认为这是不可能的。
首先,在 x86 中,我们用来寻址堆栈框架中的值的寄存器称为“基指针”,或
ebp
。我不知道你为什么这么叫它fp
。其次,我们不需要讨论
subSome()
,因为我们已经有了第二个嵌套函数,因为我们有两个函数:main()
和addSome()
。x86 中函数的标准序言和结尾如下所示:
现实世界中,正常的物理堆栈(例如一叠卡片)从底部向上增长。当您向堆栈中添加一张卡片时,它会移到堆栈顶部。x86 并非如此;在 x86 中,堆栈从顶部向下增长。该
push <32-bit-operand>
指令将寄存器减esp
4,然后将 32 位操作数存储到寄存器新值指向的内存地址esp
。该pop <32-bit-operand>
指令执行相反的操作。此外,返回值通常不保存在堆栈上。具体细节取决于实际的 ABI(应用程序二进制接口),但它通常在寄存器中返回
eax
。也许你指的是返回地址,它实际上是由指令推送到堆栈call
并由指令从堆栈弹出的ret
。因此,在内部
addSome()
,你的堆栈将如下所示:(请注意,
top-of-stack
通常不会是堆栈的真正顶部;在其他所有内容之上还会有一些内容,由调用您的标准库代码推送main()
。但显示的部分是我们关心的部分。)