前面分析的是从应用程序调用动态库的情况。动态库本身是怎么完成符号解析的呢?
根据Ian Lance Taylor的说法,最好是以PIC(-fpic)的方式编译shared lib,不这样也可以,但会增加dynamic linker做重定位的负担。以PIC方式编译的lib可以大量减少必要的relocation info,但调用non-static functions和访问global/static variables的时候都需要通过plt/got间接进行 (All problems in computer science can be solved by another level of indirection.)
如果libfoo.so是以PIC模式编译的,并调用了一个外部函数bar,则bar会出现在.rel.plt section中(readelf -r);而如果不是以PIC模式编译,则bar将出现在.rel.dyn section中。如果是后者的话,dynamic linker会在load libfoo.so的时候利用该section提供的偏移量信息,直接将其中引用bar的地方patch上,这样一来也就意味着指令本身被修改了(dynamic linker之后是不是应该重新将指令改为只读?),因此也就丧失了可被多个进程共享的特性。而如果是PIC模式,则会有一次间接的过程,我们现在分析的就是这一过程。与函数调用不同,我们发现全局变量不论是以哪种方式编译,它们的重定位信息都被置于.rel.dyn中,我想这是因为数据的访问不像控制转移一样可以借助几层跳转来完成,因此也无法进行lazy binding而必须在load时做完。
PIC模式与非PIC模式最大的不同就是前者不直接patch指令,而是patch GOT。所有的指令都访问GOT从而来达到position independence,与前文中动态解析库函数的idea非常类似。可是既然是地址无关的,怎么知道GOT的位置呢?关键在于每一个shared lib都带有自己的GOT,而且整个lib是作为一个整体被load到内存,因此GOT的基址与每条指令的相对偏移总是确定的。这样一来,一条指令在访问GOT的时候,只要算出自己当前的IP地址,再加上这个被静态确定下来的偏移量,就可以定位到自己要访问的symbol的GOT entry了。
计算当前IP地址的函数一般是__i686.get_pc_thunk.bx,它会附带在每个PIC module中,因此它与调用它的函数的相对偏移也是固定下来的。它非常简单:
mov (%esp),%ebx
ret
(gdb) disassemble
Dump of assembler code for function foo:
0x0011344c: push %ebp
0x0011344d: mov %esp,%ebp
0x0011344f: push %ebx
0x00113450: sub $0x4,%esp
0x00113453: call 0x113447 <__i686.get_pc_thunk.bx>
0x00113458: add $0x1180,%ebx
0x0011345e: mov 0xfffffff0(%ebx),%eax ; a negative number because we are accessing .got from .got.plt
0x00113464: movb $0x32,(%eax)
0x00113467: call 0x113320
0x0011346c: lea 0xffffef08(%ebx),%eax
...
Relocation section '.rel.text' at offset 0x484 contains 7 entries:
Offset Info Type Sym.Value Sym. Name
00000008 00000b02 R_386_PC32 00000000 __i686.get_pc_thunk.bx
0000000e 00000c0a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
...
$ objdump -d foo.o
Disassembly of section .text:
00000000:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 53 push %ebx
4: 83 ec 04 sub $0x4,%esp
7: e8 fc ff ff ff call 8; 0x8: __i686.get_pc_thunk.bx
c: 81 c3 02 00 00 00 add $0x2,%ebx ; 0xe: _GLOBAL_OFFSET_TABLE_
...
$ nm libfoo.so |grep _GLOBAL_OFFSET_TABLE_
000015d8 a _GLOBAL_OFFSET_TABLE_
$ readelf -S libfoo.so|grep 15d8
[20] .got.plt PROGBITS 000015d8 0005d8 00001c 04 WA 0 0 4