首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >示例x86-64程序

示例x86-64程序
EN

Code Review用户
提问于 2020-08-31 03:56:05
回答 1查看 243关注 0票数 3

我编写了以下x86程序,以确保在调用函数并退出操作系统时遵循正确的实践:

代码语言:javascript
复制
.globl _start

_start:

    # Calculate 2*3 + 7*9 = 6 + 63 = 69
    # The multiplication will be done with a separate function call

    # Parameters passed in System V ABI
    # The first 6 integer/pointer arguments are passed in:
    #   %rdi, %rsi, %rdx, %rcx, %r8, and %r9
    # The return value is passed in %rax

    # multiply(2, 3)
    # Part 1 --> Call the parameters
    mov $2, %rdi
    mov $3, %rsi
    # Part 2 --> Call the function (`push` return address onto stack and `jmp` to function label)
    call multiply
    # Part 3 --> Handle the return value from %rax (here we'll just push it to the stack as a test)
    push %rax

    # multiply(7, 9)
    mov $7, %rdi
    mov $9, %rsi
    call multiply

    # Add the two together
    # Restore from stack onto rdi for the first function
    pop %rdi
    # The previous value from multiply(7,9) is already in rax, so just add to rbx
    add %rax, %rdi

    # for the 64-bit calling convention, do syscall instead of int 0x80
    # use %rdi instead of %rbx for the exit arg
    # use $60 instead of 1 for the exit code

    movq $60, %rax    # use the `_exit` [fast] syscall
                      # rdi contains out exit code
    syscall           # make syscall


multiply:
    mov %rdi, %rax
    imul %rsi, %rax
    ret

以上是否正确地遵循x86-64的约定?我知道它可能是最基本的,但是这里还有什么可以改进的呢?

EN

回答 1

Code Review用户

回答已采纳

发布于 2020-08-31 14:40:38

为了详细说明关于这个问题的SO版本的一些评论,您缺少的主要内容是堆栈对齐,这是初学者经常忽略的SysV ABI调用约定的要求。

要求是(ABI 3.2.2):

输入参数区域的末尾应在16字节边界上对齐(如果在堆栈上传递__m256__m512,则为32或64 )。

因此,这意味着,在执行call指令之前,堆栈指针%rsp需要为16的倍数。在您的示例中,在对multiply的两个调用之间没有poppush为8个字节,因此它们不能都具有正确的对齐方式。

在这里,您的父函数是_start而不是main或C代码调用的另一个函数,这给您带来了一些麻烦:

  • 进入_start的条件在3.4abi中描述。特别是,在_start获取控制的瞬间,堆栈对齐为16个字节。另外,由于不能从_start返回(堆栈上没有返回地址),所以您必须像以前一样使用系统调用退出,因此不需要为调用者保存任何寄存器。
  • 对于main或任何其他函数,在调用函数之前,堆栈将被对齐为16个字节,因此返回地址的额外8个字节意味着,在输入到函数时,堆栈现在是“错对”的,也就是说,rsp的值比16的倍数多或少。(因为通常只以8字节增量来操作堆栈,所以只有两种可能的状态,我将称之为“对齐”和“失调”。)此外,在这样的函数中,您需要保留被调用保存的寄存器%rbx, %rbp, %r12-r15的内容。

因此,就目前情况而言,您对multiply的第一次调用具有正确的堆栈对齐方式,但是第二次调用没有。当然,在这种情况下,这只是学术上的兴趣,因为multiply没有做任何需要堆栈对齐的事情(它甚至根本没有使用堆栈),但是正确地这样做是很好的实践。

解决这一问题的一种方法是在第二次调用之前从堆栈指针中再减去8个字节,使用sub $8, %rsp或者(更有效)简单地对任意64位寄存器进行push。但是,我们为什么要费心使用堆栈来保存这个值呢?我们可以简单地把它放在一个被调用保存的寄存器中,比如%rbx,我们知道multiply必须保存它。通常情况下,这将要求我们保存和恢复这个寄存器的内容,但是由于我们处于_start的特殊情况,所以我们不必这样做。

另一个评论是,您有许多指令,如mov $7, %rdi,您在64位寄存器上操作。这样写为mov $7, %edi会更好。回想一下对32位寄存器的每一次写入都将使相应64位寄存器的上半部分为零.,所以只要您的常量是无符号的32位,那么效果是一样的,并且mov $7, %edi的编码要短一个字节,因为它不需要REX前缀。

所以我会修改你的代码

代码语言:javascript
复制
.globl _start

_start:

    # Calculate 2*3 + 7*9 = 6 + 63 = 69
    # The multiplication will be done with a separate function call

    # Parameters passed in System V ABI
    # The first 6 integer/pointer arguments are passed in:
    #   %rdi, %rsi, %rdx, %rcx, %r8, and %r9
    # The return value is passed in %rax

    # multiply(2, 3)
    # Part 1 --> Load the parameters
    mov $2, %edi
    mov $3, %esi
    # Part 2 --> Call the function (`push` return address onto stack and `jmp` to function label)
    call multiply
    # Part 3 --> Save the return value
    mov %rax, %rbx   # could also do mov %ebx, %eax if you know the result fits in 32 bits

    # multiply(7, 9)
    mov $7, %edi
    mov $9, %esi
    call multiply

    # Add the two together
    add %rbx, %rax
    mov %rax, %rdi

    # for the 64-bit calling convention, do syscall instead of int 0x80
    # use %rdi instead of %rbx for the exit arg
    # use $60 instead of 1 for the exit code

    mov $60, %eax    # use the `_exit` [fast] syscall
                      # rdi contains out exit code
    syscall           # make syscall


multiply:
    mov %rdi, %rax
    imul %rsi, %rax
    ret

如果要依赖于32位的multiply拟合结果,可以用mov %eax, %ebx替换mov %rax, %rbx以保存一个字节。同样,“将两者相加在一起”可以使用32位指令来节省更多的两个字节。

最后,对于是否使用AT&T-语法操作数大小后缀,比如addqadd,有一个文体上的观点。当一个操作数是寄存器时,它们是可选的,因为操作数大小可以从该寄存器的大小(例如,%eax的32位,%rax的64位等)推导出来。我个人的偏好是,总是使用它们,作为一个额外的小验证,证明你在写你的意思,但忽略它们(主要是)也是很常见和好的,只是要前后一致。您确实有一个不需要movq $60, %rax的实例,因此为了保持一致性,我省略了后缀。(出于上述原因,我还将其更改为%eax。)

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/248680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档