实现ELF文件解析,支持-h, -S, -s
ELF文件
编译和链接
ELF代表Executable and Linkable Format,是类Unix平台最通用的二进制文件格式。下面三种文件的格式都是ELF。
- 目标文件
.o
- 动态库文件
.so
.o
和.so
链接得到的二进制可执行文件
编译链接与执行过程中的文件转换如下图所示。
文件结构
根据冯诺伊曼原理,程序有指令和数据构成,因此ELF文件存储的内容即代码(指令)+数据+其他元信息。
ELF文件是静态程序转换为进程的桥梁,结构概览如下图:
- FILE HEDER:描述整个文件的组织结构。
- Program Header Table:描述如何将 ELF 文件中的段映射到内存中,它为操作系统的加载器提供信息,告知哪些段需要被加载到内存中、它们的权限以及如何映射。
- Section Header Table:描述 ELF 文件中的各个节,提供了每个节的详细信息,如名称、大小、类型和位置等。
- 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
./elf_reader -h a.out
./elf_reader -S a.out
./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 = §ions[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 = §ions[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]);
}
}