【linux】目標文件、可執行文件(ELF)格式解析

ELF文件、目標文件、可執行文件的關係

目標文件是源代碼經過編譯但未進行鏈接的那些中間文件,在linux中的.o文件,它跟可執行文件的內容與結構很相似,所以一般與可執行格式採用一種方式存儲,在linux下,我們可以將他們統稱ELF文件。ELF文件標準裏面把系統中採用ELF格式的文件歸爲四類:

ELF文件類型 說明 實例

可重定位文件(Relocatable File)

這類文件包含了代碼和數據,可以被用來鏈接成可執行文件或共享目標文件,靜態鏈接庫也屬於這一類

linux的.o

windows的.obj

可執行文件

(Executable File)

這類文件包含了可以直接執行的程序,它的代表就是ELF可執行文件,它們一般都沒有擴展名

比如/bin/bash文件;window的.exe 

共享目標文件

(Shared Object File)

這種文件包含了代碼和數據,可以在以下兩種情況下使用。一種是連接器可以使用這種文件跟其他的可重定位文件和共享目標文件鏈接,產生新的目標文件。第二種是動態鏈接器可以將幾個這種共享目標文件與可執行文件結合,作爲進程映像的一部分運行。

linux的.so,如/lib/glibc-2.5.so

windows的DDL

核心轉儲文件

(Core Dump File)

當進程意外終止時,系統可以將該進程的地址空間的內容及終止時的一些其他信息轉儲到核心轉儲文件。 linux下的core dump

以以下代碼編譯出來的目標文件作爲分析對象:

#include <stdio.h>
int global_init_var = 84;
int global_uninit_var;

void func1(int i)
{
	printf("%d\n",i);
}

int main(void)
{
	static int static_var = 85;
	static int static_var2;
	int a = 1;
	int b;
	func1(static_var + static_var2 + a + b);
	return a;
}

 使用objdump -h SimpleSection.o命令打印各個段的基本信息:

如上圖顯示共有代碼段、數據段、BSS段、只讀段、註釋信息段和堆棧提示段。對應的信息size表示段的長度,VMA File off表示段所在的位置。每個段的第二行中的“CONTENTS”、“ALLOC”等表示段的各種屬性;其中“CONTENTS”表示該段在文件中存在。

.text代碼段:可以使用objdump 命令;-s 參數可以將所有段的內容以十六進制的方式打印出來。-d參數可以將所有包含指令段反彙編。我們將關於代碼段的信息提取出來

Contents of section .text下就是.text段的內容,總共0x51,與使用-h參數輸出的.text長度一致,最左邊是偏移量,中間是十六進制的內容,最右邊是.text段的ASCII碼形式。對照下邊的反彙編結果,可以很明顯的看到fun1和mian函數,.text段的第一個字節"0x55"就是func1()函數的第一條“push %ebp”指令,而最後“0x50”正好是main函數的最後一條指令“ret”。

 

.data數據段:保存初始化了的全局變量和局部靜態變量。global_init_var 和static int static_var。大小正好是8個字節。

 

.bss段:存放的是未初始化的全局變量和局部靜態變量。也就是說global_uninit_var和static_var2應該存放在.bss段,準確來說.bss段位他們預留了位置,本應該是8個字節,而我們使用objdump -h命令顯示的size是4個字節。事實上只有static_var2存放在了.bss段,而global_uninit_var只是一個未定義的“COMMON”符號;這和編譯器有關,有的編譯器將全局未初始化變量存放在.bss段,有的則不存放,只是預留一個未定義的全局變量符號,等到最終鏈接後再分配到.bss段。

 

.rodata段:存放的是隻讀數據,一般是程序裏面的只讀變量(如const修飾的變量)和字符串常量。在func1裏面我們在調用“printf”的時候,用到了一個字符串常量“%d\n”,是4個字節與前面的長度符合。

上面通過一個實例大致瞭解了ELF文件的基本輪廓,總的來說就包括了指令和數據。下圖是ELF文件的總體結構:

上面講述了指令段和數據段,以下是ELF文件中其它的幾個重要結構:

 

文件頭(ELF Header)

 

ELF32_Ehdr和 ELF64_Ehdr結構體定義了ELF文件頭的相關信息;這兩個結構體的成員信息一致,只不過一個是32位版本的,另一個是64版本的。這裏以ELF32_Ehdr爲例,定義如下:

我們可以使用readelf查看SimpleSection的目標文件頭:

