目标文件重定位 table 中条目的含义
meaning of an entry in a relocation table of an object file
我在理解从 C 源文件编译的重定位条目 table 时遇到了一些问题。
我的程序如下:
//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.c
和 ld a.o b.o -e main -o ab
编译并 link 它们。
然后我objdump -r a.o
检查它的重定位table。
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
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 [=13=]x10,%rsp
8: c7 45 fc 64 00 00 00 movl [=13=]x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov [=13=]x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov [=13=]x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: c7 45 fc c8 00 00 00 movl [=13=]xc8,-0x4(%rbp)
2c: c7 05 00 00 00 00 01 movl [=13=]x1,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 [=13=]x0,%esi
3f: 48 89 c7 mov %rax,%rdi
42: b8 00 00 00 00 mov [=13=]x0,%eax
47: e8 00 00 00 00 callq 4c <main+0x4c>
4c: b8 00 00 00 00 mov [=13=]x0,%eax
51: c9 leaveq
52: c3 retq
我的问题是:
shared
at 14 和 shared
at 2e 是完全相同的对象。为什么它们有不同的符号名称?
那是同一个地址,只是搬迁类型不同。重定位类型在 x86-64-abi.
中定义
有什么区别?
在0x14
和0x3b
处:全局变量shared
的地址必须移动到寄存器%rsi
才能调用函数swap
.
但是,因为程序是用 -mcmodel=small
编译的(gcc 的默认值,另请参阅 this question),编译器可以假定地址适合 32 位并使用 movl
而不是 movq
(实际上编译器会使用其他指令,但比较 movl
和 "naive" movq
很好地解释了差异),这将需要更多字节进行编码.
因此,重定位结果是 R_X86_64_32
(即 64 位地址在没有符号扩展的情况下被截断为 32 位)而不是 R_X86_64_64
,即链接器将写入地址的 4 个低字节而不是占位符,也是 4 字节宽。
在 0x2e
你想将值 1
写入内存地址 shared
。但是,目标地址是相对于 %rip
给出的,即相对于 0x36
:
movl [=10=]x1,0x0(%rip) # 36 <main+0x36>
显然,仅通过 R_X86_64_32
放置 shared
的绝对地址不会有任何好处 - 需要更复杂的计算,这就是 R_X86_64_PC32
的目的。
再一次,由于编译器可以假定的小代码模型,32 位 rip-relative 偏移量就足够了(因此使用重定位 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)
这意味着:
S
是shared
变量的地址。
A
是-8
(调用readelf -r a.o
或objdump -r a.o
可以看出),因为重定位的偏移量相差8字节0x2e
和实际的 %rip
- 0x36
.
P
是重定位的偏移量,即0x26
。 P-A
是%rip
中的地址。
如你所见,结果不是上面R_X86_64_32
的情况下的S
,而是S - (P-A)
。它也可以在生成的二进制文件中看到 - 将在这两种不同的重定位类型的占位符处修补不同的值。
There 是 Eli Bendersky 关于这个主题的一篇很棒的文章。
我在理解从 C 源文件编译的重定位条目 table 时遇到了一些问题。 我的程序如下:
//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.c
和 ld a.o b.o -e main -o ab
编译并 link 它们。
然后我objdump -r a.o
检查它的重定位table。
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
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 [=13=]x10,%rsp
8: c7 45 fc 64 00 00 00 movl [=13=]x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov [=13=]x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov [=13=]x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: c7 45 fc c8 00 00 00 movl [=13=]xc8,-0x4(%rbp)
2c: c7 05 00 00 00 00 01 movl [=13=]x1,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 [=13=]x0,%esi
3f: 48 89 c7 mov %rax,%rdi
42: b8 00 00 00 00 mov [=13=]x0,%eax
47: e8 00 00 00 00 callq 4c <main+0x4c>
4c: b8 00 00 00 00 mov [=13=]x0,%eax
51: c9 leaveq
52: c3 retq
我的问题是:
shared
at 14 和 shared
at 2e 是完全相同的对象。为什么它们有不同的符号名称?
那是同一个地址,只是搬迁类型不同。重定位类型在 x86-64-abi.
中定义有什么区别?
在0x14
和0x3b
处:全局变量shared
的地址必须移动到寄存器%rsi
才能调用函数swap
.
但是,因为程序是用 -mcmodel=small
编译的(gcc 的默认值,另请参阅 this question),编译器可以假定地址适合 32 位并使用 movl
而不是 movq
(实际上编译器会使用其他指令,但比较 movl
和 "naive" movq
很好地解释了差异),这将需要更多字节进行编码.
因此,重定位结果是 R_X86_64_32
(即 64 位地址在没有符号扩展的情况下被截断为 32 位)而不是 R_X86_64_64
,即链接器将写入地址的 4 个低字节而不是占位符,也是 4 字节宽。
在 0x2e
你想将值 1
写入内存地址 shared
。但是,目标地址是相对于 %rip
给出的,即相对于 0x36
:
movl [=10=]x1,0x0(%rip) # 36 <main+0x36>
显然,仅通过 R_X86_64_32
放置 shared
的绝对地址不会有任何好处 - 需要更复杂的计算,这就是 R_X86_64_PC32
的目的。
再一次,由于编译器可以假定的小代码模型,32 位 rip-relative 偏移量就足够了(因此使用重定位 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)
这意味着:
S
是shared
变量的地址。A
是-8
(调用readelf -r a.o
或objdump -r a.o
可以看出),因为重定位的偏移量相差8字节0x2e
和实际的%rip
-0x36
.P
是重定位的偏移量,即0x26
。P-A
是%rip
中的地址。
如你所见,结果不是上面R_X86_64_32
的情况下的S
,而是S - (P-A)
。它也可以在生成的二进制文件中看到 - 将在这两种不同的重定位类型的占位符处修补不同的值。
There 是 Eli Bendersky 关于这个主题的一篇很棒的文章。