为什么 ELF 目标文件包含字符串文字和标准库函数的虚拟地址?
Why do ELF object files contain dummy addresses for string literals and stdlib functions?
我用 C 写了这个 Hello World :
#include<stdio.h>
int main() {
printf("Hello world !\n");
return 0;
}
用gcc编译成汇编代码
我明白了:
.file "file.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello world !"
.section .text.unlikely,"ax",@progbits
.LCOLDB1:
.section .text.startup,"ax",@progbits
.LHOTB1:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
subq , %rsp
.cfi_def_cfa_offset 16
movl $.LC0, %edi
call puts
xorl %eax, %eax
addq , %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.section .text.unlikely
.LCOLDE1:
.section .text.startup
.LHOTE1:
.ident "GCC: (GNU) 4.9.2 20150304 (prerelease)"
.section .note.GNU-stack,"",@progbits
这里没问题。但是现在,我想将汇编代码与 objdump 反汇编的代码进行比较:
对于主要功能,我得到了这个:
0000000000000000 <main>:
0: 48 83 ec 08 sub [=12=]x8,%rsp
4: bf 00 00 00 00 mov [=12=]x0,%edi
5: R_X86_64_32 .rodata.str1.1
9: e8 00 00 00 00 callq e <main+0xe>
a: R_X86_64_PC32 puts-0x4
e: 31 c0 xor %eax,%eax
10: 48 83 c4 08 add [=12=]x8,%rsp
14: c3 retq
我不明白两件事:
为什么在edi
上移动数字0意味着加载字符串"Hello world"?
另外,指令callq
调用地址e
。但是地址e
处的指令不是函数puts
而是xor
。那么真实地址是什么?
答案是 linker 应用了各种修正。当我执行 objdump -d hello.o
时,我得到这个:
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov [=10=]x0,%edi
9: e8 00 00 00 00 callq e <main+0xe>
e: b8 00 00 00 00 mov [=10=]x0,%eax
13: 5d pop %rbp
14: c3 retq
然而,objdump -d hello
的摘录产生了这个:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: bf e0 05 40 00 mov [=11=]x4005e0,%edi
40053f: e8 cc fe ff ff callq 400410 <puts@plt>
400544: b8 00 00 00 00 mov [=11=]x0,%eax
400549: 5d pop %rbp
40054a: c3 retq
不同之处在于,字符串偏移量的零和 puts
的地址现在实际上由 linker 填充。您可以使用 objdump -r hello.o
找到那些 重定位条目
hello.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000005 R_X86_64_32 .rodata
000000000000000a R_X86_64_PC32 puts-0x0000000000000004
也就是说,linker 找到了 .rodata
的实际地址(这是字符串的地址)并将其放在偏移量 0x5
和 0x5
的地址处库 puts
代码并将其放置在偏移量 0xa
.
处
This article on relocation 更详细地描述了该过程,并正确地指出虽然某些重定位发生在 link 时间,但加载程序也可以提供重定位数据。
我用 C 写了这个 Hello World :
#include<stdio.h>
int main() {
printf("Hello world !\n");
return 0;
}
用gcc编译成汇编代码 我明白了:
.file "file.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello world !"
.section .text.unlikely,"ax",@progbits
.LCOLDB1:
.section .text.startup,"ax",@progbits
.LHOTB1:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
subq , %rsp
.cfi_def_cfa_offset 16
movl $.LC0, %edi
call puts
xorl %eax, %eax
addq , %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.section .text.unlikely
.LCOLDE1:
.section .text.startup
.LHOTE1:
.ident "GCC: (GNU) 4.9.2 20150304 (prerelease)"
.section .note.GNU-stack,"",@progbits
这里没问题。但是现在,我想将汇编代码与 objdump 反汇编的代码进行比较:
对于主要功能,我得到了这个:
0000000000000000 <main>:
0: 48 83 ec 08 sub [=12=]x8,%rsp
4: bf 00 00 00 00 mov [=12=]x0,%edi
5: R_X86_64_32 .rodata.str1.1
9: e8 00 00 00 00 callq e <main+0xe>
a: R_X86_64_PC32 puts-0x4
e: 31 c0 xor %eax,%eax
10: 48 83 c4 08 add [=12=]x8,%rsp
14: c3 retq
我不明白两件事:
为什么在edi
上移动数字0意味着加载字符串"Hello world"?
另外,指令callq
调用地址e
。但是地址e
处的指令不是函数puts
而是xor
。那么真实地址是什么?
答案是 linker 应用了各种修正。当我执行 objdump -d hello.o
时,我得到这个:
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov [=10=]x0,%edi
9: e8 00 00 00 00 callq e <main+0xe>
e: b8 00 00 00 00 mov [=10=]x0,%eax
13: 5d pop %rbp
14: c3 retq
然而,objdump -d hello
的摘录产生了这个:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: bf e0 05 40 00 mov [=11=]x4005e0,%edi
40053f: e8 cc fe ff ff callq 400410 <puts@plt>
400544: b8 00 00 00 00 mov [=11=]x0,%eax
400549: 5d pop %rbp
40054a: c3 retq
不同之处在于,字符串偏移量的零和 puts
的地址现在实际上由 linker 填充。您可以使用 objdump -r hello.o
hello.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000005 R_X86_64_32 .rodata
000000000000000a R_X86_64_PC32 puts-0x0000000000000004
也就是说,linker 找到了 .rodata
的实际地址(这是字符串的地址)并将其放在偏移量 0x5
和 0x5
的地址处库 puts
代码并将其放置在偏移量 0xa
.
This article on relocation 更详细地描述了该过程,并正确地指出虽然某些重定位发生在 link 时间,但加载程序也可以提供重定位数据。