这个 GCC 错误信息 "... relocation truncated to fit..." 是什么意思?

71
我正在编写主机加速器系统的主机端。主机运行在 Ubuntu Linux 的 PC 上,并通过 USB 连接与嵌入式硬件通信。通信是通过将内存块复制到和从嵌入式硬件的内存中完成的。
在板子的内存中,有一个我用作邮箱的内存区域,我在这里写入和读取数据。邮箱被定义为一个结构体,并使用相同的定义在我的主机空间中分配一个镜像邮箱。
我过去成功地使用过这种技术,所以现在我将主机 Eclipse 项目复制到我的当前项目工作区中,并进行了适当的更名。奇怪的是,在构建主机项目时,我现在收到以下消息:
建立目标: fft2d_host 调用: GCC C 链接器 gcc -L/opt/adapteva/esdk/tools/host/x86_64/lib -o "fft2d_host" ./src/fft2d_host.o -le_host -lrt
./src/fft2d_host.o: 在函数 'main' 中: fft2d_host.c:(.text+0x280): 重定位截断以符合在 ./src/fft2d_host.o 中 COMMON 部分中定义的符号 'Mailbox' 的 R_X86_64_PC32。
这个错误是什么意思?为什么它在当前项目中无法构建,而在老项目中却没问题?
11个回答

59
你正在尝试以某种方式链接你的项目,使得相对寻址方案的目标距离超出了所选相对寻址模式的32位位移的支持范围。这可能是因为当前项目更大,因为它按不同顺序链接对象文件,或者因为存在不必要的广泛映射方案。
这个问题是一个完美的例子,说明为什么经常在错误消息的通用部分进行网页搜索是很有成效的 - 你会发现像这样的东西:

http://www.technovelty.org/code/c/relocation-truncated.html

这提供了一些治疗建议。


4
建议您检查一下是否因为没有使用-fPIC标志而意外地构建了64位目标文件。我曾因此困扰了一段时间。 - Conrad Meyer
44
在网络搜索中,使用错误信息的通用部分找到了这里! - M.M

32

引起错误的最简示例

main.S 将一个地址(32位)移动到 %eax 中。

main.S


(注:本文中的“移动”指汇编语言中的指令操作,不是物理上的移动)
_start:
    mov $_start, %eax

linker.ld

SECTIONS
{
    /* This says where `.text` will go in the executable. */
    . = 0x100000000;
    .text :
    {
        *(*)
    }
}

在 x86-64 上编译:

