1. 程式人生 > >讀書筆記--《程式設計師的自我修養》第3章:目標檔案裡有什麼(2)

讀書筆記--《程式設計師的自我修養》第3章:目標檔案裡有什麼(2)

3.4 ELF檔案結構描述

ELF檔案結構如圖所示:
這裡寫圖片描述
ELF目標檔案格式最前面是ELF檔案頭,它包含了描述整個檔案的基本屬性,如ELF檔案版本、目標機器型號、程式入口地址等。接著是ELF檔案各個段。
其中ELF檔案中與段有關的重要結構是段表。該表描述了ELF檔案包含的所有段的資訊,如每個段的段名、段長、在檔案中的偏移、讀寫許可權及段的其他屬性。

3.4.1 檔案頭
輸入命令:$readelf -h SimpleSection.o
可以看出ELF檔案頭中定義了ELF魔數、檔案機器位元組長度、資料儲存方式、版本等資訊。
ELF檔案頭結構及相關常數被定義在”/usr/include/elf.h”裡,因為ELF檔案在各個平臺下都通用,因此有32位版本和64位版本。
我們以32位檔案頭結構Elf32_Ehdr為例來描述,它的定義如下:
typedef struct {
unsigned char e_ident[16];
Elf32_Half e_type; //ELF檔案型別
Elf32_Half e_machine; //ELF檔案的CPU平臺屬性,相關常量以EM_開頭
Elf32_Word e_version; //ELF版本號,一般為1
Elf32_Addr e_entry; //入口地址,規定ELF程式的入口虛擬地址。OS載入完程式後從這裡開始執行進 程的指令
Elf32_Off e_phoff;
Elf32_Off e_shoff; //段表在檔案中的偏移
Elf32_Word e_flags; //ELF標誌位,用來標識一些ELF檔案平臺相關的屬性。
Elf32_Half e_ehsize; //ELF檔案頭本身的大小
Elf32_Half e_phentsize;
Elf32_Half e_ehnum;
Elf32_Half e_shentsize; //段表描述符的大小,一般等於sizeof(Elf32_Shdr)
Elf32_Half e_shnum; //段表描述符的數量。等於ELF檔案中擁有的段的數量。這裡為12
Elf32_Half e_shstrndx; //段表字符串表所在的段的段表中的下標。
}Elf32_Ehdr;
發現ELF檔案頭結構跟前面的readelf輸出的ELF檔案頭資訊相比,很多都是一一對應的。其中Elf32_eiden整個成員對應readelf輸出結果中的Class Data Version OS/ABI和ABIversion。

1、ELF魔數
最開始的4個位元組是所有ELF檔案都必須相同的標識碼,分別是0x7f、0x45、0x4c、0x46,第一個位元組對應ASCII字元裡面的DEL控制符,後3個位元組剛好是ELF這3個字母的ASCII。這4個位元組又被稱為ELF檔案的魔數,比如a.out格式最開始兩個位元組為0x01、0x07,PE最開始的兩個位元組是0x4d、0x5a,即ASCII字元的MZ。這種魔數用來確認檔案的型別,OS在載入可執行檔案時會確認魔數是否正確,如果不正確會拒絕載入。
接下來的一個位元組是用來標識ELF的檔案類的,0x01表示32位的,0x02表示64位的;第6個字是位元組序,規定該ELF檔案是大端的還是小端的;第7個位元組規定ELF檔案的主版本號,一般是1;後面的9個位元組ELF標準沒有定義,一般寫0,有些平臺會使用這9個位元組作為擴充套件標誌。

2、檔案型別
e_type成員表示ELF檔案型別,即前面提到的3中ELF檔案型別。系統通過這個常量判斷ELF檔案的型別,而不是通過副檔名。
這裡寫圖片描述

3、機器型別
e_machine成員表示該ELF檔案的平臺屬性。比如3表示ELF檔案只能在Intel x86機器下使用。
這裡寫圖片描述

3.4.2 段表
段表用來儲存這些段的基本屬性的結構,描述各個段的資訊。編譯器、連結器和裝載器都是依靠段表來定位和訪問各個段的屬性的。段表在ELF檔案中的位置由ELF檔案頭中的e_shoff成員決定。
輸入以下命令:$readelf -S SimpleSection.o
readelf輸出的結果就是ELF檔案段表的內容。段表是一個以“Elf32_Shdr”結構體為元素的陣列。陣列中元素的個數等於段的個數,每個“Elf32_Shdr”結構體對應一個段。“Elf32_Shdr”又稱為段描述符。它被定義在“/usr/include/elf.h”
Elf32_Shdr段描述符的結構如下:
這裡寫圖片描述


各個成員表示如下:
這裡寫圖片描述
對照結構體和readelf -S的結果,可以看到該成員對應輸出結果中從第二列“Name”開始的每一列。
1、段的型別
段的名字只有在連結和編譯過程中才有意義,它不能真正表示段的型別。我們也可以將一個數據段命名為“.text”。對於編譯器和連結器來說,主要決定段的屬性的是段的型別(sh_type)和段的標誌位(sh_flags)。段的型別相關常量以SHT_開頭。
這裡寫圖片描述
這裡寫圖片描述
2、段的標誌位
表示該段在程序虛擬地址空間中的屬性。比如是否可寫,是否可執行等。相關常量以SHF_開頭。
這裡寫圖片描述
3、段的連結資訊
如果段的型別是與連結相關的,比如重定位表、符號表等,那麼sh_link和sh_info這兩個成員所包含的意義如表所示。
這裡寫圖片描述

3.4.3 重定位表
連結器在處理目標檔案時,需要對目標檔案中某些部位進行重定位。這些重定位的資訊都記錄在ELF檔案的重定位表裡面,對於每個需要重定位的程式碼段或資料段,都會有一個相應的重定位表。
一個重定位表同時也是ELF的一個段,那麼這個段的型別(sh_type)就是“SHT_REL”型別的,它的“sh_link”表示符號表的下標,它的“sh_info”表示它作用於哪個段。比如“.rel.text”作用於“.text”段,而“.text”段的下標為1,那麼“.rel.text”的“sh_info”為1。

3.4.4 字串表
ELF檔案中用到很多字串,由於長度不同,用固定的結構比較困難。常見的做法是把字串集中起來存放到一個表,然後使用字串在表中的偏移來引用字串。
如字串表為:
這裡寫圖片描述
那麼偏移與它們對應的字串如表所示:
這裡寫圖片描述
一般字串表在ELF檔案中也以段的形式儲存,常見的段名為”.shstrtab“和”.strtab“。這兩個字串分別表示段表字符串表和字串表。字串表用來儲存普通的字串,段表字符串表用來儲存段表中用到的字串,最常見的就是段名(sh_name)。
ELF檔案頭中的”e_shstrtab“表示段表字符串表在段表中的下標。本例中是10
這裡寫圖片描述