我目前正在学习x86汇编语言(我正在学习本课程的开始),并且在理解特定情况下堆栈的工作方式时遇到了一些问题。
假设我有以下代码:
double(entier n) { return n + n; }
我试图将其转换为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
但是后来我完全被封锁了,无法找到如何返回ebx
的值。我在互联网上找到了以下解决方案:
mov [ebp + 12], ebx
pop ebp
ret
pop ebp
ret
我不知道它是如何工作的。 ebp+12
是否不是第二个参数的值? (就我而言,没有)。弹出键用于移动esp
指针,但是为什么在这种情况下我们需要2个弹出键和2个返回键?仅仅是删除函数声明期间使用的值吗?
答案 0 :(得分:4)
由于您似乎对此完全感到困惑,所以让我向您展示如何做:
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
请注意,我选择eax
代替ebx
作为寄存器。这有两个原因:
eax
是保存呼叫者的寄存器(意味着,如果需要,调用者必须注意保留其值),而ebx
是保存呼叫者的寄存器(意味着被呼叫者,即{{ 1}}必须保留其值)。如果要使用double
,则必须保存并恢复其旧值。如果我们改用ebx
之类的保存呼叫者的寄存器,则可以避免这种麻烦。 eax
是根据约定在其中找到返回值的寄存器。调用方将把eax
的值作为返回值。
在几乎所有x86调用约定中,返回值都是返回时在eax
中找到的值。因此,通过将加法的结果放在eax
中,我们不必做任何额外的工作来设置返回值。
对于这些方面的未来问题,我建议您参考启用了优化的C编译器的输出。 C编译器非常擅长生成程序集,很少犯任何错误。在类似UNIX的系统(例如Linux)上,可以使用eax
选项生成汇编代码。对于-S
,我建议您键入
gcc
以Intel语法将gcc -m32 -O3 -masm=intel -fno-omit-frame-pointer -S -o- source.c
的汇编代码打印到终端,这是您似乎正在使用的汇编样式。