生成错误的最小示例
main.S 将 地址 移动到 %eax(32 位)。
main.S
_start:
mov $_start, %eax
链接器.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 上
-
如果
ENTRY,ld 使用.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://stackoverflow.com/a/30507725/895245
接下来看看objdump -Sr main.o:
0000000000000000 <_start>:
0: b8 00 00 00 00 mov $0x0,%eax
1: R_X86_64_32 .text
如果我们查看英特尔手册中指令的编码方式,我们会发现:
-
b8 表示这是 mov 到 %eax
-
0 是要移动到 %eax 的立即值。然后重定位将修改它以包含_start 的地址。
移动到 32 位寄存器时,立即数也必须是 32 位。
但是这里,重定位必须修改那些32位,以便在链接发生后将_start的地址放入其中。
0x100000000 不适合 32 位,但 0xFFFFFFFF 可以。因此错误。
这个错误只会发生在产生截断的重定位上,例如R_X86_64_32(8 字节到 4 字节),但从不在 R_X86_64_64 上。
还有一些类型的重定位需要 sign 扩展而不是零扩展,如此处所示,例如R_X86_64_32S。另见:https://stackoverflow.com/a/33289761/895245
R_AARCH64_PREL32
提问于:How to prevent "main.o:(.eh_frame+0x1c): relocation truncated to fit: R_AARCH64_PREL32 against `.text'" when creating an aarch64 baremetal program?