UNIX/LINUX 平台可執行檔格式分析–轉載

來源:互聯網
上載者:User

UNIX/LINUX 平台可執行檔格式

分析作者:施聰 2005-01-13 17:24:31 來自:IBM DW中國

本文討論了 UNIX/LINUX 平台下三種主要的可執行檔格式:a.out(assembler and editor output 彙編器和連結編輯器的輸出)、COFF(Common Object File Format 通用物件檔案格式)、ELF(Executable and ing Format 可執行和連結格式)。

首先是對可執行檔格式的一個綜述,並通過描述 ELF 檔案載入過程以揭示可執行檔內容與載入運行操作之間的關係。隨後依此討論了此三種檔案格式,並著重討論 ELF 檔案的動態串連機制,其間也穿插了對各種檔案格式優缺點的評價。最後對三種可執行檔格式有一個簡單總結,並提出作者對可檔案格式評價的一些感想。

可執行檔格式綜述

相對於其它檔案類型,可執行檔可能是一個作業系統中最重要的檔案類型,因為它們是完成操作的真正執行者。可執行檔的大小、運行速度、資源佔用情況以及可擴充性、可移植性等與檔案格式的定義和檔案載入過程緊密相關。研究可執行檔的格式對編寫高效能程式和一些駭客技術的運用都是非常有意義的。不管何種可執行檔格式,一些基本的要素是必須的,顯而易見的,檔案中應包含代碼和資料。因為檔案可能引用外部檔案定義的符號(變數和函數),因此重定位資訊和符號資訊也是需要的。一些輔助資訊是可選的,如調試資訊、硬體資訊等。

基本上任意一種可執行檔格式都是按區間儲存上述資訊,稱為段(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:核心在新進程的堆棧中設定一些標記-值對,以指示動態連接器的相關操作。

4:核心把控制傳遞給動態連接器。

5:動態連接器檢查程式對外部檔案(共用庫)的依賴性,並在需要時對其進行載入。

6:動態連接器對程式的外部參考進行重定位,通俗的講,就是告訴程式其引用的外部變數/函數的地址,此地址位於共用庫被載入在記憶體的區間內。動態串連還有一個延遲(Lazy)定位的特性,即只在'真正'需要引用符號時才重定位,這對提高程式運行效率有極大協助。

7:動態連接器執行在ELF檔案中標記為 .init 的節的代碼,進行程式啟動並執行初始化。在早期系統中,初始化代碼對應函數 _init(void)(函數名強制固定),在現代系統中,則對應形式為void__attribute((constructor))init_function(void){……}其中函數名為任意。

8:動態連接器把控制傳遞給程式,從 ELF 檔案頭部中定義的程式進入點開始執行。在 a.out 格式和ELF格式中,程式進入點的值是顯式存在的,在 COFF 格式中則是由規範隱含定義。

從上面的描述可以看出,負載檔案最重要的是完成兩件事情:載入程式段和資料區段到記憶體;進行外部定義符號的重定位。重定位是程式串連中一個重要概念。我們知道,一個可執行程式通常是由一個含有 main() 的主程式檔案、若干目標檔案、若干共用庫(Shared Libraries)組成。(註:採用一些特別的技巧,也可編寫沒有 main 函數的程式,請參閱參考資料 2)一個 C 程式可能引用共用庫定義的變數或函數,換句話說就是程式運行時必須知道這些變數/函數的地址。

在靜態串連中,程式所有需要使用的外部定義都完全包含在可執行程式中,而動態串連則只在可執行檔中設定相關外部定義的一些引用資訊,真正的重定位是在程式運行之時。靜態串連方式有兩個大問題:如果庫中變數或函數有任何變化都必須重新編譯串連程式;如果多個程式引用同樣的變數/函數,則此變數/函數會在檔案/記憶體中出現多次,浪費硬碟/記憶體空間。

比較兩種串連方式產生的可執行檔的大小,可以看出有明顯的區別。a.out 檔案格式分析a.out 格式在不同的機器平台和不同的 UNIX 作業系統上有輕微的不同,例如在 MC680x0 平台上有 6 個 section。

下面我們討論的是最'標準'的格式。a.out 檔案包含 7 個 section,格式如下:

exec header(執行頭部,也可理解為檔案頭部)

text segment(文本段)

data segment(資料區段)

text relocations(文本重定位段)

data relocations(資料重定位段)

symbol table(符號表)

string table(字串表)

執行頭部的資料結構:

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 檔案格式分析

COFF 格式比 a.out 格式要複雜一些,最重要的是包含一個節段表(section table),因此除了 .text,.data,和 .bss 區段以外,還可以包含其它的區段。另外也多了一個可選的頭部,不同的作業系統可一對此頭部做特定的定義。

COFF 檔案格式如下:

File Header(檔案頭部)

Optional Header(可選檔案頭部)

Section 1 Header(節頭部)………

Section n Header(節頭部)

Raw Data for Section 1(節資料)

Raw Data for Section n(節資料)

Relocation Info for Sect. 1(節重定位元據)

Relocation Info for Sect. n(節重定位元據)

Line Numbers for Sect. 1(節行號資料)

Line Numbers for Sect. n(節行號資料)

Symbol table(符號表)

String table(字串表)

檔案頭部的資料結構:

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 下可選頭部的資料結構:

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 格式分析中,我們將省略相關討論。

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檔案格式分析ELF檔案有三種類型:可重定位檔案:也就是通常稱的目標檔案,尾碼為.o。共用檔案:也就是通常稱的庫檔案,尾碼為.so。

可執行檔:

本文主要討論的檔案格式,總的來說,可執行檔的格式與上述兩種檔案的格式之間的區別主要在於觀察的角度不同:一種稱為串連視圖(Linking View),一種稱為執行視圖(Execution View)。

首先看看ELF檔案的總體布局:

ELF header(ELF頭部)

Program header table(程式頭表)

Segment1(段1)

Segment2(段2)………

Sengmentn(段n)

Setion header table(節頭表,可選)

段由若干個節(Section)構成,節頭表對每一個節的資訊有相關描述。對可執行程式而言,節頭表是可選的。參考資料 1中作者談到把節頭表的所有資料全部設定為0,程式也能正確運行!ELF頭部是一個關於本檔案的路線圖(road map),從總體上描述檔案的結構。

下面是ELF頭部的資料結構:

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)

