關於ELF檔案格式的實驗

看雪資料發表於2015-11-15

現代Linux採用ELF做為其可連線和可執行檔案的格式,因此ELF格式也向我們透出了一點Linux核內的情景,就像戲臺維幕留下的一條未拉嚴的縫。本文著重講述32位ELF的同時附帶了64位的資訊,這兩種格式如此雷同,以致於初次接觸ELF的讀者不必兼顧左右。如果你對Windows比較熟悉,本文還將時時把你帶回到PE中,在它們的相似之處稍做比較。ELF檔案以“ELF頭”開始,後面可選擇的跟隨著程式頭和節頭。地理學用等高線與等溫線分別展示同一地區的地勢和氣候,程式頭和節頭則分別從載入與連線角度來描述EFL檔案的組織方式。下面我們進入正文。

 

一、ELF頭

     ELF頭也叫ELF檔案頭,它位於檔案中最開始的地方。我用系統的是Fedora Core 2,它在elf.h檔案中同時給出了ELF頭在32位系統和64位系統下的結構,我們先來看一下:

 

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;       

  Elf32_Half    e_phentsize;        

  Elf32_Half    e_phnum;        

  Elf32_Half    e_shentsize;        

  Elf32_Half    e_shnum;        

  Elf32_Half    e_shstrndx;     

} Elf32_Ehdr;

 

typedef struct

{

  unsigned char e_ident[EI_NIDENT]; 

  Elf64_Half    e_type;         

  Elf64_Half    e_machine;      

  Elf64_Word    e_version;      

  Elf64_Addr    e_entry;        

  Elf64_Off e_phoff;        

  Elf64_Off e_shoff;        

  Elf64_Word    e_flags;        

  Elf64_Half    e_ehsize;       

  Elf64_Half    e_phentsize;        

  Elf64_Half    e_phnum;        

  Elf64_Half    e_shentsize;        

  Elf64_Half    e_shnum;        

  Elf64_Half    e_shstrndx;     

} Elf64_Ehdr;

 

elf.h中關於ELF格式所有結構給出的定義,其成員欄位的型別聲名都是C語言基本型別的別名,不會再巢狀結構。可以看出32位系統和64位系統下ELF頭的結構基本相同,不同的是兩種結構中的某個成員欄位佔用位元組個數有所變化。比如e_entry由32位下佔4個位元組的Elf32_Addr變為64位下佔8個位元組的Elf64_Addr,這是因為兩種系統下CPU定址能力不同造成的。同理檔案偏移也從4位元組的Elf32_Off變為8位元組的Elf64_Off。有些成員欄位雖然型別聲名從Elf32_XXXX變成了Elf64_XXXX,該域所佔的位元組個數並未改變。如Elf32_Half和Elf64_Half都佔兩個位元組,Elf32_Word、Elf32_Sword、Elf64_Word、Elf64_Sword全都是4個位元組。儘量使用elf.h中的現有定義將使我們寫的程式具有很強的可移植性。另外ELF格式在兩種系統下的這種雷同也使得我們可放心的拋棄它們的差別,專心研究其中的一種,然後再輕鬆的掌握另一種。

 

ELF頭中每個欄位的含意如下:

 

e_ident:

    這個欄位是ELF頭結構中的第一個欄位,在elf.h中EI_NIDENT被定義為16,因此它佔用16個位元組。

    e_ident的前四個位元組順次應該是0x7f、0x45、0x4c、0x46,也就是"\177ELF"。這是ELF檔案的標誌,任何一個ELF檔案這四個位元組都完全相同。它讓熟悉Windows的人想起'MZ'和'PE\O\O'。

  第5個位元組標誌了ELF格式是32位還是64位,32位是1,64位是2。

  第6個位元組,在0x86系統上是1,表明資料儲存方式為低位元組優先。

  第10個位元組,指明瞭在e_ident中從第幾個位元組開始後面的位元組未使用。

e_type:

  ELF檔案的型別,1表示此檔案是重定位檔案,2表示可執行檔案,3表示此檔案是一個動態連線庫。

e_machine:

  CPU型別,它指出了此檔案使用何種指令集。如果是Intel 0x386 CPU此值為3,如果是AMD 64 CPU此值為62也就是16進位制的0x3E。

e_version:

  ELF檔案版本。為1。

e_entry:

  可執行檔案的入口虛擬地址。此欄位指出了該檔案中第一條可執行機器指令在程式被正確載入後的記憶體地址!(PE可執行檔案指出的是入口的相對虛擬地址RVA,它是相對於檔案載入起始地址的一個偏移值,因此理論上PE檔案可被載入到程式序空間任何位置,而ELF可執行檔案只能被載入到固定位置)。

e_phoff:

  程式頭在ELF檔案中的偏移量。如果程式頭不存在此值為0。

e_shoff:

  節頭在ELF檔案中的偏移量。如果節頭不存在此值為0。

e_ehsize:

  它描述了“ELF頭”自身佔用的位元組數。

e_phentsize:

  程式頭中的每一個結構佔用的位元組數。程式頭也叫程式頭表,可以被看做一個在檔案中連續儲存的結構陣列,陣列中每一項是一個結構,此欄位給出了這個結構佔用的位元組大小。e_phoff指出程式頭在ELF檔案中的起始偏移。

e_phnum:

  此欄位給出了程式頭中儲存了多少個結構。如果程式頭中有3個結構則程式頭在檔案中佔用了3×e_phentsize個位元組的大小。

e_shentsize:

  節頭中每個結構佔用的位元組大小。節頭與程式頭類似也是一個結構陣列,關於這兩個結構的定義將分別在講述程式頭和節頭的時候給出。

e_shnum:

  節頭中儲存了多少個結構。

e_shstrndx:

  這是一個整數索引值。節頭可以看作是一個結構陣列,用這個索引值做為此陣列的下標,它在節頭中指定的一個結構進一步給出了一個“字串表”的資訊,而這個字串表儲存著節頭中描述的每一個節的名稱,包括字串表自己也是其中的一個節。

 

  至此為止我們已經講述了“ELF頭”,在此過程中提前提到的一些將來才用的概念,不必急於瞭解。現在讀者可自己編寫一個小程式來驗證剛學到的知識,這有助於進一步的學習。ELF.elf.h檔案一般會存在於/usr/include目錄下,直接include它就可以。但我們能夠驗證的知識有限,當更多知識聯絡在一起的時候我們的理解正誤才可以得到更好的驗證。接下來我們再學習程式頭。

相關文章