汇编中的堆栈/基指针
我知道这个主题已经在这里和互联网上的其他地方被覆盖了令人恶心 - 但希望这个问题是一个简单的问题,因为我试图了解汇编......
所以如果我理解正确的话ebp(基指针)将指向堆栈顶部,esp(堆栈指针)将指向底部——因为堆栈向下增长。因此 esp 指向“当前位置”。 因此,在函数调用中,一旦将 ebp 保存在堆栈上,您就会为该函数插入一个新的堆栈帧。因此,在下图的情况下,如果从 N-3 开始,您将通过函数调用转到 N-2。但是当你处于 N-2 时 - 你的 ebp == 25 和 esp == 24 (至少最初,在任何数据放入堆栈之前)?
这是正确的还是我在这里偏离了主题?
谢谢!
(来源:wikimedia.org)
I know this topic has been covered ad nauseam here, and other places on the internet - but hopefully the question is a simple one as I try to get my head around assembly...
So if i understand correctly the ebp (base pointer) will point to the top of the stack, and the esp (stack pointer) will point to the bottom -- since the stack grows downward. esp therefore points to the 'current location'.
So on a function call, once you've saved the ebp on the stack you insert a new stack frame - for the function. So in the case of the image below, if you started from N-3 you would go to N-2 with a function call. But when you are at N-2 - is your ebp == 25 and the esp == 24 (at least initially, before any data is placed on the stack)?
Is this correct or am I off on a tangent here?
Thanks!
(source: wikimedia.org)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这实际上不仅取决于硬件架构和编译器,还取决于调用约定,只是一种商定的方式,其中函数与堆栈一起调用彼此。换句话说,函数可以按照不同的顺序将内容推入堆栈,具体取决于您的编译器设置(以及特殊的#pragma 选项等)。
看起来您正在谈论 x86 架构上的
cdecl
调用约定。在这种情况下,调用者的ebp
通常会在返回地址之后立即压入堆栈。因此,在您的示例 N-2 中,位置 25 将包含一个返回到调用函数 N-3 的指针(即,它将包含紧接在使您进入 N 的call
之后的指令的地址-2) 和位置 24 将包含旧的ebp
,并且您的 esp 将在调用后立即 = 23,然后将任何本地变量压入堆栈。 (除了某些编译器会在调用后立即在堆栈上腾出空间,因此 ESP 将为 20,而不是在函数 N-2 内上下移动。)但是请注意,在 x86 上有编译器有时可以进行一种称为帧指针省略,它避免在某些条件下将旧的
ebp
完全推入堆栈。This actually depends upon not only the hardware architecture and the compiler, but also the calling convention, which is simply an agreed-upon way in which functions work with the stack to call one another. In other words, there are different orders in which a function can push things onto the stack, depending on your compiler settings (and peculiar
#pragma
options, etc, etc).It looks like you are talking about the
cdecl
calling convention on the x86 architecture. In that case, the caller'sebp
is usually pushed onto the stack immediately after the return address. So, in your example's N-2, location 25 will contain a pointer back to the calling function N-3 (ie, it will contain the address of the instruction immediately after thecall
that got you into N-2) and location 24 will contain the oldebp
, and your esp will = 23 immediately after the call, before any locals have been pushed onto stack. (Except some compilers will make space on the stack immediately after the call, and so ESP will be 20 instead of moving up and down inside function N-2.)However be aware that on the x86 there is a particular optimization the compiler can sometimes do called frame pointer omission, which avoids pushing the old
ebp
onto the stack altogether under certain conditions.N-3
后,ebp
为28
,而esp
是25
。ebp
被推送,然后ebp
设置为当前esp
的值。现在esp
和ebp
为24
。esp
使得局部变量的空间。
esp
是现在可能
20
,具体取决于方式调用时函数的行为
N-2
。理解这个问题的最佳方法是阅读函数序言,并熟悉x86 实现。它还有助于接受
esp
和ebp
用于本地化每个函数中堆栈的使用,编译器、体系结构和平台之间存在一些差异(并且几乎与任何高于或等于 C) 级别的语言的用户。N-3
,ebp
is28
, andesp
is25
.ebp
is pushed, and thenthe
ebp
is set to the currentvalue of
esp
. Now bothesp
andebp
are24
.esp
is adjusted to makeroom for local variables.
esp
islikely now
20
, depending on howthe function behaves when calling
N-2
.The best way to get your head around this, is read about function prologues, and familiarize yourself with the x86 implementation. It also helps to accept that
esp
andebp
are used to localize use of the stack in each function, with some variation between compilers, architectures and platforms (and almost irrelevant to the user of any language higher level than, or equal to C).这取决于平台,但这通常是工作方式。
在我最熟悉的体系结构中,“调用”(也称为返回)地址位于 $ra 寄存器中,而堆栈位于调用者留下的位置。因此,所发生的情况是,返回地址被压入堆栈,就像您的(调用者)基指针一样,然后基指针被更新以指向堆栈所在的位置,并且堆栈不断向上爬行。当我不记得时,事情被推送的确切顺序以及设置的内容,但通常由被调用者来保存将要被破坏的寄存器。这样,如果被调用的函数仅使用一两个寄存器,则调用函数不需要保存所有内容。 (实际上,返回地址寄存器是相同的 - 如果函数不调用其他任何东西,它不会被压入堆栈。)
如果您反汇编程序并查看该函数,这实际上很容易理解序言和尾声。它们都遵循非常常见的模式,即在顶部“存储所有内容”,在底部“恢复所有内容”。 (请注意,有时有些“特殊”寄存器永远不会被存储或恢复,并且编译器知道它只能在没有函数调用的情况下依赖一致的值。在 MIPS 上,我认为它们是 S 寄存器, PPC 称它们为 t?)
It depends on the platform, but this is generally how things work.
On the architectures with which I am most familiar, the "calling" (aka return) address is in the $ra register and the stack is wherever it was left by the caller. So what happens is the return address gets pushed onto the stack as does your (caller's) base pointer, and then the base pointer is updated to point where the stack is and the stack keeps crawling up. The exact order of where things get pushed and what's set when I don't recall, but typically it's up to the callee to save off the registers that are going to be clobbered. That way the calling function doesn't need to save everything if the function being called only uses one or two registers. (Actually, the return address register is the same- it won't be pushed onto the stack if the function doesn't call anything else.)
This is actually pretty easy to follow if you disassemble a program and take a peek at the function prologue and epilogue. They all follow pretty common patterns of "store everything" up top and "restore everything" at the bottom. (Note that there are sometimes "special" registers which are never stored or restored and the compiler knows to expect that it can only count on the values being coherent if there were no function calls. On MIPS I think they're the S registers, and PPC calls them t?)