1 簡介
可執行鏈接格式(Executable and Linking Format)最初是由 UNIX 系統實驗室(UNIX System Laboratories,USL)開發併發布的,作爲應用程序二進制接口(Application Binary Interface,ABI)的一部分。工具接口標準(Tool Interface Standards,TIS)委員會將還在發展的 ELF 標準選作爲一種可移植的目標文件格式,可以在 32 位 Intel 體系結構上的很多操作系統中使用。
目標文件有三種類型:
- 可重定位文件(Relocatable File) (*.o) 包含適合於與其他目標文件鏈接來創建可執行文件或者共享目標文件的代碼和數據。
- 可執行文件(Executable File) (*.exe) 包含適合於執行的一個程序,此文件規定了exec() 如何創建一個程序的進程映像。
- 共享目標文件(Shared Object File) (*.so)包含可在兩種上下文中鏈接的代碼和數據。首先鏈接編輯器可以將它和其它可重定位文件和共享目標文件一起處理,
生成另外一個目標文件(比如:編譯器和連接器 把*.o和*.so一起裝配成一個*.exe文件)。其次,動態鏈接器(Dynamic Linker)可能將它與某個可執行文件以及其它共享目標一起組合,創建進程映像(比如:動態加載器把exe程序和*.so加載進內存執行)。
目標文件全部是程序的二進制表示,目的是直接在某種處理器上直接執行。
2 目標文件格式
說了那麼多,那麼elf格式文件到底是什麼樣的呢?首先elf格式文件是一種二進制文件,其次它有好多種後綴,比如.o,.so,.exe後綴名的文件都是elf格式的文件,下面讓我們來看看這種二進制文件的格式到底是怎麼樣的?
elf目標文件既要參與程序鏈接又要參與程序執行。出於方便性和效率考慮,目標文件格式提供了兩種並行視圖,分別反映了這些活動的不同需求。
上面這張圖怎麼看呢?首先左邊部分,它是以鏈接視圖來看待elf文件的,從左邊可以看出,包含了一個ELF頭部,它描繪了整個文件的組織結構。它還包括很多節區(section)。這些節有的是系統定義好的,有些是用戶在文件在通過.section命令自定義的,鏈接器會將多個輸入目標文件中的相同的節合併。節區部分包含鏈接視圖的大量信息:指令、數據、符號表、重定位信息等等。除此之外,還包含程序頭部表(可選)和節區 頭部表,程序頭部表,告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。而節區頭部表(Section Heade Table)包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
需要注意地是:儘管圖中顯示的各個組成部分是有順序的,實際上除了 ELF 頭部表以外,其他節區和段都沒有規定的順序。
右半圖是以程序執行視圖來看待的,與左邊對應,多了一個段(segment)的概念,編譯器在生成目標文件時,通常使用從零開始的相對地址,而在鏈接過程中,鏈接器從一個指定的地址開始,根據輸入目標文件的順序,以段(segment)爲單位將它們拼裝起來。其中每個段可以包括很多個節(section)。
下面一個一個來...
2.1 ELF的數據類型定義
在具體介紹ELF的格式之前,我們先來了解在ELF文件中都有哪些數據類型的定義:
名稱 | 大小 | 對齊 | 目的 |
Elf32_Addr | 4 | 4 | 無符號程序地址 |
Elf32_Half | 2 | 2 | 無符號中等整數 |
Elf32_Off | 4 | 4 | 無符號文件偏移 |
Elf32_SWord | 4 | 4 | 有符號大整數 |
Elf32_Word | 4 | 4 | 無符號大整數 |
unsigned char | 1 | 1 | 無符號小整數 |
2.2 ELF頭部結構
從上一節可知,一個ELF文件的開始部分爲ELF頭部,那麼這個ELF頭部又是怎麼樣的呢?
這裏用一個結構體來表示:
- #define EI_NIDENT 16
- typedef struct{
- unsigned char e_ident[EI_NIDENT]; //目標文件標識信息
- Elf32_Half e_type; //目標文件類型
- Elf32_Half e_machine; //目標體系結構類型
- Elf32_Word e_version; //目標文件版本
- Elf32_Addr e_entry; //程序入口的虛擬地址,若沒有,可爲0
- Elf32_Off e_phoff; //程序頭部表格(Program Header Table)的偏移量(按字節計算),若沒有,可爲0
- Elf32_Off e_shoff; //節區頭部表格(Section Header Table)的偏移量(按字節計算),若沒有,可爲0
- Elf32_Word e_flags; //保存與文件相關的,特定於處理器的標誌。標誌名稱採用 EF_machine_flag的格式。
- Elf32_Half e_ehsize; //ELF 頭部的大小(以字節計算)。
- Elf32_Half e_phentsize; //程序頭部表格的表項大小(按字節計算)。
- Elf32_Half e_phnum; //程序頭部表格的表項數目。可以爲 0。
- Elf32_Half e_shentsize; //節區頭部表格的表項大小(按字節計算)。
- Elf32_Half e_shnum; //節區頭部表格的表項數目。可以爲 0。
- Elf32_Half e_shstrndx; //節區頭部表格中與節區名稱字符串表相關的表項的索引。如果文件沒有節區名稱字符串表,此參數可以爲 SHN_UNDEF。
- }Elf32_Ehdr;
#define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT]; //目標文件標識信息
Elf32_Half e_type; //目標文件類型
Elf32_Half e_machine; //目標體系結構類型
Elf32_Word e_version; //目標文件版本
Elf32_Addr e_entry; //程序入口的虛擬地址,若沒有,可爲0
Elf32_Off e_phoff; //程序頭部表格(Program Header Table)的偏移量(按字節計算),若沒有,可爲0
Elf32_Off e_shoff; //節區頭部表格(Section Header Table)的偏移量(按字節計算),若沒有,可爲0
Elf32_Word e_flags; //保存與文件相關的,特定於處理器的標誌。標誌名稱採用 EF_machine_flag的格式。
Elf32_Half e_ehsize; //ELF 頭部的大小(以字節計算)。
Elf32_Half e_phentsize; //程序頭部表格的表項大小(按字節計算)。
Elf32_Half e_phnum; //程序頭部表格的表項數目。可以爲 0。
Elf32_Half e_shentsize; //節區頭部表格的表項大小(按字節計算)。
Elf32_Half e_shnum; //節區頭部表格的表項數目。可以爲 0。
Elf32_Half e_shstrndx; //節區頭部表格中與節區名稱字符串表相關的表項的索引。如果文件沒有節區名稱字符串表,此參數可以爲 SHN_UNDEF。
}Elf32_Ehdr;
2.2.1 e_ident
其中需要注意地是e_ident是一個16字節的數組,這個數組按位置從左到右都是有特定含義,每個數組元素的下標在標準中還存在別稱,如byte0的下標0別名爲EI_MAG0,具體如下:
名稱 | 元素下標值 | 含義 |
EI_MAG0 | 0 | 文件標識 |
EI_MAG1 | 1 | 文件標識 |
EI_MAG2 | 2 | 文件標識 |
EI_MAG3 | 3 | 文件標識 |
EI_CLASS | 4 | 文件類 |
EI_DATA | 5 | 數據編碼 |
EI_VERSION | 6 | 文件版本 |
EI_PAD | 7 | 補齊字節開始處 |
EI_NIDENT | 16 | e_ident[]大小 |
e_ident[EI_CLASS](即e_ident[4])識別目標文件運行在目標機器的類別,取值可爲三種值:ELFCLASSNONE(0)非法類別;ELFCLASS32(1)32位目標;ELFCLASS64(2)64位目標。
e_ident[EI_DATA](即e_ident[5]):給出處理器特定數據的數據編碼方式。即大端還是小端方式。取值可爲3種:ELFDATANONE(0)非法數據編碼;ELFDATA2LSB(1)高位在前;ELFDATA2MSB(2)低位在前。
其它數組元素就不作介紹了。
2.2.2 e_type
e_type表示elf文件的類型,如下定義:
名稱 | 取值 | 含義 |
ET_NONE | 0 | 未知目標文件格式 |
ET_REL | 1 | 可重定位文件 |
ET_EXEC | 2 | 可執行文件 |
ET_DYN | 3 | 共享目標文件 |
ET_CORE | 4 | Core 文件(轉儲格式) |
ET_LOPROC | 0xff00 | 特定處理器文件 |
ET_HIPROC | 0xffff | 特定處理器文件 |
ET_LOPROC~ET_HIPROC | 0xff00~0xffff | 特定處理器文件 |
2.2.3 e_machine
e_machine表示目標體系結構類型:
名稱 | 取值 | 含義 |
EM_NONE | 0 | 未指定 |
EM_M32 | 1 | AT&T WE 32100 |
EM_SPARC | 2 | SPARC |
EM_386 | 3 | Intel 80386 |
EM_68K | 4 | Motorola 68000 |
EM_88K | 5 | Motorola 88000 |
EM_860 | 7 | Intel 80860 |
EM_MIPS | 8 | MIPS RS3000 |
others | 9~ | 預留 |
2.2.4 其它
其它的已在結構體註釋中有說明,這裏不再重複.
2.3 節區(Sections)
節區中包含目標文件中的所有信息,除了:ELF 頭部、程序頭部表格、節區頭部表格。節區滿足以下條件:
(1). 目標文件中的每個節區都有對應的節區頭部描述它,反過來,有節區頭部不意味着有節區。
(2). 每個節區佔用文件中一個連續字節區域(這個區域可能長度爲 0)。
(3). 文件中的節區不能重疊,不允許一個字節存在於兩個節區中的情況發生。
(4). 目標文件中可能包含非活動空間(INACTIVE SPACE)。這些區域不屬於任何頭部和節區,其內容未指定。
2.3.1 節區頭部表格
ELF 頭部中,e_shoff 成員給出從文件頭到節區頭部表格的偏移字節數;e_shnum給出表格中條目數目;e_shentsize 給出每個項目的字節數。從這些信息中可以確切地定
位節區的具體位置、長度。
從之前的描述中可知,每一項節區在節區頭部表格中都存在着一項元素與它對應,因此可知,這個節區頭部表格爲一連續的空間,每一項元素爲一結構體,那麼這個結構體的定義如下:
- typedef struct{
- Elf32_Word sh_name; //節區名,是節區頭部字符串表節區(Section Header String Table Section)的索引。名字是一個 NULL 結尾的字符串。
- Elf32_Word sh_type; //爲節區類型
- Elf32_Word sh_flags; //節區標誌
- Elf32_Addr sh_addr; //如果節區將出現在進程的內存映像中,此成員給出節區的第一個字節應處的位置。否則,此字段爲 0。
- Elf32_Off sh_offset; //此成員的取值給出節區的第一個字節與文件頭之間的偏移。
- Elf32_Word sh_size; //此 成 員 給 出 節 區 的 長 度 ( 字 節 數 )。
- Elf32_Word sh_link; //此成員給出節區頭部表索引鏈接。其具體的解釋依賴於節區類型。
- Elf32_Word sh_info; //此成員給出附加信息,其解釋依賴於節區類型。
- Elf32_Word sh_addralign; //某些節區帶有地址對齊約束.
- Elf32_Word sh_entsize; //某些節區中包含固定大小的項目,如符號表。對於這類節區,此成員給出每個表項的長度字節數。
- }Elf32_Shdr;
typedef struct{
Elf32_Word sh_name; //節區名,是節區頭部字符串表節區(Section Header String Table Section)的索引。名字是一個 NULL 結尾的字符串。
Elf32_Word sh_type; //爲節區類型
Elf32_Word sh_flags; //節區標誌
Elf32_Addr sh_addr; //如果節區將出現在進程的內存映像中,此成員給出節區的第一個字節應處的位置。否則,此字段爲 0。
Elf32_Off sh_offset; //此成員的取值給出節區的第一個字節與文件頭之間的偏移。
Elf32_Word sh_size; //此 成 員 給 出 節 區 的 長 度 ( 字 節 數 )。
Elf32_Word sh_link; //此成員給出節區頭部表索引鏈接。其具體的解釋依賴於節區類型。
Elf32_Word sh_info; //此成員給出附加信息,其解釋依賴於節區類型。
Elf32_Word sh_addralign; //某些節區帶有地址對齊約束.
Elf32_Word sh_entsize; //某些節區中包含固定大小的項目,如符號表。對於這類節區,此成員給出每個表項的長度字節數。
}Elf32_Shdr;
2.3.1.1 sh_type
sh_type的取值如下:
名稱 | 取值 | 說明 |
SHT_NULL | 0 | 此值標誌節區頭部是非活動的,沒有對應的節區。此節區頭部 中的其他成員取值無意義。 |
SHT_PROGBITS | 1 | 此節區包含程序定義的信息,其格式和含義都由程序來解釋。 |
SHT_SYMTAB | 2 | 此節區包含一個符號表。目前目標文件對每種類型的節區都只能包含一個,不過這個限制將來可能發生變化。 一般,SHT_SYMTAB 節區提供用於鏈接編輯(指 ld 而言)的符號,儘管也可用來實現動態鏈接。 |
SHT_STRTAB | 3 | 此節區包含字符串表。目標文件可能包含多個字符串表節區。 |
SHT_RELA | 4 | 此節區包含重定位表項,其中可能會有補齊內容(addend),例 如 32 位目標文件中的 Elf32_Rela 類型。目標文件可能擁有多 個重定位節區。 |
SHT_HASH | 5 | 此節區包含符號哈希表。所有參與動態鏈接的目標都必須包含一個符號哈希表。目前,一個目標文件只能包含一個哈希表, 不過此限制將來可能會解除。 |
SHT_DYNAMIC | 6 | 此節區包含動態鏈接的信息。目前一個目標文件中只能包含一個動態節區,將來可能會取消這一限制。 |
SHT_NOTE | 7 | 此節區包含以某種方式來標記文件的信息。 |
SHT_NOBITS | 8 | 這 種 類 型 的 節 區 不 佔 用 文 件 中 的 空 間 , 其 他 方 面 和SHT_PROGBITS 相似。儘管此節區不包含任何字節,成員 sh_offset 中還是會包含概念性的文件偏移 |
SHT_REL | 9 | 此節區包含重定位表項,其中沒有補齊(addends),例如 32 位目標文件中的 Elf32_rel 類型。目標文件中可以擁有多個重定 位節區。 |
SHT_SHLIB | 10 | 此節區被保留,不過其語義是未規定的。包含此類型節區的程 序與 ABI 不兼容。 |
SHT_DYNSYM | 11 | 作爲一個完整的符號表,它可能包含很多對動態鏈接而言不必 要的符號。因此,目標文件也可以包含一個 SHT_DYNSYM 節 區,其中保存動態鏈接符號的一個最小集合,以節省空間。 |
SHT_LOPROC | 0X70000000 | 這一段(包括兩個邊界),是保留給處理器專用語義的。 |
SHT_HIPROC | OX7FFFFFFF | 這一段(包括兩個邊界),是保留給處理器專用語義的。 |
SHT_LOUSER | 0X80000000 | 此值給出保留給應用程序的索引下界。 |
SHT_HIUSER | 0X8FFFFFFF | 此值給出保留給應用程序的索引上界。 |
2.3.1.2 sh_flag
sh_flag標誌着此節區是否可以修改,是否可以執行,如下定義:
名稱 | 取值 | 含義 |
SHF_WRITE | 0x1 | 節區包含進程執行過程中將可寫的數據。 |
SHF_ALLOC | 0x2 | 此節區在進程執行過程中佔用內存。某些控制節區並不出現於目標文件的內存映像中,對於那些節區,此位應設置爲 0。 |
SHF_EXECINSTR | 0x4 | 節區包含可執行的機器指令。 |
SHF_MASKPROC | 0xF0000000 | 所有包含於此掩碼中的四位都用於處理器專用的語義。 |
2.3.1.3 sh_link 和 sh_info 字段
從2.3.1節中可知,sh_link和sh_info字段的具體含義依賴於sh_type的值:
sh_type | sh_link | sh_info |
SHT_DYNAMIC | 此節區中條目所用到的字符串表格的節區頭部索引 | 0 |
SHT_HASH | 此哈希表所適用的符號表的節區頭部索引 | 0 |
SHT_REL SHT_RELA |
相關符號表的節區頭部索引 | 重定位所適用的節區的節區頭部索引 |
SHT_SYMTAB SHT_DYNSYM |
相關聯的字符串表的節區頭部索引 | 最後一個局部符號(綁定 STB_LOCAL)的符號表索引值加一 |
其它 | SHN_UNDEF | 0 |
2.3.2 特殊節區
有些節區是系統預訂的,一般以點開頭號,因此,我們有必要了解一些常用到的系統節區。
名稱 | 類型 | 屬性 | 含義 |
.bss | SHT_NOBITS | SHF_ALLOC + SHF_WRITE |
包含將出現在程序的內存映像中的爲初始化數據。根據定義,當程序開始執行,系統將把這些數據初始化爲 0。此節區不佔用文件空間。 |
.comment | SHT_PROGBITS | (無) | 包含版本控制信息。 |
.data | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE |
這些節區包含初始化了的數據,將出現在程序的內存映像中。 |
.data1 | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE |
這些節區包含初始化了的數據,將出現在程序的內存映像中。 |
.debug | SHT_PROGBITS | (無) | 此節區包含用於符號調試的信息。 |
.dynamic | SHT_DYNAMIC | 此節區包含動態鏈接信息。節區的屬性將包含 SHF_ALLOC 位。是否 SHF_WRITE 位被設置取決於處理器。 | |
.dynstr | SHT_STRTAB | SHF_ALLOC | 此節區包含用於動態鏈接的字符串,大多數情況下這些字符串代表了與符號表項相關的名稱。 |
.dynsym | SHT_DYNSYM | SHF_ALLOC | 此節區包含了動態鏈接符號表。 |
.fini | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR |
此節區包含了可執行的指令,是進程終止代碼的一部分。程序正常退出時,系統將安排執行這裏的代碼。 |
.got | SHT_PROGBITS | 此節區包含全局偏移表。 | |
.hash | SHT_HASH | SHF_ALLOC | 此節區包含了一個符號哈希表。 |
.init | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR |
此節區包含了可執行指令,是進程初始化代碼的一部分。當程序開始執行時,系統要在開始調用主程序入口之前(通常指 C 語言的 main 函數)執行這些代碼。 |
.interp | SHT_PROGBITS | 此節區包含程序解釋器的路徑名。如果程序包含一個可加載的段,段中包含此節區,那麼節區的屬性將包含 SHF_ALLOC 位,否則該位爲 0。 | |
.line | SHT_PROGBITS | (無) | 此節區包含符號調試的行號信息,其中描述了源程序與機器指令之間的對應關係。其內容是未定義的。 |
.note | SHT_NOTE | (無) | 此節區中包含註釋信息,有獨立的格式。 |
.plt | SHT_PROGBITS | 此節區包含過程鏈接表(procedure linkage table)。 | |
.relname .relaname |
SHT_REL SHT_RELA |
這些節區中包含了重定位信息。如果文件中包含可加載的段,段中有重定位內容,節區的屬性將包含 SHF_ALLOC 位,否則該位置 0。傳統上 name 根據重定位所適用的節區給定。例如 .text 節區的重定位節區名字將是:.rel.text 或者 .rela.text。 | |
.rodata .rodata1 |
SHT_PROGBITS | SHF_ALLOC | 這些節區包含只讀數據,這些數據通常參與進程映像的不可寫段。 |
.shstrtab | SHT_STRTAB | 此節區包含節區名稱。 | |
.strtab | SHT_STRTAB | 此節區包含字符串,通常是代表與符號表項相關的名稱。如果文件擁有一個可加載的段,段中包含符號串表,節區的屬性將包含SHF_ALLOC 位,否則該位爲 0。 | |
.symtab | SHT_SYMTAB | 此節區包含一個符號表。如果文件中包含一個可加載的段,並且該段中包含符號表,那麼節區的屬性中包含SHF_ALLOC 位,否則該位置爲 0。 | |
.text | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR |
此節區包含程序的可執行指令。 |
2.4 字符串表(String Table)
首先要知道,字符串表它本身就是一個節區,從第二章描述中可知,每一個節區都存在一個節區頭部表項與之對應,所以字符串表這個節區也存在一個節區頭部表項對應,而在elf文件頭部結構中存在一個成員e_shstrndx給出這個節區頭部表項的索引位置。因此可以通過
- shstrab = (rt_uint8_t *)module_ptr +shdr[elf_module->e_shstrndx].sh_offset;
shstrab = (rt_uint8_t *)module_ptr +shdr[elf_module->e_shstrndx].sh_offset;
來得到字符串表的起始位置。
字符串表節區包含以 NULL(ASCII 碼 0)結尾的字符序列,通常稱爲字符串。ELF目標文件通常使用字符串來表示符號和節區名稱。對字符串的引用通常以字符串在字符
串表中的下標給出。
一般,第一個字節(索引爲 0)定義爲一個空字符串。類似的,字符串表的最後一個字節也定義爲 NULL,以確保所有的字符串都以 NULL 結尾。索引爲 0 的字符串在
不同的上下文中可以表示無名或者名字爲 NULL 的字符串。
允許存在空的字符串表節區,其節區頭部的 sh_size 成員應該爲 0。對空的字符串表而言,非 0 的索引值是非法的。
例如:對於各個節區而言,節區頭部的 sh_name 成員包含其對應的節區頭部字符串表節區的索引,此節區由 ELF 頭的 e_shstrndx 成員給出。下圖給出了包含 25 個字節
的一個字符串表,以及與不同索引相關的字符串。
那麼上面字符串表包含以下字符串:
索引 | 字符串 |
0 | (無) |
1 | name. |
7 | Variable |
11 | able |
16 | able |
24 | (空字符串) |
2.5 符號表(Symbol Table)
首先,字號表同樣本身是一節區,也存在一對應節區頭部表項。目標文件的符號表中包含用來定位、重定位程序中符號定義和引用的信息。符號表索引是對此數組的索引。索引 0 表示表中的第一表項,同時也作爲未定義符號的索引。符號表是由一個個符號元素組成,每個元素的數據結構如下定義:
- typedef struct {
- Elf32_Word st_name; //名稱,索引到字符串表
- Elf32_Addr st_value; //給出相關聯的符號的取值。依賴於具體的上下文.
- Elf32_Word st_size; //相關的尺寸大小
- unsigned char st_info; //給出符號的類型和綁定屬性.
- unsigned char st_other; //該成員當前包含 0,其含義沒有定義。
- Elf32_Half st_shndx; //給出相關的節區頭部表索引。某些索引具有特殊含義。
- } Elf32_sym;
typedef struct {
Elf32_Word st_name; //名稱,索引到字符串表
Elf32_Addr st_value; //給出相關聯的符號的取值。依賴於具體的上下文.
Elf32_Word st_size; //相關的尺寸大小
unsigned char st_info; //給出符號的類型和綁定屬性.
unsigned char st_other; //該成員當前包含 0,其含義沒有定義。
Elf32_Half st_shndx; //給出相關的節區頭部表索引。某些索引具有特殊含義。
} Elf32_sym;
2.5.1 st_info
st_info 中包含符號類型和綁定信息,操縱方式如:
- #define ELF32_ST_BIND(i) ((i)>>4)
- #define ELF32_ST_TYPE(i) ((i)&0xf)
- #define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))
2.5.1.1 st_info 的高四位(ELF32_ST_BIND(i))
表示符號綁定,用於確定鏈接可見性和行爲。具體的綁定類型如:
名稱 | 取值 | 說明 |
STB_LOCAL | 0 | 局部符號在包含該符號定義的目標文件以外不可見。相同名稱的局部符號可以存在於多個文件中,互不影響。 |
STB_GLOBAL | 1 | 全局符號對所有將組合的目標文件都是可見的。一個文件中對某個全局符號的定義將滿足另一個文件對相同全局符號的未定義引用。 |
STB_WEAK | 2 | 弱符號與全局符號類似,不過他們的定義優先級比較低。 |
STB_LOPROC | 13 | 處於這個範圍的取值是保留給處理器專用語義的。 |
STB_HIPROC | 15 | 處於這個範圍的取值是保留給處理器專用語義的。 |
全局符號與弱符號之間的區別主要有兩點:
(2). 當鏈接編輯器搜索歸檔庫(archive libraries)時,會提取那些包含未定義全局符號的檔案成員。成員的定義可以是全局符號,也可以是弱符號。連接編輯器不會提取檔案成員來滿足未定義的弱符號。未能解析的弱符號取值爲 0。
在每個符號表中,所有具有 STB_LOCAL 綁定的符號都優先於弱符號和全局符號。符號表節區中的 sh_info 頭部成員包含第一個非局部符號的符號表索引。
2.5.1.2 st_info的低四位ELF32_ST_TYPE(i)
定義如下:
名稱 | 取值 | 說明 |
STT_NOTYPE | 0 | 符號的類型沒有指定 |
STT_OBJECT | 1 | 符號與某個數據對象相關,比如一個變量、數組等等 |
STT_FUNC | 2 | 符號與某個函數或者其他可執行代碼相關 |
STT_SECTION | 3 | 符號與某個節區相關。這種類型的符號表項主要用於重定位,通常具有 STB_LOCAL 綁定。 |
STT_FILE | 4 | 傳統上,符號的名稱給出了與目標文件相關的源文件的名稱。文件符號具有 STB_LOCAL 綁定,其節區索引是SHN_ABS,並且它優先於文件的其他 STB_LOCAL 符號(如果有的話) |
STT_LOPROC~ STT_HIPROC |
13~15 | 此範圍的符號類型值保留給處理器專用語義用途。 |
在共享目標文件中的函數符號(類型爲 STT_FUNC)具有特別的重要性。當其他目標文件引用了來自某個共享目標中的函數時,鏈接編輯器自動爲所引用的符號創建過
程鏈接表項。類型不是 STT_FUNC 的共享目標符號不會自動通過過程鏈接表進行引用。
如果一個符號的取值引用了某個節區中的特定位置,那麼它的節區索引成員(st_shndx)包含了其在節區頭部表中的索引。當節區在重定位過程中被移動時,符號的取值也會隨之變化,對符號的引用始終會“指向”程序中的相同位置。
2.5.2 st_shndx
如前面所述,st_shndx給出相關的節區頭部表索引。但其值也存在一些特殊值,具有某些特殊的含義:
- SHN_ABS:符號具有絕對取值,不會因爲重定位而發生變化。
- SHN_COMMON: 符號標註了一個尚未分配的公共塊。符號的取值給出了對齊約束,與節區的 sh_addralign成員類似。就是說,鏈接編輯器將爲符號分配存儲空間,地址位於 st_value 的倍數處。符號的大小給出了所需要的字節數。
- SHN_UNDEF: 此節區表索引值意味着符號沒有定義。當鏈接編輯器將此目標文件與其他定義了該符號的目標文件進行組合時,此文件中對該符號的引用將被鏈接到實際定義的位置。
2.5.3 st_value
不同的目標文件類型中符號表項對 st_value 成員具有不同的解釋:
(1). 在可重定位文件中,st_value 中遵從了節區索引爲 SHN_COMMON 的符號的對齊約束。
(2). 在可重定位的文件中,st_value 中包含已定義符號的節區偏移。就是說,st_value 是從 st_shndx 所標識的節區頭部開始計算,到符號位置的偏移。
(3). 在可執行和共享目標文件中,st_value 包含一個虛地址。爲了使得這些文件的符號對動態鏈接器更有用,節區偏移(針對文 件的解釋)讓位於虛擬地址(針對內存的解釋),因爲這時與節區號無關。
儘管符號表取值在不同的目標文件中具有相似的含義,適當的程序可以採取高效的數據訪問方式。
2.6 重定位信息
重定位是將符號引用與符號定義進行連接的過程。例如,當程序調用了一個函數時,相關的調用指令必須把控制傳輸到適當的目標執行地址。
2.6.1 重定位表項
可重定位文件必須包含如何修改其節區內容的信息,從而允許可執行文件和共享目標文件保存進程的程序映像的正確信息。重定位表項就是這樣一些數據。
可重定位表項的數據結構如下定義:
- typedef struct {
- Elf32_Addr r_offset; //給出了重定位動作所適用的位置
- Elf32_Word r_info; //給出要進行重定位的符號表索引,以及將實施的重定位類型.
- } Elf32_Rel;
- typedef struct {
- Elf32_Addr r_offset;
- Elf32_Word r_info;
- Elf32_Word r_addend; //給出一個常量補齊,用來計算將被填充到可重定位字段的數值。
- } Elf32_Rela;
typedef struct {
Elf32_Addr r_offset; //給出了重定位動作所適用的位置
Elf32_Word r_info; //給出要進行重定位的符號表索引,以及將實施的重定位類型.
} Elf32_Rel;
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Word r_addend; //給出一個常量補齊,用來計算將被填充到可重定位字段的數值。
} Elf32_Rela;
重定位節區會引用兩個其它節區:符號表、要修改的節區。節區頭部的 sh_info 和sh_link 成員給出這些關係。不同目標文件的重定位表項對 r_offset 成員具有略微不同的解釋。
r_info通常分爲高8位和低8位,分別表示不同的含義:
- #define ELF32_R_SYM(i) ((i)>>8)
- #define ELF32_R_TYPE(i) ((unsigned char)(i))
- #define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))
高8位用作要進行重定位的符號表索引,通過它可以得出一個符號表項,而低8位表示將實施的重定位類型,它是和處理器相關的。
2.6.2 ELF32_R_TYPE(i)
重定位表項描述如何修改後面的指令和數據字段。一般,共享目標文件在創建時,其基本虛擬地址是 0,不過執行地址將隨着動態加載而發生變化。
2.7 程序頭部
以上說了那麼多的有關節區的相關內容,那麼現在來討論一個程序頭部表。其實節區頭部表是以elf資源的角度來看待elf文件的,即這個elf文件到底存在哪些資源,以及這些資源之間的關聯關係,而程序頭部表,則以程序運行來看elf文件的,即要運行這個elf文件,需要將哪些東西載入到內存鏡像。
程序頭部是一個表,它的起始地址在elf頭部結構中的e_phoff成員指定,數量由e_phnum表示,每個程序頭部表項的大小由e_phentsize指出。
可執行文件或者共享目標文件的程序頭部是一個結構數組,每個結構描述了一個段或者系統準備程序執行所必需的其它信息。目標文件的“段”包含一個或者多個“節區”,也就是“段內容(Segment Contents)”。程序頭部僅對於可執行文件和共享目標文件有意義。
下面來看程序頭號部表項的數據結構:
- typedef struct {
- Elf32_Word p_type; //段類型
- Elf32_Off p_offset; //段位置
- Elf32_Addr p_vaddr; //給出段的第一個字節將被放到內存中的虛擬地址
- Elf32_Addr p_paddr; //僅用於與物理地址相關的系統中
- Elf32_Word p_filesz; //給出段在文件映像中所佔的字節數
- Elf32_Word p_memsz; //給出段在內存映像中佔用的字節數
- Elf32_Word p_flags; //與段相關的標誌
- Elf32_Word p_align; //對齊
- } Elf32_phdr;
typedef struct {
Elf32_Word p_type; //段類型
Elf32_Off p_offset; //段位置
Elf32_Addr p_vaddr; //給出段的第一個字節將被放到內存中的虛擬地址
Elf32_Addr p_paddr; //僅用於與物理地址相關的系統中
Elf32_Word p_filesz; //給出段在文件映像中所佔的字節數
Elf32_Word p_memsz; //給出段在內存映像中佔用的字節數
Elf32_Word p_flags; //與段相關的標誌
Elf32_Word p_align; //對齊
} Elf32_phdr;
2.7.1 p_type
名稱 | 取值 | 說明 |
PT_NULL | 0 | 此數組元素未用。結構中其他成員都是未定義的。 |
PT_LOAD | 1 | 此數組元素給出一個可加載的段,段的大小由 p_filesz 和 p_memsz描述。文件中的字節被映射到內存段開始處。如果 p_memsz 大於p_filesz,“剩餘”的字節要清零。p_filesz 不能大於 p_memsz。可加載的段在程序頭部表格中根據 p_vaddr 成員按升序排列。 |
PT_DYNAMIC | 2 | 數組元素給出動態鏈接信息。 |
PT_INTERP | 3 | 數組元素給出一個 NULL 結尾的字符串的位置和長度,該字符串將被當作解釋器調用。這種段類型僅對與可執行文件有意義(儘管也可能在共享目標文件上發生)。在一個文件中不能出現一次以上。如果存在這種類型的段,它必須在所有可加載段項目的前面。 |
PT_NOTE | 4 | 此數組元素給出附加信息的位置和大小。 |
PT_SHLIB | 5 | 此段類型被保留,不過語義未指定。包含這種類型的段的程序與 ABI不符。 |
PT_PHDR | 6 | 此類型的數組元素如果存在,則給出了程序頭部表自身的大小和位置,既包括在文件中也包括在內存中的信息。此類型的段在文件中不能出現一次以上。並且只有程序頭部表是程序的內存映像的一部分時才起作用。如果存在此類型段,則必須在所有可加載段項目的前面。 |
PT_LOPROC~ PT_HIPROC |
0x70000000~ 0x7fffffff |
此範圍的類型保留給處理器專用語義。 |