2024-04-19 elf文件格式布局解析
上文链接:2023-06-27 操作系统分页机制的深入浅出
1、简介
-
ELF 指的是 Exeeutable and Linkable Format ,可执行链接格式。最初是由 UNIX 系统实验室 CUSL)
作为应用程序二进制接口 CABI) 而开发和发行的。工具接口标准委员会( TIS )选择了它作为 IA32 体系结构上不同操作系统之间的可移植二进制文件格式,于是它就发展成为了事实上的二进制文件格式标准。
在 ELF 规范中,把符合 ELF 格式协议的文件统称为“目标文件”或 ELF 文件,这与我们平时所说的目标文件是不同的。在大家平时的习惯中,咱们把编译后,但未经链接的文件称为目标文件,也称为待重定位文件( reloeatable file ),比如在 Linux 下用 gee -c参数生成的 .o 文件。
而平时我们所说的 ELF 文件也是指经过编译链接后的二进制可执行文件,该文件能够直接运行。
为了避免混淆,咱们采用与 ELF 规范相同的命名方式,本节中所说的目标文件即指各种类型符合 ELF
规范的文件,如二进制可执行文件和 Linux 下 .o 结尾的目标文件和 .so 结尾的动态库文件。- elf文件包括二进制可执行文件、常说的目标文件(待重定向文件—可通过链接生成可执行文件)、动态链接库文件(共享文件)
-
elf文件格式布局(详情参考: Linux 系统的/usr/include/elf.h)
其中程序头表(Program header table)中的每个条目(entry—Elf32_Phdr—占32字节)对应着程序头表后续段(Segment ),其中描述着段的类型和段在文件中的偏移等关键信息
待重定向文件 可执行文件 ELF头(ELF header) ELF头(ELF header) Program header table (程序头表)— 可选 Program header table(程序头表) Section 1(节1) Segment 1(段1) … … Section n(节n) Segment n(段n) Section header table (节头表) Section header table (节头表)— 可选 … … 待重定向文件文件体 可执行文件体
2、格式布局(部分)
-
ELF头
-
数据类型:
- Elf32_Half(无符号2字节)
- Elf32_Word(无符号4字节)
- Elf32_Addr(无符号4字节),无符号程序运行地址
- Elf32_Off(无符号4字节),无符号文件偏移量
-
数据结构(共52字节):
typedef struct { unsigned char e_ident[16]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr;
-
含义:
-
e_ident[16](16字节)
0~3: 这 4 位是固定的 ELF 文件的魔数,表明这就是 一 个 ELF文件
4:值为 0 表示该文件是不可识别类型;值为 l 表示该文件是 32 位 elf 格式的文件;值为 2 表示该文件是 64 位 elf 格式的文件。
5:值为 0 表示非法编码格式;值为 1 表示小端字节序,即 LSB (最低有效字节);值为 2 表示大端字节序,即 MSB (最高有效字节);补充:file /bin/ls 可查看系统elf字节序
6:ELF 头的版本信息,默认为 1;值为 0 表示非法版本;值为 1 表示当前版本
7~15:暂且不用,保留,均初始化为 0
-
e_type(2字节):来指定 elf 目标文件的类型
取值作用如下:
0:未知目标文件格式,忽略
1:可重定位文件
2:可执行文件
3:动态共享目标文件
4:core 文件,即程序崩溃时其内存映像的转储格式,俗称出 core 了
0xff00:特定处理器文件的扩展下边界(硬件相关的参数)
0xffff:特定处理器文件的扩展上边界(硬件相关的参数)
-
e_machine(2字节):用来描述 elf 目标文件的体系结构类型,也就是说该文件要在哪种硬件平台(哪
种机器)上才能运行取值作用如下:
0:未指定
1:AT&T WE 32100
2:SPARC
3:Intel 80386
4;Motorola 68000
5:Motorola 88000
7:Intel 80860
8:MIPS RS3000
-
e_version(4字节):表示版本信息。
-
e_entry(4字节):指明操作系统运行该程序时,将控制权转交到的虚拟地址。
-
e_phoff(4字节):指明程序头表( program header table )在文件内的字节偏移量。如果没有程
序头表,该值为 0 。 -
e_shoff(4字节):指明节头表( section header table )在文件内的字节偏移量。若没有节头表,
该值为 0 。 -
e_flags(4字节):指明与处理器相关的标志
-
e_ehsize(2字节):指明 elf header 的字节大小。
-
e_phentsize(2字节):指明程序头表( program header table )中每个条目( entry )的字节大小,
即每个用来描述段信息的数据结构的字节大小,该结构是后面要介绍的 struct Elf32 Phdr。 -
e_phnum(2字节):指明程序头表中条目的数量。实际上就是段的个数。
-
e_shentsize(2字节):指明节头表( section header table )中每个条目( entry)的字节大小,即
每个用来描述节信息的数据结构的字节大小。 -
e_shnum(2字节):节头表中条目的数量。实际上就是节的个数。
-
e_shstrndx(2字节):指明 string name table 在节头表中的索引 index 。
-
-
-
Program header table(程序头表)
-
数据类型:
- Elf32_Half(无符号2字节)
- Elf32_Word(无符号4字节)
- Elf32_Addr(无符号4字节),无符号程序运行地址
- Elf32_Off(无符号4字节),无符号文件偏移量
-
数据结构(共32字节):
typedef struct { Elf32_Word p_type; /* Segment type */ Elf32_Off p_offset; /* Segment file offset */ Elf32_Addr p_vaddr; /* Segment virtual address */ Elf32_Addr p_paddr; /* Segment physical address */ Elf32_Word p_filesz; /* Segment size in file */ Elf32_Word p_memsz; /* Segment size in memory */ Elf32_Word p_flags; /* Segment flags */ Elf32_Word p_align; /* Segment alignment */ } Elf32_Phdr;
-
含义:
-
p_type(4字节):指明程序中该段的类型。
取值作用如下:
0:忽略
1;可加载程序段
2;动态链接信息
3:动态加载器名称
4:一些辅助的附加信息
5:保留
6;程序头表
0x70000000:(硬件相关的参数)
0x7fffffff:(硬件相关的参数)
-
p_offset(4字节):指明本段在文件内的起始偏移字节。
-
p_vaddr(4字节):指明本段在内存中的起始虚拟地址 。
-
p_paddr(4字节):仅用于与物理地址相关的系统中,因为 System V 忽略用户程序中所有的物理地
址,所以此项暂且保留,未设定。 -
p_filesz(4字节):指明本段在文件中的大小。
-
p_memsz(4字节):指明本段在内存中的大小。
-
p_flags(4字节):指明与本段相关的标志
取值作用如下:
1:本段具有可执行权限
2:本段具有可写权限
4:本段具有可读权限
0xf0000000:(硬件相关的参数)
0x0ff00000:本段与操作系统相关
-
p_align(4字节):指明本段在文件和内存中的对齐方式。如果值为 0 或 1 ,则表示不对齐。否
则 p_align 应该是 2 的幂次数。
-
-
-
节头表(共40字节)
typedef struct { Elf32_Word sh_name; /* Section name (string tbl index) */ Elf32_Word sh_type; /* Section type */ Elf32_Word sh_flags; /* Section flags */ Elf32_Addr sh_addr; /* Section virtual addr at execution */ Elf32_Off sh_offset; /* Section file offset */ Elf32_Word sh_size; /* Section size in bytes */ Elf32_Word sh_link; /* Link to another section */ Elf32_Word sh_info; /* Additional section information */ Elf32_Word sh_addralign; /* Section alignment */ Elf32_Word sh_entsize; /* Entry size if section holds table */ } Elf32_Shdr;
3、解析elf文件实例
-
使用xxd命令查看我内核可执行文件(xxd是自己参照书编写的脚本,kernel.bin也是参照书编写、链接的可执行文件)
[xue@localhost 6]$ sh xxd.sh bin/kernel.bin 0 400 0000000: 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............ 0000010: 02 00 03 00 01 00 00 00 00 15 00 C0 34 00 00 00 ............4... 0000020: 58 06 00 00 00 00 00 00 34 00 20 00 02 00 28 00 X.......4. ...(. 0000030: 07 00 06 00 01 00 00 00 00 00 00 00 00 10 00 C0 ................ 0000040: 00 10 00 C0 3C 05 00 00 3C 05 00 00 05 00 00 00 ....<...<....... 0000050: 00 10 00 00 51 E5 74 64 00 00 00 00 00 00 00 00 ....Q.td........ 0000060: 00 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 ................ 0000070: 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * 0000180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
-
elf头
- 第一行: e_ident 数组(16),前 4 字节是固定的 elf 魔数,正如您看到的,它们是 Ox7f 和字符 ELF 的 ASCII:
0x45 、0x4c 、0x46 。所以您在显示区看到了 ELF 的三个字符。紧跟其后的 三个 01 分别是 e_ident[4]、 e_ident[5 ]、e_ident[6] 三个成员,代表的意义是 32 位 elf 文件、小端字节序、当前版本。后面的 9 个 00 是 ident[7] ~e_ident[15] ,这些确实都已经初始化为 0。 - 第二行:
- e_type(2):小端字节序,所以其值为 0x0002—可执行文件
- e_machine(2):0x0003—Intel 80386
- e_version(4):0x0000 0001
- e_entry(4):0xc000 1500—虚拟地址
- e_phoff(4):0x0000 0034—程序头表在文件内的字节偏移量(与e_ehsize值相同,因为elf头结束后就是程序头表)
- 第三行:
- e_shoff(4):0x0000 0658—节头表在文件内的字节偏移量
- e_flags(4):0x0000 0000
- e_ehsize(2):0x0034—即52字节
- e_phentsize(2):0x0020—即32字节(Elf32_Phdr结构大小)
- e_phnum(2):0x0002 — 代表共有2个段
- e_shentsize(2):0x0028— 即40字节(Elf_Shdr结构大小)
- 第四行:
- e_shnum(2):0x0007—节数量
- e_shstrndx(2):0x0006—string table name
- 第一行: e_ident 数组(16),前 4 字节是固定的 elf 魔数,正如您看到的,它们是 Ox7f 和字符 ELF 的 ASCII:
-
程序表头
这里只解析程序头表的第一个条目(entry)即第一个段,注意从段偏移和段大小看,第一段的包括了整个elf头和程序头+第一段,第二段的偏移直接从第一段的p_filesz+1开始
- 第四行:
- p_type(4):0x0000 0001—可加载程序段
- p_offset(4):0x0000 0000 —段1在文件内偏移地址
- p_vaddr(4):0xc000 1000—段1在内存中的起始虚拟地址
- 第五行:
- p_paddr(4):0xc000 1000—段1在内存中的起始虚拟地址
- p_filesz(4):0x0000 053c—段在文件中的大小
- p_memsz(4):0x0000 053c—段在内存中的大小
- p_flags(4):0x0000 0005—表示与本段相关的标志。 5=4+l=PF_R+PF_X,在此表示可读,可执行,根据此属性,我们推测此段为代码段 。
- 第六行:
- p_align(4):0x0000 1000—对齐
- 第四行:
补充:
1、readelf
是一个用于显示和分析可执行文件(包括二进制可执行文件和共享对象文件)的命令行工具。它是 GNU Binutils 包的一部分,通常在类 Unix 系统上使用。
readelf
命令可以提供有关可执行文件的详细信息,包括文件头、节头表、符号表、重定位表、动态链接信息等。它可以帮助开发人员了解和调试程序的二进制结构。
以下是 readelf
命令的一些常用选项:
-a
或--all
:显示所有可用的信息。-h
或--file-header
:显示文件头信息。-S
或--sections
:显示节头表信息。-l
或--program-headers
:显示程序头信息(仅适用于可执行文件)。-s
或--symbols
:显示符号表信息。-r
或--relocs
:显示重定位表信息。-d
或--dynamic
:显示动态链接信息。-A
或--arch-specific
:显示特定于体系结构的信息。
2、xxd.sh
#!/bin/bash
xxd -u -a -g 1 -s $2 -l $3 $1