为什么这会给我一个段错误

时间:2016-12-30 22:50:45

标签: assembly x86-64 gas

我正在尝试编写一个hello world程序,但它不起作用 - 为什么?

.data
    str:
        .ascii "Hello World\12\0"
.text
    .globl main
    .extern printf
    main:
        pushq %rbp
        movq %rsp,%rbp
        movq str,%rdi
        callq printf
        movq %rbp,%rsp
        popq %rbp
        ret

1 个答案:

答案 0 :(得分:0)

您违反了调用约定。由于这是在Linux上运行的64位代码,因此适用的调用约定为System V AMD64。 (标签wiki中的更详细信息。)

请注意,对于像printf这样的可变函数,此调用约定要求调用者设置RAX寄存器以指示在向量寄存器中传递的浮点参数的数量。您没有初始化RAX寄存器,因此它实际上包含垃圾数据。然后printf函数尝试从向量寄存器读取未定义数量的浮点值,从而导致分段错误。

修复代码所需要做的就是在拨打电话之前将RAX清零。有几种方法可以做到这一点。显而易见的是movq $0, %rax,但是,将寄存器清零的更佳方法是使用XOR指令 - ,例如xorq %rax, %rax。但你可以做得更好。因为在x86-64上,所有指令都隐式清除了目标寄存器的高32位,所以你可以简单地执行xorl %eax, %eax。这缩短了一个字节,执行速度会稍快。

从技术上讲,您的main函数也应返回0.调用约定指定函数使用RAX寄存器返回整数结果,因此您只需要将{{1}归零在你回来之前。目前,RAX正在printf返回结果,由于您未设置RAX,因此您实际上会从RAX返回printf的结果。这是合法的,但可能不是你想要的。

因此,您的main函数应如下所示:

main

但是你可以通过删除 pushq %rbp movq %rsp, %rbp movq str, %rdi xorl %eax, %eax callq printf xorl %eax, %eax movq %rbp, %rsp popq %rbp ret 指令来缩短代码。你没有做movq %rsp, %rbp - 相对寻址,所以你不需要这里。而且,as Peter Cordes pointed out in a comment,您可以将RBP优化为movq str, %rdi。您的最终代码就是:

movl  str, %edi