上一篇講了ELF文件的總體佈局,以及section和segment的概念。按照計劃,今天繼續講 ELF header。
講新的內容之前,先更正一個錯誤:上一篇中講section header table中的條目和文件中的section是一一對應的,其實這麼講是不對的。一個section必定有一個section header來描述它,但一個section header不一定在文件中有對應的section,因爲有的section是不佔用文件字節的。segment也是這個道理。
這篇文章本來應該上週寫的,但上週忙於突擊考試,週末又自我放縱,看了兩天《相聲有新人》,所以今天趕緊補上。
ELF header的定義可以在 /usr/include/elf.h
中找到。Elf32_Ehdr
是32位 ELF header的結構體。Elf64_Ehdr
是64位ELF header的結構體。
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number和其它信息 */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
64位和32位只是個別字段長度不同,比如 Elf64_Addr
和 Elf64_Off
都是64位無符號整數。而Elf32_Addr
和 Elf32_Off
是32位無符號整數。這導致ELF header的所佔的字節數不同。32位的ELF header佔52個字節,64位的ELF header佔64個字節。
ELF header詳解
-
e_ident
佔16個字節。前四個字節被稱作ELF的Magic Number。後面的字節描述了ELF文件內容如何解碼等信息。等一下詳細講。 -
e_type
,2字節,描述了ELF文件的類型。以下取值有意義:ET_NONE, 0, No file type ET_REL, 1, Relocatable file(可重定位文件,通常是文件名以.o結尾,目標文件) ET_EXEC, 2, Executable file (可執行文件) ET_DYN, 3, Shared object file (動態庫文件,你用gcc編譯出的二進制往往也屬於這種類型,驚訝嗎?) ET_CORE, 4, Core file (core文件,是core dump生成的吧?) ET_NUM, 5,表示已經定義了5種文件類型 ET_LOPROC, 0xff00, Processor-specific ET_HIPROC, 0xffff, Processor-specific
從
ET_LOPROC
到ET_HIPROC
的值,包含特定於處理器的語義。 -
e_machine
,2字節。描述了文件面向的架構,可取值如下(因爲文檔較老,現在有更多取值,參見/usr/include/elf.h
中的EM_
開頭的宏定義):EM_NONE, 0, No machine 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 ... ...
-
e_version
,2字節,描述了ELF文件的版本號,合法取值如下:EV_NONE, 0, Invalid version EV_CURRENT, 1, Current version,通常都是這個取值。 EV_NUM, 2, 表示已經定義了2種版本號
-
e_entry
,(32位4字節,64位8字節),執行入口點,如果文件沒有入口點,這個域保持0。 -
e_phoff
, (32位4字節,64位8字節),program header table的offset,如果文件沒有PH,這個值是0。 -
e_shoff
, (32位4字節,64位8字節), section header table 的offset,如果文件沒有SH,這個值是0。 -
e_flags
, 4字節,特定於處理器的標誌,32位和64位Intel架構都沒有定義標誌,因此eflags的值是0。 -
e_ehsize
, 2字節,ELF header的大小,32位ELF是52字節,64位是64字節。 -
e_phentsize
,2字節。program header table中每個入口的大小。 -
e_phnum
, 2字節。如果文件沒有program header table, e_phnum的值爲0。e_phentsize
乘以e_phnum
就得到了整個program header table的大小。 -
e_shentsize
, 2字節,section header table中entry的大小,即每個section header佔多少字節。 -
e_shnum
, 2字節,section header table中header的數目。如果文件沒有section header table, e_shnum的值爲0。e_shentsize
乘以e_shnum
,就得到了整個section header table的大小。 -
e_shstrndx
, 2字節。section header string table index. 包含了section header table中section name string table。如果沒有section name string table,e_shstrndx
的值是SHN_UNDEF
.
注意:program header table一般譯爲程序頭表,section header table 一般譯爲節頭表,因爲這樣的翻譯無助於理解,所以我傾向於不翻。
e_ident
回過頭來,我們仔細看看文件前16個字節,也是e_ident
。
如圖,前4個字節是ELF的Magic Number,固定爲7f 45 4c 46
。
第5個字節指明ELF文件是32位還是64位的。
第6個字節指明瞭數據的編碼方式,即我們通常說的little endian或是big endian。little endian我喜歡稱作小頭在前,低位字節在前,或者直接說低位字節在低位地址,比如0x7f454c46
,存儲順序就是46 4c 45 7f
。big endian就是大頭在前,高位字節在前,直接說就是高位字節在低位地址,比如0x7f454c46
,在文件中的存儲順序是7f 45 4c 46
。
第7個字節指明瞭ELF header的版本號,目前值都是1。
第8-16個字節,都填充爲0。
readelf讀取ELF header
我們使用readelf -h <elffile>
可以讀取文件的ELF header信息。
比如我本地有執行文件hello
,我執行reaelf -h hello
,結果如下:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1050
Start of program headers: 64 (bytes into file)
Start of section headers: 14768 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
這是我用gcc生成的執行文件,但注意它的Type是DYN (Shared object file)
,這大概是因爲,這個文件不能直接執行,是依賴於解釋器和c庫才能運行。真正的可執行文件是解釋器,而hello
相對於解釋器來說也是個共享庫文件。這是我的推斷,需要後面深入學習後驗證。
今天就講到這了,這周結束前計劃寫本系列第三篇,關於目標文件內section的知識。
2018-10-22 Mon