1. 程式人生 > >ELF檔案格式與相關命令

ELF檔案格式與相關命令

一、相關概念介紹

  1. 可執行檔案(executable)格式:

    windows平臺下是PE(protable executable)
    linux平臺下是ELF(executable linkable format)
    
  2. 目標檔案:
    原始碼(.c .cpp)編譯之後生成的檔案(.o或.obj),從檔案結構上講,它是已經編譯後的可執行檔案格式,只是沒有經過連結的過程,其中可能有些符號或有些地址還沒有被調整。它本身是按照可執行檔案格式儲存的,跟真正的可執行檔案在結構上稍有不同,因此目標檔案和可執行檔案看成是一種檔案型別。

  3. 動態連結庫(.so .dll)和靜態連結庫(.a .lib)檔案都是按照可執行檔案格式儲存。

二、ELF檔案格式分析:

  • ELF檔案格式的種類:

    可重定位檔案(relocatable)         .o   .a
    可執行檔案(executable)               
    共享目標檔案(shared object)       .so 
    核心轉儲檔案                      core dump
    
    file  檔名         該命令可以用來檢視檔案格式的具體型別。
    

    ELF檔案格式提供了兩種不同的視角,在彙編器和連結器看來,ELF檔案是由section header table描述的一系列section的集合,而執行一個ELF檔案時,在載入器

    看來它是由program header table描述的一系列segment的集合。多個原始碼編譯之後成為多個elf可重定位檔案,在連結時將多個elf可重定位檔案中對應的section合併組成一個elf可執行檔案,每個可執行檔案由segment組成。

    本文的討論基於一個執行linux的x86系統環境,使用標準ELF檔案格式。討論集中在32位程式碼,在x86-64系統上用gcc -m32產生32位程式碼。
    若編譯時發生/usr/include/features.h:364:25: fatal error: sys/cdefs.h: 沒有那個檔案或目錄,則使用命令sudo apt-get install libc6-dev-i386解決。

  • ELF可重定位檔案詳解:

    可重定位檔案圖解

    相關術語的說明:
    ELF可執行檔案大體按照圖中的形式組織,不同的是在ELF可重定位檔案中的內容稱為節(section),對應的有section header table。在ELF可執行檔案中的內容稱為段(segment),對應的有program header table。可重定位檔案中的節(section)在連結時對應節進行合併生成可執行檔案中的段(segment)。

    例項程式碼與簡化的可重定位檔案的對應

     .text           
         存放程式原始碼編譯後的機器指令        
     .data 
         存放全域性變數和區域性靜態變數
     .bss
         為未初始化的全域性變數和區域性靜態變數預留位置,目標檔案中,未初始化變數不需要佔據任何實際的磁碟空間               
    .rel.text 
        存放呼叫外部函式或者引用全域性變數等相關的重定位資訊
    .rel.data 
        一個全域性變數被初始化為其他檔案中的全域性變數地址或者外部定義函式的地址。     
    .symtab
        存放函式和全域性變數的資訊,重定位時根據.rel.text和.rel.data來修正相關地址       
    .debug
        -g選項之後才有, 除錯符號表     
    .line  
        -g選項之後才有,記錄原始碼行號和.text中機器指令的對映關係        
    .strtab     
        以null結尾的字串序列   
    ELF頭(elf header)------readelf -h filename
        包含系統相關、型別相關、載入相關、連結相關的資訊
    節頭部表(section header table)------readelf -S filename
        描述程式節,為彙編器和連結器服務。它把elf檔案分成了許多 
    

