轉載自:http://www.cnblogs.com/cswuyg/p/3830703.html 在Linux系統中,動態鏈接文件稱為動態共用對象(DSO,Dynamic Shared Objects),簡稱共用對象,一般是以.so為擴展名的文件。在Windows系統中,則稱為動態鏈接庫(Dynam ...
轉載自:http://www.cnblogs.com/cswuyg/p/3830703.html 在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是對函數引用的修正。