elf格式分析
最近研究了一下elf檔案格式,發現好多資料寫的都比較繁瑣,可能會嚴重打擊學習者的熱情,我把自己研究的結果和大家分享,希望我的描述能夠簡潔一些。
一、基礎知識
elf是一種檔案格式,用於儲存Linux程式. 它內部都有一些什麼資訊呢?大概包括編制好的計算機指令,資料,計算機在需要的時候把這個檔案讀取到記憶體中,cpu就可以從記憶體中一條一條的讀取指令來執行了。
所以說想明白elf格式,我們應該瞭解一下計算機執行程式需要那些資訊。所以這一節,我們補充一些計算機系統的基礎知識。
程序和虛擬記憶體:
Linux系統給每個程序分配了4GB的空間,其中 0xC0000000到0xFFFFFFFF 這個地址段是留給系統使用的,主要用於系統(linux 核心)和程序通訊和交換資料, 使用者可以使用3GB的空間從(0x00000000-0xBFFFFFFF).
其實計算機的記憶體是沒有那麼大的,比如我們實際使用的計算機只有2G,以前更小,只有幾百M,而且一臺計算機上不只執行一個程序,一個佔用4G,如果有10個程序,那就得著用40G了,哪有那麼打的記憶體呢?其實這個不要緊,因為作業系統分配給使用者的是虛擬記憶體,程式要可以使用3個G的記憶體。至於作業系統怎樣把虛擬記憶體轉化成實體記憶體,對於開發應用程式的工程師來說,是不需要了解的。我們直接使用虛擬記憶體就可以了,而不用擔心其它程序會侵犯到你的記憶體空間。
程序的建立和執行程序的建立和執行:
大致經歷了以下步驟
1.使用者請求執行程式時,作業系統會讀取儲存在磁碟上的可執行檔案,在linux系統上這個檔案就是我們的elf格式檔案,為使用者分配4G的虛擬記憶體空間,
2. 根據檔案的資訊指示,把不同的檔案內容放到為你分配的這3G虛擬記憶體
3. 然後根據檔案的指示,系統設定設定程式碼段和資料段暫存器
4.然後根據檔案的指示, 跳轉到使用者的程式碼的入口地址(一般就是我們的main函式)
5.從main開始,計算機就一條一條的執行我們給的指令,處理我們的資料了,直到我們程式結束。雖然在這個過程中,系統會多次切換到其他程序,但對使用者程式來說沒有影響,我們可以認為計算機只為我們服務。
通過以上我們多次看到計算機是根據檔案指示這樣的語言,所以學習elf 首先要理解elf指示了那些資訊。
二、可執行的elf檔案。
elf檔案分三種類型: 1、目標檔案(通常是.o); 2、可執行檔案(我們的執行檔案) 3、動態庫(.so)
我們先講一下可執行檔案。
可執行檔案一般分成4個部分,能擴充套件,我們理解這4部分就夠了。
1、elf檔案頭 ,這個檔案是對elf檔案整體資訊的描述,在32位系統下是56的位元組,在64位系統下是64個位元組。
對於可執行檔案來說,檔案頭包含的一下資訊與程序啟動相關
e_entry 程式入口地址
e_phoff segment偏移
e_phnum segment數量
2. segment表, 這個表是載入指示器,作業系統(確切的說是載入器,有些elf檔案,比如作業系統核心,是由其他程式載入的),該表的結構非常重要。
typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */ /*segment許可權,6表示可讀寫,5表示可讀可執行
Elf64_Off p_offset; /* Segment file offset */ /*段在檔案中的偏移*/
Elf64_Addr p_vaddr; /* Segment virtual address */ /*虛擬記憶體地址,這個表示記憶體中的
Elf64_Addr p_paddr; /* Segment physical address /*實體記憶體地址,對應用程式來說,這個欄位無用*/
Elf64_Xword p_filesz; /* Segment size in file */ /*段在檔案中的長度*/
Elf64_Xword p_memsz; /* Segment size in memory */ /在記憶體中的長度,一般和p_filesz的值一樣*/
Elf64_Xword p_align; /* Segment alignment */ /* 段對齊*/} Elf64_Phdr;
3. elf的主題,對於可執行檔案來說,最主要的就是資料段和程式碼段
4. section表,對可執行檔案來說,沒有用,在連結的時候有用,是對程式碼段資料段在連結是的一種描述。
整個elf檔案的組成可以使用下圖來描述
該圖片使用的是Linux C程式設計作者 宋勁斌的圖片
上圖program header table 實際上就是我們說的segment table. segments 是從執行的角度來描述elf檔案, sections是從連結的角度來描述elf檔案的。
本節我們只將elf檔案的執行,所以我們只講segment相關的內容。
我們將通過一個例子來講解系統載入elf的過程(64位平臺)。
我們編寫一個簡單的彙編程式
.section .data
.global data_item
data_item:
.long 3,67,28
.section .text
.global _start
_start:
mov $1,%eax
mov $4,%ebx
int $0x80
編譯連結後生成hello檔案,我們分析hello檔案.
執行:readelf -h ../asm/hello (readelf -h 是讀取elf檔案頭的命令)
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4000b0 //程式的入口地址是0x4000b0
Start of program headers: 64 (bytes into file) //segment表在檔案64位元組偏移處
Start of section headers: 240 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes) //segment頭項的長度是56位元組(32系統是32位元組)
Number of program headers: 2
Size of section headers: 64 (bytes)
Number of section headers: 6
Section header string table index: 3
對於程式的裝載,我們關心這三項:
Entry point address: 0x4000b0 //程式的入口地址是0x4000b0
Start of program headers: 64 (bytes into file) //segment表在檔案64位元組偏移處
Size of program headers: 56 (bytes) //segment頭項的長度是56位元組(32系統是32位元組)
以上內容告訴我們segment表在檔案的64位元組處,我們看看64位元組處有什麼內容。
執行 readelf -l ../asm/hello 輸出segments資訊。(readelf -l 讀取segments)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000bc 0x00000000000000bc R E 200000
LOAD 0x00000000000000bc 0x00000000006000bc 0x00000000006000bc
0x000000000000000c 0x000000000000000c RW 200000
Section to Segment mapping:
Segment Sections...
00 .text01 .data
我們看到程式有兩個segment ,分別叫做.text 和.data
.text的Offset是0,FileSiz是0x0,MemSiz是0xbc, VirtAddr是0x400000,Flags是R E,表示載入起將把elf檔案中從0位元組開始直到oxbc處的內容載入到虛擬記憶體中的0x400000處,佔用0xbc長度的記憶體。設定該記憶體的許可權是RE(可讀,可執行),這一段的內容正好是elf頭,segments table,和程式碼段。
在看看elfheader 的e_entry 的地址 0x4000b0,這個地址正好是程式碼段的起始地址。
.data的Offset是0,FileSiz是0xbc,MemSiz是0x0c, VirtAddr是0x6000bc,Flags是R W,表示載入起將把elf檔案中從bc位元組開始直到oxbc + 0xc處的內容載入到虛擬記憶體中的0x6000bc處,佔用0x0c長度的記憶體。設定該記憶體的許可權是RE(可讀,可執行)
為什麼資料段的其實地址是0x6000bc,而不是0x6000000呢,這是由Align決定的,Align決定記憶體和磁碟以1M為單位進行對映,在檔案中.data 和.text處於一個頁面中,在對映的時候,直接把整個頁面都對映到了0x6000000處,所以把資料段的偏移設定成了0x60000bc,0x600000到0x6000bc的內容不使用。
有了以上內容,系統就可以根據elf檔案建立程序了。
下一節,我們將講述靜態連結編譯的過程。