ELF格式文件詳細分析

ELF(Executable and Linkable Format)是一種對象文件的格式,用於定義不同類型的對象文件(Object files)中都放了什麼東西、以及都以什麼樣的格式去放這些東西。

一、簡介

1、分類

  • 可重定位文件(Relocatable File) .o)包含適合於與其他目標文件鏈接來創建可執行文件或者共享目標文件的代碼和數據。

  • 可執行文件(Executable File) .exe) 包含適合於執行的一個程序,此文件規定了exec() 如何創建一個程序的進程映像。

  • 共享目標文件(Shared Object File) .so) 包含可在兩種上下文中鏈接的代碼和數據。

    • 首先鏈接編輯器可以將它和其它可重定位文件和共享目標文件一起處理, 生成另外一個目標文件。
    • 其次動態鏈接器(Dynamic Linker)可能將它與某 個可執行文件以及其它共享目標一起組合,創建進程映像。

2、作用

ELF文件參與程序的連接(建立一個程序)和程序的執行(運行一個程序),所以可以從不同的角度來看待elf格式的文件:

  • 如果用於編譯和鏈接(可重定位文件),則編譯器和鏈接器將把elf文件看作是節頭表描述的節的集合,程序頭表可選。

  • 如果用於加載執行(可執行文件),則加載器則將把elf文件看作是程序頭表描述的段的集合,一個段可能包含多個節,節頭表可選。

  • 如果是共享文件,則兩者都含有。


二、格式分析

1、源文件

//============================================================================
// Name        : hello.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
using namespace std;

int main() {
    cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
    return 0;
}
$g++ hello.cpp -o hello

2、ELF頭部

$ readelf -h hello

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:               0x400750
  Start of program headers:          64 (bytes into file)
  Start of section headers:          65016 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         37
  Section header string table index: 34

文件的最開始幾個字節給出如何解釋文件的提示信息。這些信息獨立於處理器,也獨立於文件中的其餘內容。ELF Header 部分可以用以下的數據結構表示:

/* ELF Header */
#define EI_NIDENT 16
typedef struct elfhdr {
    unsigned char    e_ident[EI_NIDENT]; /* ELF Identification */
    Elf32_Half    e_type;        /* object file type */
    Elf32_Half    e_machine;    /* machine */
    Elf32_Word    e_version;    /* object file version */
    Elf32_Addr    e_entry;      /* virtual entry point */
    Elf32_Off     e_phoff;      /* program header table offset */
    Elf32_Off     e_shoff;      /* section header table offset */
    Elf32_Word    e_flags;      /* processor-specific flags */
    Elf32_Half    e_ehsize;     /* ELF header size */
    Elf32_Half    e_phentsize;    /* program header entry size */
    Elf32_Half    e_phnum;      /* number of program header entries */
    Elf32_Half    e_shentsize;    /* section header entry size */
    Elf32_Half    e_shnum;      /* number of section header entries */
    Elf32_Half    e_shstrndx;    /* section header table's "section 
                       header string table" entry offset */
} Elf32_Ehdr;

typedef struct {
    unsigned char    e_ident[EI_NIDENT];    /* Id bytes */
    Elf64_Quarter    e_type;            /* file type */
    Elf64_Quarter    e_machine;        /* machine type */
    Elf64_Half       e_version;        /* version number */
    Elf64_Addr       e_entry;         /* entry point */
    Elf64_Off        e_phoff;         /* Program hdr offset */
    Elf64_Off        e_shoff;         /* Section hdr offset */
    Elf64_Half       e_flags;         /* Processor flags */
    Elf64_Quarter    e_ehsize;        /* sizeof ehdr */
    Elf64_Quarter    e_phentsize;     /* Program header entry size */
    Elf64_Quarter    e_phnum;         /* Number of program headers */
    Elf64_Quarter    e_shentsize;     /* Section header entry size */
    Elf64_Quarter    e_shnum;         /* Number of section headers */
    Elf64_Quarter    e_shstrndx;      /* String table index */
} Elf64_Ehdr;

e_ident 數組給出了 ELF 的一些標識信息,這個數組中不同下標的含義如表所示:
這裏寫圖片描述

這些索引訪問包含以下數值的字節:

這裏寫圖片描述

