ELF可執行文件格式的理解

【注意,您可至這裏瀏覽全文: http://yihect.juliantec.info/julblog//post/4/28

 ELF(Executable and Linking Format)是一種對象文件的格式,用於定義不同類型的對象文件(Object files)中都放了什麼東西、以及都以什麼樣的格式去放這些東西。它自最早在 System V 系統上出現後,被 xNIX 世界所廣泛接受,作爲缺省的二進制文件格式來使用。可以說,ELF是構成衆多xNIX系統的基礎之一,所以作爲嵌入式Linux系統乃至內核驅動程序開發人員,你最好熟悉並掌握它。

其實,關於ELF這個主題,網絡上已經有相當多的文章存在,但是其介紹的內容比較分散,使得初學者不太容易從中得到一個系統性的認識。爲了幫助大家學習,我這裏打算寫一系列連貫的文章來介紹ELF以及相關的應用。這是這個系列中的第一篇文章,主要是通過不同工具的使用來熟悉ELF文件的內部結構以及相關的基本概念。後面的文章,我們會介紹很多高級的概念和應用,比方動態鏈接和加載,動態庫的開發,C語言Main函數是被誰以及如何被調用的,ELF格式在內核中的支持,Linux內核中對ELF section的擴展使用等等。

好的,開始我們的第一篇文章。在詳細進入正題之前,先給大家介紹一點ELF文件格式的參考資料。在ELF格式出來之後,TISC(Tool Interface Standard Committee)委員會定義了一套ELF標準。你可以從這裏(http://refspecs.freestandards.org/elf/)找到詳細的標準文檔。TISC委員會前後出了兩個版本,v1.1和v1.2。兩個版本內容上差不多,但就可讀性上來講,我還是推薦你讀 v1.2的。因爲在v1.2版本中,TISC重新組織原本在v1.1版本中的內容,將它們分成爲三個部分(books):

a) Book I

介紹了通用的適用於所有32位架構處理器的ELF相關內容

b) Book II

介紹了處理器特定的ELF相關內容,這裏是以Intel x86 架構處理器作爲例子介紹

c) Book III

介紹了操作系統特定的ELF相關內容,這裏是以運行在x86上面的 UNIX System V.4 作爲例子介紹

值得一說的是,雖然TISC是以x86爲例子介紹ELF規範的,但是如果你是想知道非x86下面的ELF實現情況,那也可以在http://refspecs.freestandards.org/elf/中找到特定處理器相關的Supplment文檔。比方ARM相關的,或者MIPS相關的等等。另外,相比較UNIX系統的另外一個分支BSD Unix,Linux系統更靠近 System V 系統。所以關於操作系統特定的ELF內容,你可以直接參考v1.2標準中的內容。

這裏多說些廢話:別忘了 Linus 在實現Linux的第一個版本的時候,就是看了介紹Unix內部細節的書:《The of the Unix Operating System》,得到很多啓發。這本書對應的操作系統是System V 的第二個Release。這本書介紹了操作系統的很多設計觀念,並且行文簡單易懂。所以雖然現在的Linux也吸取了其他很多Unix變種的設計理念,但是如果你想研究學習Linux內核,那還是以看這本書作爲開始爲好。這本書也是我在接觸Linux內核之前所看的第一本介紹操作系統的書,所以我極力向大家推薦。(在學校雖然學過操作系統原理,但學的也是很糟糕最後導致期末考試才四十來分,記憶彷彿還在昨天:))

好了,還是回來開始我們第一篇ELF主題相關的文章吧。這篇文章主要是通過使用不同的工具來分析對象文件,來使你掌握ELF文件的基本格式,以及瞭解相關的基本概念。你在讀這篇文章的時候,希望你在電腦上已經打開了那個 v1.2 版本的ELF規範,並對照着文章內容看規範裏的文字。

首先,你需要知道的是所謂對象文件(Object files)有三個種類:

1) 可重定位的對象文件(Relocatable file)

