目标文件重定位表中条目的含义

Bec*_*ter 6 c linker x86-64 relocation

我在理解从 C 源文件编译的重定位表的条目时遇到了一些问题。我的程序如下:

//a.c
extern int shared;
int main(){
    int a = 100;
    swap(&a, &shared);
    a = 200;
    shared = 1;
    swap(&a, &shared);
}
//b.c
int shared = 1;
void swap(int* a, int* b) {
    if (a != b)
        *b ^= *a ^= *b, *a ^= *b;
}
Run Code Online (Sandbox Code Playgroud)

我使用以下命令gcc -c -fno-stack-protector a.c b.cld a.o b.o -e main -o ab. 然后我objdump -r a.o检查它的重定位表。

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
0000000000000014 R_X86_64_32       shared
0000000000000021 R_X86_64_PC32     swap-0x0000000000000004
000000000000002e R_X86_64_PC32     shared-0x0000000000000008
000000000000003b R_X86_64_32       shared
0000000000000048 R_X86_64_PC32     swap-0x0000000000000004
Run Code Online (Sandbox Code Playgroud)

的拆卸a.o

Disassembly of section .text:

0000000000000000 <main>:
0:  55                      push   %rbp
1:  48 89 e5                mov    %rsp,%rbp
4:  48 83 ec 10             sub    $0x10,%rsp
8:  c7 45 fc 64 00 00 00    movl   $0x64,-0x4(%rbp)
f:  48 8d 45 fc             lea    -0x4(%rbp),%rax
13: be 00 00 00 00          mov    $0x0,%esi
18: 48 89 c7                mov    %rax,%rdi
1b: b8 00 00 00 00          mov    $0x0,%eax
20: e8 00 00 00 00          callq  25 <main+0x25>
25: c7 45 fc c8 00 00 00    movl   $0xc8,-0x4(%rbp)
2c: c7 05 00 00 00 00 01    movl   $0x1,0x0(%rip)  # 36 <main+0x36>
33: 00 00 00 
36: 48 8d 45 fc             lea    -0x4(%rbp),%rax
3a: be 00 00 00 00          mov    $0x0,%esi
3f: 48 89 c7                mov    %rax,%rdi
42: b8 00 00 00 00          mov    $0x0,%eax
47: e8 00 00 00 00          callq  4c <main+0x4c>
4c: b8 00 00 00 00          mov    $0x0,%eax
51: c9                      leaveq 
52: c3                      retq  
Run Code Online (Sandbox Code Playgroud)

我的问题是: shared在 14 和shared2e 是完全相同的对象。为什么它们有不同的符号名称?

ead*_*ead 3

这是相同的地址,但重定位类型不同。重定位类型在x86-64-abi中定义。

有什么不同?

At 0x14and 0x3b:必须将全局变量的地址shared移至寄存器%rsi才能调用该函数swap

但是,因为程序是用-mcmodel=small(gcc的默认值,另请参阅这个问题)编译的,所以编译器可以假设该地址适合32位并使用movl而不是movq(实际上编译器会使用其他指令,但movl与“naive”相比movq很好地解释了差异),这需要编码更多字节。

因此,最终的重定位是R_X86_64_32(即 64 位地址在没有符号扩展的情况下截断为 32 位)而不是R_X86_64_64,即链接器将写入地址的 4 个低字节而不是占位符,占位符也是 4 个字节宽。

您想0x2e将值写入1内存地址shared。然而,目标地址是相对于 给出的%rip,即相对于0x36

movl   $0x1,0x0(%rip)  # 36 <main+0x36>
Run Code Online (Sandbox Code Playgroud)

shared显然,仅仅输入via的绝对地址R_X86_64_32不会有任何好处——需要更复杂的计算,这就是目的R_X86_64_PC32

再次,由于编译器可以假设的代码模型较小,32 位 rip 相对偏移量就足够了(因此使用重定位R_X86_64_PC32而不是R_X86_64_PC64重定位),并且占位符只有 4 个字节宽。

取自 x86-64-abi,重定位的公式为(第 4.4 节):

result = S+A-P (32bit-word, i.e. the lower 4 bytes of the result) 
S = the value of the symbol whose index resides in the relocation entry 
A = the addend used to compute the value of the relocatable field 
P = the place (section offset or address) of the storage unit being relocated (computed using r_offset)
Run Code Online (Sandbox Code Playgroud)

这意味着:

  • S是变量的地址shared
  • A-8(例如可以通过调用readelf -r a.oor看到objdump -r a.o),因为重定位的偏移量0x2e与实际的%rip-之间存在 8 个字节的差异0x36
  • P是重定位的偏移量,即0x26P-A是 中的地址%rip

可以看到,结果并不S像上面的情况R_X86_64_32,而是S - (P-A)。在生成的二进制文件中也可以看到 - 将在这两种不同的重定位类型的占位符处修补不同的值。


Eli Bendersky 有一篇关于这个主题的精彩文章