1. 程式人生 > >Linux核心完全註釋 閱讀筆記:3.5、Linux 0.11目標檔案格式

Linux核心完全註釋 閱讀筆記:3.5、Linux 0.11目標檔案格式

為了生成核心程式碼檔案,Linux 0.11使用了兩種編譯器。第一種是彙編編譯器as86和相應的連結程式(或稱為連結器)ld86。它們專門用於編譯和連結,執行在實地址模式下的16位核心引導扇區程式bootsect.s和設定程式setup.s。第二種是GNU的彙編器as(gas)和C語言編譯器gcc以及相應的連結程式ld。編譯器用於為源程式檔案產生對應的二進位制程式碼和資料目標檔案。連結程式用於對相關的所有目標檔案進行組合處理,形成一個可被核心載入執行的目標檔案,即可執行檔案。

1、目標檔案格式

         在Linux 0.11系統中,GNU gcc或as編譯輸出的目標模組檔案和連結程式所生成的可執行檔案都使用了UNIX傳統的a.out格式。目標檔案格式示意圖如下:

a.out格式7個區的基本定義和用途如下所述:

執行頭部分(exec header):該部分中含有一些引數(exec結構),是有關目標檔案的整體結構資訊。例如程式碼和資料區的長度、未初始化資料區的長度、對應源程式檔名以及目標檔案建立時間等。核心使用這些引數把執行檔案載入到記憶體中並執行,而連結程式ld使用這些引數將一些模組檔案組合成一個可執行檔案。這是目標檔案唯一必要的組成部分。

程式碼區(text segment):由編譯器或彙編器生成的二進位制指令程式碼和資料資訊,含有程式執行時被載入到記憶體中的指令程式碼和相關資料。可以以只讀形式被載入。

資料區(data segment)

:由編譯器或彙編器生成的二進位制指令程式碼和資料資訊,這部分含有已經初始化過的資料,資料區總是被載入到可讀寫的記憶體中。

程式碼重定位部分(text relocations):這部分含有供連結程式使用的記錄資料。在組合目標模組檔案時,用於定位程式碼段中的指標或地址。當連結程式需要改變目的碼的地址時,就需要修正和維護這些地方。

資料重定位部分(data relocations):類似於程式碼重定位部分的作用,但是用於資料段中指標的重定位。

符號表部分(symbol table):這部分同樣含有供連結程式使用的記錄資料。這些記錄資料儲存著模組檔案中定義的全域性符號以及需要從其他模組檔案中輸入的符號,或者是由連結器定義的符號,用於在模組檔案之間對命名的變數和函式(符號)進行交叉引用。

字串表部分(string table):該部分含有與符號名相對應的字串。用於除錯程式,除錯目的碼,與連結程式無關。這些資訊可包含源程式程式碼和行號、區域性符號以及資料結構描述資訊等。

         目標檔案的檔案頭中含有一個長度為32位元組的exec資料結構,通常稱為檔案頭結構或者執行頭結構。有關a.out結構的詳細資訊請參見include/a.out.h檔案後的介紹。檔案頭結構定義如下:

struct exec{

         unsigned long a_magic; //執行檔案魔數,使用N_MAGIC等巨集訪問

         unsigned a_text; //程式碼長度,位元組數

         unsigned a_data; //資料長度,位元組數

         unsigned a_bss; //檔案中的未初始化資料區長度,位元組數

         unsigned a_syms; //檔案中的符號表長度,位元組數

         unsigned a_entry; //執行開始地址

         unsigned a_trsize; //程式碼重定位資訊長度,位元組數

         unsigned a_drsize; //資料重定位資訊長度,位元組數

};

         根據a.out檔案中頭結構魔數字段的值,我們又可把a.out格式的檔案分成幾種型別。Linux 0.11系統使用了其中兩種型別:模組目標檔案使用了OMAGIC(Old Magic)型別的a.out格式,它指明檔案是目標檔案或者是不純的可執行檔案,其魔數是0x107(八進位制0407)。而執行檔案使用了ZMAGIC型別的a.out格式,它指明檔案為需求分頁處理(demang-paging,即需求載入load on demand)的可執行檔案,其魔數是0x10b(八進位制0413)。這兩種格式的主要區別在於它們對各個部分的儲存分配方式上。雖然該結構的總長度只有32位元組,但是對於一個ZMAGIC型別的執行檔案來說,其檔案開始部分卻需要專門留出1024位元組的空間給頭結構使用。除頭結構佔用的32個位元組以外,其餘部分均為0。從1024位元組之後才開始放置程式的正文段和資料段等資訊。而對於一個OMAGIC型別的.o模組檔案來說,檔案開始部分的32位元組頭結構後面緊接著就是程式碼區和資料區

         a_entry欄位指定了程式程式碼開始執行的地址。而a_syms、a_trsize和a_drsize欄位則分別說明了資料段後符號表、程式碼和資料段重定位資訊的大小。對於可執行檔案來說並不需要符號表和重定位資訊,因此除非連結程式為了除錯目的而包括了符號資訊,執行檔案中的這幾個欄位的值通常為0。

         Linux 0.11系統的模組檔案和執行檔案都是a.out格式的目標檔案,但是隻有編譯器生成的模組檔案中包含用於連結程式的重定位資訊。程式碼段和資料段的重定位資訊均由重定位記錄項構成,每個記錄的長度為8位元組,其結構如下:

