Linux中ELF区域与文件偏移量的关系
ELF(Executable and Linkable Format)是Linux系统中可执行文件、目标文件和共享库的标准格式。理解ELF文件中各个区域与文件偏移量之间的关系对于程序分析、调试和逆向工程至关重要。
目录
一、ELF文件基本结构
二、关键概念:虚拟地址(VMA) vs 文件偏移量(Offset)
三、ELF头与文件偏移量
四、程序头表与段(Segments)
五、节头表与节(Sections)
六、常见节区及其偏移量关系
七、地址转换示例
八、使用工具查看ELF信息
九、实际应用场景
一、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 16typedef 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系统编程和逆向工程的基础知识,对于深入理解程序加载和执行过程至关重要。