1. 程式人生 > >Linux共享對象之編譯參數fPIC(轉)

Linux共享對象之編譯參數fPIC(轉)

性能 recompile 般的 全局變量 文件 usr 命令 說明 令行

最近在看Linux編程的基礎知識,打算對一些比較有趣的知識做一些匯總備忘,本文圍繞fPIC展開,學習參考見文末。   在Linux系統中,動態鏈接文件稱為動態共享對象(DSO,Dynamic Shared Objects),簡稱共享對象,一般是以.so為擴展名的文件。在Windows系統中,則稱為動態鏈接庫(Dynamic Linking Library),很多以.dll為擴展名。這裏只備忘Linux的共享對象。 在實現一共享對象時,最一般的編譯鏈接命令行為:
 g++ -fPIC -shared test.cc -o lib.so
或者是:
    g++ -fPIC test.cpp -c -o test.o
    ld -shared test.o -o lib.so
上面的命令行中-shared表明產生共享庫,而-fPIC則表明使用地址無關代碼。PIC:Position Independent Code. Linux下編譯共享庫時,必須加上-fPIC參數,否則在鏈接時會有錯誤提示(有資料說AMD64的機器才會出現這種錯誤,但我在Inter的機器上也出現了):
/usr/bin/ld: test.o: relocation R_X86_64_32 against `a local symbol‘ can not be used when making a shared object; recompile with -fPIC
test.o: could not read symbols: Bad value
collect2: ld returned 1 exit status
如何確認一個共享對象是PIC呢?
readelf -d foo.so |grep TEXTREL 
如果上邊的shell有任何輸出,則說明這foo.so不是PIC。TEXTREL表示代碼段重定位表地址,PIC的共享對象不會包含任何代碼段重定位表。 fPIC的目的是什麽?共享對象可能會被不同的進程加載到不同的位置上,如果共享對象中的指令使用了絕對地址、外部模塊地址,那麽在共享對象被加載時就必須根據相關模塊的加載位置對這個地址做調整,也就是修改這些地址,讓它在對應進程中能正確訪問,而被修改到的段就不能實現多進程共享一份物理內存,它們在每個進程中都必須有一份物理內存的拷貝。fPIC指令就是為了讓使用到同一個共享對象的多個進程能盡可能多的共享物理內存,它背後把那些涉及到絕對地址、外部模塊地址訪問的地方都抽離出來,保證代碼段的內容可以多進程相同,實現共享。 抽離出這部分特殊的指令、地址之後,放到了一個叫做GOT(Global Offset Table)的地方,它放在數據段中,每一個進程都有獨立的一份,裏面的內容可能是變量的地址、函數的地址,不同進程它的內容很可能是不同的,這部分就是被隔離開的“地址相關”內容。模塊被加載的時候,會把GOT表的內容填充好(在沒有延遲綁定的情況下)。代碼段要訪問到GOT時,通過類似於window的call/pop/sub指令得到GOT對應項的地址。 對於模塊中全局變量的訪問,為了解決可執行文件跟模塊可能擁有同一個全局變量的問題(此時,模塊內的全局變量會被覆蓋為可執行文件中的全局變量),對模塊中的全局變量訪問也通過GOT間接訪問。 這樣子,每一次訪問全局變量、外部函數都需要去計算在GOT中的位置,然後再通過對應項的值訪問變量、調用函數。從運行性能上來說,比裝載時重定位要差點。裝載時重定位就是不使用fPIC參數,代碼段需要一個重定位表,在裝載時修正所有特殊地址,以後運行時不需要再有GOT位置計算和間接訪問。(但是,我在自己機子上測試,編譯鏈接共享庫時,沒法不使用fPIC參數,可能多數系統都要求必須有fPIC) 如果在裝載時就去計算GOT的內容,那麽會影響加載速度,於是就有了延遲綁定
(Lazy Binding),直到用時才去填充GOT。它使用到了PLT(Procedure Linkage Table):每一項都是一小段代碼,對應於本運行模塊要引用的函數。函數調用時,先到這裏,然後再到GOT。在函數第一次被調用時,進入PLT跳到加載器,加載器計算函數的真正地址,然後將地址寫入GOT對應項,以後的調用就直接從PLT跳到GOT記錄的函數位置。這樣也減少了運行時多次調用多次計算GOT位置。 PIC的共享對象也會有重定位表,數據段中的GOT、數據的絕對地址引用,這些都是需要重定位的。
  readelf -r Lib.so
  可以看到共享對象的重定位表,.rel.dyn是對數據引用的修正,.rel.plt是對函數引用的修正。

Linux共享對象之編譯參數fPIC(轉)