在Linux下編譯共享庫必須加上-fpic。這是為什麼呢?
首先看一個簡單的例子:
#include <stdio.h> int fun1() { printf("fun1\n"); }
先不加-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的選項的,否則共享庫剩下的僅僅是硬盤上的空間,而沒有剩下內存。