e_ident[EI_MAG0]~e_ident[EI_MAG3]e_ident[0]~e_ident[3]被稱爲魔數(Magic Number),其值一般爲0x7f,'E','L','F'
e_ident[EI_CLASS](即e_ident[4])識別目標文件運行在目標機器的類別,取值可爲三種值:ELFCLASSNONE(0)非法類別;ELFCLASS32(1)32位目標;ELFCLASS64(2)64位目標。
e_ident[EI_DATA](即e_ident[5]):給出處理器
特定數據的數據編碼方式。即大端還是小端方式。取值可爲3種:ELFDATANONE(0)非法數據編碼;ELFDATA2LSB(1)高位在前;ELFDATA2MSB(2)低位在前。

ELF Header 中各個字段的說明如表:
這裏寫圖片描述


3、程序頭部(Program Header)

$ readelf -l hello

Elf file type is EXEC (Executable file)
Entry point 0x400750
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000ad4 0x0000000000000ad4  R E    200000
  LOAD           0x0000000000000df8 0x0000000000600df8 0x0000000000600df8
                 0x0000000000000268 0x0000000000000380  RW     200000
  DYNAMIC        0x0000000000000e18 0x0000000000600e18 0x0000000000600e18
                 0x00000000000001e0 0x00000000000001e0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000000958 0x0000000000400958 0x0000000000400958
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000df8 0x0000000000600df8 0x0000000000600df8
                 0x0000000000000208 0x0000000000000208  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 

可執行文件或者共享目標文件的程序頭部是一個結構數組,每個結構描述了一個段 或者系統準備程序執行所必需的其它信息。目標文件的“段”包含一個或者多個“節區”, 也就是“段內容(Segment Contents)”。程序頭部僅對於可執行文件和共享目標文件 有意義。 可執行目標文件在 ELF 頭部的 e_phentsizee_phnum 成員中給出其自身程序頭部 的大小。程序頭部的數據結構:

/* Program Header */
typedef struct {
    Elf32_Word    p_type;        /* segment type */
    Elf32_Off    p_offset;    /* segment offset */
    Elf32_Addr    p_vaddr;    /* virtual address of segment */
    Elf32_Addr    p_paddr;    /* physical address - ignored? */
    Elf32_Word    p_filesz;    /* number of bytes in file for seg. */
    Elf32_Word    p_memsz;    /* number of bytes in mem. for seg. */
    Elf32_Word    p_flags;    /* flags */
    Elf32_Word    p_align;    /* memory alignment */
} Elf32_Phdr;

typedef struct {
    Elf64_Half    p_type;        /* entry type */
    Elf64_Half    p_flags;    /* flags */
    Elf64_Off    p_offset;    /* offset */
    Elf64_Addr    p_vaddr;    /* virtual address */
    Elf64_Addr    p_paddr;    /* physical address */
    Elf64_Xword    p_filesz;    /* file size */
    Elf64_Xword    p_memsz;    /* memory size */
    Elf64_Xword    p_align;    /* memory & file alignment */
} Elf64_Phdr;

其中各個字段說明:

  • p_type 此數組元素描述的段的類型,或者如何解釋此數組元素的信息。具體如下圖。
  • p_offset 此成員給出從文件頭到該段第一個字節的偏移。
  • p_vaddr 此成員給出段的第一個字節將被放到內存中的虛擬地址。
  • p_paddr 此成員僅用於與物理地址相關的系統中。因爲 System V 忽略所有應用程序的物理地址信息,此字段對與可執行文件和共享目標文件而言具體內容是指定的。
  • p_filesz 此成員給出段在文件映像中所佔的字節數。可以爲 0。
  • p_memsz 此成員給出段在內存映像中佔用的字節數。可以爲 0。
  • p_flags 此成員給出與段相關的標誌。
  • p_align 可加載的進程段的 p_vaddr 和 p_offset 取值必須合適,相對於對頁面大小的取模而言。此成員給出段在文件中和內存中如何 對齊。數值 0 和 1 表示不需要對齊。否則 p_align 應該是個正整數,並且是 2 的冪次數,p_vaddr 和 p_offset 對 p_align 取模後應該相等。

可執行 ELF 目標文件中的段類型:
這裏寫圖片描述