e_ident(ELF魔數):該成員與readelf輸出的Magic、Class、Data、Version、OS/ABI、ABI Version對應。 readelf輸出的Magic的16字節正好對應e_ident這個成員;對於ELF文件前四個字節都必須相同,分別爲0x7F、0x45、0x4c、0x46第一個字節對應ASCII控制,後面三個字節剛好是ELF這三個字母的ASCII,這4個字節稱爲ELF魔數,這種魔數用來確認文件的類型,操作系統在加載可執行文件的時候會確認魔數是否正確,如果不正確會拒絕加載。下一個字節對應Class用來表示ELF文件類的;第六個字節對應Data規定ELF文件是大端還是小端,第七個字節規定ELF文件的主版號,一般是1。對應的數據如下圖所示

e_type(文件類型):對應 readelf輸出的Type表示ELF文件類型。包括REL (可重定位文件)、EXEC(可執行文件)、DYN(共享文件);系統是根據這三個常量判斷文件類型,而不是擴展名。

剩下的成員與readelf輸出的參數,一一對應,此處不再贅述。綜上文件頭中描述整個文件的基本信息,以及段表的位置和大小、程序頭的位置和大小。

 

段表(Section header Table)

段表是ELF文件中除了頭文件以外最重要的結構,它描述了各個段的信息,比如段的段名、長度、在文件中的偏移、讀寫權限等等。段表是以一個元素是“ELF32_Shdr”結構體的數組。數組中的每一個元素對應一個段。ELF32_Shdr結構體的定義如下:

使用readelf -S SimpleSection.o查看目標文件的段表

根據上圖,總共有11個元素,ELF段表文件的第一個元素時無效的,所以有效的段描述符有十個,也就是說有十個有效的段。readelf命令的輸出與ELF32_Shdr結構體定義的成員一一對應,以下是對部分參數的詳細解釋:

sh_type(段的類型):對應readelf命令輸出的Type。段名只是在鏈接和編譯過程中有意義,但是不能代表段的類型。比如我們有可以將一個數據段命名爲.text。段對應的類型如下圖所示:

sh_flag(段的標誌位):對應readelf命令輸出的Flg;段的標誌位是指該段在進程虛擬空間中的屬性。比如是否可執行、可寫。對應的值如下圖所示:

sh_link、sh_info(段的鏈接信息):如果段的類型是與鏈接有關的,比如重定位、符號表。那麼這兩個成員的意義如下圖所示,對於其他段這兩個參數沒有意義。

 

重定位表(Relocation Table)

重定位是鏈接器對目標文件中的某些部分進行地址的重新定義。這些信息都會記錄在重定位表中 。可能會注意到在講述段表的時候,使用readelf -S命令輸出中就有一個“rel.text”段,該段就是重定位表,並且是作用域.text段,它對應類型是“SHT_REL”。回顧我們最開始寫的SimpleSetion文件有對“printf”函數的調用,這個就是引用了絕對地址。它所對應的sh_link的值9也就是說該重定位表使用的符號表在段表中的下標是9;h_info的值爲1,表示該重定位表作用於的段在段表的下標,即.text。對比上圖段表的信息,與此處結果符合。

 

字符串表

ELF文件中用到了很多的字符串,比如段表、變量名。因爲每個字符串的長度不一,所以將所有的字符串放到一張表中,如下圖所示:

那麼偏移與所對的字符串的關係:

在使用字符串時,只需給出對應的偏移值即可。保存字符串常見的有.strtab和.shstrtab,分別存放普通字符串和段名字符串。在頭文件結構中有e_shstrndx這樣一個參數,表示是段表字符串在段表中的下標。所以,只有分析文件頭,就可以得到段表和段表字符串的位置,從而解析整個elf文件。

 

符號表(Symbol Table)

符號的作用是當多個不同目標鏈接時函數和變量之間的相互引用。對於鏈接而言,只關心全局符號的相互引用。局部符號、段名、行號等符號是次要的。ELF文件中符號也是在一個符號表中,是一個ELF_Sym結構;結構定義如下:

使用  readelf -s SimpleSection.o查看目標文件的符號表:

詳解:

st_value(符號值):分爲以下幾種情況

  • 在目標文件中,如果是符號的定義並且該符號不是“COMMON”(未初始化的全局符號)類型,則st_value表示該符號在所在段中中的偏移,即符號所對應的函數或變量位於由st_shndx指定的段,偏移st_value的位置。
  • 在目標文件中。如果符號是“COMMON”類型的,則st_value表示該符號的對齊屬性。
  • 在可執行文件中,st_value表示符號的虛擬地址。

st_info(符號類型和綁定信息):低四位表示類型;高28位表示符號綁定信息。具體數值如下圖所示:

st_shndx(符號所在段):如果符號定義在本文件中,那麼這個成員表示符號所在段在段表中的下標;還有幾種特殊的情況,如下圖所示:

 

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