gpt4 book ai didi

c - 了解 x86 IA32 程序集中函数调用的前/后汇编代码

转载 作者:太空宇宙 更新时间:2023-11-04 02:02:37 25 4
gpt4 key购买 nike

所以我们有以下代码,设置一个带参数的函数调用,省略主体(等等等等),然后在函数末尾弹出。

pushl %ebp
movl %esp, %ebp
pushl %ebx
movl 8(%ebp), %ebx
movl 12(%ebp), %ecx
etc
etc
etc
//end of function
popl %ebx
popl %ebp

这是我(认为)我理解的。

假设我们有 %esp 指向内存地址 100。

pushl %ebp

所以这基本上使 %ebp 指向 %esp 指向的位置(内存地址 100)+ 4。所以现在 %ebp 指向内存地址 104。这使我们当前的内存状态看起来像这样:

----------
|100|%esp
|104|%ebp
----------

然后我们有下一行代码:

movl %esp, %ebp

所以据我了解,ebp 现在指向内存地址 100。我对为什么要执行此步骤有一点直觉,但我的困惑是下一行:

pushl %ebx

推送 ebx 的目的是什么,我假设它会指向内存地址 104?我对 ebp (104) 正下方的空间应该如何引用“旧堆栈指针”有一个模糊的想法,所以我可以理解为什么接下来的两行将 8 和 12 添加到 ebp 作为“参数”我们的功能,而不是 4 和 8。

但我对为什么我们首先将 ebx 压入堆栈感到困惑。

我也不明白popping,为什么要pop ebx和ebp?

在他不得不 sleep 之前与某人谈论这个,他提到我们没有引用我们的堆栈指针是 100 的事实——直到我们弹出 ebp 回来。现在,我认为 ebp 的值(value)是 100,所以我不明白他试图表达的观点。

所以澄清一下:

  1. 到目前为止,我的理解是否正确?

  2. 为什么我们要将 ebx 压入堆栈?

  3. 位于 ebp 正下方的“对旧堆栈指针的引用”是什么?那是我们推的 ebx 吗?

  4. 有没有我不明白的地方,比如我们推送的 ebx 和紧跟在(我们的论点)之后的行中的 ebx 之间的某种区别?被推送的 ebp 和紧随其后的行中的 ebp 有区别吗?

  5. 为什么我们在最后弹出?

如果这很难理解,我深表歉意。我知道有人问过类似的问题,但我试图以一种对我有意义的方式直观地理解和描绘函数调用中到底发生了什么。

注意:我编辑了一些关于我对正在发生的事情的理解的重要内容,特别是关于 ebp。

最佳答案

正如约阿希姆所说 in a comment关于你的问题,压入一个寄存器会将寄存器的内容压入栈中;它不会推送对寄存器或其他任何内容的引用。我不确定你是不是说这就是发生的事情,否则这张图就不清楚了:

----------
|100|%esp
|104|%ebp
----------

不过,我会尝试解释它的作用和原因。


假设 %esp0x100 当调用者调用我们的函数并且 call 之后的指令是 0x200。当我们执行 call 时,我们压入 0x200(返回地址)并跳转到该过程。那么我们的堆栈是:

          Address  Value
%esp --> 0x100 0x200

%ebp 是某个值或其他值;它可能指向堆栈,也可能不指向堆栈。它甚至不需要表示地址。所以 %ebp 在这一点上对我们来说毫无意义。

但是虽然它对我们没有意义,调用者确实希望它在调用前后保持不变,所以我们必须保留它。假设它包含值 0xDEADBEEF。我们推送它,所以堆栈现在看起来像这样:

          Address  Value
0x100 0x 200
%esp --> 0x0fc 0xDEADBEEF

在大多数情况下,我们可以将所有内容都作为 %esp 的偏移量来处理,这也适用于此。但是如果编译器正在编译一些处理可变长度数组或其他特性的 C 代码,我们通常会希望从我们推送的第一件事而不是我们推送的最后一件事开始索引。为此,我们将 %ebp 设置为我们现在所在的位置。然后事情看起来像这样:

                Address  Value
0x100 0x 200
%esp, %ebp --> 0x0fc 0xDEADBEEF

请注意,%ebp 指向的地址处的值是 %ebp 的旧值,因此您可以遍历堆栈,正如您提到的那样之前的。

接下来,我们推送 %ebx,我们称其值为 0xBEEFCAFE。这是与函数序言没有直接关系的第一件事。然后我们的堆栈看起来像这样:

          Address  Value
0x100 0x 200
%ebp --> 0x0fc 0xDEADBEEF
%esp --> 0x0f8 0xBEEFCAFE

但为什么我们要推送 %ebx?好吧,事实证明,x86 C 调用约定规定,与 %ebp 一样,%ebx 必须保持调用前的状态。因此,由于您省略的代码可能会更改 %ebx,因此它必须保留初始值,以便为调用者恢复它。

在我们恢复 %ebx 之后,我们弹出 %ebp,同时恢复它的值,因为那也必须是调用后保存。最后我们回来了。


TL;DR %ebp%ebx 被压入和弹出,因为它们在函数体的执行过程中被操纵, 但 x86 C 调用约定规定调用前后值必须保持相同,因此必须保留初始值以便我们可以恢复它们。

关于c - 了解 x86 IA32 程序集中函数调用的前/后汇编代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25173836/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com