R_X86_64_32S和R_X86_64_64重定位是什么意思?

63

我试图在64位FreeBSD上编译C应用程序时,遇到了以下错误:

重定位R_X86_64_32S无法在制作共享对象时使用;请使用-fPIC重新编译

R_X86_64_32S重定位是什么?R_X86_64_64呢?

我已经通过谷歌搜索得到了该错误的可能原因,但如果有人能告诉我R_X86_64_32S的真实含义,那就太好了。


10
相关信息:最近Linux发行版已经开始默认启用 位置无关可执行文件(PIE),用于gcc编译器。如果尝试将任何非PIE代码链接到可执行文件中,包括使用mov $symbol,%edi而不是lea symbol(%rip),%rdi的汇编代码,都会出现此错误。如果想要传统的位置相关可执行文件,请参阅在x86-64 Linux中不再允许32位绝对地址?,了解如何使用gcc -no-pie -fno-pie - Peter Cordes
1
@PeterCordes 我认为你的评论值得单独回答。 - Manuel Selva
@ManuelSelva:我的评论中的链接是指我对这个主题的回答。它并不完全回答了这个问题(关于重定位是什么)。这里已经有很好的答案了。 - Peter Cordes
6个回答

42

R_X86_64_32SR_X86_64_64是针对amd64架构编译代码的重定位类型名称。您可以在amd64 ABI上查找所有这些名称。

根据ABI,R_X86_64_64可分为:

  • R_X86_64 - 所有名称都以此为前缀
  • 64 - 直接64位重定位

R_X86_64_32S可分为:

  • R_X86_64 - 前缀
  • 32S - 将值截断为32位并进行符号扩展

在两种情况下基本上意味着“指向此重定位的符号的值加上任何附加量”。对于R_X86_64_32S,链接器会验证生成的值是否符号扩展为原始64位值。

现在,在可执行文件中,代码段和数据段被分配了指定的虚拟基地址。可执行代码不是共享的,每个可执行文件都有自己新的地址空间。这意味着编译器完全知道数据段将在哪里,并且可以直接引用它。另一方面,库只能知道其数据段将位于距基地址指定偏移的地方;该基地址的值只能在运行时知道。因此,所有库都必须使用可以在内存中的任何位置执行的代码来生成,称为位置独立代码(简称PIC)。

现在,当解决您的问题时,错误消息会说明问题所在。


35

为了理解这些内容,您必须首先:

标准

R_X86_64_64R_X86_64_32R_X86_64_32S均由System V AMD ABI定义,其中包含ELF文件格式的AMD64特定信息。

这些是重定位项的ELF32_R_TYPE字段的所有可能值,由System V ABI 4.1 (1997)指定,该标准规定了ELF格式的体系结构中立部分。该标准仅规定了该字段,但未规定其特定于体系结构的值。
在4.4.1“重定位类型”下,我们可以看到摘要表:
Name          Field   Calculation
------------  ------  -----------
R_X86_64_64   word64  A + S
R_X86_64_32   word32  A + S
R_X86_64_32S  word32  A + S

我们稍后会解释这个表格。

还有一条注释:

R_X86_64_32R_X86_64_32S重定位将计算出的值截断为32位。链接器必须验证R_X86_64_32(R_X86_64_32S)重定位生成的值是否对原始的64位值进行了零扩展(符号扩展)。

R_X86_64_64和R_X86_64_32的示例

让我们先看一下R_X86_64_64R_X86_64_32

.section .text
    /* Both a and b contain the address of s. */
    a: .long s
    b: .quad s
    s:

然后:

as --64 -o main.o main.S
objdump -dzr main.o

包含:

0000000000000000 <a>:
   0:   00 00                   add    %al,(%rax)
                        0: R_X86_64_32  .text+0xc
   2:   00 00                   add    %al,(%rax)

0000000000000004 <b>:
   4:   00 00                   add    %al,(%rax)
                        4: R_X86_64_64  .text+0xc
   6:   00 00                   add    %al,(%rax)
   8:   00 00                   add    %al,(%rax)
   a:   00 00                   add    %al,(%rax)