這是由彙編器彙編生成的 .o 文件。後面的鏈接器(link editor)拿一個或一些 Relocatable object files 作爲輸入,經鏈接處理後,生成一個可執行的對象文件 (Executable file) 或者一個可被共享的對象文件(Shared object file)。我們可以使用 ar 工具將衆多的 .o Relocatable object files 歸檔(archive)成 .a 靜態庫文件。如何產生 Relocatable file,你應該很熟悉了,請參見我們相關的基本概念文章和JulWiki。另外,可以預先告訴大家的是我們的內核可加載模塊 .ko 文件也是 Relocatable object file。

2) 可執行的對象文件(Executable file)

這我們見的多了。文本編輯器vi、調式用的工具gdb、播放mp3歌曲的軟件mplayer等等都是Executable object file。你應該已經知道,在我們的 Linux 系統裏面,存在兩種可執行的東西。除了這裏說的 Executable object file,另外一種就是可執行的腳本(如shell腳本)。注意這些腳本不是 Executable object file,它們只是文本文件,但是執行這些腳本所用的解釋器就是 Executable object file,比如 bash shell 程序。

3) 可被共享的對象文件(Shared object file)

這些就是所謂的動態庫文件,也即 .so 文件。如果拿前面的靜態庫來生成可執行程序,那每個生成的可執行程序中都會有一份庫代碼的拷貝。如果在磁盤中存儲這些可執行程序,那就會佔用額外的磁盤空間;另外如果拿它們放到Linux系統上一起運行,也會浪費掉寶貴的物理內存。如果將靜態庫換成動態庫,那麼這些問題都不會出現。動態庫在發揮作用的過程中,必須經過兩個步驟:

a) 鏈接編輯器(link editor)拿它和其他Relocatable object file以及其他shared object file作爲輸入,經鏈接處理後,生存另外的 shared object file 或者 executable file。

b) 在運行時,動態鏈接器(dynamic linker)拿它和一個Executable file以及另外一些 Shared object file 來一起處理,在Linux系統裏面創建一個進程映像。

以上所提到的 link editor 以及 dynamic linker 是什麼東西,你可以參考我們基本概念中的相關文章。對於什麼是編譯器,彙編器等你應該也已經知道,在這裏只是使用他們而不再對他們進行詳細介紹。爲了下面的敘述方便,你可以下載test.tar.gz包,解壓縮後使用"make"進行編譯。編譯完成後,會在目錄中生成一系列的ELF對象文件,更多描述見裏面的 README 文件。我們下面的論述都基於這些產生的對象文件。

make所產生的文件,包括 sub.o/sum.o/test.o/libsub.so/test 等等都是ELF對象文件。至於要知道它們都屬於上面三類中的哪一種,我們可以使用 file 命令來查看:

 

[yihect@juliantec test]$ file sum.o sub.o test.o libsub.so test
sum.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
sub.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
test.o:    ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
libsub.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
test:      ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped

 

結果很清楚的告訴我們他們都屬於哪一個類別。比方 sum.o 是應用在x86架構上的可重定位文件。這個結果也間接的告訴我們,x86是小端模式(LSB)的32位結構。那對於 file 命令來說,它又能如何知道這些信息?答案是在ELF對象文件的最前面有一個ELF文件頭,裏面記載了所適用的處理器、對象文件類型等各種信息。在TISCv1.2的規範中,用下面的圖描述了ELF對象文件的基本組成,其中ELF文件頭赫然在目。

ELF 文件頭

等等,爲什麼會有左右兩個很類似的圖來說明ELF的組成格式?這是因爲ELF格式需要使用在兩種場合:

a) 組成不同的可重定位文件,以參與可執行文件或者可被共享的對象文件的鏈接構建;

b) 組成可執行文件或者可被共享的對象文件,以在運行時內存中進程映像的構建。

所以,基本上,圖中左邊的部分表示的是可重定位對象文件的格式;而右邊部分表示的則是可執行文件以及可被共享的對象文件的格式。正如TISCv1.2規範中所闡述的那樣,ELF文件頭被固定地放在不同類對象文件的最前面。至於它裏面的內容,除了file命令所顯示出來的那些之外,更重要的是包含另外一些數據,用於描述ELF文件中ELF文件頭之外的內容。如果你的系統中安裝有 GNU binutils 包,那我們可以使用其中的 readelf 工具來讀出整個ELF文件頭的內容,比如:

 

[yihect@juliantec test]$ readelf -h ./sum.o
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:                              REL (Relocatable file)   Machine:                           Intel 80386   Version:                           0x1   Entry point address:               0x0   Start of program headers:          0 (bytes into file)   Start of section headers:          184 (bytes into file)   Flags:                             0x0   Size of this header:               52 (bytes)   Size of program headers:           0 (bytes)   Number of program headers:         0   Size of section headers:           40 (bytes)   Number of section headers:         9   Section header string table index: 6  

 

這個輸出結果能反映出很多東西。那如何來看這個結果中的內容,我們還是就着TISCv1.2規範來。在實際寫代碼支持ELF格式對象文件格式的時候,我們都會定義許多C語言的結構來表示ELF格式的各個相關內容,比方這裏的ELF文件頭,你就可以在TISCv1.2規範中找到這樣的結構定義(注意我們研究的是針對x86架構的ELF,所以我們只考慮32位版本,而不考慮其他如64位之類的):

ELF 文件頭結構

這個結構裏面出現了多種數據類型,同樣可以在規範中找到相關說明:

ELF 相關數據類型

在我們以後一系列文章中,我們會着重拿實際的程序代碼來分析,介時你會在頭文件中找到同樣的定義。但是這裏,我們只討論規範中的定義,暫不考慮任何程序代碼。在ELF頭中,字段e_machine和e_type指明瞭這是針對x86架構的可重定位文件,最前面有個長度爲16字節的字段中有一個字節表示了它適用於32bits機器,而不是64位的。除了這些之外,另外ELF頭還告訴了我們其他一些特別重要的信息,分別是:

a) 這個sum.o的進入點是0x0(e_entry),這表面Relocatable objects不會有程序進入點。所謂程序進入點是指當程序真正執行起來的時候,其第一條要運行的指令的運行時地址。因爲Relocatable objects file只是供再鏈接而已,所以它不存在進入點。而可執行文件test和動態庫.so都存在所謂的進入點,你可以用 readelf -h 看看。後面我們的文章中會介紹可執行文件的e_entry指向C庫中的_start,而動態庫.so中的進入點指向 call_gmon_start。這些後面再說,這裏先不深入討論。

b) 這個sum.o文件包含有9個sections,但卻沒有segments(Number of program headers爲0)。

那什麼是所謂 sections 呢?可以說,sections 是在ELF文件裏頭,用以裝載內容數據的最小容器。在ELF文件裏面,每一個 sections 內都裝載了性質屬性都一樣的內容,比方:

1) .text section 裏裝載了可執行代碼;

2) .data section 裏面裝載了被初始化的數據;

3) .bss section 裏面裝載了未被初始化的數據;

4) 以 .rec 打頭的 sections 裏面裝載了重定位條目;

5) .symtab 或者 .dynsym section 裏面裝載了符號信息;

6) .strtab 或者 .dynstr section 裏面裝載了字符串信息;

7) 其他還有爲滿足不同目的所設置的section,比方滿足調試的目的、滿足動態鏈接與加載的目的等等。

一個ELF文件中到底有哪些具體的 sections,由包含在這個ELF文件中的 section head table(SHT)決定。在SHT中,針對每一個section,都設置有一個條目,用來描述對應的這個section,其內容主要包...(後續省略。。。)

 

【注意,您可至這裏瀏覽全文: http://yihect.juliantec.info/julblog//post/4/28

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章