(轉) Linux下生成動態連接庫是否必須使用 -fPIC 的問題

在 Linux 下製做動態連接庫,「標準」 的作法是編譯成位置無關代碼(Position Independent Code,PIC),而後連接成一個動態連接庫。常常遇到的一個問題是 -fPIC 是否是必需,由於好像不加常常也能正常運行,只是建立 .so 的時候會有一個警告。函數

搜索、試驗了一下,答案彷佛是這樣:操作系統

(1) 一般的建議是始終加上 -fPIC 生成位置無關代碼;指針

(2) AMD64 下,必須使用位置無關代碼,不然鏈接失敗:code

   relocation R_X86_64_32S against `a local symbol' can not be used when making a shared object; recompile with -fPIC進程

(3) IA32 下,鏈接成功,但有警告:內存

warning: creating a DT_TEXTREL in object.it

這樣的 .so 文件能夠徹底正常工做。io

 

可執行文件在連接時就知道每一行代碼、每個變量會被放到線性地址空間的什麼位置,所以這些地址能夠都做爲常數寫到代碼裏面。對動態庫,這就不行了,這要等到加載時才知道。無非下面兩種方法:編譯

(1) 可重定位代碼(relocatable code):Windows DLL 以及不使用 -fPIC 的 Linux SO。table

生成動態庫時假定它被加載在地址 0 處。加載時它會被加載到一個地址(base),這時要進行一次重定位(relocation),把代碼、數據段中全部的地址加上這個 base 的值。這樣代碼運行時就能使用正確的地址了。

(2) 位置無關代碼(position independent code):使用 -fPIC 的 Linux SO。

這樣的代碼自己就能被放到線性地址空間的任意位置,無需修改就能正確執行。一般的方法是獲取指令指針(如 IA32 的 EIP 寄存器)的值,加上一個偏移獲得全局變量/函數的地址。

PIC vs. relocatable:

(1) PIC 的缺點主要就是代碼有可能長一些。例如 IA32,因爲不能直接使用 [EIP+constant] 這樣的尋址方式,甚至不能直接將 EIP 的值交給其餘寄存器,要用到 GOT(global offset table)來定位全局變量和函數。這樣致使代碼的效率略低。

(2) PIC 的加載速度稍快,由於不須要作重定位。

(3) 多個進程引用同一個 PIC 動態庫時,能夠共用內存。這一個庫在不一樣進程中的虛擬地址不一樣,但操做系統顯然會把它們映射到同一塊物理內存上。對於可重定位代碼,則必須爲每一個庫都在物理內存中複製一份副本,由於須要修改其中的地址。固然,主流現代操做系統都啓用了分頁內存機制,這使得重定位時可使用 COW(copy on write)來節省內存(32 位 Windows 就是這樣作的);然而,頁面的粒度仍是比較大的(例如 IA32 上是 4KiB),至少對於代碼段來講能節省的至關有限。

注:對於 AMD64,因爲 AMD64 實現了 [RIP+constant] 的尋址方式,第 (1) 點不成立。

這樣,把動態庫編譯成 PIC 只有好處沒有壞處,於是 Linux AMD64 要求用於生成動態庫的目標文件必須使用 -fPIC 編譯也合情合理了。

相關文章
相關標籤/搜索