欢迎访问我的博客新地址:www.heybrt.tk
从helloworld回顾程序的编译过程之三——静态链接2
本文上接:从helloworld回顾程序的编译过程之三——静态链接1
http://blog.csdn.net/you1314520me/article/details/8916113
3、符号的解析和重定位
符号的解析和重定位主要是说将符号引用的位置正确地对应到其定义位置,在程序链接之前,符号的定义和引用位置所在的段都还没有分配地址空间,从上面2中所示main.o等目标文件看到它们段表的VMA和LMA都是空的,
因此这时候也没有办法确定符号的引用位置和定义位置,但是在链接之后,符号的定义和引用位置所在的段都将被分配地址空间,因此需要在链接过程中确定引用和定义的位置。
1)确定符号的定义位置
在链接器将各目标的文件的段合并并为之分配地址空间之后,各符号所在段的位置也就确定了,再回头看2中给出的test.o文件的段表内容,可以看到合并之后test目标文件的代码段.text段的地址为08048094,
这时链接器需要根据相对偏移量计算出符号表的定义位置,这里以add.c文件中的add函数为例进行说明:
假设函数add相对于add.o的.text段的偏移量为Pos, 经过链接之后main.o中的.text段相对于test.o的.text的段偏移量为Pos_Sec,而test.o的.text段在虚地址空间里的位置为Pos_Des,
则符号add定义位置就是:Pos_Des + Pos_Sec + Pos,如下图所示:
2)确定符号的引用位置
下面是main.o、add.o和test三个目标文件的反汇编代码,使用的命令为:objdump -d 目标文件名
目标文件main.o的代码段的反汇编内容为:
- # objdump -d main.o
- main.o: file format elf32-i386
- Disassembly of section .text:
- 00000000 <main>:
- 0: 55 push %ebp
- 1: 89 e5 mov %esp,%ebp
- 3: 83 e4 f0 and $0xfffffff0,%esp
- 6: 83 ec 20 sub $0x20,%esp
- 9: c7 44 24 1c 0d 00 00 movl $0xd,0x1c(%esp)
- 10: 00
- 11: c7 44 24 18 1a 00 00 movl $0x1a,0x18(%esp)
- 18: 00
- 19: 8b 44 24 18 mov 0x18(%esp),%eax
- 1d: 8b 54 24 1c mov 0x1c(%esp),%edx
- 21: 01 c2 add %eax,%edx
- 23: a1 00 00 00 00 mov 0x0,%eax
- 28: 8d 04 02 lea (%edx,%eax,1),%eax
- 2b: 89 44 24 1c mov %eax,0x1c(%esp)
- 2f: a1 00 00 00 00 mov 0x0,%eax
- 34: dd 05 00 00 00 00 fldl 0x0
- 3a: 89 44 24 0c mov %eax,0xc(%esp)
- 3e: dd 5c 24 04 fstpl 0x4(%esp)
- 42: 8b 44 24 1c mov 0x1c(%esp),%eax
- 46: 89 04 24 mov %eax,(%esp)
- 49: e8 fc ff ff ff call 4a <main+0x4a>
- 4e: b8 00 00 00 00 mov $0x0,%eax
- 53: c9 leave
- 54: c3 ret
这里需要注意:
(1) 49行的fc ff ff ff是指一个暂时未确定的空地址,也就是这里调用了一个外部的函数。
(2)main.o的代码段的起始位置为00000000,这是因为此时还没有对main.o的.text段进行地址空间分配
目标文件add.o的代码段的反汇编内容为:
- # objdump -d add.o
- add.o: file format elf32-i386
- Disassembly of section .text:
- 00000000 <add>:
- 0: 55 push %ebp
- 1: 89 e5 mov %esp,%ebp
- 3: 8b 45 0c mov 0xc(%ebp),%eax
- 6: 8b 55 08 mov 0x8(%ebp),%edx
- 9: 8d 04 02 lea (%edx,%eax,1),%eax
- c: 89 c2 mov %eax,%edx
- e: 03 55 10 add 0x10(%ebp),%edx
- 11: a1 00 00 00 00 mov 0x0,%eax
- 16: 8d 04 02 lea (%edx,%eax,1),%eax
- 19: 5d pop %ebp
- 1a: c3 ret
目标文件test的代码段的反汇编内容为
- # objdump -d test
- test: file format elf32-i386
- Disassembly of section .text:
- 08048094 <add>:
- 8048094: 55 push %ebp
- 8048095: 89 e5 mov %esp,%ebp
- 8048097: 8b 45 0c mov 0xc(%ebp),%eax
- 804809a: 8b 55 08 mov 0x8(%ebp),%edx
- 804809d: 8d 04 02 lea (%edx,%eax,1),%eax
- 80480a0: 89 c2 mov %eax,%edx
- 80480a2: 03 55 10 add 0x10(%ebp),%edx
- 80480a5: a1 08 91 04 08 mov 0x8049108,%eax
- 80480aa: 8d 04 02 lea (%edx,%eax,1),%eax
- 80480ad: 5d pop %ebp
- 80480ae: c3 ret
- 80480af: 90 nop
- 080480b0 <main>:
- 80480b0: 55 push %ebp
- 80480b1: 89 e5 mov %esp,%ebp
- 80480b3: 83 e4 f0 and $0xfffffff0,%esp
- 80480b6: 83 ec 20 sub $0x20,%esp
- 80480b9: c7 44 24 1c 0d 00 00 movl $0xd,0x1c(%esp)
- 80480c0: 00
- 80480c1: c7 44 24 18 1a 00 00 movl $0x1a,0x18(%esp)
- 80480c8: 00
- 80480c9: 8b 44 24 18 mov 0x18(%esp),%eax
- 80480cd: 8b 54 24 1c mov 0x1c(%esp),%edx
- 80480d1: 01 c2 add %eax,%edx
- 80480d3: a1 10 91 04 08 mov 0x8049110,%eax
- 80480d8: 8d 04 02 lea (%edx,%eax,1),%eax
- 80480db: 89 44 24 1c mov %eax,0x1c(%esp)
- 80480df: a1 0c 91 04 08 mov 0x804910c,%eax
- 80480e4: dd 05 18 91 04 08 fldl 0x8049118
- 80480ea: 89 44 24 0c mov %eax,0xc(%esp)
- 80480ee: dd 5c 24 04 fstpl 0x4(%esp)
- 80480f2: 8b 44 24 1c mov 0x1c(%esp),%eax
- 80480f6: 89 04 24 mov %eax,(%esp)
- 80480f9: e8 96 ff ff ff call 8048094 <add>
- 80480fe: b8 00 00 00 00 mov $0x0,%eax
- 8048103: c9 leave
- 8048104: c3 ret
可以看到:
(1)在main.o和add.o中的main函数和add函数的地址都是空的,说明此时还没用进行空间分配,但是链接之后的test文件中main函数和add函数的地址都已经确定为080480b0和08048094了,这说明在链接之后,已经完成地址空间的分配。
(2)在链接之前的目标文件中,外部符号的引用位置都是空着,链接之后其位置已经被修改为真实的地址了,这说明已经完成对符号的重定位工作。
例如main.o的反汇编内容里49行的fc ff ff ff是指一个暂时未确定的空地址,就像main.c中引用的外部符号g_iAddValue2和add,在将main.c编译成目标文件main.o时,由于main.c和add.c是独立编译的,
所以此时main.o中并不知道这两个符号的定义位置在哪里。此时编译器只是暂时用假地址"0xfc ff ff ff"代替。
3)符号的重定位是怎么完成的呢?
重定位是通过重定位表(段)来完成的,它主要描述对应段的重定位信息,每个包含需要重定位符号的段都会有一个重定位表,也就是,如果一个段中有需要重定位的符号,那就会为这个段再生成一个重定位表,这个重定位表中就描述了如何对段内的符号进行重定位。
可以通过命令:objdump -r目标文件名,来查看各目标文件的重定位表:
目标文件 main.o的重定位表为:
- # objdump -r main.o
- main.o: file format elf32-i386
- RELOCATION RECORDS FOR [.text]:
- OFFSET TYPE VALUE
- 00000024 R_386_32 g_iMainValue
- 00000030 R_386_32 g_iAddValue2
- 00000036 R_386_32 g_dMainValue
- 0000004a R_386_PC32 add
可以看到在main.o中还有四个符号需要在链接时分配地址空间之后才能确定位置。
目标文件 add.o的重定位表为:
- # objdump -r add.o
- add.o: file format elf32-i386
- RELOCATION RECORDS FOR [.text]:
- OFFSET TYPE VALUE
- 00000012 R_386_32 g_iAddValue1
可以看到在add.o中还有一个符号需要在链接时分配地址空间之后才能确定位置。
目标文件 test的重定位表为:
- # objdump -r test
- test: file format elf32-i386
对于链接之后的目标文件test中已经没有需要再重定位的符号了,它也就没有重定位表了,如果一个目标文件还有重定位表就说明它还有符号的位置未被确定。
从2中3)小节中对三个目标文件main.o、add.o和test的符号表内容中可以看到,在main.o中有两个被标识为UND的符号:g_iAddValue2和add,但是这两个符号在test的符号表中已经完成重定位。
4)综合上面所述的内容,可以得到链接器对符号的重定位过程如下所述:
(1)合并目标文件;链接器扫描所有的目标文件main.o和add.o,得到它们各自段的信息,并将对应的段合并起来。
(2)确定各符号的定义位置;连接器在合并各段之后就对各段进行虚地址空间分配,同时也就确定了各个符号的定义位置。
(1)查询符号引用位置,将符号的引用位置指向其正确的定义位置。连接器在第一遍扫描各个目标文件main.o和add.o时,就会将它们的符号表统一收集起来,形成一个全局的符号表,经过此次扫描之后的所有的符号引用都被统一到了该表中,
从该表中就可以看到哪些符号没有位置还未确定,未确定的索引为UND,将所有的UND符号全部都修改为其定义位置。
附:
文件中什么东西才是文中所述的符号呢?什么样的符号会被存储到符号表中?
通常所说的符号就是指函数名和变量名,但是局部变量就不在符号表中,具体而言就是下面的符号将会被放置到符号表中:
1)定义在本目标文件中的全局变量。例如main.c里面的g_iMainValue,add.c里面的g_iAddValue2;
2)引用的外部函数和变量
3)段名,它由编译器产生。
4)静态变量
5)行号信息。
转自网易名为“逍遥子”的博客