27

我对汇编很陌生,我不明白它到底意味着什么,在一个过程结束时,你用ret语句写了一个数字。

像这样:

Function Proc
push ax cx
.
...body...
.
pop cx ax
ret 2 
Function endp

我知道这与堆栈指针在函数末尾应该返回到哪里有关?

它有什么作用?

4

6 回答 6

29

是的,但ret 2也会从堆栈中删除 2 个字节的参数。据推测,您的函数被称为:

push some_parameter
call Function

此时,一个cdecl函数——“调用者清理”函数(通常由 C 使用)——将需要add sp, 2“清理堆栈”,删除参数。这样的函数将以普通的ret.

您所拥有的stdcall功能是“被调用者清理”功能(例如,由 Windows API 使用)不需要add sp, 2- 它已由ret 2.

如果您不知道,call请将返回地址放在堆栈上(并将ret其弹出),这样您就不能只pop在函数中获取参数。

于 2013-07-13T09:55:18.650 回答
25

假设我有一个程序来添加两个单词并将总和留在EAX. 这些词是我想传递给堆栈上的过程的参数。IE:

push word1
push word2
call addtwob

该过程看起来像:

addtwob proc

push ebp
mov  ebp,esp
mov  eax, [ebp+6]    
add  eax, [ebp+8]
pop ebp
ret 4

Endp

[ebp+6][ebp+8]地址word2word1在堆栈上。 ret 4只是像往常一样返回,但随后将 4 添加到堆栈指针 ( esp),因此您不必pop word2 pop word1在从调用返回后退出堆栈,因此它无需弹出先前的推送即可清理/平衡堆栈。

于 2015-07-03T20:43:10.107 回答
10

正如亚历克斯所说,这意味着返回。在 x86 汇编中,当编译器到达这一行时(例如,在子程序的末尾),它会从堆栈中弹出最后一个值,该值应该是返回地址,并将其分配给 IP 寄存器。您可以通过编写简单的汇编代码并使用 Turbo Debugger 进行编译来更好地理解这一点。如果您不熟悉汇编程序,则有一个 GUI。您可以在此处找到 GUI 。

当您在子程序中从堆栈中弹出和压入值时,您应该存储返回地址,因为在子路由结束时,您需要将其推回堆栈之前的return行。

祝你好运!

于 2013-07-13T09:50:02.700 回答
4

它意味着返回,就像return高级语言中的 a 一样。

在大多数机器上,它会在从堆栈中进入子程序之前弹出程序计数器的先前值并将其复制到 PC 的寄存器中。

对于 x86,参数是堆栈上的参数数量。这仅适用于使用的约定是让子例程处理重置堆栈的情况。

于 2013-07-13T09:37:54.467 回答
3

对于 x86 32 位“ret 的可选数字(16 位或 32 位)参数指定从堆栈中弹出返回地址后要释放的堆栈字节或字的数量。通常,这些字节或字用作输入被调用过程的参数。"-- https://docs.oracle.com/cd/E19455-01/806-3773/instructionset-67/index.html

所以首先它弹出返回地址,然后可选数字是指在弹出返回地址后增加堆栈的字节数。

ret 4总共是esp+=832 位模式,包括弹出 4 字节返回地址和调用者推送的 4 字节。

于 2019-12-03T17:26:01.213 回答
2

您似乎在询问带有 x86_64 操作数的近回报Intel指令集参考手册中给出了处理器遇到接近RET时硬件遵循的算法如下:

(* Near return *)
IF instruction = near return
    THEN;
    IF OperandSize = 32
        THEN
        IF top 4 bytes of stack not within stack limits
            THEN #SS(0); FI; //throw protected mode exception
        EIP ← Pop(); 
        ELSE
        IF OperandSize = 64
            THEN
            IF top 8 bytes of stack not within stack limits
                THEN #SS(0); FI; //throw protected mode exception
            RIP ← Pop();
            ELSE (* OperandSize = 16 *)
            IF top 2 bytes of stack not within stack limits
                THEN #SS(0); FI; //throw protected mode exception
            tempEIP ← Pop();
            tempEIP ← tempEIP AND 0000FFFFH;
            IF tempEIP not within code segment limits
                THEN #GP(0); FI; //throw protected mode exception
            EIP ← tempEIP;
        FI;
    FI;
    IF instruction has immediate operand
        THEN (* Release parameters from stack *)
        IF StackAddressSize = 32
            THEN
            ESP ← ESP + SRC;
            ELSE
            IF StackAddressSize = 64
                THEN
                RSP ← RSP + SRC;
                ELSE (* StackAddressSize = 16 *)
            SP ← SP + SRC;
            FI;
        FI;
    FI;
FI;
  • 根据该算法,每当遇到接近返回时,都会检查返回地址是否在 SS 范围内。如果返回地址有效,则根据操作数大小将栈顶弹出到 RIP 或 EIP。

  • 如果操作数大小为 16 位,则临时位置保存弹出的返回地址,该地址与值 0x0000FFFF 进行“与”运算,并在检查 CS 限制后加载到 EIP 中。

  • 正如您的问题所询问的那样,如果近 RET 指令操作码有操作数会发生什么。这取决于堆栈地址的大小。根据该大小,RSP ESP 或 SP 由操作数增加,并且在所有近 RET 指令在硬件上完成执行之后。

于 2017-11-09T20:34:04.490 回答