Linux系統ELF檔案二進位制格式分析 四

2021-06-30 16:19:44 字數 3228 閱讀 2063

六、重定位項

重定位是將elf檔案中未定義的符號關聯到有效位置的過程,特別是目標檔案中這一項尤為重要。本例中引用了c語言庫函式printf和exit,鏈結時必須替換為該程序的虛擬位址空間中機器**所在位置。

每個elf中,都有專門的型別為rel的節包含重定位項,標識了需要進行重定位的位置。每一項都是用相同的資料結構表示的。

1.      資料結構

由於歷史原因,有兩種型別的重定位資訊,由兩種不同資料結構。第一種重定位結構稱為普通重定位,對應的節的型別為sht_rel,結構如下:

typedef struct elf32_rel  elf32_rel;
r_offset指定需要重定位項的位置,r_info不僅提供了符號表中的位置,還包括重定位型別的資訊。其中高8位中指示符號表中位置,低8位指示重定位型別。在計算重定位位置時,將根據重定位型別,對該值進行不同處理。獲取相應值的巨集定義如下:

#define elf32_r_sym(x) ((x) >> 8)

#define elf32_r_type(x) ((x) & 0xff)

第二種重定位結構稱為新增常數的重定位,對應節的型別為sht_rela,結構如下:

typedef struct elf32_rela elf32_rela;
需要注意的是,第一種重定位結構也存在這個加數值,該加數值不在結構中儲存,而是鏈結器根據該值應該出現的位置獲取的。

目標檔案中這個加數值在鏈結成可執行檔案時會參與運算得到乙個新的值,後面將會用例子進行說明如何計算得出新結果。

檢視本例中目標檔案的重定位項,結果如下:

2.      重定位型別

linux存在兩種重定位型別:相對重定位(對應型別為上圖中r_386_pc32)和絕對重定位(對應型別為上圖中r_386_32)。相對重定位主要用於子例程呼叫。絕對重定位的重定位項指向記憶體中編譯時就已知的資料,例如字串常數。

呼叫子過程是通過彙編指令call進行,x86架構32位系統中,call命令機器碼為0xe8,緊跟在call命令機器碼後的是4個位元組的位址資訊,但這個資訊不是被呼叫子過程的準確位址,而是上一節提到的「加數」,這個加數是乙個相對位置,可以根據這個相對值確定準確位址。目標檔案和可執行檔案都存在這個加數,但由於目標檔案鏈結到可執行檔案時子過程位址會改變,這個加數也會改變,因此需要通過計算得到改變後的值,根據重定位型別不同,計算方法也不同。

當定位型別為相對重定位時:

e = s – p +a

當定位型別為絕對重定位時:

e = s + a

其中:s表示「重定位符號位置」,p表示「重定位項的位置」,a表示「目標檔案加數值」。

e表示「可執行檔案加數值」。

下面以本例中main函式中呼叫add子過程為例說明計算方法。

彙編方式檢視目標檔案**如下,命令為objdump --disassemble elf檔案:

可以看到,main函式在檔案中偏移為0x1c,0x44處呼叫了子過程add,0x45開始的4位元組值為0xfffffffc,也就是加數,他是以補碼形式表示的,換算成十進位制為-4,即a=-4。對照上一小節目標檔案重定位表可知,偏移為0x45的重定位項名字是add,彙編**「call 45」即表示呼叫了該重定位項。

彙編方式檢視可執行檔案**如下:

可執行檔案中,add子過程入口為0x8048434,即s=0x08048434;重定位項位置為0x08048479,即p=0x08048479。所以可得e為:

e=s –p + a

= 0x08048434 - 0x08048479 + (-4)

= 134513716 – 134513785 – 4

= -73

-73的二進位制補碼為0xffffffb7,正如可執行檔案0x08048479處的4個位元組的值一樣。

-73實際上是乙個偏移量,指明了重定位符號跟當前位置的差值。但有的讀者會注意到,add子過程入口0x08048434和重定位項偏移0x08048479差值為-69,而不是-73,兩者相差4,正好是「目標檔案加數」的絕對值。這樣的結果是跟ia-32處理器工作方式有關的。在執行可執行檔案時,0x08048478處call add指令被執行後,cpu的ip暫存器即指向下一條指令的位址,即0x0804847d,前後兩條指令差值正好是4。但由於是呼叫了子過程,ip指令需要跳轉到子過程的入口,那麼如何從現在ip=0x0804847d這個位置跳轉到add指令的正確位置呢?add子過程入口0x08048434和重定位項偏移0x08048479差值為-69,而重定位項偏移和ip=0x0804847d差值為-4,則add子過程入口和ip差值為-69-4=-73,0x0804847d加上十進位制-73,為0x08048434,正好是add子過程的入口位址。

七、動態鏈結

.dynsym儲存了有關符號表,包含了所有需要通過外部引用解決的符號。本例中引用外部符號如下:

.dynamic儲存了乙個陣列,陣列型別為elf32_dyn型別,以下內容都是關於這個節的。

1.      資料結構

typedef struct dynamic d_un;

} elf32_dyn;

d_tag用於區分各種指定資訊型別的標記,該結構中的聯合根據該標記進行解釋。d_un或者儲存乙個虛擬位址,或者儲存乙個整數。

最重要的標記如下:

dt_needed指定改程式執行所需的乙個動態庫,d_un指向乙個字串表項,給出庫的名稱。本例當中只用到標準庫。

dt_strtab儲存了字元表的位置,其中包括了dynamic節所需的所有動態鏈結庫和符號名稱。

dt_symtab儲存了符號表的位置,其中包含了dynamic節所需的所有資訊。

dt_init和dt_fint儲存了用於初始化和結束程式的函式。

至此,關於linux系統中elf檔案的格式已基本分析完畢。

Linux系統ELF檔案二進位制格式分析 二

本文接著 linux系統elf檔案二進位制格式分析 一 繼續分析elf檔案格式 二 程式頭表 程式頭表由幾個項組成,結構類似於陣列,項數記錄在elf檔案頭的e phnum欄位,各項有統一的結構,每個項都描述了乙個段的資訊。1.資料結構 typedef struct elf32 phdrelf32 p...

ELF二進位制目標檔案詳解

以下內容為 找出所有引用的外部模組並鏈結起來,這些外部模組或函式庫一般來自於開發者,作業系統和c執行庫。鏈結程式取出這些函式庫,修訂指標位置 重定位 並交叉引用模組中的符號解析,最終產生乙個可執行模組。符號可以是全域性的也可以是區域性的。全域性符號可以在模組內部定義,或由另一模組外部引用。靜態庫是在...

readelf的elf 二進位制檔案格式分析工具小記

readeklf工具和 objdump 命令提供的功能類似,但是它顯示的資訊更為具體,並且它不依賴 bfd 庫 bfd 庫是乙個 gnu 專案,它的目標就是希望通過一種統一的介面來處理不同的目標檔案 elf 檔案型別 elf executable and linking format 是一種物件檔案...