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文件解析,支持-h, -S, -s的更多相关文章

  1. ELF文件解析(二):ELF header详解

    上一篇讲了ELF文件的总体布局,以及section和segment的概念.按照计划,今天继续讲 ELF header. 讲新的内容之前,先更正一个错误:上一篇中讲section header tabl ...

  2. ELF文件解析(一):Segment和Section

    ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的 ...

  3. Android ELF文件解析

    0X01  ELF初认识 elf文件是linux下的二进制文件,相当于windows下的PE文件,Android系统里的dll. 解析elf文件两个用处:1.so加固:2.frida(xposed)检 ...

  4. ELF文件解析器支持x86x64ELF文件

    此文为静态分析ELF文件结构,遍历其中Elf_Ehdr文件头信息,遍历Elf_Shdr节表头信息,并将所有节放置在左侧树控件上,遍历Elf_Phdr程序头也放置在左侧树控件上,并着重分析字符串表,重定 ...

  5. C++库文件解析(conio.h)

    转载:https://blog.csdn.net/ykmzy/article/details/51276596 Conio.h 控制台输入输出库该文内容部分参照百度百科 Conio.h 在C stan ...

  6. python3 elf文件解析

    原地址:https://github.com/guanchao/elfParser 作者是用python2写的,现在给出我修改后的python3版本.(测试发现有bug,以后自己写个,0.0) 1 # ...

  7. C语言中.h和.c文件解析(很精彩)

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析 ...

  8. C语言中.h和.c文件解析

    整理自C语言中.h和.c文件解析(很精彩) Part.1(林锐<高质量C/C++编程>) 通过头文件来调用库功能.在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的 ...

  9. 转-C语言中.h和.c文件解析

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:       1.预处理阶段 2.词 ...

  10. 如何让你的Apache支持include文件解析和支持shtml的相关配置

    源地址:http://www.itokit.com/2011/0430/65992.html Apache支持include文件解析shtml首先要应该修改Apache配置文件httpd.conf . ...

随机推荐

  1. manim边学边做--常用多边形

    多边形是常见的几何结构,它的形状看似千变万化,其实都可以由几种常用的多边形组合而成. 本篇介绍manim中提供的几个绘制常用多边形的模块. Triangle:等边三角形 Square:正方形 Rect ...

  2. EF Core – 乐观并发

    前言 之前写过 EF Core 悲观并发, 这篇主要讲一下乐观并发. 乐观并发的机制可以看这篇. Why Need This? 如果你用 EF Core 做数据管理, 建议你每个 Entity 都配置 ...

  3. uni-app v3.0.0-alpha-3090220231010001

    https://uniapp.dcloud.net.cn/tutorial/ #-------------------------------------------------------- 未分类 ...

  4. BOOST库array使用 类似std库的vector

    BOOST库的array,  类似std库的vector. 下图所示书籍的下载地址,我的另一篇博客内有记载: https://www.cnblogs.com/happybirthdaytoyou/p/ ...

  5. jpa 多条件模糊查询,分页并排序

    jpa 多条件模糊查询,分页并排序很难吗,这样写不就几行代码的事吗?搞不明白你们写的怎么长篇大论花里胡哨的,看的一脸懵逼. jpa多字段模糊查询,持久层字段还是要一一对应的,但是你可以在service ...

  6. IDEA更改远程git仓库地址

    前言 我们在使用IDEA开发时,一般会配置好对应的git仓库,这样就比较容易对代码进行控制以及协同开发.   但有时候,我们远程的仓库地址由于这样那样的原因,需要迁移(这在爱折腾的企业是常有的事情). ...

  7. Ai大模型推理-未完善

    环境 安装Conda 最低要求 CUDA 版本为 11.3 #获取安装脚本 wget -c 'https://repo.anaconda.com/archive/Anaconda3-2024.06-1 ...

  8. Android 基于 Choreographer 的渲染机制详解

    本文介绍了 App 开发者不经常接触到但是在 Android Framework 渲染链路中非常重要的一个类 Choreographer.包括 Choreographer 的引入背景.Choreogr ...

  9. BTF:实践指南

    BPF 是 Linux 内核中基于寄存器的虚拟机,可安全.高效和事件驱动的方式执行加载至内核的字节码.与内核模块不同,BPF 程序经过验证以确保它们终止并且不包含任何可能锁定内核的循环.BPF 程序允 ...

  10. 云原生周刊:K8s 上的 gRPC 名称解析和负载平衡

    开源项目推荐 Kraken Kraken 是一个基于 P2P 的 Docker 注册表,专注于可扩展性和可用性.它专为混合云环境中的 Docker 镜像管理.复制和分发而设计.借助可插拔的后端支持,K ...