ELF目標文件格式的最前部是ELF文件頭。包含了整個文件的基本屬性。比如ELF文件版本,目標機器型號,程序入口地址等。然后是ELF的各個段,其中ELF文件中與段有關的重要結構就是段表。段表描述了ELF文件包含的所有段的信息,比如每個段的段名,段的長度,在文件中的偏移,讀寫權限及段的其他屬性。
一 文件頭;
通過readelf命令來詳細查看ELF文件的頭信息:
root@zhf-maple:/home/zhf/c_prj# readelf -h main.o
ELF 頭:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
類別: ELF64
數據: 2 補碼,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
類型: REL (可重定位文件)
系統架構: Advanced Micro Devices X86-64
版本: 0x1
入口點地址: 0x0
程序頭起點: 0 (bytes into file)
Start of section headers: 1048 (bytes into file)
標志: 0x0
本頭的大小: 64 (字節)
程序頭大小: 0 (字節)
Number of program headers: 0
節頭大小: 64 (字節)
節頭數量: 13
字符串表索引節頭: 12
我們將Elf32_Ehdr結構與readelf命令輸出的信息對比可得如下對應關系:
成員 |
readelf輸出結果 |
e_ident |
Magic,類別,數據,版本,OS/ABI,ABI |
e_type |
類型 |
e_machine |
系統架構 |
e_version |
版本 |
e_entry |
入口點地址 |
e_phoff |
start of program headers |
e_shoff |
start of section headers |
e_flags |
標志 |
e_ehsize |
文件頭的大小 |
e_phentsize |
程序頭大小 |
e_phnum |
number of program headers |
e_shentsize |
節頭大小 |
e_shnum |
number of program headers |
e_shstrndx |
字符串表段索引 |
下面我來逐步分析各個部分:
魔數:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
7f 、45、4c、46分別對應ascii碼的Del(刪除)、字母E、字母L、字母F。這四個字節被稱為ELF文件的魔數,操作系統在加載可執行文件時會確認魔數是否正確,如果不正確則拒絕加載。
第五個字節標識ELF文件是32位(01)還是64位(02)的。
第六個字節標識該ELF文件字節序是小端(01)還是大端(02)的。
第七個字節指示ELF文件的版本號,一般是01。
后九個字節ELF標准未做定義。一般為00.
文件類型:
e_type成員標識文件類型,ELF文件有三種類型,如下表所示。
常量標識 |
值 |
類型 |
ET_REL |
1 |
可重定位文件,一般位.o文件 |
ET_EXEC |
2 |
可執行文件 |
ET_DYN |
3 |
共享目標文件,一般位.so文件 |
機器類型:
ELF文件格式被設計成可以在多個平台下使用,但並不表示同一個ELF文件可以在不同的平台下使用,而是表示不同平台下的ELF文件都遵循同一套ELF標准.e_machine成員就表示該ELF文件的平台屬性。
常量標識 |
值 |
系統架構 |
EM_M32 |
1 |
AT&T WE 32100 |
EM_SPARC |
2 |
SPARC |
EM_386 |
3 |
Intel 80386 |
EM_68K |
4 |
Motorola m68k family |
EM_88K |
5 |
Motorola m88k family |
EM_860 |
6 |
Intel 80860 |
二 段表:
在ELF文件中有各種各樣的段,段表就是保存這些段的基本屬性的結構。它描述了ELF的各個段的信息,比如每個段的段名,段的長度,在文件中的偏移,讀寫權限及段的其他屬性。編譯器,鏈接器和裝載器都是依靠段表來定位和訪問各個段的屬性的。前面使用objdump -h來顯示各個段,但只是顯示了關鍵的幾個段。我們用readelf來輸出段表的內容。段表是存在Elf32_Shdr中,它是一個結構體數組。各個成員的含義如下表:
成員 |
含義 |
sh_name |
段名,位於一個叫“.shstrtab”的字符串表 |
sh_type |
段的類型,詳細內容看后文 |
sh_flags |
段的標志位,詳細內容見后文 |
sh_addr |
段在被加載后在進程地址空間中的虛擬地址,當段不能被加載時,它為0 |
sH_offset |
段在elf文件中的偏移,如果該段不存在於文件中,則它無意義 |
sh_szie |
段的長度 |
sh_link |
段的鏈接信息,詳細內容見后文 |
sh_info |
段的鏈接信息,詳細內容見后文 |
sh_addralign |
段地址對齊 |
sh_entsize |
項的長度,有的段包含一些固定大小的項,比如符號表,sh_enrsize就是用來指示這些項的大小 |
我們用readelf -S main.o來查看內容。
root@zhf-maple:/home/zhf/c_prj# readelf -S main.o
共有 13 個節頭,從偏移量 0x418 開始:
節頭:
[號] 名稱 類型 地址 偏移量
大小 全體大小 旗標 鏈接 信息 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000004c 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000320
0000000000000060 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 0000008c
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000094
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 00000094
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000098
0000000000000024 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000bc
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000380
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000118
0000000000000198 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 000002b0
000000000000006c 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 000003b0
0000000000000061 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
從上面的輸出的第一行可以看到,段表從0x418開始,觀察其他段的偏移量可以發現,段表位於所有段之后,就是文件的末尾,該ELF文件有12個段,每個段的大小也就是sizeof(Elf32_Shdr)的大小,為40字節,所以整個段表的大小就是12*40 = 480個字節,再加上之前的0x418,總共1048+480=1528個字節.
段的類型
上面輸出的第三列就是段的類型,段的類型的相關常量如下表所示:
常量 |
值 |
含義 |
SHT_NULL |
0 |
無效段 |
SHT_PROGBITS |
1 |
程序段、代碼段、數據段都是此種類型 |
SHT_SYMTAB |
2 |
表示該段的類內容為符號表 |
SHT_STRTAB |
3 |
表示該段的內容是字符串表 |
SHT_RELA |
4 |
重定位表,該段包含了重定位信息 |
SHT_HASH |
5 |
符號表的哈希表 |
SHT_DYNAMIC |
6 |
動態鏈接信息 |
SHT_NOTE |
7 |
提示性信息 |
SHT_NOBITS |
8 |
表示該段在文件中沒有內容,比如.bss段 |
SHT_REL |
9 |
該段包含了重定位信息 |
SHT_SHLIB |
10 |
該段保留 |
SHT_DNYSYM |
11 |
動態鏈接的符號表 |
段的標志位
以上輸出的flg一列指出了該段在進程虛擬空間中的屬性。
常量 |
值 |
含義 |
SHF_WRITE |
1 |
表示該段在進程空間中可寫 |
SHF_ALLOC |
2 |
表示該段需要在進程空間中分配空間 |
SHF_EXECINSTR |
4 |
表示該段在進程空間中可以被執行 |
段的鏈接信息
sh_type |
sh_link |
sh_info |
SHT_DYNAMIC |
該段所使用的字符串表在段表中的下標 |
0 |
SHT_HASH |
該段所使用的符號表在段表中的下標 |
0 |
SHT_REL,SHT_RELA |
該段所使用的符號表在段表中的下標 |
該重定位表所作用的段在段表中的下標 |
SHT_SYMTAB、SHT_DNYSYM |
操作系統相關 |
操作系統相關 |
other |
SHN_UNDEF |
0 |
可重定位表:
鏈接器在處理目標文件時,須要對目標文件中某些部位進行重定位,即代碼段和數據段中那些對絕對地址的引用的位置。這些重定位的信息都記錄在ELF文件表里面,對於每個須要重定位的代碼段和數據段,都會有一個相應的重定位表,例如 .rel.text 表對應.text段。也就是說,重定位表記錄了須要被重定位的地址都在相應段的哪些地方。比如.rela.text就是針對.text段的重定位表,因為.text段中至少有一個絕對地址的引用,那就是對printf函數的調用。而.data沒有對絕對地址的調用
字符串表:
ELF文件中用到了很多字符串。比如段名,變量名等。因為字符串的長度往往是不定的,所以用固定的結構來表示比較困難。常見的做法就是把字符串集中起來存放到一個表。然后使用字符串在表中的偏移來引用字符串。
下圖展示了一個長度為 25 字節的字符串表:
字符串引用示例:
常見的段名是.strtab(String Table)或者是.shstrtab(Section Header String Table) }