1. 概述
目標(biāo)文件是指源代碼經(jīng)過(guò)編譯后沒(méi)有被鏈接的那些中間文件(Linux下的.o)。因?yàn)槟繕?biāo)文件的內(nèi)容和結(jié)構(gòu)與可執(zhí)行文件很像,所以目標(biāo)文件按照可執(zhí)行的文件的格式存儲(chǔ)。此外,動(dòng)態(tài)鏈接庫(kù)和靜態(tài)鏈接庫(kù)也是按照可執(zhí)行文件的格式存儲(chǔ)的。
2.初步了解目標(biāo)文件
下圖顯示了將源代碼編譯為目標(biāo)文件后,目標(biāo)文件的結(jié)構(gòu)和內(nèi)容。如上圖所示,目標(biāo)文件大致可以分為File Header,.text, .data, .bss四部分。
- File Header:文件頭。文件頭存儲(chǔ)了整個(gè)文件的屬性信息,包括文件是否可執(zhí)行,是靜態(tài)鏈接還是動(dòng)態(tài)鏈接以及入口地址等信息。
- .text :代碼段。代碼段存儲(chǔ)了源代碼經(jīng)過(guò)編譯后的機(jī)器指令。
- .data:數(shù)據(jù)段。數(shù)據(jù)段主要存儲(chǔ)了已經(jīng)初始化的全局變量或靜態(tài)變量。
- .bss:未初始化的全局變量和靜態(tài)變量存儲(chǔ)在bss段。因?yàn)槲闯跏蓟娜肿兞亢挽o態(tài)變量默認(rèn)是0,如果放在data段就要為其分配存儲(chǔ)空間,沒(méi)有必要,因此放在了bss段。bss段在編譯后實(shí)際大小為0,不占存儲(chǔ)空間。但是程序運(yùn)行時(shí),bss段是要占內(nèi)存空間的,因此可執(zhí)行文件必須要記錄所有未初始化的全局變量和靜態(tài)變量的大小的總和,作為bss段的大小。所以,bss段只是為未初始化的全局變量和靜態(tài)變量預(yù)留位置,并沒(méi)有實(shí)際的內(nèi)容。
1. 為什么要將程序的指令和數(shù)據(jù)進(jìn)行分段呢?原因有如下三點(diǎn):
- 當(dāng)程序被裝載后,數(shù)據(jù)和指令被映射到兩個(gè)不同的虛存區(qū)域。數(shù)據(jù)區(qū)域?qū)M(jìn)程是可寫可讀的,而指令區(qū)只是可讀的,這樣就避免了進(jìn)程修改指令帶來(lái)的問(wèn)題。
- 當(dāng)系統(tǒng)中運(yùn)行著多個(gè)該程序的副本時(shí),它們的指令是相同的,數(shù)據(jù)可能不同。因此在內(nèi)存中只需保存一份該程序的指令,這樣就節(jié)省了大量的存儲(chǔ)空間。
- 為了提高緩存的命中率。將數(shù)據(jù)和指令分離有助于提高程序的局部性。
3. 目標(biāo)文件詳細(xì)結(jié)構(gòu)
上圖為第二節(jié)中代碼編譯后的目標(biāo)文件中所有段的信息。下面,本文將分析各個(gè)段的存儲(chǔ)中內(nèi)容。
- 首先,ELF Header,文件頭,第二節(jié)中已經(jīng)說(shuō)明。主要存儲(chǔ)了該目標(biāo)文件屬性信息,包括文件是否可執(zhí)行,是動(dòng)態(tài)鏈接還是靜態(tài)鏈接以及可執(zhí)行文件入口等信息。
- text:代碼段。
- data:數(shù)據(jù)段。
- .rodata:只讀數(shù)據(jù)段。.rodata段存儲(chǔ)的是程序里的只讀變量(const修飾的變量)和字符串常量。在操作系統(tǒng)加載的可執(zhí)行文件的時(shí)候,rodata會(huì)映射成只讀,這樣對(duì)這個(gè)段的任何修改都會(huì)被視為非法操作,保證程序安全性。
- .comment段。注釋段。
-
.shstrtab: 字符串表。在ELF文件中用到了很多字符串,比如段名,變量名等。當(dāng) ELF 文件的其它部分需要引用字符串時(shí),只需提供該字符串在字符串表中的位置索引即可。如下圖所示:
image.png - symtab:ELF符號(hào)表,是一個(gè)ELF32_Sym結(jié)構(gòu)的數(shù)組。
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
Elf32_Sym結(jié)構(gòu)中最主要的是以下三個(gè)成員:
1.st_name: 符號(hào)名。這個(gè)成員包含了該符號(hào)名在字符串表中的下標(biāo)符號(hào)名,也即該符號(hào)名在符號(hào)串表中的下標(biāo)。
2.st_value:符號(hào)相對(duì)應(yīng)的值。這個(gè)值跟符號(hào)有關(guān),可能是一個(gè)絕對(duì)值,也可能是一個(gè)地址等,不同的符號(hào),它所對(duì)應(yīng)的值含義不同符號(hào)值。如果這個(gè)符號(hào)是一個(gè)函數(shù)或變量的定義,那么這個(gè)值就是函數(shù)或者是變量的地址(.data段)。
3.st_shndx:該符號(hào)所在的段。
- Section Table:段表。描述ELF文件各個(gè)段的信息,你如每個(gè)段的段名、段長(zhǎng)度、在文件中的偏移、讀寫權(quán)限以及段的其他屬性。
- .rel.text:當(dāng)鏈接噐把這個(gè)目標(biāo)文件和其他文件結(jié)合時(shí),.text節(jié)中的許多位置都需要修改。一般而言,任何調(diào)用外部函數(shù)或者引用全局變量(包括本目標(biāo)文件內(nèi)的全局變量,因?yàn)樵阪溄訒r(shí)要多個(gè)目標(biāo)文件的相同段合并,這樣數(shù)據(jù)的地址就會(huì)改變,所以要重定位)的指令都需要修改。另一方面調(diào)用本地函數(shù)的指令則不需要修改。