三、例項解析ELF檔案:

  • 原始碼:

    main.c
    int add(int a,int b);
    static int si;//.bss
    extern int buf[];
    int *copy = &buf[0];//.rel.data
    int main()
    {
        int a = 3;
        int b = 5;
        int c = add(a,b);//.rel.text
        char *s = "hello c";//.rodata
        static int si;//.bss
        return 0;
    }
    
    add.c
    int buf[2];
    int add(int a,int b)
    {
        return (a+b);
    }
    
    makefile(為了簡化討論,makefile檔案中沒有新增-g選項)
    all:main
    main:main.o add.o
        gcc -o main main.o add.o -m32
    main.o:main.c
        gcc -c main.c -m32
    add.o:add.c
        gcc -c add.c -m32
    clean:
        rm -rf *.o main
    
  • ELF頭的描述
    readelf -h main.o
    這裡寫圖片描述

    readelf -h main
    這裡寫圖片描述

    從ELF頭中type的值可判斷出檔案的具體型別,同時也可以從ELF頭的入口地址判斷出檔案的具體型別(elf可重定位檔案僅僅只是編譯後的檔案,沒有經過連結過程,不能執行,因此不能給出正確的入口地址,只能給出一個非法的地址0x0,標識該檔案是可重定位檔案)

    ELF檔案頭包含的資訊有:
    系統相關資訊:elf檔案魔數(標識elf檔案)、平臺位數、資料編碼方式、elf頭部版本、硬體平臺e_machine、目標檔案版本e_version、處理器特定標誌e_ftags
    目標檔案型別:e_type標識目標檔案型別,1為可重定位檔案,2為可執行檔案,3為共享檔案
    載入相關資訊:e_entry為程式進入點,e_phoff為程式表頭偏移量,e_ehsize為elf頭部長度,e_phentsize為程式表頭中一個條目的長度,e_phnum為程式頭表條目個數
    連結相關資訊:e_shoff為節頭表偏移量,e_shentsize為節頭表中一個條目的長度,e_shnum為節頭表條目個數,e_shstmdx為節頭表字符索引

    ELF檔案頭結構被定義在”/usr/include/elf.h“,資料結構中的EI_NIDENT在elf.h中被定義為16,魔數(magic)同樣也是16位元組的序列。最開始的4個位元組是所有ELF檔案必須相同的標識碼0x7f對應ASCII字元中的DEL控制符,0x45 0x4c 0x46分別對應ELF三個字元。第五個位元組標識ELF檔案類(0x01表示32位,0x02表示64位),第六個位元組標識位元組序(0x0表示無效0x01表示小端0x02表示大端),第七個位元組表示ELF檔案版本(通常是1),其餘九個位元組填充為0。資料結構中e_ident陣列包含了Magic到ABI Version的資訊,其餘成員與圖中一一對應。

    ELF頭的資料結構:
    

    這裡寫圖片描述

  • ELF可重定位檔案section header table的描述
    readelf -S main.o
    這裡寫圖片描述

    section header table為彙編器和連結器服務,elf檔案被分成了許多節,每個節儲存著用於不同目的的資料。每一個節在節頭部表中都有一個表項描述該節的屬性,節的屬性包括小節名在字元表中的索引,型別,屬性,執行時的虛擬地址,檔案偏移,以位元組為單位的大小,小節的對齊等資訊

    ELF的section header table的資料結構:

    typedef struct{
        Elf32_Word    sh_name;        /*小節名在字元表中的索引*/
        Elf32_Word    sh_type;        /*小節的型別*/
        Elf32_Word    sh_flags;       /*小節的屬性*/
        Elf32_Addr    sh_addr;        /*小節在執行時的虛擬地址*/
        Elf32_Off     sh_offset;      /*小節的檔案偏移*/
        Elf32_Word    sh_size;        /*小節的大小*/
        Elf32_Word    sh_link;        /*連結另外一小節的索引*/
        Elf32_Word    sh_info;        /*附加的小節資訊*/
        Elf32_Word    sh_addralign;   /*小節的對齊*/
        Elf32_Word    sh_entsize;     /*一些section儲存著一張固定大小入口的表*/
    }Elf32_Shdr;
    
  • ELF可執行檔案program header table的描述

    program header table告訴系統如何建立一個程序映像,它是從載入執行的角度來看待elf檔案。elf檔案被分成許多段(segment),elf檔案中的程式碼、連結資訊、註釋都是以段(segment)的形式存放。每個段都在program header table中有一個表項描述,包含以下屬性:段的型別,段的駐留位置相對於檔案開始處的偏移,段在記憶體中的首位元組地址,段在檔案映像中的位元組數,段在記憶體映像中的位元組數,段在記憶體和檔案中的對齊標記。

    readelf -l main
    這裡寫圖片描述

    對 一個ELF可執行程式而言,一個基本的段是標記p_type為INTERP的段,它表明了執行此程式所需要的程式直譯器(/lib/ld- linux.so.2),實際上也就是動態聯結器(dynamiclinker)。最重要的段是標記p_type為LOAD的段,它表明了為執行程 序而需要載入到記憶體的資料。檢視上面實際輸入,可以看見有兩個可LOAD段,第一個為可讀可執行的程式碼段.text(FLg為RE),第二個為可讀可寫的資料段.data(Flg為RW)。

    ELF的program header table的資料結構:
    這裡寫圖片描述

    readelf -S main
    這裡寫圖片描述

    本圖中列出了main被連結前所有的節(section),上圖中展示了section到segment的對映關係,以下詳細介紹main被連結前所有的節(section)以及其他一些節(section)

    .comment
        編譯器版本資訊
    .data   
        初始化了的資料
    .debug
        除錯資訊
    .dynamic
        動態連結資訊
    .dynstr
        動態連結時需要的字串
    .dynsym
        動態符號表
    .hash
        符號雜湊表
    .shstrtab
        section string table節名錶
    .strtab
        string table字串表,用於儲存ELF檔案中用到的各種字串
    .symtab
        symbol table符號表
    .note
        額外的編譯器資訊
    .init
        程序的初始化程式碼,在main函式之前執行(c語言中)
    .fini
        程序的終止程式碼
    .interp
        程式的解釋程式(interpreter)的路徑,若該section中有一個可裝載的段(segment),那麼
    該section的屬性的SH_ALLOC位將被設定,否則,該位不會被設定。
    .plt
        儲存著過程連結表(procedure linkage table)
    .got
        儲存著全域性偏移量表(global offset table)
    

    .eh_frame的相關資訊參見:http://book.2cto.com/201309/33387.html簡單來說,在ELF檔案的彙編檔案(.s)中,存在一些偽指令,這些偽指令輔助編譯過程建立棧幀資訊。它們被儲存在目標檔案的段“.eh_frame”中。

  • 其他分析ELF檔案的工具: