1. 程式人生 > >程式設計師的自我修養第七章讀書筆記-動態連結

程式設計師的自我修養第七章讀書筆記-動態連結

動態連結的確有很多優勢,比靜態連結要靈活得多,但它是以犧牲一部分效能為代價的。據統計ELF程式在靜態連結下要比動態庫稍微快點,大約為1%-%5,當然這取決於程式本身的特性及執行環境等。我們知道動態連結比靜態連結慢的主要原因是動態連結下對於全域性和靜態的資料訪問都要進行復雜的GOT定位,然後間接定址;對於模組間的呼叫也要先定位GOT,然後再進行間接跳轉,如此一來,程式的執行速度必定會減慢。另外一個減慢執行速度的原因是動態庫的連結工作在執行時完成,即程式開始執行時,動態連結器都要進行一次連結工作,動態連結器會尋找並轉載所需要的共享物件,然後進行符號查詢地址重定位等工作,這些工作勢必減慢程式的啟動速度。這是影響動態連結效能的兩個主要問題,現主要介紹優化動態連結效能的一些方法

延遲繫結實現

ELF採用了一種延遲繫結的做法,基本思想就是當函式第一次被呼叫到時才進行繫結(符號查詢、重定位等),如果沒有用到則不進行繫結。

ELF使用PLT(Procedure Linkage Table)的方法來實現,這種方法使用了一些很精巧的指令序列來完成。

動態連結相關結構

動態連結情況下,可執行檔案的裝載和靜態連結情況基本一樣。首先作業系統會讀取可執行檔案的頭部,檢查檔案的合法性,然後從頭部中的“Program Header”中的每個”Segment”的虛擬地址、檔案地址和屬性,並將它們對映到程序虛擬空間的相應位置,這些步驟跟靜態連結情況下的裝載基本無異。

在靜態連結情況下,作業系統接著就可以把控制權轉交給可執行檔案的入口地址,然後程式開始執行,一切看起來非常直觀。但是在動態連結情況下,作業系統還不能在裝載完可執行檔案之後就把控制權轉交給可執行檔案

,因為我們知道可執行檔案依賴於很多共享物件。這時候,可執行檔案裡對於很多外部符號的引用還處於無效地址的狀態,即還沒有跟相應的共享物件中的實際位置連結起來。所以在對映可執行檔案之後,作業系統會先啟動一個動態連結器

在Linux下,動態連結器ld.so實際上是一個共享物件,作業系統同樣通過對映的方式將它載入到程序的地址空間中。作業系統在載入完動態連結器之後,就將控制權交給動態連結器的入口地址(與可執行檔案一樣,共享物件也有入口地址)。當動態連結器得到控制權之後,它開始執行一系列自身的初始化操作,然後根據當前的環境引數,開始對可執行檔案進行動態連結工作。當所有動態連結工作完成之後,動態連結器會將控制權交給可執行檔案的入口地址

,程式開始正式執行。

1.“.interp”段

“.interp”(interpreter)段是ELF可執行檔案中的一個段。裡邊儲存的可執行檔案所需要的動態連結器的路徑,一般為”/lib/ld-linux.so.2”.這個路徑一般是個軟連結。指向“/lib/ld-2.6.1.so”。動態連結器在Linux下是Glibc的一部分。

2.“.dynamic”段

動態連結ELF中最重要的結構應該是”.dynamic“段,這個段裡面儲存了動態連結器所需要的基本資訊,比如依賴於哪些共享物件、動態連結符號表的位置、動態連結重定位表的位置、共享物件初始化的地址等。”.dynamic”段可以看成是動態連結下ELF檔案的“檔案頭”

另外Linux還提供了一個命令用來檢視一個程式主模組或一個共享庫依賴於哪些共享庫:ldd.

3.動態符號表

動態符號表“.dynsym”與靜態符號表”.symtab”不同的是,它只儲存了與動態連結相關的符號,對於那些模組內部的符號,比如模組私有變數則不儲存。很多時候動態連結的模組同時擁有“.dynsym”和”.symtab”兩個表,“.symtab”中儲存了所有符號,包括”.dynsym”中的符號。

與“.symtab”類似,動態符號表也需要一些輔助的表,比如用於儲存符號名的字串表。靜態連結時叫做符號字串表”.strtab”(String Table)
,在這裡就是動態符號字串表“.dynstr”(Dynamic String Table);由於動態連結下,我們需要在程式執行時查詢符號,為了加快符號的查詢過程,往往還有輔助的符號雜湊表

動態符號表的結構與靜態連結的符號表幾乎一樣,我們可以簡單地將匯入函式看作是對其他目標檔案中函式的引用,把匯出函式看作是本目標檔案定義的函式就可以了。

4.動態連結重定位表

共享物件需要重定位的主要原因是匯入符號的存在。動態連結下,無論是可執行檔案或共享物件,一旦它依賴於其他共享物件,也就是說有匯入的符號時,那麼它的程式碼或資料中就會有對於匯入符號的引用。在編譯時這些匯入符號的地址未知,在靜態連結中,這些位置的地址引用在最終連結時被修正。但是在動態連結中,匯入符號的地址在執行時才確定,所以需要在執行時將這些匯入符號的引用修正,即需要重定位。

動態連結重定位表–動態連結重定位相關結構

共享物件的重定位與我們在前面“靜態連結”中分析過的目標檔案的重定位十分類似。唯一的區別是目標檔案的重定位是在靜態連結時完成的,而共享物件的重定位是在裝載時完成的。

在靜態連結中,目標檔案裡面包含有專門用於表示重定位資訊的重定位表,比如“.rel.text”表示時程式碼段的重定位表,”.rel.data”時資料段的重定位表。動態連結的檔案中,也有類似的重定位表分別叫做“.rel.dyn”和”.rel.plt”,它們分別相當於“.rel.text”,和”.rel.data”。”.rel.dyn”實際上時對資料引用的修正,它所修正的位置位於“.got”以及資料段;而”.rel.plt”是對函式引用的修正,它所修正的位置位於“.got.plt”。

5.動態連結時程序堆疊初始化資訊

站在動態連結器的角度看,當作業系統把控制權交給它的時候,它將開始連結工作,那麼至少它需要知道關於可執行檔案和本程序的一些資訊,比如可執行檔案有幾個段、每個段的屬性、程式的入口地址(因為動態連結器到時候需要把控制權交給可執行檔案)等。這些資訊往往由作業系統傳遞給動態連結器,儲存在程序的堆疊裡面。另外,程序初始化的時候,堆疊裡面儲存了關於程序執行環境和命令列引數等資訊。事實上,堆疊裡面還儲存了動態連結器所需要的一些輔助資訊陣列(Auxiliary Vector)