ELF檔案解析(二):ELF header詳解
上一篇講了ELF檔案的總體佈局,以及section和segment的概念。按照計劃,今天繼續講 ELF header。
講新的內容之前,先更正一個錯誤:上一篇中講section header table中的條目和檔案中的section是一一對應的,其實這麼講是不對的。一個section必定有一個section header來描述它,但一個section header不一定在檔案中有對應的section,因為有的section是不佔用檔案位元組的。segment也是這個道理。
這篇文章本來應該上週寫的,但上週忙於突擊考試,週末又自我放縱,看了兩天《相聲有新人》,所以今天趕緊補上。
ELF header定義
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_Halfe_type;/* Object file type */ Elf32_Halfe_machine;/* Architecture */ Elf32_Worde_version;/* Object file version */ Elf32_Addre_entry;/* Entry point virtual address */ Elf32_Off e_phoff;/* Program header table file offset */ Elf32_Off e_shoff;/* Section header table file offset */ Elf32_Worde_flags;/* Processor-specific flags */ Elf32_Halfe_ehsize;/* ELF header size in bytes */ Elf32_Halfe_phentsize;/* Program header table entry size */ Elf32_Halfe_phnum;/* Program header table entry count */ Elf32_Halfe_shentsize;/* Section header table entry size */ Elf32_Halfe_shnum;/* Section header table entry count */ Elf32_Halfe_shstrndx;/* Section header string table index */ } Elf32_Ehdr; typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf64_Halfe_type;/* Object file type */ Elf64_Halfe_machine;/* Architecture */ Elf64_Worde_version;/* Object file version */ Elf64_Addre_entry;/* Entry point virtual address */ Elf64_Off e_phoff;/* Program header table file offset */ Elf64_Off e_shoff;/* Section header table file offset */ Elf64_Worde_flags;/* Processor-specific flags */ Elf64_Halfe_ehsize;/* ELF header size in bytes */ Elf64_Halfe_phentsize;/* Program header table entry size */ Elf64_Halfe_phnum;/* Program header table entry count */ Elf64_Halfe_shentsize;/* Section header table entry size */ Elf64_Halfe_shnum;/* Section header table entry count */ Elf64_Halfe_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