【问题标题】:Why does this give me a segfault为什么这会给我一个段错误
【发布时间】:2016-12-30 22:50:44
【问题描述】:

我正在尝试编写一个 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

【问题讨论】:

  • 你应该使用movabsq $str, %rdi来加载地址。您所做的是加载字符串的前 8 个字节,然后可怜的 printf 试图用作地址并出现故障。此外,您需要在 call 之前将 %al 归零。
  • @Jester mov $str,%rdi 就足够了,因为 ABI 将数据和代码放在地址空间的前 2 GB 内。
  • @fuz: mov $str, %edi 当然更好,因为 GNU as 没有将 7 字节符号扩展的 mov-immediate 优化为 5 字节的零扩展 mov-立即。
  • 要格式化代码,不要使用<code>,选择代码并在编辑器中按下{}按钮。
  • 确保您最初遵循目标平台的课程信息/教程 + 汇编器语法(在这种情况下,即 gas + 64b clib)。并且气体语法在 IMO 中对于人类来说比 Intel 语法更难读/写,所以你必须特别注意所有那些奇怪的符号,比如 $ 是否领先于数字,使其完全不同的指令.一旦您对基础知识有所熟悉并完全了解指令在 CPU 中的工作原理,您就可以轻松地选择不同的语法/平台示例来获得灵感,但最初它们只会让您感到困惑。

标签: assembly x86-64 gnu-assembler


【解决方案1】:

您违反了调用约定。由于这是在 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 寄存器返回整数结果,因此您只需在返回之前再次将 RAX 归零。现在,printfRAX 中返回它的结果,并且由于你没有设置RAX,你实际上是从main 返回printf 的结果。这是合法的,但可能不是你想要的。

所以,您的 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 指令来使代码更短。你没有做RBP-relative 寻址,所以你不需要在这里。而且,as Peter Cordes pointed out in a comment,您可以将movq str, %rdi 优化为movl str, %edi。您的最终代码将是:

  pushq  %rbp         # reserve 8 bytes of space on the stack
  movl   str,  %edi   # get address of string constant
  xorl   %eax, %eax   # clear RAX register in preparation for calling printf
  callq  printf
  xorl   %eax, %eax   # clear RAX register so we return 0
  popq   %rbp         # clean up the stack
  ret

【讨论】:

  • movq str, %rdi 从字符串中加载 8 个字节,这是 printf 段错误的主要原因(除非 OP 有一个使用 AL 进行计算跳转的旧 libc,而不仅仅是 0 / 非 0 检查保存所有 XMM regs)。您希望 mov $str, %edilea str(%rip), %rdi 传递指向标签的指针。
猜你喜欢
  • 2013-11-14
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-10
相关资源
最近更新 更多