4、節區(Sections)

$ readelf -S hello
There are 37 section headers, starting at offset 0xfdf8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000030  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002c8  000002c8
       0000000000000138  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400400  00000400
       0000000000000168  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400568  00000568
       000000000000001a  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400588  00000588
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             00000000004005c8  000005c8
       0000000000000030  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             00000000004005f8  000005f8
       00000000000000a8  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         00000000004006a0  000006a0
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004006c0  000006c0
       0000000000000080  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000400740  00000740
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         0000000000400750  00000750
       00000000000001e2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000400934  00000934
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000400940  00000940
       0000000000000016  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000400958  00000958
       0000000000000044  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         00000000004009a0  000009a0
       0000000000000134  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600df8  00000df8
       0000000000000010  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e08  00000e08
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e18  00000e18
       00000000000001e0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000050  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601050  00001050
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601060  00001060
       0000000000000118  0000000000000000  WA       0     0     32
  [27] .comment          PROGBITS         0000000000000000  00001060
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .debug_aranges    PROGBITS         0000000000000000  00001094
       0000000000000030  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  000010c4
       00000000000015ca  0000000000000000           0     0     1
  [30] .debug_abbrev     PROGBITS         0000000000000000  0000268e
       00000000000003bf  0000000000000000           0     0     1
  [31] .debug_line       PROGBITS         0000000000000000  00002a4d
       000000000000075e  0000000000000000           0     0     1
  [32] .debug_str        PROGBITS         0000000000000000  000031ab
       0000000000009aad  0000000000000001  MS       0     0     1
  [33] .debug_macro      PROGBITS         0000000000000000  0000cc58
       0000000000002512  0000000000000000           0     0     1
  [34] .shstrtab         STRTAB           0000000000000000  0000fc98
       0000000000000159  0000000000000000           0     0     1
  [35] .symtab           SYMTAB           0000000000000000  0000f170
       00000000000007b0  0000000000000018          36    56     8
  [36] .strtab           STRTAB           0000000000000000  0000f920
       0000000000000378  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

節區中包含目標文件中的所有信息,除了:ELF 頭部、程序頭部表格、節區頭部表格。節區滿足以下條件:

  • 目標文件中的每個節區都有對應的節區頭部描述它,反過來,有節區頭部不意 味着有節區。
  • 每個節區佔用文件中一個連續字節區域(這個區域可能長度爲 0)。
  • 文件中的節區不能重疊,不允許一個字節存在於兩個節區中的情況發生。
  • 目標文件中可能包含非活動空間(INACTIVE SPACE)。這些區域不屬於任何頭部和節區,其內容指定。

節區頭部表格

ELF 頭部中,e_shoff 成員給出從文件頭到節區頭部表格的偏移字節數;e_shnum 給出表格中條目數目;e_shentsize 給出每個項目的字節數。從這些信息中可以確切地定 位節區的具體位置、長度。

每個節區頭部數據結構描述:

/* Section Header */
typedef struct {
    Elf32_Word    sh_name;    /* name - index into section header
                       string table section */
    Elf32_Word    sh_type;    /* type */
    Elf32_Word    sh_flags;    /* flags */
    Elf32_Addr    sh_addr;    /* address */
    Elf32_Off     sh_offset;    /* file offset */
    Elf32_Word    sh_size;    /* section size */
    Elf32_Word    sh_link;    /* section header table index link */
    Elf32_Word    sh_info;    /* extra information */
    Elf32_Word    sh_addralign;    /* address alignment */
    Elf32_Word    sh_entsize;    /* section entry size */
} Elf32_Shdr;

typedef struct {
    Elf64_Half    sh_name;    /* section name */
    Elf64_Half    sh_type;    /* section type */
    Elf64_Xword   sh_flags;    /* section flags */
    Elf64_Addr    sh_addr;    /* virtual address */
    Elf64_Off     sh_offset;    /* file offset */
    Elf64_Xword   sh_size;    /* section size */
    Elf64_Half    sh_link;    /* link to another */
    Elf64_Half    sh_info;    /* misc info */
    Elf64_Xword   sh_addralign;    /* memory alignment */
    Elf64_Xword   sh_entsize;    /* table entry size */
} Elf64_Shdr;

