引起错误的最简示例
main.S
将一个地址(32位)移动到 %eax
中。
main.S
(注:本文中的“移动”指汇编语言中的指令操作,不是物理上的移动)
_start:
mov $_start, %eax
linker.ld
SECTIONS
{
. = 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://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”?
-fPIC
标志而意外地构建了64位目标文件。我曾因此困扰了一段时间。 - Conrad Meyer