在可重定位程序中使用 PC 相对寻址的绝对寻址。修改记录是怎样的?

Absolute addressing using PC-relative addressing in a relocatable program. What would the modification record be like?

我正在尝试解决 Leland L. Beck 的 系统软件:系统编程简介(对于 VTU)3/e 第 2.2 节中的以下练习。

There is an assembler for a machine that has only program-counter relative addressing. If we are to assemble an instruction whose operands is an absolute address in memory - for example LDA 100, to load register A from address (hexadecimal) 100 in memory. How might such an instruction be assembled in a relocatable program?

What relocation operations would be required?

我对如何使用 PC 相对寻址来完成绝对寻址感到有点困惑。我相信汇编程序需要为加载程序生成一个命令,指示它存储程序的起始地址(比如 BEGIN)并在遇到 LDA 100 时跳转到地址 100通过将 PC 减少 PC - 100,从而跳转到内存中的绝对地址 100。但是,我不确定修改记录会是什么样子。

有人可以对此进行澄清吗?

我认为你需要一个给出绝对地址的重定位记录,让动态链接器/runtime-fixup-applier计算正确的相对位移以从重定位的位置到达绝对地址适用。

可能没有那么简单。例如x86-64 RIP-relative寻址是相对于指令的末尾,但是例如mov [RIP+rel32], imm32是用寻址方式的rel32部分之后紧接编码的。但如果没有立即数,通常是在指令的末尾。因此,寻址模式相对的点可能不是固定位置 wrt。您必须申请的职位。

但我们可以将其留给汇编程序,让它添加一些偏移量以解决基址中的差异,这样重定位最终将以正确的绝对地址为目标。

这使重定位记录保持紧凑,与 "normal" 的大小相同:只是一个应用它的位置、一个类型和一个 32 位绝对地址或机器使用的任何宽度。 (您甚至可以将绝对地址编码到 PC-relative 偏移量所在的位置,如果它总是足够宽的话。)

或正确的相对偏移量以达到相对于某些基数的所需绝对地址,例如0. 这就是 GNU/Linux ELF .o 文件使用的内容,PIE 可执行文件也是如此。这也解决了偏移重定位的问题,以考虑存储位置和相对位置之间的任何可变距离。

因此,例如要将整个图像从 0 重定位到 0x10000,您只需从每个 absolute-target 相对重定位中减去 0x10000


顺便说一句,您可以在 GNU/Linux 和 GAS 上针对 i386 相对调用指令在实践中执行此操作。 x86 上的 Near 调用始终使用 call rel32 编码,但支持必要重定位的平台上的优秀汇编程序可让您编写绝对目标并为链接器提供正确的重定位。 (Call an absolute pointer in x86 machine code)

# foo.s
.globl _start
_start:
nop                 # some padding so the base of the call address doesn't start at 0
nop
call 0x123456       # relative call to that absolute address

gcc -m32 -c foo.s构建,用objdump -drwC -Mintel反汇编

foo.o:     file format elf32-i386

Disassembly of section .text:

00000000 <_start>:
   0:   90                      nop
   1:   90                      nop
   2:   e8 52 34 12 00          call   123459 <_start+0x123459> 3: R_386_PC32   *ABS*

readelf -a foo.o 显示重定位部分如下:

Relocation section '.rel.text' at offset 0x94 contains 1 entry:
 Offset     Info    Type            Sym.Value  Sym. Name
00000003  00000002 R_386_PC32       

目标地址不是此搬迁记录的一部分;它被编码到现有的机器代码中。这适用于 i386 但可能并不总是适用于 x86-64。没有 -m32 的建筑给我们:

Relocation section '.rela.text' at offset 0xb0 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000003  000000000002 R_X86_64_PC32                        123452

无论哪种方式,请注意 "offset" 是应用它的地方(2 NOP 字节加上 "call" 的操作码字节意味着 rel32 从 3 字节开始进入该部分,从基数为 0。)x86-64 重定位中的 0x123452 是真正的目标 ...6 减去 rel32 的长度(4 个字节)。

"name + addend" 列 header 对于您通过使用偏移量定位符号名称而获得的重定位很有意义。例如mov eax, [global_array + 12] 需要从链接器放置 global_array.

开头的 12 个字节开始加载

另请注意,我们查看的是 un-linked .o 文件,而不是链接的可执行文件。 x86-64 ELF shared objects 不允许运行时修复 32 位绝对目标;整个 object 可能随机位于超过 +-2GiB 之外。 (这就是我使用 -m32 的原因)。

似乎 32 位 PIE 可执行文件也不能正确支持这一点。可能是因为 position-independent 就在名字中。 :P 使用 gcc -m32 -pie -nostdlib foo.s 构建给了我们 e8 4f 24 12 00 的编码,它适用于 0x1000 的图像库。 (即使在 GDB 中设置断点并启动 PIE 可执行文件以应用重定位后也是如此。)

但是如果我们使用 gcc -m32 -shared -nostdlib foo.s 构建共享库,文本重定位仍然是允许的:

$ gcc -m32 -shared -nostdlib foo.s && objdump -drwC -Mintel a.out

a.out:     file format elf32-i386


Disassembly of section .text:

00001000 <_start>:
    1000:       90                      nop
    1001:       90                      nop
    1002:       e8 4f 24 12 00          call   123456 <_DYNAMIC+0x1204ae>

请注意,反汇编使用重定位信息来计算正确的最终调用目标。

但我认为这实际上是错误的,因为 readelf 输出没有显示任何重定位。执行它仍然失败(甚至跳转到正确的地址);我们得到 0xf7ffc002 <+2>: e8 4f 24 12 00 call 0xf811e456

无论如何,我认为 GNU/Linux 上的运行时重定位失败只是因为我在滥用文本重定位。 .o object 文件的搬迁记录完全有效。