linux下動態連結程式庫的載入及解析過程

來源:互聯網
上載者:User

http://hi.baidu.com/hust_chen/blog/item/54a8c516231d0c0ec93d6d3e.html

 

linux下動態連結程式庫的載入及解析過程(ZZ)2008-12-18 15:19

表面上看,動態連結程式庫(dll)的載入及解析是一個十分繁複的過程,其中牽涉到的資料結構及其之間的關係也讓人望而生畏。Whatever,學習這 事情,說到底是沒有捷徑可走的,除了死啃僅有的一些資料,如ELF format、Loaders and Linkers,借用objdump及gdb來跟蹤程式的執行流程,對於理解過程也是頗有協助的,anyway,不嫌煩就好了。
在不同的體系下,dll的載入及解析過程是有差別的,這是因為不同體系下產生的ELF檔案不盡相同,譬如說,在IA32下,會產生.plt這個dynamic section,但到了MIPS下,便成了.MIPS.stubs。
儘管如此,排除表面上的差異,其本質卻是一致的。先說說ELF檔案的format,ELF實質是可執行檔的一種格式,對於機器而言,所有的可執行檔不 過是二進位代碼的流水罷了,但為了方便管理這些二進位代碼,編譯器便將這些機器碼分為幾個段,並統計出相關的資訊,通俗地說,也就是對機器碼封裝封裝而 已。Technically speaking, 每個ELF檔案都有一個ELF header,儲存的是program header與section header的位移量。關於program header與section header,以後再說唄。

如果某個檔案是動態編譯的,那麼其對應的ELF檔案中肯定會有一個.dynamic section,這個section包含了dll的載入與解析所需要的資訊。.dynamic中的entry因類型不同而被賦予不同的含義,最為常用的是下面幾種類型的entry:

·DT_NEEDED:d_tag=1,d_val中的值為needed library的name在string table中位移量。如果所需要動態庫的數量為n個,則相應類型為DT_NEEDED的entry也應該有n條;

·DT_PLTGOT:d_tag=3,d_ptr中的值為.plt(procedure linkage table)或.got(global offset table)的起始地址,在MIPS體系下,這個值是指向.got的起始地址;

·DT_HASH:d_tab=4,d_ptr中的值為hash table的起始地址;

·DT_STRTAB:d_tab=5,d_ptr中的值為string table的起始地址;

·DT_SYMTAB:d_tab=6,d_ptr中的值為symbol table的起始地址;

·DT_RPATH:d_tab=15,d_val中的值為needed library所在的路徑名稱(註:needed library不一定就會在這個路徑下,detail參見dll的載入過程)在string table中的起始地址。

這裡面牽涉到了一些重要的資料結構:

(1) hash table:其作用是根據symbol name找到其在symbol table中的位置。簡單地說,是以symbol name作為尋找的key,返回指向symbol table的index(表示第index個symbol entry)。當然,實際的做法沒有這般簡單,因為要考慮到hash table的地址衝突問題。hash table包括兩部分:bucket與chain。假設x代表symbol name的值,y=bucket[x%nbucket],如果y所指向的symbol entry不是所求的entry,那麼就得利用chain[y]來尋找下一個index,直到成功為止。

這裡還牽涉到一個問題:如何得知y所指向的symbol entry是否所求的entry?一個顯然的答案是用已知的symbol name與該symbol entry所對應的name作匹配,但因為symbol table中的entry並沒有包含symbol name這個欄位,所以必須要藉助string table來得出其所對應的symbol name(參見下面)。

(2) symbol table:其儲存的是符號的相關資訊。其中,比較重要的是st_name與st_value。st_name是該entry所對應的name在string table中的位移量;st_value是該symbol在程式碼片段中的起始地址。

(3) string table:純粹意義上的由一組字串組成的表格。每個字串以'/0'標記結束,特別注意的是,string table第一個元素的值為'/0'。

那為什麼不直接把symbol name放到symbol table中,而間接用string table儲存呢?因為symbol name的長度是不確定的,為了保證symbol table的每個entry的大小一致,便把symbol name移到string table中。

 

dll的載入過程:

根據DT_NEEDED可以得到所需dll的名稱在string table中的位移量,通過訪問string table便可以得到dll的名稱了。下一步便是找這些dll所在的路徑,以便將其載入到記憶體中。1)搜尋DT_RPATH所指向的路徑(也是通過 string table來得到路徑的名稱);2)搜尋環境變數中的路徑;3)搜尋/usr/lib及/lib。如果在這幾個地方都找不到所需的dll,那麼載入便失敗 了。

 

dll函數的解析過程:

所謂的lazy mode,也就是當需要dll中的某個函數時方將其起始地址計算出來,而不是事先便把dll的所有函數的起始地址都計算好,這是因為並非dll中的每個函數都有機會被調用。

由於檔案中可能多次調用同一外部函數,這便要考慮如何使得每個外部函數只被解析一次,而不是每調用一次便解析一次。實現的方法是利用.got 與.plt(或者是MIPS體系下的.MIPS.stubs)。.got是全域位移量表,存放的是全域符號(自然包括所調用的外部函數的符號)所對應的二 進位代碼在ELF檔案中的起始地址。.plt是一段一段的小代碼,每段小代碼對應一個動態庫中的函數符號,細節可以查看Loaders and Linkers。 大致的過程如下(以MIPS為例):調用外部函數的二進位代碼中肯定有一句無條件轉移語句,該語句表明程式會跳到got[n]所指向的地址中,在該函數符 號沒有被解析前,這個地址是指向stub中相應的某段小代碼(這是由編譯器事先綁定好的),這段小代碼中有個跳躍陳述式,是轉移到got[0]所指向的地 址,這個地址其實便是符號解析函數所在的起始地址,當完成符號解析這個工作後,便會把got[n]中的內容改為該函數符號的起始地址,這樣,當第二次調用 該函數時,便會無須再進行第二次解析。相比之下,IA32的函數解析過程較為複雜,不過在很多地方都很找到相關資料。

至於如何計算函數的起始地址,well,通過hash table、symbol table、string table便可以了,不過前提是要搞清解析哪個函數符號,一般來說,這可以通過分析ELF檔案來找出傳遞的參數,從而擷取想要的資訊。

當然,想偷懶也是可以的,C標準庫提供了函數dlopen()、dlsym(),主要是用於解析函數符號的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.