實現ELF檔案解析,支援-h, -S, -s

咪啪魔女發表於2024-12-01

ELF檔案

編譯和連結

ELF代表Executable and Linkable Format,是類Unix平臺最通用的二進位制檔案格式。下面三種檔案的格式都是ELF。

  • 目標檔案.o
  • 動態庫檔案.so
  • .o.so連結得到的二進位制可執行檔案

編譯連結與執行過程中的檔案轉換如下圖所示。

image-20241130162807177

檔案結構

根據馮諾伊曼原理,程式有指令和資料構成,因此ELF檔案儲存的內容即程式碼(指令)+資料+其他元資訊。

ELF檔案是靜態程式轉換為程序的橋樑,結構概覽如下圖:

  1. FILE HEDER:描述整個檔案的組織結構。
  2. Program Header Table:描述如何將 ELF 檔案中的段對映到記憶體中,它為作業系統的載入器提供資訊,告知哪些段需要被載入到記憶體中、它們的許可權以及如何對映。
  3. Section Header Table:描述 ELF 檔案中的各個節,提供了每個節的詳細資訊,如名稱、大小、型別和位置等。
  4. Section / Segment:節從連結角度描述elf檔案,段從記憶體載入角度描述elf檔案。

image-20241130141031157

ELF檔案用於連結和載入兩個階段,有兩個檢視,連結檢視和執行檢視。

檢視 儲存內容 資料結構 使用階段 檔案格式
連結檢視 靜態程式,用節(Section)組織 Section Header Table 編譯,連結 .o ,.so
執行檢視 載入後到記憶體分佈,用段(Segment)組織 Program Header Table 載入 可執行程式

Section 和 Segment 是 邏輯到物理的對映關係

  • 一個Segment對應多個Section
  • 一個Section只能對應一個Segment

典型的對應關係:

執行段名稱(Segments) 包含的節(Sections) 許可權
PT_LOAD(程式碼段) .text,.rodata R E
PT_LOAD(資料段) .data,.bss RW
PT_DYNAMIC .dynamic,.got,.plt RW
PT_INTERP .interp R
PT_NOTE .note R
PT_SHLIB .shstrtab,.symtab RW
PT_TLS .tbss,.tdata R,RW

載入階段,載入器會按照Segment來組織虛擬記憶體,構造一個程序的記憶體空間,完成一個靜態檔案到程序的轉換。

ELF檔案解析

基本思路:根據ELF Header中的元資訊,跳轉到對應部分進行解析。

image-20241201124307267

readelf -l fileName

解析ELF檔案的檔案頭,資料結構如下,逐個解析即可:

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf64_Half	e_type;			/* Object file type */
  Elf64_Half	e_machine;		/* Architecture */
  Elf64_Word	e_version;		/* Object file version */
  Elf64_Addr	e_entry;		/* Entry point virtual address */
  Elf64_Off	e_phoff;		/* Program header table file offset */
  Elf64_Off	e_shoff;		/* Section header table file offset */
  Elf64_Word	e_flags;		/* Processor-specific flags */
  Elf64_Half	e_ehsize;		/* ELF header size in bytes */
  Elf64_Half	e_phentsize;		/* Program header table entry size */
  Elf64_Half	e_phnum;		/* Program header table entry count */
  Elf64_Half	e_shentsize;		/* Section header table entry size */
  Elf64_Half	e_shnum;		/* Section header table entry count */
  Elf64_Half	e_shstrndx;		/* Section header string table index */
} Elf64_Ehdr;
成員 含義 備註
e_ident 檔案資訊 下標:
[0.3]:魔數
4:檔案類
5:資料編碼
6:檔案版本
7:補齊
e_type 檔案型別 ET_NONE,ET_REL,ER_EXEC,ET_DYN,ET_CORE
e_machine 機器架構 EM_NONE,EM_M32,EM_SPARC,EM_386,EM_68K,EM_88K,EM_860,EM_MIPS
e_version 目標檔案版本 EV_NONE,EV_CURRENT
e_entry 入口項地址 上文圖中的Entry Point指標
e_phoff 程式頭部表偏移 Program Header Table Offset
e_shoff 節頭表偏移 Section Header Table Offset
e_flags 檔案中與特定處理器相關的標誌
e_ehsize ELF 檔案頭部的位元組長度 ELF Header Size
e_phentsize 程式頭部表中每個表項的位元組長度 Program Header Entry Size
e_phnum 程式頭部表的項數 Program Header Entry Number
e_shentsize 節頭的位元組長度 Section Header Entry Size
e_shnum 節頭表中的項數 Section Header Number
e_shstrndx 節頭表中與節名字串表相關的表項的索引值 Section Header Table Index Related With Section Name String Table

