【问题标题】:Stack / base pointers in assembly汇编中的堆栈/基指针
【发布时间】:2011-02-13 03:39:24
【问题描述】:

我知道这个话题已经在此处以及互联网上的其他地方讨论过令人作呕 - 但希望这个问题是一个简单的问题,因为我试图让我的头脑了解组装......

所以如果我理解正确,ebp(基指针)将指向堆栈的顶部,而 esp(堆栈指针)将指向底部——因为堆栈向下增长。 esp 因此指向“当前位置”。 因此,在函数调用中,一旦您将 ebp 保存在堆栈中,您就会插入一个新的堆栈帧 - 用于该函数。因此,在下图的情况下,如果您从 N-3 开始,您将通过函数调用转到 N-2。但是当你在 N-2 时 - 你的 ebp == 25 和 esp == 24 (至少在最初,在任何数据放入堆栈之前)?

这是正确的还是我在这里切线?

谢谢!


(来源:wikimedia.org

【问题讨论】:

  • 我将继续并将其标记为 C,因为这听起来像是您试图了解 C 编译器生成的代码是做什么的。如果不正确,请随意删除该标记.

标签: assembly x86 callstack backtrace stack-frame


【解决方案1】:

这取决于平台,但这通常是工作方式。

在我最熟悉的架构中,“调用”(也称为返回)地址位于 $ra 寄存器中,而堆栈位于调用者留下的任何位置。所以发生的事情是返回地址被压入堆栈,就像你的(调用者的)基指针一样,然后基指针被更新到堆栈所在的位置并且堆栈不断向上爬。当我不记得时,事物被推送的确切顺序以及设置的内容,但通常由被调用者来保存将被破坏的寄存器。这样,如果被调用的函数只使用一个或两个寄存器,调用函数就不需要保存所有内容。 (实际上,返回地址寄存器是一样的——如果函数没有调用其他任何东西,它就不会被压入堆栈。)

如果您反汇编程序并查看函数序言和结语,这实际上很容易理解。它们都遵循非常常见的模式,即在顶部“存储所有内容”并在底部“恢复所有内容”。 (请注意,有时有些“特殊”寄存器永远不会存储或恢复,编译器知道如果没有函数调用,它只能指望值是一致的。在 MIPS 上,我认为它们是 S 寄存器, PPC 称它们为 t?)

【讨论】:

    【解决方案2】:

    这实际上不仅取决于硬件架构和编译器,还取决于calling convention,这只是一种商定的方式,函数与堆栈一起调用彼此。换句话说,根据您的编译器设置(以及特殊的#pragma 选项等),函数可以将事物压入堆栈的顺序不同。

    您似乎在谈论 x86 架构上的 cdecl 调用约定。在这种情况下,调用者的ebp 通常在返回地址之后立即被压入堆栈。因此,在您的示例的 N-2 中,位置 25 将包含一个指向调用函数 N-3 的指针(即,它将包含紧跟在使您进入 N-2 的 call 之后的指令的地址)和位置24 将包含旧的ebp,并且您的 esp 将在调用之后立即 = 23,在任何本地人被压入堆栈之前。 (除了一些编译器会在调用后立即在堆栈上腾出空间,因此 ESP 将是 20 而不是在函数 N-2 内上下移动。)

    但是请注意,在 x86 上,编译器有时可以进行一项特殊的优化,称为 frame pointer omission,它可以避免在某些条件下将旧的 ebp 完全推入堆栈。

    【讨论】:

      【解决方案3】:
      1. 调用N-3后,ebp28,而esp25
      2. 旧的ebp被推送,然后 ebp 设置为当前 esp 的值。现在espebp24
      3. 最后,esp 调整为 局部变量的空间。 esp 是 现在可能20,取决于如何 调用时函数的行为 N-2

      了解这一点的最佳方法是阅读 function prologues,并熟悉 x86 实现。它还有助于接受 espebp 用于在每个函数中本地化堆栈的使用,编译器、体系结构和平台之间存在一些差异(并且几乎与任何高于或等于的语言的用户无关到 C)。

      【讨论】:

      • 很遗憾我不能接受两个答案 - 谢谢你的指点!
      猜你喜欢
      • 1970-01-01
      • 2017-08-01
      • 2014-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-17
      • 1970-01-01
      • 2011-01-22
      相关资源
      最近更新 更多