ELF Header:

Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

Class: ELF32Data: 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定義的條目,結構描述一個段或其他系統準備執行該程式所需要的資訊。

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;

在詳細討論可執行檔程式頭表之前,首先查看一個實際檔案的輸出:

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_

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_ 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表的形式如下.PLT0: pushl 4(%ebx) /* GOT表的地址儲存在寄存器ebx中 */jmp *8(%ebx)nop; nopnop; nop.PLT1: jmp *name1@GOT(%ebx)pushl $offsetjmp .PLT0@PC.PLT2: jmp *func@GOT(%ebx)pushl $offsetjmp .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。

我想我們也許應該用更平和的心態來對曾經的技術有一個公正的評價。

參考資料

1. 《LINUX VIRUSES - ELF FILE FORMAT》 Marius Van Oers

2. 《A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux 》 breadbox

3. 《The Linux Virus Writing And Detection HOWTO》Alexander Bartolich

4. 《從程式員角度看ELF》Hongjiu Lu alert7(譯)

5. 《XCOFF Object File Format》

6. 《Executable and Linkable Format(ELF)》

7. 《elf檔案格式--另一文本方式的elf文檔》alert7(譯)

8. 《如何修改動態庫符號表》wangdb

9. 《分析ELF的載入過程》opera

10. 《Before main() 分析》 alert7

11. 《Linkers & Loaders》John R. Levine

12. 《Running a.out executables on modern Red Hat Linux》

13. 《Cheating the ELF》

14. 《ELF Binary Analysis Tools》

15. 《dbxread.c》

16. 《Manual Reference Pages - A.OUT (5)》

17. 《Linux下緩衝區溢位攻擊的原理及對策》

18. 《Microsoft Portable Executable and Common Object File Format Specification》

19. 《COFF的檔案結構》redleaves

20. 《Common Object File Format (COFF)》

21. 《Everything Is Byte》 mala

關於作者施聰,成都人,進階程式員、網路設計師。長期從事基於UNIX/LINUX下的c/c++程式設計和資料庫建模工作。可通過javer@163.com或memncmp@yahoo.com.cn和他聯絡。

http://www.ibm.com/developerworks/cn/linux/l-excutff/

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.