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

7

我在理解由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;
}

我使用以下命令编译和链接它们: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
< p > 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  

我的问题是:在14处和2e处的shared完全是相同的对象。为什么它们有不同的符号名称?
1个回答

3
那是相同的地址,但重定位类型不同。重定位类型在x86-64-abi中定义。
有什么区别?
0x140x3b处:全局变量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>

显然,仅仅通过使用R_X86_64_32来放置shared的绝对地址是没有用处的 - 需要进行更复杂的计算,这就是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)

这意味着:
- Sshared 变量的地址。 - A-8(例如通过调用 readelf -r a.oobjdump -r a.o 可以看到),因为重定位 0x2e 的偏移量与实际 %rip - 0x36 之间存在8字节的差异。 - P 是重定位的偏移量,即 0x26P-A%rip 中的地址。
正如您所见,结果不是像上面的 R_X86_64_32 那样的 S,而是 S - (P-A)。这也可以在生成的二进制文件中看到——这两种不同的重定位类型将在占位符处修补不同的值。
关于此主题,Eli Bendersky 撰写了一篇很棒的文章,请参考

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