赞
踩
ELF文件(目标文件)格式主要四种:
可重定向文件:
可执行文件:
共享目标文件:
核心转储文件(core dump):
目标文件既要参与程序链接又要参与程序执行。 出于方便性和效率考虑, 目标文件格式提供了两种并行视图,分别反映了这些活动的不同需求。
ELF header:ELF 文件头
Section or Segment:节或段
Section header table:节头表,可选
注意:尽管图中显示的各个组成部分是有顺序的,实际上除了 ELF 头部表以外,其他节区和段都没有规定的顺序。
文件的最开始几个字节给出如何解释文件的提示信息。 这些信息独立于处理器, 也独立于文件中的其余内容。
还是对 hello.c 这段代码进行分析,对编译出来的 a.out 文件执行命令:readelf -h a.out
ELF 文件头中的定义如下:
段由若干个节(Section)构成,节头表对每一个节的信息有相关描述。对可执行程序而言,节头表是可选的。 ELF 头部是一个关于本文件的路线图(road map),从总体上描述文件的结构。下面是ELF头部的数据结构:
1 typedef struct 2 { 3 unsigned char e_ident[EI_NIDENT]; /* 魔数和相关信息 */ 4 Elf32_Half e_type; /* 目标文件类型 */ 5 Elf32_Half e_machine; /* 硬件体系 */ 6 Elf32_Word e_version; /* 目标文件版本 */ 7 Elf32_Addr e_entry; /* 程序进入点 */ 8 Elf32_Off e_phoff; /* 程序头部偏移量 */ 9 Elf32_Off e_shoff; /* 节头部偏移量 */ 10 Elf32_Word e_flags; /* 处理器特定标志 */ 11 Elf32_Half e_ehsize; /* ELF头部长度 */ 12 Elf32_Half e_phentsize; /* 程序头部中一个条目的长度 */ 13 Elf32_Half e_phnum; /* 程序头部条目个数 */ 14 Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */ 15 Elf32_Half e_shnum; /* 节头部条目个数 */ 16 Elf32_Half e_shstrndx; /* 节头部字符表索引 */ 17 } Elf32_Ehdr;
e_ident[EI_NIDENT]:ELF魔数
e_type:ELF 文件类型
名称 | 取值 | 含义 |
EM_NONE | 0 | 未指定 |
EM_M32 | 1 | AT&T WE 32100 |
EM_SPARC | 2 | SPARC |
EM_386 | 3 | Intel 80386 |
EM_68K | 4 | Motorola 68000 |
EM_88K | 5 | Motorola 88000 |
EM_860 | 7 | Intel 80860 |
EM_MIPS | 8 | MIPS RS3000 |
其它值都是保留的。特定处理器的 ELF 名称会使用机器名来进行区分。 |
名称 | 取值 | 含义 |
EV_NONE | 0 | 非法版本 |
EV_CURRENT | 1 | 当前版本 |
ELF 头部中大多数字段都是对子头部数据的描述,其意义相对比较简单。值得注意的是某些病毒可能修改字段 e_entry(程序进入点)的值,以指向病毒代码。
紧接ELF头部的是程序头表。执行命令:readelf -l a.out
程序头是一个结构数组,包含了 ELF 头表中字段 e_phnum 定义的条目,此结构描述一个段或其他系统准备执行该程序所需要的信息。
结构体位于:/usr/include/elf.h
1 typedef struct { 2 Elf32_Word p_type; /* 段类型 */ 3 Elf32_Off p_offset; /* 段位置相对于文件开始处的偏移量 */ 4 Elf32_Addr p_vaddr; /* 段在内存中的地址 */ 5 Elf32_Addr p_paddr; /* 段的物理地址 */ 6 Elf32_Word p_filesz; /* 段在文件中的长度 */ 7 Elf32_Word p_memsz; /* 段在内存中的长度 */ 8 Elf32_Word p_flags; /* 段的标记 */ 9 Elf32_Word p_align; /* 段在内存中对齐标记 */ 10 } Elf32_Phdr;
对一个ELF可执行程序而言,一个基本的段是标记 p_type 为 PT_INTERP 的段,它表明了运行此程序所需要的程序解释器(/lib/ld-linux.so.2),实际上也就是动态连接器(dynamic linker)。
最重要的段是标记 p_type 为 PT_LOAD 的段,它表明了为运行程序而需要加载到内存的数据。查看上面实际输入,可以看见有两个可 LOAD 段,第一个为只读可执行(FLg 为 R E ),第二个为可读可写(Flg 为 RW)。
节区中包含目标文件中的所有信息,除了: ELF 头部、程序头部表格、节区头部表格。节区满足以下条件:
很多节区中包含了程序和控制信息。 下面的表中给出了系统使用的节区, 以及它们的类型和属性。
名称 | 类型 | 属性 | 含义 |
.bss | SHT_NOBITS | SHF_ALLOC +SHF_WRITE | 包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。 此节区不占用文件空间。 |
.comment | SHT_PROGBITS | (无) | 包含版本控制信息。 |
.data | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | 这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.data1 | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE | |
.debug | SHT_PROGBITS | (无) | 此节区包含用于符号调试的信息。 |
.dynamic | SHT_DYNAMIC | 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器。 | |
.dynstr | SHT_STRTAB | SHF_ALLOC | 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称。 |
.dynsym | SHT_DYNSYM | SHF_ALLOC | 此节区包含了动态链接符号表。 |
.fini | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。 |
.got | SHT_PROGBITS | 此节区包含全局偏移表。 | |
.hash | SHT_HASH | SHF_ALLOC | 此节区包含了一个符号哈希表。 |
.init | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码。 |
.interp | SHT_PROGBITS | 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 | |
.line | SHT_PROGBITS | (无) | 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的。 |
.note | SHT_NOTE | (无) | 此节区中包含注释信息,有独立的格式。 |
.plt | SHT_PROGBITS | 此节区包含过程链接表( procedure linkage table)。 | |
.relname | SHT_REL | 这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。 例如 .text 节区的重定位节区名字将是: .rel.text 或者 .rela.text。 | |
.relaname | SHT_RELA | ||
.rodata | SHT_PROGBITS | SHF_ALLOC | 这些节区包含只读数据, 这些数据通常参与进程映像的不可写段。 |
.rodata1 | SHT_PROGBITS | SHF_ALLOC | |
.shstrtab | SHT_STRTAB | 此节区包含节区名称。 | |
.strtab | SHT_STRTAB | 此节区包含字符串, 通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表, 节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 | |
.symtab | SHT_SYMTAB | 此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0。 | |
.text | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 此节区包含程序的可执行指令。 |
在分析这些节区的时候,需要注意如下事项:
执行命令 readelf -S a.out 可以查看到节表头,这里面保存了 ELF 文件中的各种各样的节。节表是 ELF 文件中除了文件头以外最重要的结构,它描述了 ELF 各个 节 的信息,比如每个节的节名、节的长度、在文件中的偏移、读写权限及节的其他属性。即 ELF 文件的节结构就是由节表来决定的,编译器、链接器和装载器都是依靠节表来定位和访问各个节的属性的。节表在 ELF 文件中的位置由 ELF 文件头的"e_shoff"成员决定。
1 There are 31 section headers, starting at offset 0x19d8: 2 3 Section Headers: 4 [Nr] Name Type Address Offset Size EntSize Flags Link Info Align 5 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 6 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 7 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 8 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 9 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8 10 [ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000060 0000000000000018 A 6 1 8 11 [ 6] .dynstr STRTAB 0000000000400318 00000318 000000000000003d 0000000000000000 A 0 0 1 12 [ 7] .gnu.version VERSYM 0000000000400356 00000356 0000000000000008 0000000000000002 A 5 0 2 13 [ 8] .gnu.version_r VERNEED 0000000000400360 00000360 0000000000000020 0000000000000000 A 6 1 8 14 [ 9] .rela.dyn RELA 0000000000400380 00000380 0000000000000018 0000000000000018 A 5 0 8 15 [10] .rela.plt RELA 0000000000400398 00000398 0000000000000030 0000000000000018 AI 5 24 8 16 [11] .init PROGBITS 00000000004003c8 000003c8 000000000000001a 0000000000000000 AX 0 0 4 17 [12] .plt PROGBITS 00000000004003f0 000003f0 0000000000000030 0000000000000010 AX 0 0 16 18 [13] .plt.got PROGBITS 0000000000400420 00000420 0000000000000008 0000000000000000 AX 0 0 8 19 [14] .text PROGBITS 0000000000400430 00000430 0000000000000192 0000000000000000 AX 0 0 16 20 [15] .fini PROGBITS 00000000004005c4 000005c4 0000000000000009 0000000000000000 AX 0 0 4 21 [16] .rodata PROGBITS 00000000004005d0 000005d0 0000000000000011 0000000000000000 A 0 0 4 22 [17] .eh_frame_hdr PROGBITS 00000000004005e4 000005e4 0000000000000034 0000000000000000 A 0 0 4 23 [18] .eh_frame PROGBITS 0000000000400618 00000618 00000000000000f4 0000000000000000 A 0 0 8 24 [19] .init_array INIT_ARRAY 0000000000600e10 00000e10 0000000000000008 0000000000000000 WA 0 0 8 25 [20] .fini_array FINI_ARRAY 0000000000600e18 00000e18 0000000000000008 0000000000000000 WA 0 0 8 26 [21] .jcr PROGBITS 0000000000600e20 00000e20 0000000000000008 0000000000000000 WA 0 0 8 27 [22] .dynamic DYNAMIC 0000000000600e28 00000e28 00000000000001d0 0000000000000010 WA 6 0 8 28 [23] .got PROGBITS 0000000000600ff8 00000ff8 0000000000000008 0000000000000008 WA 0 0 8 29 [24] .got.plt PROGBITS 0000000000601000 00001000 0000000000000028 0000000000000008 WA 0 0 8 30 [25] .data PROGBITS 0000000000601028 00001028 0000000000000010 0000000000000000 WA 0 0 8 31 [26] .bss NOBITS 0000000000601038 00001038 0000000000000008 0000000000000000 WA 0 0 1 32 [27] .comment PROGBITS 0000000000000000 00001038 0000000000000035 0000000000000001 MS 0 0 1 33 [28] .shstrtab STRTAB 0000000000000000 000018cc 000000000000010c 0000000000000000 0 0 1 34 [29] .symtab SYMTAB 0000000000000000 00001070 0000000000000648 0000000000000018 30 47 8 35 [30] .strtab STRTAB 0000000000000000 000016b8 0000000000000214 0000000000000000 0 0 1 36 Key to Flags: 37 W (write), A (alloc), X (execute), M (merge), S (strings), l (large) 38 I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) 39 O (extra OS processing required) o (OS specific), p (processor specific)
除了使用 readelf 可以查看节的详细信息外,也可以使用 objdump 来查看 ELF 文件中节,只不过 objdump 只列出 ELF 文件中关键的节,省略了其他辅助节。如下:
ELF 头部中, e_shoff 成员给出从文件头到节区头部表格的偏移字节数; e_shnum 给出表格中条目数目; e_shentsize 给出每个项目的字节数。 从这些信息中可以确切地定位节区的具体位置、长度。
节区头部表格中比较特殊的几个下标如下:
名称 | 取值 | 说明 |
SHN_UNDEF | 0 | 标记未定义的、缺失的、不相关的,或者没有含义的节区引用 |
SHN_LORESERVE | OXFF00 | 保留索引的下界 |
SHN_LOPROC | 0XFF00 | |
SHN_HIPROC | 0XFF1F | 保留给处理器特殊的语义 |
SHN_ABS | OXFFF1 | 包含对应引用量的绝对取值。这些值不会被重定位所影响 |
SHN_COMMON | OXFFF2 | 相对于此节区定义的符号是公共符号。如 FORTRAN 中 COMMON 或者未分配的 C 外部变量。 |
SHN_HIRESERVE | 0XFFFF | 保留索引的上界 |
介于 SHN_LORESERVE 和 SHN_HIRESERVE 之间的表项不会出现在节区头部表中。
节头表结构体在 /usr/include/elf.h 中,结构如下:
1 typedef struct elf32_shdr { 2 ELF32_Word sh_name; /* 节名 */ 3 ELF32_Word sh_type; /* 节的类型 */ 4 ELF32_Word sh_flags; /* 节的标志位 */ 5 ELF32_Addr sh_addr; /* 节的虚拟地址 */ 6 ELF32_Off sh_offset; /* 节偏移 */ 7 ELF32_Word sh_size; /* 节的长度 */ 8 ELF32_Word sh_link; /* 节连接信息 */ 9 ELF32_Word sh_info; /* 节连接信息 */ 10 ELF32_Word sh_addralign; /* 节地址对齐 */ 11 ELF32_Word sh_entsize; /* 项的长度 */ 12 } ELF32_Shdr;
名称 | 取值 | 说明 |
SHT_NULL | 0 | 此值标志节区头部是非活动的,没有对应的节区。此节区头部中的其他成员取值无意义。 |
SHT_PROGBITS | 1 | 此节区包含程序定义的信息,其格式和含义都由程序来解释。 |
SHT_SYMTAB | 2 | 此节区包含一个符号表。目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能发生变化。一般, SHT_SYMTAB 节区提供用于链接编辑(指 ld 而言)的符号,尽管也可用来实现动态链接。 |
SHT_STRTAB | 3 | 此节区包含字符串表。目标文件可能包含多个字符串表节区。 |
SHT_RELA | 4 | 此节区包含重定位表项, 其中可能会有补齐内容( addend),例如 32 位目标文件中的 Elf32_Rela 类型。目标文件可能拥有多个重定位节区。 |
SHT_HASH | 5 | 此节区包含符号哈希表。所有参与动态链接的目标都必须包含一个符号哈希表。目前,一个目标文件只能包含一个哈希表,不过此限制将来可能会解除。 |
SHT_DYNAMIC | 6 | 此节区包含动态链接的信息。目前一个目标文件中只能包含一个动态节区,将来可能会取消这一限制。 |
SHT_NOTE | 7 | 此节区包含以某种方式来标记文件的信息。 |
SHT_NOBITS | 8 | 这种类型的节区不占用文件中的空间,其他方面和 SHT_PROGBITS 相似。 尽管此节区不包含任何字节, 成员 sh_offset 中还是会包含概念性的文件偏移 |
SHT_REL | 9 | 此节区包含重定位表项, 其中没有补齐( addends),例如 32 位目标文件中的 Elf32_rel 类型。目标文件中可以拥有多个重定位节区。 |
SHT_SHLIB | 10 | 此节区被保留,不过其语义是未规定的。包含此类型节区的程序与 ABI 不兼容。 |
SHT_DYNSYM | 11 | 作为一个完整的符号表,它可能包含很多对动态链接而言不必要的符号。因此,目标文件也可以包含一个 SHT_DYNSYM 节区,其中保存动态链接符号的一个最小集合,以节省空间。 |
SHT_LOPROC | 0X70000000 | 这一段(包括两个边界),是保留给处理器专用语义的。 |
SHT_HIPROC | OX7FFFFFFF | |
SHT_LOUSER | 0X80000000 | 此值给出保留给应用程序的索引下界。 |
SHT_HIUSER | 0X8FFFFFFF | 此值给出保留给应用程序的索引上界。 |
其它的节区类型是保留的。 |
sh_flags:节的标志位。节的标志位表示该节在进程虚拟地址空间中的属性,比如是否可写,是否可执行等。相关常量以 SHF_ 开头:
sh_addr:节的虚拟地址。如果该节可以被加载,则 sh_addr 为该节被加载后在进程地址空间中的虚拟地址;否则 sh_addr 为0
Name | sh_type | sh_flag |
.bss | SHT_NOBITS | SHF_ALLOC + SHF_WRITE |
.comment | SHT_PROGBITS | none |
.data | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE |
.data1 | SHT_PROGBITS | SHF_ALLOC + SHF_WRITE |
.debug | SHT_PROGBITS | none |
.dynamic | SHT_DYNAMIC | SHF_ALLOC + SHF_WRITE 在有些系统下 .dynamic 节可能是只读的,所以没有 SHF_WRITE 标志位 |
.hash | SHT_HASH | SHF_ALLOC |
.line | SHT_PROGBITS | none |
.note | SHT_NOTE | none |
.rodata | SHT_PROGBITS | SHF_ALLOC |
.rodata1 | SHT_PROGBITS | SHF_ALLOC |
.shstrtab | SHT_STRTAB | none |
.strtab | SHT_STRTAB | 如果该 ELF 文件中有可装载的段需要用到该字符串表,那么该字符串表也将被装载到进程空间,则有 SHF_ALLOC 标志位 |
.symtab | SHT_SYMTAB | 同字符串表 |
.text | SHT_PROGBITS | SHF_ALLOC |
sh_link 和 sh_info:节连接信息。
sh_type | sh_link | sh_info |
SHT_DYNAMIC | 该节所用的字符串表在节表中的下标 | 0 |
SHT_HASH | 该节所使用的符号表在节表中的下标 | 0 |
SHT_REL | 该节所使用的相应符号表在节表中的下标 | 该重定位表所作用的节在节表中的下标 |
SHT_RELA | ||
SHT_SYMTAB | 操作系统相关的 | 操作系统相关的 |
SHT_DNYSYM | ||
other | SHN_UNDEF | 0 |
sh_addralign:节地址对齐。
索引为零(SHN_UNDEF)的节区头部也是存在的,尽管此索引标记的是未定义的节区引用。这个节区的内容固定如下(SHN_UNDEF(0)节区的内容):
字段名称 | 取值 | 说明 |
sh_name | 0 | 无名称 |
sh_type | SHT_NULL | 非活动 |
sh_flags | 0 | 无标志 |
sh_addr | 0 | 无地址 |
sh_offset | 0 | 无文件偏移 |
sh_size | 0 | 无尺寸大小 |
sh_link | SHN_UNDEF | 无链接信息 |
sh_info | 0 | 无辅助信息 |
sh_addralign | 0 | 无对齐要求 |
sh_entsize | 0 | 无表项 |
当前的映射如下:
ELF 格式可以比 COFF 格式包含更多的调试信息。在 I386 平台 LINUX 系统下,用命令 file 查看一个 ELF 可执行程序的可能输出是:a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。