各個字段的解釋如下:
這裏寫圖片描述

索引爲零(SHN_UNDEF)的節區頭部是存在的,儘管此索引標記的是未定義的節區應用。這個節區固定爲:

這裏寫圖片描述

sh_type 字段 節區類型定義:

這裏寫圖片描述

sh_flags 字段定義了一個節區中包含的內容是否可以修改、是否可以執行等信息。 如果一個標誌位被設置,則該位取值爲 1。 定義的各位都設置爲 0。

這裏寫圖片描述

其中已經定義了的各位含義如下:

  • SHF_WRITE: 節區包含進程執行過程中將可寫的數據。
  • SHF_ALLOC: 此節區在進程執行過程中佔用內存。某些控制節區- 並不出現於目標文件的內存映像中,對於那些節區,此位應設置爲 0。
  • SHF_EXECINSTR: 節區包含可執行的機器指令。
  • SHF_MASKPROC: 所有包含於此掩碼中的四位都用於處理器專用的語義。

根據節區類型的不同,sh_link 和 sh_info 的具體含義也有所不同:
這裏寫圖片描述

特殊節區
系統使用的節區,以及它們 的類型和屬性:
這裏寫圖片描述

在分析這些節區的時候,需要注意如下事項:

  • 以“.”開頭的節區名稱是系統保留的。應用程序可以使用沒有前綴的節區名稱,以避 免與系統節區衝突。
  • 目標文件格式允許人們定義不在上述列表中的節區。
  • 目標文件中也可以包含多個名字相同的節區。
  • 保留給處理器體系結構的節區名稱一般構成爲:處理器體系結構名稱簡寫 + 節區名稱。
  • 處理器名稱應該與 e_machine 中使用的名稱相同。例如 .FOO.psect 街區是由FOO 體系結構定義的 psect 節區。

5、字符串表(String Table)

字符串表節區包含以 NULL(ASCII 碼 0)結尾的字符序列,通常稱爲字符串。ELF 目標文件通常使用字符串來表示符號和節區名稱。對字符串的引用通常以字符串在字符 串表中的下標給出。

一般,第一個字節(索引爲 0)定義爲一個空字符串。類似的,字符串表的最後一 個字節也定義爲 NULL,以確保所有的字符串都以 NULL 結尾。索引爲 0 的字符串在 不同的上下文中可以表示無名或者名字爲 NULL 的字符串。

允許存在空的字符串表節區,其節區頭部的 sh_size 成員應該爲 0。對空的字符串 表而言,非 0 的索引值是非法的。

在使用、分析字符串表時,要注意以下幾點:

  • 字符串表索引可以引用節區中任意字節。
  • 字符串可以出現多次
  • 可以存在對子字符串的引用
  • 同一個字符串可以被引用多次。
  • 字符串表中也可以存在未引用的字符串。

6、符號表(Symbol Table)

目標文件的符號表中包含用來定位、重定位程序中符號定義和引用的信息。符號表 索引是對此數組的索引。索引 0 表示表中的第一表項,同時也作爲 定義符號的索引。

/* Symbol Table Entry */
typedef struct elf32_sym {
    Elf32_Word    st_name;    /* name - index into string table */
    Elf32_Addr    st_value;    /* symbol value */
    Elf32_Word    st_size;    /* symbol size */
    unsigned char    st_info;    /* type and binding */
    unsigned char    st_other;    /* 0 - no defined meaning */
    Elf32_Half    st_shndx;    /* section header index */
} Elf32_Sym;

typedef struct {
    Elf64_Half    st_name;    /* Symbol name index in str table */
    Elf_Byte    st_info;    /* type / binding attrs */
    Elf_Byte    st_other;    /* unused */
    Elf64_Quarter    st_shndx;    /* section index of symbol */
    Elf64_Xword    st_value;    /* value of symbol */
    Elf64_Xword    st_size;    /* size of symbol */
} Elf64_Sym;

這裏寫圖片描述

三、參考

http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html
http://bdxnote.blog.163.com/blog/static/8444235201532911597959/
http://blog.csdn.net/wh8_2011/article/details/50812434
https://segmentfault.com/a/1190000007103522?utm_source=tuicool&utm_medium=referral

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章