2

我目前正在学习 x86 汇编。但是,在将堆栈用于函数调用时,我仍然不清楚。我知道调用指令将涉及将返回地址压入堆栈,然后将程序计数器加载到要调用的函数的地址。ret 指令将该地址加载回程序计数器。

我的困惑是,在过程/函数中调用 ret 指令是否重要?它会始终找到存储在堆栈中的正确返回地址,还是堆栈指针当前必须指向存储返回地址的位置?如果是这样,我们不能只使用 push 和 pop 而不是 call 和 ret 吗?

例如,下面的代码可能是第一个进入函数的代码,如果我们将不同的寄存器压入堆栈,则必须仅在寄存器以相反的顺序弹出后调用 ret 指令,以便在 pop %ebp 指令之后,堆栈指针将指向返回地址所在的堆栈上的正确位置,还是无论在哪里调用它都会找到它?提前致谢

push %ebp
mov %ebp, %esp
//push other registers

...
//pop other registers
mov %esp, %ebp
(could ret instruction go here for example and still pop the correct return address?)
pop %ebp
ret
4

3 回答 3

6

您必须保留找到的堆栈和非易失性寄存器。调用函数不知道你可能对它们做了什么 - 调用函数将简单地继续执行它的下一条指令ret。只有ret在你完成清理之后。

ret将始终在堆栈顶部查找其返回地址并将pop其放入EIP. 如果ret是“远”返回,那么它还将pop代码段放入CS寄存器中(这也将被推送以call进行“远”调用)。既然这些是最先被推开的东西call,那么它们肯定是最后被推开的东西ret。否则你最终会在ret某个未定义的地方结束。

于 2017-10-12T16:33:57.460 回答
6

CPU 不知道什么是函数/等...该指令将从那里的跳转ret指向的内存中获取值。esp例如,您可以执行以下操作(以说明 CPU 对您如何组织源代码不感兴趣):

   ; slow alternative to "jmp continue_there_address"
   push continue_there_address
   ret
continue_there_address:
   ...

此外,您不需要从堆栈中恢复寄存器,(甚至不需要将它们恢复到原始寄存器),只要在执行esp时指向返回地址ret,它将被使用:

    call SomeFunction
    ...

SomeFunction:
    push eax
    push ebx
    push ecx
    add  esp,8   ; forget about last 2 push
    pop  ecx     ; ecx = original eax
    ret          ; returns back after call

如果您的函数应该与代码的其他部分互操作,您可能仍希望按照您正在编程的平台的调用约定的要求存储/恢复寄存器,因此从调用者的角度来看,您不会修改某些寄存器值应该保留,等等......但这些都不会打扰 CPU 和执行指令ret,CPU 只是从堆栈([esp])加载值,然后跳转到那里。

此外,当返回地址存储到堆栈时,它与其他压入堆栈的值没有任何区别,所有这些值都只是写入内存中的值,因此ret没有机会以某种方式在堆栈中找到“返回地址”并跳过“值”,对于 CPU,内存中的值看起来相同,每个 32 位值就是 32 位值。无论它是由call, push,mov还是其他东西存储的,都没有关系,信息(价值的来源)不会被存储,只有价值。

如果是这样,我们不能只使用 push 和 pop 而不是 call 和 ret 吗?

您当然可以push首选将返回地址放入堆栈(我的第一个示例)。但你不能这样做pop eip,没有这样的指示。实际上就是ret 这样,所以pop eip实际上是同一件事,但是没有 x86 汇编程序员使用这样的助记符,并且操作码与其他pop指令不同。您当然可以pop将返回地址放入不同的寄存器中,例如eax,然后执行jmp eax,以进行慢速ret替代(也可以修改eax)。

也就是说,复杂的现代 x86 CPU 确实会跟踪一些call/ret配对(以预测下一个ret将返回的位置,因此它可以快速预取代码),所以如果您将使用其中一种替代的非标准方式,在某些时候CPU 将意识到它的返回地址预测系统偏离了真实状态,它必须丢弃所有这些缓存/预加载并从真实值中重新获取所有内容eip,因此您可能会因为混淆它而付出性能损失。

于 2017-10-12T16:56:24.897 回答
2

在示例代码中,如果 return 是在 before 完成的pop %ebp,它将尝试返回到函数开始时 ebp 中的“地址”,这将是返回的错误地址。

于 2017-10-12T16:38:27.293 回答