【问题标题】:How to use pop and ret in MASM如何在 MASM 中使用 pop 和 ret
【发布时间】:2019-11-27 02:49:19
【问题描述】:

我最近开始学习使用 MASM 语言的 x86 汇编。

我正在使用 Isreal Gbati 的 Udemy 课程“x86 Assembly Language From Ground Up”来学习。

下面的代码来自课程中的一节课,(这不是我想出的代码)。此函数由 C 程序中的 main 调用。这里是:

#include <stdio.h>
#include <stdlib.h>

extern int AdderASM(int a, int b, int c);

int main(void)
{
    int a = 17;
    int b = 11;
    int c = 14;
    int sum = AdderASM(a, b, c);

    printf("A = %d\n", a);
    printf("B = %d\n", b);
    printf("C = %d\n", c);

    printf("SUM FROM ASSEMBLY FUNCTION = %d\n", sum);

    return 0;
}

这是程序集:

.386
.model flat, c

.code

AdderASM    PROC

            PUSH EBP             
            MOV EBP, ESP        

            MOV EAX, [EBP+8]     
            MOV ECX, [EBP+12]   
            MOV EDX, [EBP+16]   

            ADD EAX, ECX        
            ADD EAX, EDX        

            POP EBP

            RET

AdderASM    ENDP
            END

我不明白以下内容:

当我们使用pop 时,据我了解有点像在C 中使用free()。如果我错了,请纠正我

那么为什么我们只在 EBP 寄存器上使用 pop 呢?我们不应该同时弹出 ECX 和 EDX 寄存器吗?

我知道在 C 函数中,由malloc() 分配内存的指针需要在函数结束之前释放。使用的寄存器都是通用的 32 位寄存器,但 EBP 作为堆栈帧指针有特殊用途。这就是它需要被释放的原因吗?

另外,我知道ret 是在过程结束时使用的,但是我们怎么知道这个函数是在返回一个值呢?

为了更好地解释我的问题,这里是用 C 编写的相同函数:

int AdderClang(int a, int b, int c)
{
    return a + b + c;
}

如果我只输入return; 而不是return a + b + c;,我不知道会发生什么,但这不会是预期的结果。我们也可以知道这个 C 函数返回一个 int,因为它在声明中告诉我们。

所有这些都可能在课程后面进行解释,我相信我的问题的答案很简单。但是,我试图慢慢来,以确保我明白我在做什么。是的,我知道 Assembly 不是 C,因此像我这样比较这两种语言可能不是正确的方法,但我正在学习 Assembly 以更好地理解 C 中的内存管理内容。

感谢大家的宝贵时间!

【问题讨论】:

    标签: c memory-management x86 masm cpu-registers


    【解决方案1】:

    当我们使用pop 时,据我了解有点像在C 中使用free()

    事实并非如此。 push xx 复制到堆栈顶部,并移动堆栈指针,使新顶部低于压入的值(请记住,在 x86 上,堆栈在内存中向下增长)。 pop x 则相反:将栈顶复制到x,然后移动栈指针,使新栈顶高于弹出的值(即从栈中移除该值)。

    实际上,伪 C 等效项是这样的:

    void push(int x) {
      --esp;
      *esp = x;
    }
    
    void pop(int *x) {
      *x = *esp;
      ++esp;
    }
    

    因此,pop ebp 并不意味着“清理ebp 寄存器”,而是意味着“从堆栈中弹出一个值并将其存储在ebp 寄存器中”。由于我们之前推送了ebp,这只是将其还原为结束我们函数的一部分。


    另外,我知道ret 是在过程结束时使用的,但是我们怎么知道这个函数是在返回一个值呢?

    你可以说在汇编世界中,每个函数都返回一个值。调用约定指定如何返回值。在 x86 上,返回值存储在 eax 寄存器中。所以ret 跳转到调用函数的位置,此时eax 中的任何内容都是调用者作为返回值得到的。这就是该函数计算 eax 中的总和的原因,这样它就在调用者所期望的位置。

    同样,在伪 C 中,您可以想象一个普通的 C return 语句是这样实现的:

    void return(int x) {
      eax = x;
      ret;
    }
    

    【讨论】:

    • 另一种说法是,在函数的开始/结束处推送/弹出是一种保存/恢复调用者寄存器值的方法,围绕您自己的使用。
    猜你喜欢
    • 1970-01-01
    • 2013-12-22
    • 1970-01-01
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    • 2016-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多