as -o main.o main.S
ld -o main.out -T linker.ld main.o
ld的输出结果:
(.text+0x1): relocation truncated to fit: R_X86_64_32 against `.text'

请注意:
- 如果没有指定其他部分,as 会把所有内容放在 .text 中。 - 如果没有指定 ENTRYld 将使用 .text 作为默认入口点。因此,_start.text 的第一个字节。
解决方法:请使用以下 linker.ld,并将起始地址减去 1:
SECTIONS
{
    . = 0xFFFFFFFF;
    .text :
    {
        *(*)
    }
}

注意:

  • 在这个例子中,我们不能使用 .global _start_start 变成全局符号,否则它仍然会失败。我认为这是因为全局符号有对齐约束(0xFFFFFFF0 可以工作)。TODO:在ELF标准中哪里记录了这一点?

  • .text 段也有一个对齐约束,即 p_align == 2M。但是,我们的链接器足够聪明,可以将该段放置在 0xFFE00000,填充零直到 0xFFFFFFFF 并设置 e_entry == 0xFFFFFFFF。虽然这样可以实现,但生成的可执行文件过大。

在 Ubuntu 14.04 AMD64 和 Binutils 2.24 上测试通过。

解释

首先,您必须使用最简单的示例来理解重定位:https://dev59.com/WGct5IYBdhLWcg3wZcmL#30507725

接下来,请查看 objdump -Sr main.o 的输出:

0000000000000000 <_start>:
   0:   b8 00 00 00 00          mov    $0x0,%eax
                        1: R_X86_64_32  .text

如果我们查看Intel手册中指令的编码方式,我们可以看到:
- `b8` 表示这是一个往 `%eax` 寄存器中移动数据的 `mov` 指令; - `0` 是要移动到 `%eax` 中的立即数。链接后重定位将其修改为 `_start` 的地址。
当移动到32位寄存器时,立即数也必须是32位的。
但是,在这里,重定位必须修改这些32位以将 `_start` 的地址放入其中。
`0x100000000` 无法放入32位内,但 `0xFFFFFFFF` 可以。因此出现了错误。
这个错误只会发生在生成截断的重定位上,例如 `R_X86_64_32`(8个字节到4个字节),而从不发生在 `R_X86_64_64` 上。
还有一些类型的重定位需要进行符号扩展,而不是像这里所示的零扩展,例如 `R_X86_64_32S`。另请参见:https://dev59.com/SW025IYBdhLWcg3wSkAq#33289761 R_AARCH64_PREL32 来源:如何在创建aarch64裸机程序时防止“main.o:(.eh_frame + 0x1c):对“.text”进行的重定位被截断以适合R_AARCH64_PREL32”?

如果你正在编写汇编代码,那么获取 objdump 提供的信息的另一种方法是使用汇编清单文件。在汇编命令中包含 -al=(file),如下所示:as ... test.s -al=test.lst 此外,请考虑相对地址的来源。在某些情况下,它不是指令的地址,而可能是下一条指令的地址。在 6502 汇编中,BEQ $ 的编码为 F0 FE,原因就在于此。 - cardiff space man

18
在 Cygwin 上,-mcmodel=medium 已经是默认设置,且无法解决问题。对于我来说,将 -Wl,--image-base -Wl,0x10000000 添加到 GCC 链接器中可以修复错误。

1
@garyp 我不知道midenok是如何发现的,但我看到0x1000 0000是32位DLL的MS默认基地址,而0x1 8000 0000是64位DLL的默认值:MS链接器/BASE选项文档 - cxw

16

我在构建一个需要大量堆栈空间(超过2 GiB)的程序时遇到了这个问题。解决方案是添加标志-mcmodel=medium,这个标志被GCC和英特尔编译器都支持。


2
我确认这一点。您还不能使用-fPIC将其编译为库:https://software.intel.com/en-us/forums/intel-fortran-compiler-for-linux-and-mac-os-x/topic/268374#comment-1590873 - Marco Sulla
2
如果这不是显而易见的话,**如果没有必要,不要使用-mcmodel=medium*,因为当处理大型(-mlarge-data-threshold默认为64kiB)静态/全局数组时,它会使汇编代码效率降低。首先寻找其他原因,例如尝试使用-fPIC。不明显的是,为什么超过2GB的堆栈*与默认的-mcmodel=small不兼容,因为全局符号不涉及堆栈内存,并且堆栈已经在正常(-mcmodel=small)可执行文件的低2GiB之外。请参阅https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html。 - Peter Cordes
你在Cygwin上遇到过这个问题吗? - Hashim Aziz
不记得了,但可能不是。 - Rufflewind

15
经常出现这个错误意味着你的程序太大了,而且通常是因为它包含一个或多个非常大的数据对象。例如:
char large_array[1ul << 31];
int other_global;
int main(void) { return other_global; }

如果在默认模式下且没有进行优化编译,x86-64/Linux上会产生“重定位截断以适应”错误。 (如果您打开了优化,至少从理论上讲,它可以确定large_array未使用和/或other_global从未被写入,因此生成不会触发问题的代码。)

默认情况下,GCC在该体系结构上使用其“小代码模型”,其中程序的所有代码和静态分配的数据必须适合地址空间的最低2GB。 (精确的上限约为2GB-2MB,因为任何程序地址空间的最低2MB永久不可用。如果您正在编译共享库或位置无关可执行文件,则所有代码和数据仍必须适合两个千兆字节,但它们不再固定在地址空间底部。)large_array单独占用了所有空间,因此将other_global分配给超出限制的地址,而为main生成的代码无法到达它。您将从链接器获得加密错误,而不是从编译器获得有用的“large_array太大”错误,因为在更复杂的情况下,编译器无法知道other_global将无法到达,因此它甚至不尝试简单情况。

大多数情况下,解决此错误的正确方法是重构程序,使其不需要巨大的静态数组和/或千兆字节的机器码。但是,如果您真的因某种原因需要它们,您可以使用"中等"或"大型"代码模型来解除限制,代价是生成的代码效率略有降低。这些代码模型是针对x86-64特定的;大多数其他体系结构都存在类似的东西,但“模型”集合和相关限制将有所不同。(例如,在32位体系结构上,您可能会有一个“小”模型,其中代码和数据的总量被限制在224字节左右。)

谢谢。正如问题中所指出的,这不是x86代码。虽然这是很久以前的事情了,但我仍然不确定代码模型大小与嵌入式处理器有何关系?这不是x86特定的限制/属性吗? - ysap
@ysap 每个 CPU 架构都有类似的限制 - 通常是由某些机器指令的立即操作数中装入多少位设置的。我写下这个答案,因为一个更新的问题被关闭为此问题的重复,并且我认为现有的回答并没有很好地解决那个人的问题,并且我只是用 x86-64 作为一个方便的例子。(问题中的错误信息包含术语 R_X86_64_PC32,但这确实听起来像您正在编译 x86-64 的代码。也许您真正的问题是 makefile 没有调用应该调用的交叉编译器。) - zwol
我确实在进行交叉编译,但是嘿,即使那家公司不再存在了... 感谢您的建议。 - ysap

11

记得按顺序处理错误信息。在我的案例中,上面的错误是“未定义引用”,但我直接跳过它去查看更有趣的“重定位截断”错误。实际上,我的问题是一个旧库导致了“未定义引用”的消息。一旦我解决了这个问题,“重定位截断”错误也消失了。


我也遇到过这个问题:.o文件已经过期了。头文件引用了不存在的函数。重新编译后问题解决了,我猜在这种情况下链接器会认为位置是“非常大”的而不是“不存在的” :) - robert
很遗憾,未解决的符号通常会产生这种“有趣”但无信息量的消息。处理工具链失败的启发式方法是,如果在修复所有未定义的符号后仍然收到此消息,则存在真正的截断问题。在大师级别上,您可能能够将一些截断错误与特定的未定义符号关联起来,并成功地治愈那些不涉及未定义符号的错误。 - cardiff space man

2
我可能错了,但是根据我的经验,还有另一种可能导致错误的原因,根本原因是编译器(或平台)的限制,这很容易重现并解决。下面是最简单的例子:
1. 声明一个1GB的数组:
char a[1024 x 1024 x 1024]; 结果:可以正常工作,没有警告。自然可以使用1073741824代替三重积。
2. 将上一个数组扩大一倍:
char a[2 x 1024 x 1024 x 1024]; 在GCC中的结果是:"error: size of array 'a' is negative" => 这暗示接受/期望的数组参数的类型为signed int。
3. 基于前面的结果,进行强制类型转换:
char a[(unsigned)2 x 1024 x 1024 x 1024]; 结果:出现错误"relocation truncated to fit",以及这个警告:"integer overflow in expression of type 'int'"。
4. 解决方法:使用动态内存。函数malloc()接受一个size_t类型的参数,它是unsigned long long int的typedef,从而避免了这个限制。
这是我在Windows上使用GCC的经验。仅供参考。

欢迎来到SO并感谢您的回答!该问题是在大约10年前提出的,而我现在已经远离那个任务,因此无法评估您的答案。请继续发布更多的答案、问题和评论,并鼓励其他用户阅读和评论。 - ysap

1

我在MIPS机器上遇到了“重定位截断”错误。在mips上,-mcmodel=medium标志不可用,而是可以使用-mxgot来解决。


1

使用GCC编译器时,有一个-Wl,--default-image-base-low选项,有时可以帮助解决此类错误,例如在某些MSYS2 / MinGW配置中。


1

在64位Windows上链接调用nasm函数的c++程序时,我遇到了这个错误。我使用nasm进行汇编,g++编译c++并进行链接。

在我的情况下,这个错误意味着我需要在nasm汇编代码的顶部加上DEFAULT REL。

在NASM文档中有详细说明: 第11章:编写64位代码(Unix,Win64)

回想起来很明显,但我花了几天时间才找到答案,所以我决定发布这篇文章。

这是C++程序的最小版本:

> extern "C" { void matmul(void); }
 int main(void) {
     matmul();
     return 0;
}

这是nasm程序的最小版本:

    ; "DEFAULT REL" means we can access data in .bss, .data etc
; because we generate position-independent code in 64-bit "flat" memory model.
; see NASM docs
; Chapter 11: Writing 64-bit Code (Unix, Win64)
;DEFAULT REL

global matmul

section .bss
align 32       ; because we want to move 256 bit packed aligned floats to and from it
saveregs  resb 32

section .text
matmul:
push   rbp     ; prologue
mov    rbp,rsp ; aligns the stack pointer

    ; preserve ymm6 in local variable 'saveregs'
    vmovaps [saveregs], ymm6

    ; restore ymm6 from local variable 'saveregs'
    vmovaps ymm6, [saveregs]

mov   rsp,rbp ; epilogue
pop   rbp     ; re-aligns the stack pointer
ret

将DEFAULT REL注释掉后,我收到了上面的错误信息:

g++ -std=c++11 -c   SO.cpp -o SOcpp.o
\bin\nasm -f win64  SO.asm -o SOnasm.obj
g++ SOcpp.o SOnasm.obj -o SO.exe
SOnasm.obj:SO.asm:(.text+0x9): relocation truncated to fit: IMAGE_REL_AMD64_ADDR32 against `.bss'
SOnasm.obj:SO.asm:(.text+0x12): relocation truncated to fit: IMAGE_REL_AMD64_ADDR32 against `.bss'
collect2.exe: error: ld returned 1 exit status

欢迎来到 Stack Overflow 并感谢您的回答!该问题是在大约10年前提出的,而我现在已经离开了那个任务,因此无法评估您的答案。请鼓励您继续发布更多的回答、问题和评论,并呼吁其他用户阅读和评论。 - ysap

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