1. 程式人生 > >elf格式分析

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     .text

   01     .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檔案建立程序了。

   下一節,我們將講述靜態連結編譯的過程。