在编译共享库必须加上-fpic。这是为什么呢?
首先看一个简单的例子:
#include <stdio.h> int fun1() { printf("fun1 "); }
先不加-fpic的情况下生成库,反汇编查看fun1的机器码
0000044c <fun1>: 44c: 55 push %ebp 44d: 89 e5 mov %esp,%ebp 44f: 83 ec 18 sub $0x18,%esp 452: c7 04 24 b2 04 00 00 movl $0x4b2,(%esp) 459: e8 fc ff ff ff call 45a <fun1+0xe> 45e: c9 leave 45f: c3 ret
可以看出调用printf的位置是那个唯一的一个call,并不是跳转到plt表,有关plt表的内容可以查看我前面的博文。也就是说在该库被加载时需要修改代码段来达到重定位的效果。那么每一个加载这个共享库的程序都要有这个库的一份拷贝,这样实际上就没有达到共享库的效果。
看下运行时的机器码
0xb771d44c <+0>: 55 push %ebp 0xb771d44d <+1>: 89 e5 mov %esp,%ebp 0xb771d44f <+3>: 83 ec 18 sub $0x18,%esp 0xb771d452 <+6>: c7 04 24 b2 d4 71 b7 movl $0xb771d4b2,(%esp) 0xb771d459 <+13>: e8 42 b2 ea ff call 0xb75c86a0 <puts> 0xb771d45e <+18>: c9 leave 0xb771d45f <+19>: c3 ret
显然代码段被修改了。
再看一下再加了-fpic的情况下生成的库,反汇编看下fun1的机器码
0000045c <fun1>: 45c: 55 push %ebp 45d: 89 e5 mov %esp,%ebp 45f: 53 push %ebx 460: 83 ec 14 sub $0x14,%esp 463: e8 ef ff ff ff call 457 <__i686.get_pc_thunk.bx> 468: 81 c3 8c 1b 00 00 add $0x1b8c,%ebx 46e: 8d 83 ee e4 ff ff lea -0x1b12(%ebx),%eax 474: 89 04 24 mov %eax,(%esp) 477: e8 04 ff ff ff call 380 <puts@plt> 47c: 83 c4 14 add $0x14,%esp 47f: 5b pop %ebx 480: 5d pop %ebp 481: c3 ret 482: 90 nop 483: 90 nop 484: 90 nop 485: 90 nop 486: 90 nop 487: 90 nop 488: 90 nop
看过很多汇编代码的人知道printf有时候是puts,所以这段机器码中printf就对应第二个call,也就是跳转到plt表中去查找puts符号,那么这样就达到了共享库的效果,此时每一个需要该库的程序只是有一个plt表的拷贝,而代码段所有应用程序是共享的。
再看下运行时机器码
0xb773045c <+0>: 55 push %ebp 0xb773045d <+1>: 89 e5 mov %esp,%ebp 0xb773045f <+3>: 53 push %ebx 0xb7730460 <+4>: 83 ec 14 sub $0x14,%esp 0xb7730463 <+7>: e8 ef ff ff ff call 0xb7730457 <__i686.get_pc_thunk.bx> 0xb7730468 <+12>: 81 c3 8c 1b 00 00 add $0x1b8c,%ebx 0xb773046e <+18>: 8d 83 ee e4 ff ff lea -0x1b12(%ebx),%eax 0xb7730474 <+24>: 89 04 24 mov %eax,(%esp) 0xb7730477 <+27>: e8 04 ff ff ff call 0xb7730380 <puts@plt> 0xb773047c <+32>: 83 c4 14 add $0x14,%esp 0xb773047f <+35>: 5b pop %ebx 0xb7730480 <+36>: 5d pop %ebp 0xb7730481 <+37>: c3 ret
显然是一致的。
所以,在编译共享库时是必须加上-fpic的选项的,否则共享库省下的仅仅是硬盘上的空间,而没有省下内存。