在Ubuntu 14.04和Binutils 2.24上进行测试。

暂时忽略反汇编(因为这是数据,没有意义),只看标签、字节和重定位。

第一个重定位:

0: R_X86_64_32  .text+0xc

这意味着:
  • 0:作用于字节0(标签a
  • R_X86_64_:AMD64 System V ABI的所有重定位类型使用的前缀
  • 32:标签s的64位地址被截断为32位地址,因为我们只指定了一个.long(4个字节)
  • .text:我们在.text部分
  • 0xc:这是重定位条目的一个字段,称为“加数”

重定位的地址计算如下:

A + S

在哪里:

  • A:被加数,在此处为 0xC
  • S:重定位之前符号的值,在此处为 00 00 00 00 == 0

因此,重定位后,新地址将是 0xC == 12 字节进入 .text 部分。

这正是我们所期望的,因为 s 在一个 .long(4 字节)和一个 .quad(8 字节)之后。

R_X86_64_64 是类似的,但更简单,因为这里没有必要截断 s 的地址。这通过在 Field 列上使用 word64 而不是 word32 来指示标准。

R_X86_64_32S vs R_X86_64_32

R_X86_64_32SR_X86_64_32 之间的区别是链接器何时会抱怨“重定位被截断以适应”:

  • 32:如果重定位后截断的值没有零扩展旧值,则会发出投诉,即截断的字节必须为零:

    例如:FF FF FF FF 80 00 00 0080 00 00 00 会生成一个投诉,因为 FF FF FF FF 不为零。

  • 32S:如果重定位后截断的值没有 符号扩展 旧值,则会发出投诉。

    例如:FF FF FF FF 80 00 00 0080 00 00 00 是可以的,因为 80 00 00 00 的最后一位和截断的位都是 1。

另请参见:GCC 报错 "... relocation truncated to fit..." 是什么意思?

R_X86_64_32S 可以通过以下方式生成:

.section .text
.global _start
_start:
    mov s, %eax
    s:

然后:

as --64 -o main.o main.S
objdump -dzr main.o

给出:
0000000000000000 <_start>:
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax
                        3: R_X86_64_32S .text+0x7

现在我们可以通过链接脚本观察“重定位”被截断以适应32S
SECTIONS
{
    . = 0xFFFFFFFF80000000;
    .text :
    {
        *(*)
    }
}

现在:
ld -Tlink.ld a.o

很好,因为:0xFFFFFFFF80000000 被截断为 80000000,这是一个符号扩展。

但如果我们改变链接器脚本为:

. = 0xFFFF0FFF80000000;

现在它生成了错误,因为那个0使它不再是符号扩展。

使用32S进行内存访问但对立即数使用32的原因:何时更好地使用带符号扩展重定位(如R_X86_64_32S)而不是零扩展(如R_X86_64_32)的汇编器?

R_X86_64_32S和PIE(位置无关可执行文件)

不能在位置无关可执行文件中使用R_X86_64_32S,例如使用gcc -pie完成,否则链接将失败:

relocation R_X86_64_32S against `.text' can not be used when making a PIE object; recompile with -fPIC

l

我已经提供了一个最小化的示例来解释它:什么是gcc和ld中用于位置无关可执行文件的-fPIE选项?

“mov s, %eax” 是一个有点令人困惑的有符号32位绝对重定位示例,因为你通常不会写这个,除非是出于意外。很容易忽略你遗漏了“(%rip)”,以及它是一个加载而不是 mov-immediate($s,这将是零扩展绝对地址,或者使用 mov $s,%rax 进行有符号操作)。更好的方法是 mov s(%rdi), %eax(或 LEA),或 sub $s, %rdi(或 cmp $s, %rdi)。在一个寻址模式中使用带有寄存器偏移量的静态地址是32位绝对地址的主要用例之一。 - Peter Cordes
1
相关:x86-64 Linux不再允许32位绝对地址?:在Linux发行版上配置gcc时,使用-pie -fpie作为默认选项现在很常见,因此这将影响那些制作可执行文件以及有意制作共享库的人。 - Peter Cordes
@Ciro,-mcmodel=large是否默认指示编译器使用R_X86_64_64? - Dmitry Mikushin
@DmitryMikushin 抱歉,我不知道,请告诉我如果你找到了答案。 - Ciro Santilli OurBigBook.com
1
我认为是这样的。有趣的是,GCC crt启动对象仍然是R_X86_64_32。需要使用-mcmode = large标志手动重新创建它们,如此处所示:https://sourceware.org/bugzilla/show_bug.cgi?id=26386 - Dmitry Mikushin
@DmitryMikushin 好的,不幸的是我并不感到惊讶。crt对象很邪恶。 - Ciro Santilli OurBigBook.com

5
这意味着你编译共享对象时没有使用应该加上的-fPIC标志:
 gcc -shared foo.c -o libfoo.so # Wrong

您需要调用

 gcc -shared -fPIC foo.c -o libfoo.so # Right

在 ELF 平台(Linux)下,共享对象使用位置无关代码进行编译 - 可以从内存中的任何位置运行的代码。如果没有设置此标志,则生成的代码是位置相关的,因此无法使用此共享对象。

3
我有一个类似的问题,但是即使加了-fPIC选项,我仍然会遇到错误:gcc -std=c99 -Wall -pedantic -shared -fopenmp -fPIC -static test.c -o libtest.so。 有什么想法吗?谢谢! - Ciprian Tomoiagă
这个例子清楚地展示了如何使用gcc的-fPIC标志来修复与"x86-64-32s"和"r-x86-64-64"相关的错误。 - alphaGeek
2
@CiprianTomoiagă 晚到派对了,但在我的情况下...我的依赖链中有一个静态库没有使用 -fPIC 编译。所以我得到了这个错误,即使最外层的库在指令中已经使用了 -fPIC。我想没有什么奇迹吧。解决方法是回到链的底部找到没有使用 -fPIC 编译的静态库并进行更改。 - Timothy John Laird

3
我遇到了这个问题,发现这个答案对我没有帮助。我试图将一个静态库与一个共享库链接在一起。我还研究了将-fPIC开关放在命令行的早期位置(如其他答案中建议的)。 唯一解决我的问题的是将静态库改为共享库。我怀疑关于-fPIC的错误消息可能会由多种原因引起,但基本上您需要查看您的库是如何构建的,并怀疑以不同方式构建的库。

静态库没有使用-fPIC编译是有道理的,因此会使用一些绝对重定位。因此,您无法将该代码链接到可重定位共享对象中。 - Peter Cordes

2
上面的答案展示了这些重定位是什么,我发现使用GCC -mcmodel=large标志构建x86_64对象可以防止R_X86_64_32S,因为在此模型中编译器对重定位地址没有任何假设。
在以下情况下:
extern int myarr[];

int test(int i)
{
  return myarr[i];
}

使用gcc -O2 -fno-pie -c test_array.c编译并使用objdump -drz test_array.o反汇编,我们得到:

 0: 48 63 ff                movslq %edi,%rdi
 3: 8b 04 bd 00 00 00 00    mov    0x0(,%rdi,4),%eax
        6: R_X86_64_32S myarr
 a: c3                      ret    

使用-mcmodel=large,即gcc -mcmodel=large -O2 -fno-pie -c test_array.c,我们得到以下结果:
 0: 48 b8 00 00 00 00 00    movabs $0x0,%rax
 7: 00 00 00 
        2: R_X86_64_64  myarr
 a: 48 63 ff                movslq %edi,%rdi
 d: 8b 04 b8                mov    (%rax,%rdi,4),%eax
10: c3                      ret    

2
在我的情况下,问题出现是因为编译程序期望在远程目录中找到共享库,但实际上只有相应的静态库存在于错误的位置。
实际上,这个重定位错误是一个文件未找到错误的伪装。
我已经详细说明了我如何应对这个问题,在这个其他线程中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接