1. 概述
目標文件是指源代碼經過編譯后沒有被鏈接的那些中間文件(Linux下的.o)。因為目標文件的內容和結構與可執行文件很像,所以目標文件按照可執行的文件的格式存儲。此外,動態鏈接庫和靜態鏈接庫也是按照可執行文件的格式存儲的。
2.初步了解目標文件
下圖顯示了將源代碼編譯為目標文件后,目標文件的結構和內容。源代碼與目標文件.png
如上圖所示,目標文件大致可以分為File Header,.text, .data, .bss四部分。
- File Header:文件頭。文件頭存儲了整個文件的屬性信息,包括文件是否可執行,是靜態鏈接還是動態鏈接以及入口地址等信息。
- .text :代碼段。代碼段存儲了源代碼經過編譯后的機器指令。
- .data:數據段。數據段主要存儲了已經初始化的全局變量或靜態變量。
- .bss:未初始化的全局變量和靜態變量存儲在bss段。因為未初始化的全局變量和靜態變量默認是0,如果放在data段就要為其分配存儲空間,沒有必要,因此放在了bss段。bss段在編譯后實際大小為0,不占存儲空間。但是程序運行時,bss段是要占內存空間的,因此可執行文件必須要記錄所有未初始化的全局變量和靜態變量的大小的總和,作為bss段的大小。所以,bss段只是為未初始化的全局變量和靜態變量預留位置,并沒有實際的內容。
1. 為什么要將程序的指令和數據進行分段呢?原因有如下三點:
- 當程序被裝載后,數據和指令被映射到兩個不同的虛存區域。數據區域對進程是可寫可讀的,而指令區只是可讀的,這樣就避免了進程修改指令帶來的問題。
- 當系統中運行著多個該程序的副本時,它們的指令是相同的,數據可能不同。因此在內存中只需保存一份該程序的指令,這樣就節省了大量的存儲空間。
- 為了提高緩存的命中率。將數據和指令分離有助于提高程序的局部性。
3. 目標文件詳細結構
目標文件詳細內容與結構.jpg
上圖為第二節中代碼編譯后的目標文件中所有段的信息。下面,本文將分析各個段的存儲中內容。
- 首先,ELF Header,文件頭,第二節中已經說明。主要存儲了該目標文件屬性信息,包括文件是否可執行,是動態鏈接還是靜態鏈接以及可執行文件入口等信息。
- text:代碼段。
- data:數據段。
- .rodata:只讀數據段。.rodata段存儲的是程序里的只讀變量(const修飾的變量)和字符串常量。在操作系統加載的可執行文件的時候,rodata會映射成只讀,這樣對這個段的任何修改都會被視為非法操作,保證程序安全性。
- .comment段。注釋段。
-
.shstrtab: 字符串表。在ELF文件中用到了很多字符串,比如段名,變量名等。當 ELF 文件的其它部分需要引用字符串時,只需提供該字符串在字符串表中的位置索引即可。如下圖所示:
image.png - symtab:ELF符號表,是一個ELF32_Sym結構的數組。
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結構中最主要的是以下三個成員:
1.st_name: 符號名。這個成員包含了該符號名在字符串表中的下標符號名,也即該符號名在符號串表中的下標。
2.st_value:符號相對應的值。這個值跟符號有關,可能是一個絕對值,也可能是一個地址等,不同的符號,它所對應的值含義不同符號值。如果這個符號是一個函數或變量的定義,那么這個值就是函數或者是變量的地址(.data段)。
3.st_shndx:該符號所在的段。
- Section Table:段表。描述ELF文件各個段的信息,你如每個段的段名、段長度、在文件中的偏移、讀寫權限以及段的其他屬性。
- .rel.text:當鏈接噐把這個目標文件和其他文件結合時,.text節中的許多位置都需要修改。一般而言,任何調用外部函數或者引用全局變量(包括本目標文件內的全局變量,因為在鏈接時要多個目標文件的相同段合并,這樣數據的地址就會改變,所以要重定位)的指令都需要修改。另一方面調用本地函數的指令則不需要修改。