readelf -S fileName

解析ELF檔案的節頭表。

依照檔案頭資訊得到節頭表:(elf_header為Elf64_Ehdr型別指標)

  • 獲得節頭表地址:elf_header + elf_header->e_shoff

  • 遍歷節頭表:表大小:elf_header->e_shnum

  • 節頭表中每個元素的資料結構如下

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Word	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Word	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to another section */
  Elf64_Word	sh_info;		/* Additional section information */
  Elf64_Word	sh_addralign;		/* Section alignment */
  Elf64_Word	sh_entsize;		/* Entry size if section holds table */
} Elf64_Shdr;

readelf -s fileName

解析ELF檔案中符號表。

符號表作為一個節儲存,遍歷所有節,根據``elf_shdr->sh_type`判斷是否為符號表,如果是則解析該節(elf_shdr為Elf64_Shdr型別指標)

  • 節中元素數量:elf_shdr->sh_size / elf_shdr->sh_entsize

  • 符號表作為節中元素結構如下:

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  Elf64_Addr	st_value;		/* Symbol value */
  Elf64_Word	st_size;		/* Symbol size */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char	st_other;		/* Symbol visibility */
  Elf64_Section	st_shndx;		/* Section index */
} Elf64_Sym;

程式執行結果

編譯: gcc -o elf_reader elf_reader.c

  1. ./elf_reader -h a.out

image-20241201125827776

  1. ./elf_reader -S a.out

image-20241201131018524

  1. ./elf_reader -s a.out

image-20241201131159022

原始碼

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

// 主要函式:進行ELF檔案解析
void parse_elf_header(const Elf64_Ehdr *elf_header);        //-h: 解析檔案頭
void parse_section_headers(const Elf64_Ehdr *elf_header);   //-S: 解析節頭表
void parse_symbol_table(const Elf64_Ehdr *elf_header);      //-s: 解析符號表

// 輔助函式:格式化輸出
const char* get_elf64_st_type_name(unsigned char info);
const char* get_elf64_st_bind_name(unsigned char info); 
const char* get_elf64_st_visibility_name(unsigned char other);
const char* get_section_type_name(Elf64_Word type);
const char* get_section_flags_name(Elf64_Xword flags);
const char* get_class_name(unsigned char class_value);
const char* get_data_name(unsigned char data_value);
const char* get_version_name(unsigned char version_value);
const char* get_os_name(unsigned char os_value);
const char* get_type_name(unsigned char type_value);
const char* get_machine_name(unsigned char machine_value);
void print_symbol_table(const char *strtab, Elf64_Sym *symbols, int count, const char *symtab_name);


int main(int argc, char * argv[])
{
    // 獲取程式引數
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <elf-file> <-h|-S|-s>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    const char *option = argv[1];
    const char *filename = argv[2];

    // 開啟檔案
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("Failed to open ELF file");
        exit(EXIT_FAILURE);
    }

    // mmap對映
    off_t file_size = lseek(fd, 0, SEEK_END);
    char *map = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("Memory mapping failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    Elf64_Ehdr *elf_header = (Elf64_Ehdr *)map;

    // 處理不同引數對應的情況
    if(strcmp(option, "-h") == 0)
    {
        parse_elf_header(elf_header);
    }
    else if(strcmp(option, "-S") == 0)
    {
        parse_section_headers(elf_header);
    }
    else if(strcmp(option, "-s") == 0)
    {
        parse_symbol_table(elf_header);
    }

    // 關閉檔案
    munmap(map, file_size);
    close(fd);

    return 0;
}

// -h: 解析檔案頭
void parse_elf_header(const Elf64_Ehdr *elf_header) {
    printf("ELF Header:\n");
    printf("  Magic:   ");
    for (int i = 0; i < EI_NIDENT; ++i) {
        printf("%02X ", elf_header->e_ident[i]);
    }
    printf("\n");

    printf("  Class:                             %s\n", get_class_name(elf_header->e_ident[EI_CLASS]));
    printf("  Data:                              %s\n", get_data_name(elf_header->e_ident[EI_DATA]));
    printf("  Version:                           %s\n", get_version_name(elf_header->e_ident[EI_VERSION]));
    printf("  OS/ABI:                            %s\n", get_os_name(elf_header->e_ident[EI_OSABI]));
    printf("  Type:                              %s\n", get_type_name(elf_header->e_type));
    printf("  Machine:                           %s\n", get_machine_name(elf_header->e_machine));
    printf("  Version:                           %d\n", elf_header->e_version);
    printf("  Entry point address:               %#lx\n", elf_header->e_entry);
    printf("  Start of program headers:          %ld (bytes into file)\n", elf_header->e_phoff);
    printf("  Start of section headers:          %ld (bytes into file)\n", elf_header->e_shoff);
    printf("  Flags:                             %#lx\n", elf_header->e_flags);
    printf("  Size of this header:               %d (bytes)\n", elf_header->e_ehsize);
    printf("  Size of program headers:           %d (bytes)\n", elf_header->e_phentsize);
    printf("  Number of program headers:         %d\n", elf_header->e_phnum);
    printf("  Size of section headers:           %d (bytes)\n", elf_header->e_shentsize);
    printf("  Number of section headers:         %d\n", elf_header->e_shnum);
    printf("  Section header string table index: %d\n", elf_header->e_shstrndx);
}

// -S: 解析節頭表
void parse_section_headers(const Elf64_Ehdr *elf_header) {
    // 找到節頭表和字串表
    Elf64_Shdr *sections = (Elf64_Shdr *)((char *)elf_header + elf_header->e_shoff);
    const char *strtab = (char *)elf_header + sections[elf_header->e_shstrndx].sh_offset;

    printf("Section Headers:\n");
    printf("  [Nr] Name              Type             Address           Offset\n");
    printf("       Size              EntSize          Flags  Link  Info  Align\n");

    // 遍歷節頭
    for (int i = 0; i < elf_header->e_shnum; i++) {
        // 列印節號、名稱、型別、地址、偏移
        printf("  [%2d] %-17s %-16s %016lx  %08lx\n",
               i,
               &strtab[sections[i].sh_name],
               get_section_type_name(sections[i].sh_type),
               sections[i].sh_addr,
               sections[i].sh_offset);

        // 列印節大小、條目大小、標誌、連結索引、資訊、對齊
        printf("       %016lx  %016lx  %-6s %4u %4u %5lu\n",
               sections[i].sh_size,
               sections[i].sh_entsize,
               get_section_flags_name(sections[i].sh_flags),
               sections[i].sh_link,
               sections[i].sh_info,
               sections[i].sh_addralign);
    }
}

// -s: 解析符號表
void parse_symbol_table(const Elf64_Ehdr *elf_header) {
    // 找到節頭表指標和字串表
    Elf64_Shdr *sections = (Elf64_Shdr *)((char *)elf_header + elf_header->e_shoff);
    const char *strtab = (char *)elf_header + sections[elf_header->e_shstrndx].sh_offset;

    // 遍歷每個節
    for (int i = 0; i < elf_header->e_shnum; i++) {
        // 如果是符號表
        if (sections[i].sh_type == SHT_SYMTAB) {  // .symtab符號表
            Elf64_Shdr *symtab = &sections[i];
            Elf64_Sym *symbols = (Elf64_Sym *)((char *)elf_header + symtab->sh_offset);
            int count = symtab->sh_size / symtab->sh_entsize;  // 符號個數
            const char *symstrtab = (char *)elf_header + sections[symtab->sh_link].sh_offset;
            print_symbol_table(symstrtab, symbols, count, ".symtab");
        }
        // 如果是動態符號表
        else if (sections[i].sh_type == SHT_DYNSYM) {  // .dynsym符號表
            Elf64_Shdr *dynsymtab = &sections[i];
            Elf64_Sym *dynsymbols = (Elf64_Sym *)((char *)elf_header + dynsymtab->sh_offset);
            int dynsym_count = dynsymtab->sh_size / dynsymtab->sh_entsize;  // 動態符號個數
            const char *dynsymstrtab = (char *)elf_header + sections[dynsymtab->sh_link].sh_offset;
            print_symbol_table(dynsymstrtab, dynsymbols, dynsym_count, ".dynsym");
        }
    }
}


// 獲取Class欄位資訊
const char* get_class_name(unsigned char class_value) {
    switch(class_value) {
        case ELFCLASS32: return "ELF32";
        case ELFCLASS64: return "ELF64";
        default: return "Unknown";
    }
}

// 獲取Data欄位資訊
const char* get_data_name(unsigned char data_value) {
    switch(data_value) {
        case ELFDATA2LSB: return "2's complement, little endian";
        case ELFDATA2MSB: return "2's complement, big endian";
        default: return "Unknown";
    }
}

// 獲取Version欄位資訊
const char* get_version_name(unsigned char version_value) {
    switch(version_value) {
        case 0: return "Invalid Version";
        case 1: return "1 (current)";
        default: return "Invalid Version";
    }
}

// 獲取OS欄位資訊
const char* get_os_name(unsigned char os_value) {
    switch(os_value) {
        case ELFOSABI_NONE:    return "UNIX - System V";
        case ELFOSABI_LINUX:    return "Linux";
        case ELFOSABI_SOLARIS: return "Solaris";
        case ELFOSABI_FREEBSD: return "FreeBSD";
        default: return "Others";
    }
}

// 獲取Type欄位資訊
const char* get_type_name(unsigned char type_value) {
    switch(type_value) {
        case ET_NONE: return "NONE (None)";
        case ET_REL:  return "REL (Relocatable file)";
        case ET_EXEC: return "EXEC (Executable file)";
        case ET_DYN:  return "DYN (Shared object file)";
        case ET_CORE: return "CORE (Core file)";
        default: return "Unknown";
    }
}

// 獲取Machine欄位資訊
const char* get_machine_name(unsigned char machine_value) {
    switch(machine_value) {
        case EM_386:    return "Intel 80386";
        case EM_ARM:    return "ARM";
        case EM_X86_64: return "AMD x86-64";
        case EM_AARCH64: return "ARM AARCH64";
        default: return "Unknown";
    }
}



// 解析節型別
const char* get_section_type_name(Elf64_Word type) {
    switch (type) {
        case SHT_NULL:       return "NULL";
        case SHT_PROGBITS:   return "PROGBITS";
        case SHT_SYMTAB:     return "SYMTAB";
        case SHT_STRTAB:     return "STRTAB";
        case SHT_RELA:       return "RELA";
        case SHT_HASH:       return "HASH";
        case SHT_DYNAMIC:    return "DYNAMIC";
        case SHT_NOTE:       return "NOTE";
        case SHT_NOBITS:     return "NOBITS";
        case SHT_REL:        return "REL";
        case SHT_SHLIB:      return "SHLIB";
        case SHT_DYNSYM:     return "DYNSYM";
        default:             return "UNKNOWN";
    }
}

// 解析節標誌
const char* get_section_flags_name(Elf64_Xword flags) {
    static char flag_str[64];
    flag_str[0] = '\0';

    if (flags & SHF_WRITE) strcat(flag_str, "W");
    if (flags & SHF_ALLOC) strcat(flag_str, "A");
    if (flags & SHF_EXECINSTR) strcat(flag_str, "X");
    if (flags & SHF_MERGE) strcat(flag_str, "M");
    if (flags & SHF_STRINGS) strcat(flag_str, "S");

    return flag_str[0] == '\0' ? "None" : flag_str;
}

// 獲取符號型別
const char* get_elf64_st_type_name(unsigned char info) {
    switch (ELF64_ST_TYPE(info)) {
        case STT_NOTYPE: return "NOTYPE";
        case STT_OBJECT: return "OBJECT";
        case STT_FUNC: return "FUNC";
        case STT_SECTION: return "SECTION";
        case STT_FILE: return "FILE";
        default: return "UNKNOWN";
    }
}

// 獲取符號繫結
const char* get_elf64_st_bind_name(unsigned char info) {
    switch (ELF64_ST_BIND(info)) {
        case STB_LOCAL: return "LOCAL";
        case STB_GLOBAL: return "GLOBAL";
        case STB_WEAK: return "WEAK";
        default: return "UNKNOWN";
    }
}

// 獲取符號可見性
const char* get_elf64_st_visibility_name(unsigned char other) {
    switch (ELF64_ST_VISIBILITY(other)) {
        case STV_DEFAULT: return "DEFAULT";
        case STV_INTERNAL: return "INTERNAL";
        case STV_HIDDEN: return "HIDDEN";
        case STV_PROTECTED: return "PROTECTED";
        default: return "UNKNOWN";
    }
}

void print_symbol_table(const char *strtab, Elf64_Sym *symbols, int count, const char *symtab_name) {
    printf("Symbol table '%s' contains %d entries:\n", symtab_name, count);
    printf("   Num:    Value          Size Type    Bind   Vis      Ndx Name\n");
    
    for (int i = 0; i < count; i++) {
        // 符號表內容輸出,按照readelf -s格式進行對齊
        printf("%6d: %016lx  %-5lu %-7s %-6s %-8s %-4d %s\n", 
               i,
               symbols[i].st_value,
               symbols[i].st_size,
               // 解析符號型別
               get_elf64_st_type_name(symbols[i].st_info),
               // 解析符號繫結
               get_elf64_st_bind_name(symbols[i].st_info),
               // 解析符號可見性
               get_elf64_st_visibility_name(symbols[i].st_other),
               symbols[i].st_shndx,
               &strtab[symbols[i].st_name]);
    }
}

參考

  • 相關概念

  • ELF檔案資料結構

  • 程式碼實現

相關文章