開發人員開發的代碼,需要經過編譯和連結兩個過程,才能產生最終的可執行檔,或共用檔案(Linux的.so或windows的.dll檔案)。其中連結檔案(或稱為目標檔案,在Linux下為.o,在windows下為.obj)是編譯器編譯後產生的檔案。在連結時,連結器再將它連結成為可執行檔或共用檔案等。
目標檔案的格式主要是Linux平台下的ELF(ExecutableLinkable Format)和Windows平台的PE(Portable
Executable)。
本文以ELF為例介紹目標檔案。
ELF檔案的結構如,左右兩側分別為連結視角和執行視角,執行視角中,主要是將連結視角中的多個屬性相同的Section(如.rodata和.code都是唯讀)的Section作為一個Segment,以便在作業系統進程應用程式裝載時,可以減少因按頁分配(4KB每頁)導致的記憶體浪費。
本文主要是連結視角來研究ELF檔案。
1. 檔案頭:
主要包括ELF魔數、硬體平台版本、入口地址、程式頭入口和長度、段表的位置和長度及段的數量等。
ELF魔數:每一種檔案有一個固定的魔數,作業系統可以通過ELF魔數判斷是否為正確的可執行檔,如果不正確,則拒絕執行;
段表的位置和長度:連結器通過此資訊擷取段表的位置和長度資訊
段表字串表的下標:段表字串表的下標,通過此下標可以找到段表字串表(此段中儲存了段表中用到的字串)
2. 段表(Setion header tabls):
描述ELF檔案中每一個段的段名、類型、地址、長度、位移、對齊要求等資訊。
這裡的“段名”是一個數值,為段表字串表中的位移。段表字串表也是一個段,裡面儲存了段表中用到的字串。
3. 段(Sections):
ELF檔案中有很多段,比較重要的為程式碼片段、資料區段、唯讀資料區段、BSS段、重定位表、符號表等,
1) 程式碼片段
儲存編譯產生的機器指令序列
2) 資料區段
已初始化的全域變數
3) 唯讀資料區段
唯讀變數,編譯器可能將唯讀全域變數放在.rodata段,而局部常量定義可能在編譯期間將使用的代碼替換為了值,除非有取地址操作,才在棧上分配空間。但這都取決於編譯器。
4) BSS段
未經初始化的全域變數和靜態變數(未經初始化的全域變數可能放在BSS段,也可能不放,取決於編譯器)。
5) 重定位表
主要包括:需要重定位的指令地址、需要重定位的符號、以及類型。連結器根據指令地址找到需要重定位的內容,然後通過符號在符號表中找到對應符號的地址,然後再根據類型將此地址填寫到指令中。
6) 符號表
包括名稱、值、大小、類型和綁定資訊等。
名稱:符號的名稱
值:如果符號是一個函數或變數,則值是此函數或變數的地址
大小:符號佔用的空間的大小
類型:包括局部符號、全域符號、弱符號。如果是局部符號,則不需要進行重定位;如果是全域符號或弱符號,則需要進行重定位操作;
綁定資訊:可能的取值包括資料對象(變數、資料等)、函數、SECTION、FILE