我目前正在学习 x86 汇编语言(我正处于课程的开始阶段),并且在理解堆栈在一个特定情况下的工作方式时遇到了一些问题。
假设我有这个代码:
double(entier n) { return n + n; }
Run Code Online (Sandbox Code Playgroud)
我试图将其转换为 x86 代码,结果是这样的:
push ebp #save old pointer stack
mov ebp, esp #put new pointer stack
mov ebx, dword[ebp + 8] #get argument n and put it in ebx
add ebx, dword[ebp + 8] #add n to ebx
Run Code Online (Sandbox Code Playgroud)
但是后来我完全被阻止了,无法找到如何返回ebx. 我在互联网上找到了一个解决方案,如下所示:
mov [ebp + 12], ebx
pop ebp
ret
pop ebp
ret
Run Code Online (Sandbox Code Playgroud)
我不明白它是如何工作的。不是ebp+12第二个参数的值吗?(就我而言,没有)。pop 用于移动esp指针,但为什么在这种情况下我们需要 2 pop 和 2 return?是否只是删除函数声明期间使用过的值?
既然您似乎对此感到非常困惑,那么让我向您展示如何做到这一点:
double: push ebp ; establish...
mov ebp, esp ; ...stack frame
mov eax, [ebp + 8] ; load argument from stack into eax
add eax, eax ; add it to itself
leave ; tear down the stack frame
ret ; return to the caller
Run Code Online (Sandbox Code Playgroud)
请注意,我选择了eax代替ebx寄存器。这是出于两个原因:
eax是调用者保存的寄存器(意味着如果需要调用者必须注意保存其值),而ebx是被调用者保存的寄存器(意味着被调用者,即double必须保存其值)。如果我们想使用ebx,我们必须保存并恢复它的旧值。如果我们改用调用者保存的寄存器eax,我们可以避免这种努力。eax是按照约定在其中找到返回值的寄存器。调用者会将 的值eax作为返回值。
在几乎所有 x86 调用约定中,返回值是eax在返回时找到的任何值。因此,通过将加法的结果放入eax,我们不必做任何额外的工作来设置返回值。
对于这些方面的未来问题,我建议您参考打开优化的 C 编译器的输出。C 编译器非常擅长生成汇编,很少出错。在类 UNIX 系统(如 Linux)上,您可以使用该-S选项生成汇编代码。因为gcc我建议你打字
gcc -m32 -O3 -masm=intel -fno-omit-frame-pointer -S -o- source.c
Run Code Online (Sandbox Code Playgroud)
将汇编代码以source.c英特尔语法打印到终端,这是您似乎正在使用的汇编风格。
另请参阅如何从 GCC/clang 程序集输出中去除“噪音”?更多细节。