ELF(Executable and Linkable Format)是Linux系统中可执行文件、目标文件和共享库的标准格式。理解ELF文件中各个区域与文件偏移量之间的关系对于程序分析、调试和逆向工程至关重要。
目录
二、关键概念:虚拟地址(VMA) vs 文件偏移量(Offset)
一、ELF文件基本结构
ELF文件由以下几部分组成:
ELF头(ELF Header)
程序头表(Program Header Table) - 用于程序加载
节头表(Section Header Table) - 用于链接和调试
各种节(Sections)和段(Segments)
二、关键概念:虚拟地址(VMA) vs 文件偏移量(Offset)
文件偏移量(Offset): 指数据在ELF文件中的物理位置,从文件开始计算的字节数
虚拟地址(VMA, Virtual Memory Address): 指该数据在进程虚拟地址空间中的位置
加载地址(LMA, Load Memory Address): 指数据在物理内存中的位置(通常与VMA相同)
三、ELF头与文件偏移量
ELF头总是位于文件的起始位置(偏移量0),包含以下关键信息:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff; // 程序头表文件偏移量
Elf32_Off e_shoff; // 节头表文件偏移量
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
e_phoff
: 程序头表在文件中的偏移量e_shoff
: 节头表在文件中的偏移量
四、程序头表与段(Segments)
程序头表描述了如何将文件映射到进程地址空间,每个条目(程序头)描述一个段:
typedef struct {
Elf32_Word p_type; // 段类型
Elf32_Off p_offset; // 段在文件中的偏移量
Elf32_Addr p_vaddr; // 段的虚拟地址
Elf32_Addr p_paddr; // 段的物理地址
Elf32_Word p_filesz; // 段在文件中的大小
Elf32_Word p_memsz; // 段在内存中的大小
Elf32_Word p_flags; // 段权限标志
Elf32_Word p_align; // 段对齐方式
} Elf32_Phdr;
关键字段关系:
p_offset
→ 文件偏移量p_vaddr
→ 虚拟地址运行时加载器会将
p_offset
处的p_filesz
字节数据映射到p_vaddr
地址
五、节头表与节(Sections)
节头表描述了文件中的各个节,主要用于链接和调试:
typedef struct {
Elf32_Word sh_name; // 节名称字符串表索引
Elf32_Word sh_type; // 节类型
Elf32_Word sh_flags; // 节标志
Elf32_Addr sh_addr; // 节在内存中的地址
Elf32_Off sh_offset; // 节在文件中的偏移量
Elf32_Word sh_size; // 节大小
Elf32_Word sh_link; // 链接到其他节
Elf32_Word sh_info; // 附加信息
Elf32_Word sh_addralign; // 节对齐要求
Elf32_Word sh_entsize; // 条目大小(如果有)
} Elf32_Shdr;
关键字段关系:
sh_offset
→ 文件偏移量sh_addr
→ 虚拟地址(如果该节会被加载到内存)
六、常见节区及其偏移量关系
以下是典型ELF文件中的常见节区及其与文件偏移量的关系:
.text节: 包含可执行代码
sh_offset
: 代码在文件中的位置sh_addr
: 代码在内存中的虚拟地址
.data节: 包含已初始化的全局变量
sh_offset
: 数据在文件中的位置sh_addr
: 数据在内存中的虚拟地址
.bss节: 包含未初始化的全局变量
sh_offset
: 通常为0(因为.bss不占用文件空间)sh_addr
: 内存中的虚拟地址sh_size
: 需要在内存中分配的大小
.rodata节: 只读数据
sh_offset
: 只读数据在文件中的位置sh_addr
: 只读数据在内存中的虚拟地址
.symtab/.strtab节: 符号表和字符串表
sh_offset
: 符号信息在文件中的位置sh_addr
: 通常为0(这些节不会被加载到内存)
七、地址转换示例
假设一个简单的ELF文件布局:
文件偏移量 内容
0x000 ELF头
0x034 程序头表
0x100 .text节 (文件大小0x200)
0x300 .data节 (文件大小0x100)
0x400 节头表
对应的程序头可能如下:
p_type=PT_LOAD, p_offset=0x100, p_vaddr=0x08048000, p_filesz=0x200, p_memsz=0x200
p_type=PT_LOAD, p_offset=0x300, p_vaddr=0x08049000, p_filesz=0x100, p_memsz=0x120
地址转换关系:
文件偏移0x100处的代码 → 虚拟地址0x08048000
文件偏移0x300处的数据 → 虚拟地址0x08049000
八、使用工具查看ELF信息
readelf: 查看ELF文件结构
readelf -h file # 查看ELF头 readelf -l file # 查看程序头 readelf -S file # 查看节头
objdump: 反汇编和查看节信息
objdump -h file # 查看节信息 objdump -d file # 反汇编代码
nm: 查看符号表
nm file
九、实际应用场景
调试器使用: 调试器需要将虚拟地址转换为文件偏移量来定位源代码
二进制补丁: 修改文件时需要知道要修改的数据在文件中的位置
动态链接: 动态链接器需要解析重定位信息,涉及地址计算
核心转储分析: 将核心文件中的地址映射回原始文件位置
理解ELF区域与文件偏移量之间的关系是Linux系统编程和逆向工程的基础知识,对于深入理解程序加载和执行过程至关重要。