目标文件分为三种:可重定位目标文件,可执行目标文件和共享目标文件。Linux中使用ELF格式 (举例的两个.c文件:)
/* main.c */ /* $begin main */ int sum(int *a, int n); int array[2] = {1, 2}; int main() { int val = sum(array, 2); return val; } /* $end main */ /* sum.c */ /* $begin sum */ int sum(int *a, int n) { int i, s = 0; for (i = 0; i < n; i++) { s += a[i]; } return s; } /* $end sum */3.重定位 当汇编器生成一个目标模块时,它并不知道数据和代码最终放在内存中的什么位置,也不知道这个模块引用的任何外部定义的函数或全局变量的位置。 所以当汇编器遇到对未知位置的引用时就会生成一个重定位条目,告诉链接器在目标文件合并时如何修改这个引用。 代码的重定位条目在.rel.text中,已初始化数据的重定位条目在.rel.data中。
看重定位条目: $ readelf -r main.o $ readelf -r sum.o offset时需要被修改的引用的节偏移。 type告知链接器如何修改新的引用(R_X86_64_PC32是重定位PC相对地址的引用,R_X86_64_32是重定位绝对地址的引用)。这里显示的PLT32。 symbol标识被修改引用应该指向的符号。 addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。反汇编main.o也能看到这部分数据。
重定位相对引用:
$ objdump -dx main 00000000000005fa <main>: 5fa: 55 push %rbp 5fb: 48 89 e5 mov %rsp,%rbp 5fe: 48 83 ec 10 sub $0x10,%rsp 602: be 02 00 00 00 mov $0x2,%esi 607: 48 8d 3d 02 0a 20 00 lea 0x200a02(%rip),%rdi # 201010 <array> 60e: e8 08 00 00 00 callq 61b <sum> 613: 89 45 fc mov %eax,-0x4(%rbp) 616: 8b 45 fc mov -0x4(%rbp),%eax 619: c9 leaveq 61a: c3 retq 000000000000061b <sum>: 61b: 55 push %rbp 61c: 48 89 e5 mov %rsp,%rbp 61f: 48 89 7d e8 mov %rdi,-0x18(%rbp) 623: 89 75 e4 mov %esi,-0x1c(%rbp) 626: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 62d: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp) 634: eb 1d jmp 653 <sum+0x38> 636: 8b 45 f8 mov -0x8(%rbp),%eax 639: 48 98 cltq 63b: 48 8d 14 85 00 00 00 lea 0x0(,%rax,4),%rdx 642: 00 643: 48 8b 45 e8 mov -0x18(%rbp),%rax 647: 48 01 d0 add %rdx,%rax 64a: 8b 00 mov (%rax),%eax 64c: 01 45 fc add %eax,-0x4(%rbp) 64f: 83 45 f8 01 addl $0x1,-0x8(%rbp) 653: 8b 45 f8 mov -0x8(%rbp),%eax 656: 3b 45 e4 cmp -0x1c(%rbp),%eax 659: 7c db jl 636 <sum+0x1b> 65b: 8b 45 fc mov -0x4(%rbp),%eax 65e: 5d pop %rbp 65f: c3 retq objdump -dx main.o 0000000000000000 <main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp 8: be 02 00 00 00 mov $0x2,%esi d: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 14 <main+0x14> 10: R_X86_64_PC32 array-0x4 14: e8 00 00 00 00 callq 19 <main+0x19> 15: R_X86_64_PLT32 sum-0x4 19: 89 45 fc mov %eax,-0x4(%rbp) 1c: 8b 45 fc mov -0x4(%rbp),%eax 1f: c9 leaveq 20: c3 retq r.offset = 0x15 r.symbol = sum r.type = R_X86_64_PLT32(书上的是PC32) r.addend = -4 计算引用的运行时地址 ADDR(s) = ADDR(.text) ; ADDR(r.symbol) = ADDR(sum)更新该引用,使它在运行时指向sum程序 refaddr = ADDR(s) + r.offset = 0x5fa + 0x15 = 0x60f *refptr = (unsigned)(ADDR(r.symbol) + r.addend - refaddr) = (unsigned)(0x61b + (-4) - 0x60f) = (unsigned)0x8在得到的可执行文件中,call指令的重定位形式: 60e: e8 08 00 00 00 callq 61b < sum >在运行时,call指令将存放在0x60e处,当CPU执行call指令时,PC的值为0x613(0x5fa+19)即call指令的下一条指令的地址。为执行这条指令,CPU将PC压入栈中,然后PC<—PC+0x8 = 0x61b,这样下一条指令就是sum例程的第一条指令。