如何在MASM中使用弹出和后退

Rob*_*Man 5 c x86 memory-management masm cpu-registers

我最近开始使用MASM语言学习x86 Assembly。

我正在使用Isreal Gbati编写的Udemy课程“从头开始使用x86汇编语言”进行学习。

下面的代码来自该课程中的一课(不是我想出的代码)。此函数在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;
}
Run Code Online (Sandbox Code Playgroud)

这是程序集:

.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
Run Code Online (Sandbox Code Playgroud)

我不理解以下内容:

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

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

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

另外,我知道ret在过程的末尾使用了它,但是我们如何知道该函数完全返回一个值?

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

int AdderClang(int a, int b, int c)
{
    return a + b + c;
}
Run Code Online (Sandbox Code Playgroud)

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

所有这些都可以在本课程的后面部分进行解释,我敢肯定,对我的问题的回答很简单。但是,我试图慢慢走,以确保我了解自己在做什么。是的,我知道Assembly不是C,因此比较两种语言可能不是正确的方法,但是我正在学习Assembly以更好地理解C中的内存管理知识。

谢谢大家的时间!

Rei*_*ica 6

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

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

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

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

void pop(int *x) {
  *x = *esp;
  ++esp;
}
Run Code Online (Sandbox Code Playgroud)

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


另外,我知道ret在过程的末尾使用了它,但是我们如何知道该函数完全返回一个值?

您可以说在汇编世界中,每个函数都返回一个值。调用约定指定如何返回值。在x86上,返回值存储在eax寄存器中。因此ret跳转到调用该函数的eax位置,那时调用者将获得什么作为返回值。这就是函数在中计算总和的原因eax,因此恰好在调用者期望的位置。

再次,在伪C中,您可以想象正常的C return语句是这样实现的:

void return(int x) {
  eax = x;
  ret;
}
Run Code Online (Sandbox Code Playgroud)

  • 另一种说法是,在函数的开始/结尾处推入/弹出是一种保存/恢复调用者的注册值的方法,与您自己使用它有关。 (2认同)