重定位 R_X86_64_32S 对符号 `stdout@@GLIBC_2.2.5' 不能在制作 PIE 对象时使用

relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a PIE object

问题

我目前正在阅读 this book 以及有关动态 linking 的章节,代码如下:

link_example.s

.globl main

.section .data

output:
    .ascii "Yeet\n[=12=]"

.section .text
main:
    enter [=12=], [=12=]
    movq stdout, %rdi
    movq $output, %rsi
    call fprintf

    movq [=12=], %rax
    leave
    ret

现在按照书上的要求,我需要按如下方式编译成link动态C库:

gcc -rdynamic link_example.s -o link_example

但我收到以下错误消息:

/usr/bin/ld: /tmp/cchUlvqS.o: relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a PIE object; recompile with -fPIE
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

我做错了什么?

你试过什么?

添加 -fPIE 标志

我通过添加 -fPIE 标志尝试了编译器的建议:

gcc -rdynamic -fPIE link_example.s -o link_example

但我仍然遇到同样的错误。

正在搜索类似的帖子

我发现 similar post 说,我只需要使用 -shared 标志:

gcc -shared link_example.s -o link_example

但这给了我:

/usr/bin/ld: /tmp/ccxktZan.o: relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

如果我添加 -fPIC 标志:

gcc -shared -fPIC link_example.s -o link_example

然后我得到这个:

/usr/bin/ld: /tmp/ccKIQ9sl.o: relocation R_X86_64_32S against symbol `stdout@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

让我向您展示如何修复您书中的汇编语言,使其适用于您的编译器的默认设置。

正如对问题的评论所说,问题是您的编译器默认生成 position-independent executables。这意味着 stdoutfprintfoutput 的地址在 link 时未知,因此 linker 无法“重新定位”指令指的是他们。

然而, 在 link 时已知的是这些东西的地址与程序计数器之间的偏移量].这意味着,如果您编写的程序集稍有不同,它就可以工作。像这样:

.globl main

.section .data

output:
    .ascii "Yeet\n[=10=]"

.section .text
main:
    enter [=10=], [=10=]
    movq stdout(%rip), %rdi
    leaq output(%rip), %rsi
    call fprintf@PLT

    movq [=10=], %rax
    leave
    ret

请注意,这三者的变化略有不同。 mov stdout, %rdi 变为 mov stdout(%rip), %rdi —— 只是同一指令的不同寻址 mode。从固定地址 stdout 处的内存加载变为从 RIP 寄存器(也称为程序计数器)的固定 位移 stdout 处从内存加载。另一方面,用mov $output, %rsi加载固定地址output,变成lea output(%rip), %rsi。我建议您将此视为 一直是 一个 load-effective-address 操作,但是旧代码(在固定地址处执行 executable )能够用 move-immediate 而不是实际的 lea 指令来表达该操作。最后,call fprintf 变成了 call fprintf@PLT。这告诉 linker 调用需要经过 过程 linkage table -- 你的书应该解释这是什么为什么需要它。

顺便说一句,我看到了这个汇编语言的其他几个问题,其中最重要的是:

  • 字符串 "Yeet\n[=24=]" 属于 read-only 数据部分。
  • x86-64 ABI 表示需要通过适当设置 eax 来告知像 fprintf 这样的可变参数函数它们正在接收的浮点参数的数量。
  • enterleave 在 x86-64 上是不必要的。 (另外,enter 是一个非常慢的微编码指令,根本不应该使用。)

我会写这样的东西:

    .section .rodata, "a", @progbits
.output:
    .string "Yeet\n"

    .section .text, "ax", @progbits
    .globl main
    .type  main, @function
main:
    sub    , %rsp
    mov    stdout(%rip), %rdi
    lea    .output(%rip), %rsi
    xor    %eax, %eax
    call   fprintf@PLT
    xor    %eax, %eax
    add    , %rsp
    ret

(你需要在函数的开头从%rsp中减去8,然后再把它加回来,因为ABI说%rsp必须总是16的倍数call 指令处 -- 这意味着它 不是 进入任何函数时的 16 的倍数,而是 %rsp mod 16 是 8,因为 call 压入八个字节(return 地址)。作为 enter 和 [= 的副作用,你免费得到这个28=],但把那些拿出来,你必须手工完成。)