轉自 https://www.ibm.com/developerworks/cn/linux/l-excutff/html
相對於其它文件類型,可執行文件多是一個操做系統中最重要的文件類型,由於它們是完成操做的真正執行者。可執行文件的大小、運行速度、資源佔用狀況以及可擴展性、可移植性等與文件格式的定義和文件加載過程緊密相關。研究可執行文件的格式對編寫高性能程序和一些黑客技術的運用都是很是有意義的。linux
無論何種可執行文件格式,一些基本的要素是必須的,顯而易見的,文件中應包含代碼和數據。由於文件可能引用外部文件定義的符號(變量和函數),所以重定位信息和符號信息也是須要的。一些輔助信息是可選的,如調試信息、硬件信息等。基本上任意一種可執行文件格式都是按區間保存上述信息,稱爲段(Segment)或節(Section)。不一樣的文件格式中段和節的含義可能有細微區別,但根據上下文關係能夠很清楚的理解,這不是關鍵問題。最後,可執行文件一般都有一個文件頭部以描述本文件的整體結構。編程
相對可執行文件有三個重要的概念:編譯(compile)、鏈接(link,也可稱爲連接、聯接)、加載(load)。源程序文件被編譯成目標文件,多個目標文件被鏈接成一個最終的可執行文件,可執行文件被加載到內存中運行。由於本文重點是討論可執行文件格式,所以加載過程也相對重點討論。下面是LINUX平臺下ELF文件加載過程的一個簡單描述。設計模式
1:內核首先讀ELF文件的頭部,而後根據頭部的數據指示分別讀入各類數據結構,找到標記爲可加載(loadable)的段,並調用函數 mmap()把段內容加載到內存中。在加載以前,內核把段的標記直接傳遞給 mmap(),段的標記指示該段在內存中是否可讀、可寫,可執行。顯然,文本段是隻讀可執行,而數據段是可讀可寫。這種方式是利用了現代操做系統和處理器對內存的保護功能。著名的Shellcode( 參考資料 17)的編寫技巧則是突破此保護功能的一個實際例子。數組
2:內核分析出ELF文件標記爲 PT_INTERP 的段中所對應的動態鏈接器名稱,並加載動態鏈接器。現代 LINUX 系統的動態鏈接器一般是 /lib/ld-linux.so.2,相關細節在後面有詳細描述。數據結構
3:內核在新進程的堆棧中設置一些標記-值對,以指示動態鏈接器的相關操做。app
4:內核把控制傳遞給動態鏈接器。ide
5:動態鏈接器檢查程序對外部文件(共享庫)的依賴性,並在須要時對其進行加載。函數
6:動態鏈接器對程序的外部引用進行重定位,通俗的講,就是告訴程序其引用的外部變量/函數的地址,此地址位於共享庫被加載在內存的區間內。動態鏈接還有一個延遲(Lazy)定位的特性,即只在"真正"須要引用符號時才重定位,這對提升程序運行效率有極大幫助。工具
7:動態鏈接器執行在ELF文件中標記爲 .init 的節的代碼,進行程序運行的初始化。在早期系統中,初始化代碼對應函數 _init(void)(函數名強制固定),在現代系統中,則對應形式爲
1
2
3
4
5
6
|
void
__attribute((constructor))
init_function(void)
{
……
}
|
其中函數名爲任意。
8:動態鏈接器把控制傳遞給程序,從 ELF 文件頭部中定義的程序進入點開始執行。在 a.out 格式和ELF格式中,程序進入點的值是顯式存在的,在 COFF 格式中則是由規範隱含定義。
從上面的描述能夠看出,加載文件最重要的是完成兩件事情:加載程序段和數據段到內存;進行外部定義符號的重定位。重定位是程序鏈接中一個重要概念。咱們知道,一個可執行程序一般是由一個含有 main() 的主程序文件、若干目標文件、若干共享庫(Shared Libraries)組成。(注:採用一些特別的技巧,也可編寫沒有 main 函數的程序,請參閱 參考資料 2)一個 C 程序可能引用共享庫定義的變量或函數,換句話說就是程序運行時必須知道這些變量/函數的地址。在靜態鏈接中,程序全部須要使用的外部定義都徹底包含在可執行程序中,而動態鏈接則只在可執行文件中設置相關外部定義的一些引用信息,真正的重定位是在程序運行之時。靜態鏈接方式有兩個大問題:若是庫中變量或函數有任何變化都必須從新編譯鏈接程序;若是多個程序引用一樣的變量/函數,則此變量/函數會在文件/內存中出現屢次,浪費硬盤/內存空間。比較兩種鏈接方式生成的可執行文件的大小,能夠看出有明顯的區別。
a.out 格式在不一樣的機器平臺和不一樣的 UNIX 操做系統上有輕微的不一樣,例如在 MC680x0 平臺上有 6 個 section。下面咱們討論的是最"標準"的格式。
a.out 文件包含 7 個 section,格式以下:
執行頭部的數據結構:
1
2
3
4
5
6
7
8
9
10
|
struct exec {
unsigned long a_midmag; /* 魔數和其它信息 */
unsigned long a_text; /* 文本段的長度 */
unsigned long a_data; /* 數據段的長度 */
unsigned long a_bss; /* BSS段的長度 */
unsigned long a_syms; /* 符號表的長度 */
unsigned long a_entry; /* 程序進入點 */
unsigned long a_trsize; /* 文本重定位表的長度 */
unsigned long a_drsize; /* 數據重定位表的長度 */
};
|
文件頭部主要描述了各個 section 的長度,比較重要的字段是 a_entry(程序進入點),表明了系統在加載程序並初試化各類環境後開始執行程序代碼的入口。這個字段在後面討論的 ELF 文件頭部中也有出現。由 a.out 格式和頭部數據結構咱們能夠看出,a.out 的格式很是緊湊,只包含了程序運行所必須的信息(文本、數據、BSS),並且每一個 section 的順序是固定的。這種結構缺少擴展性,如不能包含"現代"可執行文件中常見的調試信息,最初的 UNIX 黑客對 a.out 文件調試使用的工具是 adb,而 adb 是一種機器語言調試器!
a.out 文件中包含符號表和兩個重定位表,這三個表的內容在鏈接目標文件以生成可執行文件時起做用。在最終可執行的 a.out 文件中,這三個表的長度都爲 0。a.out 文件在鏈接時就把全部外部定義包含在可執行程序中,若是從程序設計的角度來看,這是一種硬編碼方式,或者可稱爲模塊之間是強藕和的。在後面的討論中,咱們將會具體看到ELF格式和動態鏈接機制是如何對此進行改進的。
a.out 是早期UNIX系統使用的可執行文件格式,由 AT&T 設計,如今基本上已被 ELF 文件格式代替。a.out 的設計比較簡單,但其設計思想明顯的被後續的可執行文件格式所繼承和發揚。能夠參閱 參考資料 16 和閱讀 參考資料 15 源代碼加深對 a.out 格式的理解。 參考資料 12 討論瞭如何在"現代"的紅帽LINUX運行 a.out 格式文件。
COFF 格式比 a.out 格式要複雜一些,最重要的是包含一個節段表(section table),所以除了 .text,.data,和 .bss 區段之外,還能夠包含其它的區段。另外也多了一個可選的頭部,不一樣的操做系統可一對此頭部作特定的定義。
COFF 文件格式以下:
文件頭部的數據結構:
1
2
3
4
5
6
7
8
9
10
|
struct filehdr
{
unsigned short f_magic; /* 魔數 */
unsigned short f_nscns; /* 節個數 */
long f_timdat; /* 文件創建時間 */
long f_symptr; /* 符號表相對文件的偏移量 */
long f_nsyms; /* 符號表條目個數 */
unsigned short f_opthdr; /* 可選頭部長度 */
unsigned short f_flags; /* 標誌 */
};
|
COFF 文件頭部中魔數與其它兩種格式的意義不太同樣,它是表示針對的機器類型,例如 0x014c 相對於 I386 平臺,而 0x268 相對於 Motorola 68000系列等。當 COFF 文件爲可執行文件時,字段 f_flags 的值爲 F_EXEC(0X00002),同時也表示此文件沒有未解析的符號,換句話說,也就是重定位在鏈接時就已經完成。由此也能夠看出,原始的 COFF 格式不支持動態鏈接。爲了解決這個問題以及增長一些新的特性,一些操做系統對 COFF 格式進行了擴展。Microsoft 設計了名爲 PE(Portable Executable)的文件格式,主要擴展是在 COFF 文件頭部之上增長了一些專用頭部,具體細節請參閱 參考資料 18,某些 UNIX 系統也對 COFF 格式進行了擴展,如 XCOFF(extended common object file format)格式,支持動態鏈接,請參閱 參考資料 5。
緊接文件頭部的是可選頭部,COFF 文件格式規範中規定可選頭部的長度能夠爲 0,但在 LINUX 系統下可選頭部是必須存在的。下面是 LINUX 下可選頭部的數據結構:
1
2
3
4
5
6
7
8
9
10
11
12
|
typedef struct
{
char magic[2]; /* 魔數 */
char vstamp[2]; /* 版本號 */
char tsize[4]; /* 文本段長度 */
char dsize[4]; /* 已初始化數據段長度 */
char bsize[4]; /* 未初始化數據段長度 */
char entry[4]; /* 程序進入點 */
char text_start[4]; /* 文本段基地址 */
char data_start[4]; /* 數據段基地址 */
}
COFF_AOUTHDR;
|
字段 magic 爲 0413 時表示 COFF 文件是可執行的,注意到可選頭部中顯式定義了程序進入點,標準的 COFF 文件沒有明確的定義程序進入點的值,一般是從 .text 節開始執行,但這種設計並很差。
前面咱們提到,COFF 格式比 a.out 格式多了一個節段表,一個節頭條目描述一個節數據的細節,所以 COFF 格式能包含更多的節,或者說能夠根據實際須要,增長特定的節,具體表如今 COFF 格式自己的定義以及稍早說起的 COFF 格式擴展。我我的認爲,節段表的出現多是 COFF 格式相對 a.out 格式最大的進步。下面咱們將簡單描述 COFF 文件中節的數據結構,由於節的意義更多體如今程序的編譯和鏈接上,因此本文不對其作更多的描述。此外,ELF 格式和 COFF格式對節的定義很是類似,在隨後的 ELF 格式分析中,咱們將省略相關討論。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct COFF_scnhdr
{
char s_name[8]; /* 節名稱 */
char s_paddr[4]; /* 物理地址 */
char s_vaddr[4]; /* 虛擬地址 */
char s_size[4]; /* 節長度 */
char s_scnptr[4]; /* 節數據相對文件的偏移量 */
char s_relptr[4]; /* 節重定位信息偏移量 */
char s_lnnoptr[4]; /* 節行信息偏移量 */
char s_nreloc[2]; /* 節重定位條目數 */
char s_nlnno[2]; /* 節行信息條目數 */
char s_flags[4]; /* 段標記 */
};
|
有一點須要注意:LINUX系統中頭文件coff.h中對字段s_paddr的註釋是"physical address",但彷佛應該理解爲"節被加載到內存中所佔用的空間長度"。字段s_flags標記該節的類型,如文本段、數據段、BSS段等。在COFF的節中也出現了行信息,行信息描述了二進制代碼與源代碼的行號之間的對映關係,在調試時頗有用。
參考資料 19是一份對COFF格式詳細描述的中文資料,更詳細的內容請參閱 參考資料 20。
ELF文件有三種類型: 可重定位文件:也就是一般稱的目標文件,後綴爲.o。 共享文件:也就是一般稱的庫文件,後綴爲.so。 可執行文件:本文主要討論的文件格式,總的來講,可執行文件的格式與上述兩種文件的格式之間的區別主要在於觀察的角度不一樣:一種稱爲鏈接視圖(Linking View),一種稱爲執行視圖(Execution View)。
首先看看ELF文件的整體佈局:
段由若干個節(Section)構成,節頭表對每個節的信息有相關描述。對可執行程序而言,節頭表是可選的。 參考資料 1中做者談到把節頭表的全部數據所有設置爲0,程序也能正確運行!ELF頭部是一個關於本文件的路線圖(road map),從整體上描述文件的結構。下面是ELF頭部的數據結構:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* 魔數和相關信息 */
Elf32_Half e_type; /* 目標文件類型 */
Elf32_Half e_machine; /* 硬件體系 */
Elf32_Word e_version; /* 目標文件版本 */
Elf32_Addr e_entry; /* 程序進入點 */
Elf32_Off e_phoff; /* 程序頭部偏移量 */
Elf32_Off e_shoff; /* 節頭部偏移量 */
Elf32_Word e_flags; /* 處理器特定標誌 */
Elf32_Half e_ehsize; /* ELF頭部長度 */
Elf32_Half e_phentsize; /* 程序頭部中一個條目的長度 */
Elf32_Half e_phnum; /* 程序頭部條目個數 */
Elf32_Half e_shentsize; /* 節頭部中一個條目的長度 */
Elf32_Half e_shnum; /* 節頭部條目個數 */
Elf32_Half e_shstrndx; /* 節頭部字符表索引 */
} Elf32_Ehdr;
|
下面咱們對ELF頭表中一些重要的字段做出相關說明,完整的ELF定義請參閱 參考資料6和 參考資料 7。
e_ident[0]-e_ident[3]包含了ELF文件的魔數,依次是0x7f、'E'、'L'、'F'。注意,任何一個ELF文件必須包含此魔數。 參考資料 3中討論了利用程序、工具、/Proc文件系統等多種查看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格式規範中定義這幾個字節是被忽略的,但其實是這幾個字節徹底能夠可被利用。如病毒Lin/Glaurung.676/666( 參考資料 1)設置e_ident[7]爲0x21,表示本文件已被感染;或者存放可執行代碼( 參考資料 2)。ELF頭部中大多數字段都是對子頭部數據的描述,其意義相對比較簡單。值得注意的是某些病毒可能修改字段e_entry(程序進入點)的值,以指向病毒代碼,例如上面提到的病毒Lin/Glaurung.676/666。
一個實際可執行文件的文件頭部形式以下:(利用命令readelf)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80483cc
Start of program headers: 52 (bytes into file)
Start of section headers: 14936 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31
|
緊接ELF頭部的是程序頭表,它是一個結構數組,包含了ELF頭表中字段e_phnum定義的條目,結構描述一個段或其餘系統準備執行該程序所須要的信息。
1
2
3
4
5
6
7
8
9
10
|
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;
|
在詳細討論可執行文件程序頭表以前,首先查看一個實際文件的輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
INTERP 0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00684 0x00684 R E 0x1000
LOAD 0x000684 0x08049684 0x08049684 0x00118 0x00130 RW 0x1000
DYNAMIC 0x000690 0x08049690 0x08049690 0x000c8 0x000c8 RW 0x4
NOTE 0x000108 0x08048108 0x08048108 0x00020 0x00020 R 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version
.gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .data .dynamic .ctors .dtors .jcr .got .bss
04 .dynamic
05 .note.ABI-tag
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 080480f4 0000f4 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048108 000108 000020 00 A 0 0 4
[ 3] .hash HASH 08048128 000128 000040 04 A 4 0 4
[ 4] .dynsym DYNSYM 08048168 000168 0000b0 10 A 5 1 4
[ 5] .dynstr STRTAB 08048218 000218 00007b 00 A 0 0 1
[ 6] .gnu.version VERSYM 08048294 000294 000016 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 080482ac 0002ac 000030 00 A 5 1 4
[ 8] .rel.dyn REL 080482dc 0002dc 000008 08 A 4 0 4
[ 9] .rel.plt REL 080482e4 0002e4 000040 08 A 4 b 4
[10] .init PROGBITS 08048324 000324 000017 00 AX 0 0 4
[11] .plt PROGBITS 0804833c 00033c 000090 04 AX 0 0 4
[12] .text PROGBITS 080483cc 0003cc 0001f8 00 AX 0 0 4
[13] .fini PROGBITS 080485c4 0005c4 00001b 00 AX 0 0 4
[14] .rodata PROGBITS 080485e0 0005e0 00009f 00 A 0 0 32
[15] .eh_frame PROGBITS 08048680 000680 000004 00 A 0 0 4
[16] .data PROGBITS 08049684 000684 00000c 00 WA 0 0 4
[17] .dynamic DYNAMIC 08049690 000690 0000c8 08 WA 5 0 4
[18] .ctors PROGBITS 08049758 000758 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049760 000760 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049768 000768 000004 00 WA 0 0 4
[21] .got PROGBITS 0804976c 00076c 000030 04 WA 0 0 4
[22] .bss NOBITS 0804979c 00079c 000018 00 WA 0 0 4
[23] .comment PROGBITS 00000000 00079c 000132 00 0 0 1
[24] .debug_aranges PROGBITS 00000000 0008d0 000098 00 0 0 8
[25] .debug_pubnames PROGBITS 00000000 000968 000040 00 0 0 1
[26] .debug_info PROGBITS 00000000 0009a8 001cc6 00 0 0 1
[27] .debug_abbrev PROGBITS 00000000 00266e 0002cc 00 0 0 1
[28] .debug_line PROGBITS 00000000 00293a 0003dc 00 0 0 1
[29] .debug_frame PROGBITS 00000000 002d18 000048 00 0 0 4
[30] .debug_str PROGBITS 00000000 002d60 000bcd 01 MS 0 0 1
[31] .shstrtab STRTAB 00000000 00392d 00012b 00 0 0 1
[32] .symtab SYMTAB 00000000 003fa8 000740 10 33 56 4
[33] .strtab STRTAB 00000000 0046e8 000467 00 0 0 1
|
對一個ELF可執行程序而言,一個基本的段是標記p_type爲PT_INTERP的段,它代表了運行此程序所須要的程序解釋器(/lib/ld-linux.so.2),實際上也就是動態鏈接器(dynamic linker)。最重要的段是標記p_type爲PT_LOAD的段,它代表了爲運行程序而須要加載到內存的數據。查看上面實際輸入,能夠看見有兩個可LOAD段,第一個爲只讀可執行(FLg爲R E),第二個爲可讀可寫(Flg爲RW)。段1包含了文本節.text,注意到ELF文件頭部中程序進入點的值爲0x80483cc,正好是指向節.text在內存中的地址。段二包含了數據節.data,此數據節中數據是可讀可寫的,相對的只讀數據節.rodata包含在段1中。ELF格式能夠比COFF格式包含更多的調試信息,如上面所列出的形式爲.debug_xxx的節。在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。
ELF文件中包含了動態鏈接器的全路徑,內核定位"正確"的動態鏈接器在內存中的地址是"正確"運行可執行文件的保證, 參考資料 13討論瞭如何經過查找動態鏈接器在內存中的地址以達到顛覆(Subversiver)動態鏈接機制的方法。
最後咱們討論ELF文件的動態鏈接機制。每個外部定義的符號在全局偏移表(Global Offset Table GOT)中有相應的條目,若是符號是函數則在過程鏈接表(Procedure Linkage Table PLT)中也有相應的條目,且一個PLT條目對應一個GOT條目。對外部定義函數解析多是整個ELF文件規範中最複雜的,下面是函數符號解析過程的一個描述。
1:代碼中調用外部函數func,語句形式爲call 0xaabbccdd,地址0xaabbccdd實際上就是符號func在PLT表中對應的條目地址(假設地址爲標號.PLT2)。
2:PLT表的形式以下
1
2
3
4
5
6
7
8
9
10
|
.PLT0: pushl 4(%ebx) /* GOT表的地址保存在寄存器ebx中 */
jmp *8(%ebx)
nop; nop
nop; nop
.PLT1: jmp *name1@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
.PLT2: jmp *func@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
|
3:查看標號.PLT2的語句,其實是跳轉到符號func在GOT表中對應的條目。
4:在符號沒有重定位前,GOT表中此符號對應的地址爲標號.PLT2的下一條語句,便是pushl $offset,其中$offset是符號func的重定位偏移量。注意到這是一個二次跳轉。
5:在符號func的重定位偏移量壓棧後,控制跳到PLT表的第一條目,把GOT[1]的內容壓棧,並跳轉到GOT[2]對應的地址。
6:GOT[2]對應的其實是動態符號解析函數的代碼,在對符號func的地址解析後,會把func在內存中的地址設置到GOT表中此符號對應的條目中。
7:當第二次調用此符號時,GOT表中對應的條目已經包含了此符號的地址,就可直接調用而不須要利用PLT表進行跳轉。
動態鏈接是比較複雜的,但爲了得到靈活性的代價一般就是複雜性。其最終目的是把GOT表中條目的值修改成符號的真實地址,這也可解釋節.got包含在可讀可寫段中。
動態鏈接是一個很是重要的進步,這意味着庫文件能夠被升級、移動到其餘目錄等等而不須要從新編譯程序(固然,這不意味庫能夠任意修改,如函數入參的個數、數據類型應保持兼容性)。從很大程度上說,動態鏈接機制是ELF格式代替a.out格式的決定性緣由。若是說面對對象的編程本質是面對接口(interface)的編程,那麼動態鏈接機制則是這種思想的地一個很是典型的應用,具體的講,動態鏈接機制與設計模式中的橋接(BRIDGE)方法比較相似,而它的LAZY特性則與代理(PROXY)方法很是類似。動態鏈接操做的細節描述請參閱 參考資料 8,9,10,11。經過閱讀命令readelf、objdump 的源代碼以及 參考資料 14中所說起的相關軟件源代碼,能夠對ELF文件的格式有更完全的瞭解。
不一樣時期的可執行文件格式深入的反映了技術進步的過程,技術進步一般是針對解決存在的問題和適應新的環境。早期的UNIX系統使用a.out格式,隨着操做系統和硬件系統的進步,a.out格式的侷限性愈來愈明顯。新的可執行文件格式COFF在UNIX System VR3中出現,COFF格式相對a.out格式最大變化是多了一個節頭表(section head table),可以在包含基礎的文本段、數據段、BSS段以外包含更多的段,可是COFF對動態鏈接和C++程序的支持仍然比較困難。爲了解決上述問題,UNIX系統實驗室(UNIX SYSTEM Laboratories USL) 開發出ELF文件格式,它被做爲應用程序二進制接口(Application binary Interface ABI)的一部分,其目的是替代傳統的a.out格式。例如,ELF文件格式中引入初始化段.init和結束段.fini(分別對應構造函數和析構函數)則主要是爲了支持C++程序。1994年6月ELF格式出如今LINUX系統上,如今ELF格式做爲UNIX/LINUX最主要的可執行文件格式。固然咱們徹底有理由相信,在未來還會有新的可執行文件格式出現。
上述三種可執行文件格式都很好的體現了設計思想中分層的概念,由一個總的頭部刻畫了文件的基本要素,再由若干子頭部/條目刻畫了文件的若干細節。比較一下可執行文件格式和以太數據包中以太頭、IP頭、TCP頭的設計,我想咱們能很好的感覺分層這一重要的設計思想。 參考資料 21從全局的角度討論了各類文件的格式,並提出一個比較誇張的結論:Everything Is Byte!
最後的題外話:大多數資料中對a.out格式的評價較低,常見的詞語有黑暗年代(dark ages)、醜陋(ugly)等等,固然,從現代的觀點來看,的確是比較簡單,可是若是沒有曾經的簡單何來今天的精巧?正如咱們今天能夠評價石器時代的技術是ugly,那麼未來的人們也能夠嘲諷今天的技術是很是ugly。我想咱們也許應該用更平和的心態來對曾經的技術有一個公正的評價。