程序員的自我修養第七章讀書筆記-動態鏈接

動態鏈接的確有很多優勢,比靜態鏈接要靈活得多,但它是以犧牲一部分性能爲代價的。據統計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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章