使用gcc生成so檔案時,一般要加一個-fPIC選項,這個選項是啥意思?本文介紹了PIC的來龍去脈,
1 鏈接視圖看動態共享庫和靜態庫
如果我們在主程式main.c中使用到一個函式foobar(),對main.c進行編譯,編譯器還不知道foobar()函式的地址,在編譯階段生成的main.o中,foobar一個未決議的參考,
聯結器將main.o鏈接成可執行檔案時,必須確定main.o中所參考的foorbar()函式的性質,如果foobar()是一個定義在其他靜態目標模塊(靜態庫或者目標檔案)中的函式,那么聯結器會按照靜態鏈接的規則將main.o中的foobar()函式地址參考重定位,
gcc -o main.out mian.c ./libfoo.a
如果foobar()是定義在某個動態共享庫中的函式,那么聯結器就會把foobar標記為一個動態鏈接的符號,foobar此時是一個對動態符號的參考,不對它進行重定位,
gcc -o main.out main.c ./libfoo.so
2 執行視圖看動態庫
動態庫發展程序有三個階段,
2.1 演進階段1:靜態共享庫
靜態共享庫不是靜態庫,是指共享庫在編譯時已經確定了自己在行程虛擬地址空間的位置,作業系統在某個特定的地址劃分出一些地址塊,為那些已知的模塊預留足夠的空間,在鏈接視圖中,靜態共享庫在行程的虛擬地址空間的地址已經被決定了,如果后續要升級靜態共享庫,還是需要重新編譯,鏈接可執行ELF,
2.2 演進階段2:裝載時重定位
裝載重定位是指在鏈接時,對所有絕對地址的參考不作重定位,推遲到裝載時再完成,程式是按照整體進行裝載,程式中指令和資料相對位置不會改變,程式的基地址可能每次裝載時不一樣,一旦模塊裝載地址確定,系統就可以對程式中所有的絕對地址進行重定位,
Linux GCC選項
gcc -shared -o libbook.so book.c
使用裝載時重定位,動態鏈接模塊被裝載映射到虛擬地址空間后,指令會被修改,多個行程無法共享同一份指令代碼,指令被重定位以后對每個應用程式來說是不同的,演進階段2的裝載時重定位,無法解決指令部分需要在多個行程之間共享的需求,也就失去了動態鏈接節省記憶體的優勢,
2.3 演進階段3:地址無關代碼(Position Independent Code, PIC)
要在多個行程之間共享指令部分,需要將指令進行分類,按照是否跨模塊分兩類:模塊內參考和模塊外部參考; 按照參考方式分兩類:指令參考和資料訪問,這樣一共有四種組合:模塊內部的函式呼叫、跳轉,模塊內部的資料訪問(比如模塊中定義的全域變數、靜態變數),模塊間的函式呼叫、跳轉,模塊間的資料訪問(比如其他模塊中定義的全域變數),
型別一 模塊內部函式呼叫、跳轉
模塊內部的跳轉、函式呼叫都可以使用相對地址呼叫,不需要重定位,
型別二 模塊內部資料訪問
模塊內部指令與模塊內部資料之間的相對位置是固定的,只需要相對于當前指令加上固定的偏移量就可以訪問模塊內部資料,
型別三 模塊間的資料訪問
模塊間的資料訪問目的地址要等到裝載時才能確定,ELF檔案的做法是在資料段里面建一個指向這些模塊間全域變數的指標陣列,也被稱為全域偏移表(Global Offset Table, GOT),當代碼需要參考模塊間全域變數時,可以通過GOT中的表項間接的參考,由于GOT是存放在資料段中,所以動態庫在裝載時可以被修改,每個行程都有獨立的副本,相互之間不受影響,在編譯時可以確定GOT相對于當前指令的偏移,編譯器決定GOT內的每一項(4個位元組為一項,一個指標)對應于哪一個全域變數名稱,也就GOT給出了需要重定位的全域變數有哪些,以及該全域變數相對于GOT的位置,動態聯結器在裝載模塊時會查找每個變數所在地址,然后填充GOT中的各個項,確保GOT中每個指標所指向的地址是正確的,
型別四 模塊間的函式呼叫、跳轉
與型別三類似,也是通過GOT,在GOT中存放的是目標函式的地址,當模塊需要呼叫目標函式時,可以通過GOT進行間接跳轉,
使用GCC產生地址無關代碼
gcc -fPIC -shared -o libbook.so book.c
注意:這里的-fPIC中的PIC是大寫,也有小寫的-fpic(產生的代碼相對較小,而且較快),在有些平臺使用小寫的-fpic選項有一些限制,而大寫的-fPIC沒有這個問題,絕大多數情況使用-fPIC,
可以通過以下方法查看一個so是不是PIC,如果是PIC,以下命令會有輸出,如果不是PIC,則不會有任何輸出,
readelf -d libbook.so | grep TEXTREL
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/160365.html
標籤:其他
