四、 ELF 文件格式分析
-
ELF文件(目標文件)格式主要四種:
-
可重定向文件:
- 文件保存着代碼和適當的數據,用來和其他的目標文件一起來創建一個可執行文件或者是一個共享目標文件。(目標文件或者靜態庫文件,即linux通常后綴為 .a 和 .o 的文件)
-
可執行文件:
- 文件保存着一個用來執行的程序。(例如bash,gcc等)
-
共享目標文件:
- 共享庫。文件保存着代碼和合適的數據,用來被下連接編輯器和動態鏈接器鏈接。(linux下后綴為 .so 的文件。)
- 執行文件的格式與上述兩種文件的格式之間的區別主要在於觀察的角度不同:一種稱為連接視圖(Linking View),一種稱為執行視圖(Execution View)
-
核心轉儲文件(core dump):
- 當進程意外終止時,系統可以將該進程的地址空間的內容及終止時的一些其他信息轉儲到核心轉儲文件
-
目標文件既要參與程序鏈接又要參與程序執行。 出於方便性和效率考慮, 目標文件格式提供了兩種並行視圖,分別反映了這些活動的不同需求。
-
ELF header:ELF 文件頭
- 包含了描述整個文件的基本屬性,比如 ELF 文件版本、目標機器型號、程序入口地址等等
- Program header table:程序表頭。如果存在的話,告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表
-
Section or Segment:節或段
- ELF 文件中包含的各個節,具體見后面章節。
-
Section header table:節頭表,可選
- 每個節區在表中都有一項,描述了 ELF 文件包含的所有節的信息,比如每個節的節名、節的長度、在文件中的偏移、讀寫權限及段的其他屬性
- 用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。
- String tables:字符串表
- Symbol tables:符號表
注意:盡管圖中顯示的各個組成部分是有順序的,實際上除了 ELF 頭部表以外,其他節區和段都沒有規定的順序。
4.1 ELF header:文件頭
文件的最開始幾個字節給出如何解釋文件的提示信息。 這些信息獨立於處理器, 也獨立於文件中的其余內容。
還是對 hello.c 這段代碼進行分析,對編譯出來的 a.out 文件執行命令:readelf -h a.out
-
ELF 文件頭中的定義如下:
- Magic:ELF 魔數
- Class:文件機器字節長度
- Data:數據存儲方式
- Version:版本
- OS/ABI:運行平台
- ABI Version:ABI版本
- Type:ELF 文件類型
- Machine:硬件平台
- Version:硬件平台版本
- Entry point address:入口地址
- Start of program headers:程序頭入口和長度
- Start of section headers:節頭的位置和長度
- Flags:處理器標志
- Size of this header:ELF 文件頭的大小
- Size of program headers:程序頭的大小
- Number of program headers:程序頭的數量
- Size of section headers:節的大小
- Number of section headers:節的數量
- Section header string table index:段表頭字符串表的位置
段由若干個節(Section)構成,節頭表對每一個節的信息有相關描述。對可執行程序而言,節頭表是可選的。 ELF 頭部是一個關於本文件的路線圖(road map),從總體上描述文件的結構。下面是ELF頭部的數據結構:
- /usr/include/elf.h
1 typedef struct 2 { 3 unsigned char e_ident[EI_NIDENT]; /* 魔數和相關信息 */ 4 Elf32_Half e_type; /* 目標文件類型 */ 5 Elf32_Half e_machine; /* 硬件體系 */ 6 Elf32_Word e_version; /* 目標文件版本 */ 7 Elf32_Addr e_entry; /* 程序進入點 */ 8 Elf32_Off e_phoff; /* 程序頭部偏移量 */ 9 Elf32_Off e_shoff; /* 節頭部偏移量 */ 10 Elf32_Word e_flags; /* 處理器特定標志 */ 11 Elf32_Half e_ehsize; /* ELF頭部長度 */ 12 Elf32_Half e_phentsize; /* 程序頭部中一個條目的長度 */ 13 Elf32_Half e_phnum; /* 程序頭部條目個數 */ 14 Elf32_Half e_shentsize; /* 節頭部中一個條目的長度 */ 15 Elf32_Half e_shnum; /* 節頭部條目個數 */ 16 Elf32_Half e_shstrndx; /* 節頭部字符表索引 */ 17 } Elf32_Ehdr;
-
e_ident[EI_NIDENT]:ELF魔數
- e_ident標識索引如下:
- 對應的信息是 Magic、Class、Data、Version、OS/ABI 和 ABI Version
- e_ident[0] - e_ident[3] 包含了ELF文件的魔數,依次是0x7f、'E'、'L'、'F'。注意,任何一個ELF文件必須包含此魔數。
- e_ident[4] 表示硬件系統的位數,1代表32位,2代表64位。
- e_ident[5] 表示數據編碼方式(字節序),1代表小端排序(最大有意義的字節占有最低的地址),2代表大端排序(最大有意義的字節占有最高的地址)。
- e_ident[6] 指定ELF文件的主版本號,一般為1。
- e_ident[7]到e_ident[14]是填充符,通常是0。ELF格式規范中定義這幾個字節是被忽略的,但實際上是這幾個字節完全可以可被利用。
- e_ident[7] 為0x21,表示本文件已被感染;或者存放可執行代碼。
-
e_type:ELF 文件類型
- ET_NONE:值為0。未知目標文件格式
- ET_REL:值為 1。可重定位文件,一般為 .o 文件
- ET_EXEC:值為2。可執行文件
- ET_DYN:值為 3。共享目標文件,一般為 .so 文件
- ET_CORE:值為4。Core 文件(轉儲格式)
- ET_LOPROC:值為 0xff00。特定處理器文件
- ET_HIPROC:值為 0xffff。特定處理器文件
- ET_LOPROC 和 ET_HIPROC 之間的取值用來標識與處理器相關的文件格式。
- e_machine:ELF 文件的 CPU 平台屬性。相關常量以 EM_ 開頭
名稱 |
取值 |
含義 |
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 |
其它值都是保留的。特定處理器的 ELF 名稱會使用機器名來進行區分。 |
- e_version:目標文件版本,一般為常數 1
名稱 |
取值 |
含義 |
EV_NONE |
0 |
非法版本 |
EV_CURRENT |
1 |
當前版本 |
- e_entry:程序入口地址,規定 ELF 程序入口的虛擬地址,操作系統在加載完該程序后從這個地址開始執行進程的指令。可重定位文件一般沒有入口地址,則這個值為 0
- e_phoff:對應 Start of program headers。程序頭部表格( Program Header Table)的偏移量(按字節計算)。如果文件沒有程序頭部表格,可以為 0。
- e_shoff:對應 Start of section headers。節區頭部表格( Section Header Table) 的偏移量(按字節計算)。 如果文件沒有節區頭部表格,可以為 0。
- e_flags:ELF 標志位,用來標識一些 ELF 文件平台相關的屬性。相關常量的格式一般為 EF_machine_flag,machine 為平台,flag 為標志
- e_ehsize:ELF 文件頭本身的大小(以字節計算),對應 Size of this header
- e_phentsize:對應 Size of program headers。程序頭部表格的表項大小(按字節計算)
- e_phnum:對應 Number of program headers。程序頭部表格的表項數目。可以為 0。
- e_shentsize:對應 Size of section headers,節區頭部表格的表項大小(按字節計算)。
- e_shnum:對應 Number of section headers,節區頭部表格的表項數目。可以為 0。
- e_shstrndx:節區頭部表格中與節區名稱字符串表相關的表項的索引。 如果文件沒有節區名稱字符串表,此參數可以為 SHN_UNDEF。
ELF 頭部中大多數字段都是對子頭部數據的描述,其意義相對比較簡單。值得注意的是某些病毒可能修改字段 e_entry(程序進入點)的值,以指向病毒代碼。
4.2 Program header table:程序頭表
緊接ELF頭部的是程序頭表。執行命令:readelf -l a.out
程序頭是一個結構數組,包含了 ELF 頭表中字段 e_phnum 定義的條目,此結構描述一個段或其他系統准備執行該程序所需要的信息。
結構體位於:/usr/include/elf.h
1 typedef struct { 2 Elf32_Word p_type; /* 段類型 */ 3 Elf32_Off p_offset; /* 段位置相對於文件開始處的偏移量 */ 4 Elf32_Addr p_vaddr; /* 段在內存中的地址 */ 5 Elf32_Addr p_paddr; /* 段的物理地址 */ 6 Elf32_Word p_filesz; /* 段在文件中的長度 */ 7 Elf32_Word p_memsz; /* 段在內存中的長度 */ 8 Elf32_Word p_flags; /* 段的標記 */ 9 Elf32_Word p_align; /* 段在內存中對齊標記 */ 10 } Elf32_Phdr;
對一個ELF可執行程序而言,一個基本的段是標記 p_type 為 PT_INTERP 的段,它表明了運行此程序所需要的程序解釋器(/lib/ld-linux.so.2),實際上也就是動態連接器(dynamic linker)。
-
最重要的段是標記 p_type 為 PT_LOAD 的段,它表明了為運行程序而需要加載到內存的數據。查看上面實際輸入,可以看見有兩個可 LOAD 段,第一個為只讀可執行(FLg 為 R E ),第二個為可讀可寫(Flg 為 RW)。
- 段一包含了文本節 .text ,注意到 ELF 文件頭部中程序進入點的值為 0x400430,它會指向節.text在內存中的地址。
- 段二包含了數據節 .data,此數據節中數據是可讀可寫的,相對的只讀數據節 .rodata 包含在段一中。
4.3 Section:節區
-
節區中包含目標文件中的所有信息,除了: ELF 頭部、程序頭部表格、節區頭部表格。節區滿足以下條件:
- 目標文件中的每個節區都有對應的節區頭部描述它, 反過來, 有節區頭部不意味着有節區。
- 每個節區占用文件中一個連續字節區域(這個區域可能長度為 0)。
- 文件中的節區不能重疊,不允許一個字節存在於兩個節區中的情況發生。
- 目標文件中可能包含非活動空間( INACTIVE SPACE)。這些區域不屬於任何頭部和節區,其內容未指定。
很多節區中包含了程序和控制信息。 下面的表中給出了系統使用的節區, 以及它們的類型和屬性。
名稱 |
類型 |
屬性 |
含義 |
.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 |
SHT_REL |
這些節區中包含了重定位信息。如果文件中包含可加載的段,段中有重定位內容,節區的屬性將包含 SHF_ALLOC 位,否則該位置 0。傳統上 name 根據重定位所適用的節區給定。 例如 .text 節區的重定位節區名字將是: .rel.text 或者 .rela.text。 |
|
.relaname |
SHT_RELA |
||
.rodata |
SHT_PROGBITS |
SHF_ALLOC |
這些節區包含只讀數據, 這些數據通常參與進程映像的不可寫段。 |
.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 |
此節區包含程序的可執行指令。 |
-
在分析這些節區的時候,需要注意如下事項:
- 以"."開頭的節區名稱是系統保留的。 應用程序可以使用沒有前綴的節區名稱, 以避免與系統節區沖突。
- 目標文件格式允許人們定義不在上述列表中的節區。
- 目標文件中也可以包含多個名字相同的節區。
- 保留給處理器體系結構的節區名稱一般構成為:處理器體系結構名稱簡寫 + 節區名稱。
- 處理器名稱應該與 e_machine 中使用的名稱相同。例如 .FOO.psect 街區是由 FOO 體系結構定義的 psect 節區。
- 另外,有些編譯器對如上節區進行了擴展, 這些已存在的擴展都使用約定俗成的名稱,如:.sdata、.tdesc、.sbss、.lit4、.lit8、.reginfo、.gptab、.liblist、.conflict 等等
4.4 Section header table:節區頭部表格
執行命令 readelf -S a.out 可以查看到節表頭,這里面保存了 ELF 文件中的各種各樣的節。節表是 ELF 文件中除了文件頭以外最重要的結構,它描述了 ELF 各個 節 的信息,比如每個節的節名、節的長度、在文件中的偏移、讀寫權限及節的其他屬性。即 ELF 文件的節結構就是由節表來決定的,編譯器、鏈接器和裝載器都是依靠節表來定位和訪問各個節的屬性的。節表在 ELF 文件中的位置由 ELF 文件頭的"e_shoff"成員決定。
1 There are 31 section headers, starting at offset 0x19d8: 2 3 Section Headers: 4 [Nr] Name Type Address Offset Size EntSize Flags Link Info Align 5 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 6 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 7 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 8 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 9 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8 10 [ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000060 0000000000000018 A 6 1 8 11 [ 6] .dynstr STRTAB 0000000000400318 00000318 000000000000003d 0000000000000000 A 0 0 1 12 [ 7] .gnu.version VERSYM 0000000000400356 00000356 0000000000000008 0000000000000002 A 5 0 2 13 [ 8] .gnu.version_r VERNEED 0000000000400360 00000360 0000000000000020 0000000000000000 A 6 1 8 14 [ 9] .rela.dyn RELA 0000000000400380 00000380 0000000000000018 0000000000000018 A 5 0 8 15 [10] .rela.plt RELA 0000000000400398 00000398 0000000000000030 0000000000000018 AI 5 24 8 16 [11] .init PROGBITS 00000000004003c8 000003c8 000000000000001a 0000000000000000 AX 0 0 4 17 [12] .plt PROGBITS 00000000004003f0 000003f0 0000000000000030 0000000000000010 AX 0 0 16 18 [13] .plt.got PROGBITS 0000000000400420 00000420 0000000000000008 0000000000000000 AX 0 0 8 19 [14] .text PROGBITS 0000000000400430 00000430 0000000000000192 0000000000000000 AX 0 0 16 20 [15] .fini PROGBITS 00000000004005c4 000005c4 0000000000000009 0000000000000000 AX 0 0 4 21 [16] .rodata PROGBITS 00000000004005d0 000005d0 0000000000000011 0000000000000000 A 0 0 4 22 [17] .eh_frame_hdr PROGBITS 00000000004005e4 000005e4 0000000000000034 0000000000000000 A 0 0 4 23 [18] .eh_frame PROGBITS 0000000000400618 00000618 00000000000000f4 0000000000000000 A 0 0 8 24 [19] .init_array INIT_ARRAY 0000000000600e10 00000e10 0000000000000008 0000000000000000 WA 0 0 8 25 [20] .fini_array FINI_ARRAY 0000000000600e18 00000e18 0000000000000008 0000000000000000 WA 0 0 8 26 [21] .jcr PROGBITS 0000000000600e20 00000e20 0000000000000008 0000000000000000 WA 0 0 8 27 [22] .dynamic DYNAMIC 0000000000600e28 00000e28 00000000000001d0 0000000000000010 WA 6 0 8 28 [23] .got PROGBITS 0000000000600ff8 00000ff8 0000000000000008 0000000000000008 WA 0 0 8 29 [24] .got.plt PROGBITS 0000000000601000 00001000 0000000000000028 0000000000000008 WA 0 0 8 30 [25] .data PROGBITS 0000000000601028 00001028 0000000000000010 0000000000000000 WA 0 0 8 31 [26] .bss NOBITS 0000000000601038 00001038 0000000000000008 0000000000000000 WA 0 0 1 32 [27] .comment PROGBITS 0000000000000000 00001038 0000000000000035 0000000000000001 MS 0 0 1 33 [28] .shstrtab STRTAB 0000000000000000 000018cc 000000000000010c 0000000000000000 0 0 1 34 [29] .symtab SYMTAB 0000000000000000 00001070 0000000000000648 0000000000000018 30 47 8 35 [30] .strtab STRTAB 0000000000000000 000016b8 0000000000000214 0000000000000000 0 0 1 36 Key to Flags: 37 W (write), A (alloc), X (execute), M (merge), S (strings), l (large) 38 I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) 39 O (extra OS processing required) o (OS specific), p (processor specific)
除了使用 readelf 可以查看節的詳細信息外,也可以使用 objdump 來查看 ELF 文件中節,只不過 objdump 只列出 ELF 文件中關鍵的節,省略了其他輔助節。如下:
ELF 頭部中, e_shoff 成員給出從文件頭到節區頭部表格的偏移字節數; e_shnum 給出表格中條目數目; e_shentsize 給出每個項目的字節數。 從這些信息中可以確切地定位節區的具體位置、長度。
節區頭部表格中比較特殊的幾個下標如下:
名稱 |
取值 |
說明 |
SHN_UNDEF |
0 |
標記未定義的、缺失的、不相關的,或者沒有含義的節區引用 |
SHN_LORESERVE |
OXFF00 |
保留索引的下界 |
SHN_LOPROC |
0XFF00 |
|
SHN_HIPROC |
0XFF1F |
保留給處理器特殊的語義 |
SHN_ABS |
OXFFF1 |
包含對應引用量的絕對取值。這些值不會被重定位所影響 |
SHN_COMMON |
OXFFF2 |
相對於此節區定義的符號是公共符號。如 FORTRAN 中 COMMON 或者未分配的 C 外部變量。 |
SHN_HIRESERVE |
0XFFFF |
保留索引的上界 |
介於 SHN_LORESERVE 和 SHN_HIRESERVE 之間的表項不會出現在節區頭部表中。
節頭表結構體在 /usr/include/elf.h 中,結構如下:
1 typedef struct elf32_shdr { 2 ELF32_Word sh_name; /* 節名 */ 3 ELF32_Word sh_type; /* 節的類型 */ 4 ELF32_Word sh_flags; /* 節的標志位 */ 5 ELF32_Addr sh_addr; /* 節的虛擬地址 */ 6 ELF32_Off sh_offset; /* 節偏移 */ 7 ELF32_Word sh_size; /* 節的長度 */ 8 ELF32_Word sh_link; /* 節連接信息 */ 9 ELF32_Word sh_info; /* 節連接信息 */ 10 ELF32_Word sh_addralign; /* 節地址對齊 */ 11 ELF32_Word sh_entsize; /* 項的長度 */ 12 } ELF32_Shdr;
- sh_name:節名。節名是個字符串,它位於一個叫做 ".shstrtab"的字符串表中。sh_name 是節名字符串在 ".shstrtab"中的偏移
- sh_type:節的類型。節的名字只在編譯和鏈接的過程中有意義,但它不能真正的表示節的類型。我們可以將一個數據段命名為 ".text",對於編譯器和鏈接器來說,主要決定節的屬性是節的類型(sh_type)和節的標志位(sh_flags)。節的類型相關常量以 SHT_ 開頭,如下:
名稱 |
取值 |
說明 |
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 |
此值給出保留給應用程序的索引上界。 |
其它的節區類型是保留的。 |
-
sh_flags:節的標志位。節的標志位表示該節在進程虛擬地址空間中的屬性,比如是否可寫,是否可執行等。相關常量以 SHF_ 開頭:
- SHF_WRITE:值為1,表示該節在進程空間中可寫
- SHF_ALLOC:值為2,表示該節在進程空間中需要分配空間。有些包含指示或控制信息的節不需要在進程空間中被分配空間,它們一般不會有這個標志。像代碼段、數據段和 .bss 段都會有這個標志
- SHF_EXECINSTR:值為4,表示該節在進程空間中可以被執行,一般指代碼節
- SHF_MASKPROC:值為 0xf000000,所有包含於此掩碼中的四位都用於處理器專用的語義。
-
sh_addr:節的虛擬地址。如果該節可以被加載,則 sh_addr 為該節被加載后在進程地址空間中的虛擬地址;否則 sh_addr 為0
- 系統保留段的屬性如下表
Name |
sh_type |
sh_flag |
.bss |
SHT_NOBITS |
SHF_ALLOC + SHF_WRITE |
.comment |
SHT_PROGBITS |
none |
.data |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE |
.data1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE |
.debug |
SHT_PROGBITS |
none |
.dynamic |
SHT_DYNAMIC |
SHF_ALLOC + SHF_WRITE 在有些系統下 .dynamic 節可能是只讀的,所以沒有 SHF_WRITE 標志位 |
.hash |
SHT_HASH |
SHF_ALLOC |
.line |
SHT_PROGBITS |
none |
.note |
SHT_NOTE |
none |
.rodata |
SHT_PROGBITS |
SHF_ALLOC |
.rodata1 |
SHT_PROGBITS |
SHF_ALLOC |
.shstrtab |
SHT_STRTAB |
none |
.strtab |
SHT_STRTAB |
如果該 ELF 文件中有可裝載的段需要用到該字符串表,那么該字符串表也將被裝載到進程空間,則有 SHF_ALLOC 標志位 |
.symtab |
SHT_SYMTAB |
同字符串表 |
.text |
SHT_PROGBITS |
SHF_ALLOC |
- sh_offset:節偏移。如果該節存在於文件中,則表示該節在文件中的偏移;否則無意義。比如 sh_offset 對於 BSS 節來說就沒有意義。SHT_NOBITS 類型的節區不占用文件的空間, 因此其 sh_offset 成員給出的是其概念性的偏移。
- sh_size:此成員給出節區的長度(字節數)。除非節區的類型是 SHT_NOBITS,否則節區占用文件中的 sh_size 字節。類型為 SHT_NOBITS 的節區長度可能非零, 不過卻不占用文件中的空間。
-
sh_link 和 sh_info:節連接信息。
- sh_link 此成員給出節區頭部表索引鏈接。其具體的解釋依賴於節區類型。
- sh_info 此成員給出附加信息,其解釋依賴於節區類型。
- 如果節的類型是與鏈接相關的(不論是動態鏈接或靜態鏈接),比如重定位表、符號表等,那么這兩個成員所包含的意義如下表所示。對於其他類型的段,這兩個成員沒有意義。
sh_type |
sh_link |
sh_info |
SHT_DYNAMIC |
該節所用的字符串表在節表中的下標 |
0 |
SHT_HASH |
該節所使用的符號表在節表中的下標 |
0 |
SHT_REL |
該節所使用的相應符號表在節表中的下標 |
該重定位表所作用的節在節表中的下標 |
SHT_RELA |
||
SHT_SYMTAB |
操作系統相關的 |
操作系統相關的 |
SHT_DNYSYM |
||
other |
SHN_UNDEF |
0 |
-
sh_addralign:節地址對齊。
- 某些節區帶有地址對齊約束。例如,如果一個節區保存一個 doubleword, 那么系統必須保證整個節區能夠按雙字對齊。 sh_addr 對 sh_addralign 取模,結果必須為 0。目前僅允許取值為 0 和 2 的冪次數。數值 0 和 1 表示節區沒有對齊約束。
- sh_entsize:項的長度。有些節包含了一些固定大小的項,比如符號表,它包含的每個符號所占的大小都是一樣的。對於這種節,sh_entsize 表示每個項的大小。如果為 0 ,則表示該節不包含固定大小的段。
索引為零(SHN_UNDEF)的節區頭部也是存在的,盡管此索引標記的是未定義的節區引用。這個節區的內容固定如下(SHN_UNDEF(0)節區的內容):
字段名稱 |
取值 |
說明 |
sh_name |
0 |
無名稱 |
sh_type |
SHT_NULL |
非活動 |
sh_flags |
0 |
無標志 |
sh_addr |
0 |
無地址 |
sh_offset |
0 |
無文件偏移 |
sh_size |
0 |
無尺寸大小 |
sh_link |
SHN_UNDEF |
無鏈接信息 |
sh_info |
0 |
無輔助信息 |
sh_addralign |
0 |
無對齊要求 |
sh_entsize |
0 |
無表項 |
當前的映射如下:
ELF 格式可以比 COFF 格式包含更多的調試信息。在 I386 平台 LINUX 系統下,用命令 file 查看一個 ELF 可執行程序的可能輸出是:a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped。