歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

UNIX/LINUX 平台可執行文件格式分析

本文討論了 UNIX/LINUX 平台下三種主要的可執行文件格式:a.out(assembler and link editor output 匯編器和鏈接編輯器的輸出)、COFF(Common Object File Format 通用對象文件格式)、ELF(Executable and Linking Format 可執行和鏈接格式)。首先是對可執行文件格式的一個綜述,並通過描述 ELF 文件加載過程以揭示可執行文件內容與加載運行操作之間的關系。隨後依此討論了此三種文件格式,並著重討論 ELF 文件的動態連接機制,其間也穿插了對各種文件格式優缺點的評價。最後對三種可執行文件格式有一個簡單總結,並提出作者對可文件格式評價的一些感想。

參考資料 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 程序可能引用共享庫定義的變量或函數,換句話說就是程序運行時必須知道這些變量/函數的地址。在靜態連接中,程序所有需要使用的外部定義都完全包含在可執行程序中,而動態連接則只在可執行文件中設置相關外部定義的一些引用信息,真正的重定位是在程序運行之時。靜態連接方式有兩個大問題:如果庫中變量或函數有任何變化都必須重新編譯連接程序;如果多個程序引用同樣的變量/函數,則此變量/函數會在文件/內存中出現多次,浪費硬盤/內存空間。比較兩種連接方式生成的可執行文件的大小,可以看出有明顯的區別。

 



 

 

回頁首

 

參考資料 16 和閱讀 參考資料 15 源代碼加深對 a.out 格式的理解。 參考資料 12 討論了如何在"現代"的紅帽LINUX運行 a.out 格式文件。

 



 

 

回頁首

 

參考資料 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。

 



 

 

回頁首

 

參考資料 1中作者談到把節頭表的所有數據全部設置為0,程序也能正確運行!ELF頭部是一個關於本文件的路線圖(road map),從總體上描述文件的結構。下面是ELF頭部的數據結構:

Copyright © Linux教程網 All Rights Reserved