游戏攻略

实现ELF文件解析,支持-h, -S, -s

栏目:游戏攻略 日期: 作者:游戏资讯

ELF文件

编译和链接

ELF代表 Executable and Linkable Format ,是类Unix平台最通用的二进制文件格式。下面三种文件的格式都是ELF。

  • 目标文件 .o
  • 动态库文件 .so
  • .o .so 链接得到的二进制可执行文件

编译链接与执行过程中的文件转换如下图所示。

文件结构

根据冯诺伊曼原理,程序有指令和数据构成,因此ELF文件存储的内容即代码(指令)+数据+其他元信息。

ELF文件是静态程序转换为进程的桥梁,结构概览如下图:

  1. FILE HEDER :描述整个文件的组织结构。
  2. Program Header Table :描述如何将 ELF 文件中的段映射到内存中,它为操作系统的加载器提供信息,告知哪些段需要被加载到内存中、它们的权限以及如何映射。
  3. Section Header Table :描述 ELF 文件中的各个节,提供了每个节的详细信息,如名称、大小、类型和位置等。
  4. Section / Segment :节从链接角度描述elf文件,段从内存加载角度描述elf文件。

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中的元信息,跳转到对应部分进行解析。

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

  1. ./elf_reader -S a.out

  1. ./elf_reader -s a.out

源代码

#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文件数据结构

  • 代码实现

关键词:

相关资讯

  • 2024年12月3日小鸡庄园最新答案

    小鸡庄园每天过0点都会更新最新答题的答案。用户答对问题就可以免费领取180g小鸡专属饲料。大家都很好奇支付宝小鸡庄园2024年...
  • 实现ELF文件解析,支持-h, -S, -s

    ELF文件 编译和链接 ELF代表 Executable and Linkable...
  • 崩坏星穹铁道2.7版本自选五星英雄推荐

    崩坏星穹铁道将在2.7版本上线之时为所有玩家送上常驻自选英雄,那么大家知道这次的自选礼包选谁比较好呢?下面就让我们来看看崩坏星...
  • 浪漫餐厅11月30日兑换码分享

    浪漫餐厅是一款合成+建造游戏,官方经常会发放兑换码。11月30日有什么兑换码呢?下面为大家带来浪漫餐厅11月30日兑换码分享。...
  • 《漫威争锋》纳摩介绍

    《 漫威 争锋》是一款 超级英雄 团队PVP射击游戏。纳摩是游戏中的一个英雄,身为古埃及复仇之神的代言人,马克·史贝特...
  • 《迷雾计划》游戏开发公司介绍

    《迷雾计划》是一款开放世界生存恐怖游戏,由Chicken Launcher开发公司开发。该公司是一家经验丰富的工作室,之前曾开...