struct relocation_info

{

         int r_address; //段內需要重定位的地址

         unsigned int r_symbolnum:24; //含義與r_extern有關,指定符號表中一個符號或者一個段

         unsigned int r_pcrel:1; //1bit,PC相關的標誌

         unsigned int r_length:2; //2bit,指定要被重定位欄位長度(2的次方)

         unsigned int r_extern:1; //外部標誌位, 1:以符號的值重定位, 0:以段的地址重定位

         unsigned int r_pad:4; //沒有使用的4bit,但最好將他們復位

};

         重定位項的功能有兩個。一是當代碼段被重定位到一個不同的基地址處時,重定位項則用於指出需要修改的地方。二是在模組檔案中存在對未定義符號引用時,當此未定義符號最終被定義時連結程式就可以使用相應重定位項對符號的值進行修正。

         目標檔案的最後部分是符號表和相關的字串表。符號表記錄項的結構如下:

struct nlist{

         union{

                   char *n_name;         //字串指標,

                   struct nlist *n_next; //或者是指向另一個符號項結構的指標,

                   long n_strx;          //或者是符號名稱在字串表中的位元組偏移值

}n_un;

unsigned char n_type; //該位元組分成3個欄位,參見a.out.h檔案146-154

char n_other; //通常不用

short n_desc;

unsigned long n_value; //符號的值

};

2Linux 0.11中的目標檔案格式

         磁碟上a.out執行檔案的各區在程序邏輯地址空間中的對應關係如下圖所示:

Linux 0.11系統中程序的邏輯地址空間大小是64MB。對於ZMAGIC型別的a.out執行檔案,它的程式碼區的長度是記憶體頁面的整數倍。由於Linux 0.11核心使用需求頁(Demand-paging)技術,即在一頁程式碼實際要使用的時候才被載入到實體記憶體頁面中,而在進行載入操作的fs/execve()函式中僅僅為其設定了分頁機制的頁目錄項和頁表項,因此需求頁技術可以加快程式的載入速度。

         圖中bss是程序的未初始化資料區,用於存放靜態的未初始化資料。在開始執行程式時,bss的第一頁記憶體會被設定為全0。圖中heap是堆空間區,用於分配程序在執行過程中動態申請的記憶體空間。

3、連結程式輸出

         連結程式對輸入的一個或多個模組檔案以及相關的庫函式模組進行處理,最終生成相應的二進位制執行檔案或者是一個所有模組組合而成的大模組檔案。

         對於a.out格式的模組檔案來說,由於段型別是預先知道的,因此連結程式對a.out格式的模組檔案進行儲存分配比較容易。例如,對於具有兩個輸入模組檔案和需要連結一個庫函式模組的情況,其儲存分配情況如下圖所示:

4、連結程式預定義變數

         在連結過程中,連結器ld和ld86會使用變數記錄下執行程式中每個段的邏輯地址。因此在程式中可以通過訪問這幾個外部變數來獲得程式中段的位置。連結器預定義的外部變數通常至少有etext、_etext、edata、_edata、end和_end。

         變數名etext和_etext的地址是程式正文段結束後的第1個地址;edata和_edata的地址是初始化資料區後面的第1個地址;end和_end的地址是未初始化資料區(bss)後的第1個地址位置。帶下劃線’_’字首的名稱等同於不帶下劃線的對應名稱,它們之間的唯一區別在於ANSI、POSIX等標準中沒有定義符號etext、edata和end。

5System.map檔案

         在編譯核心時,linux/Makefile檔案產生的System.map檔案就用於存放核心符號表資訊。當核心執行出錯時,通過System.map檔案中的符號表解析,就可以查到一個地址值對應的變數名,或反之。

         儘管核心本身實際上不使用System.map,但其他程式,像klogd、lsof、ps以及其他像dosemu等許多軟體都需要一個正確的System.map檔案。利用該檔案,這些程式就可以根據已知的記憶體地址查找出對應的核心變數名稱,便於對核心的除錯工作。