头文件定义了 ELF 可执行二进制文件的格式。这些文件包括普通的可执行文件,即可以直接执行的应用程序文件;可重定位目标文件,即 *.o 文件;核心转储 core 文件;和共享目标文件,即共享库 *.so 文件。
使用 ELF 文件格式的可执行文件的组成是这样的:一个 ELF 文件头,后面是一个程序头表,或者是一个节(即 section,后文也用节指代 section,用段指代 segment)头表,或两者都有。ELF 文件头总是位于文件中偏移量为 0 的位置。程序头表和节头表在文件中的偏移量则由 ELF 文件头定义。这两个表描述了整个 ELF 文件其余部分的细节。
这个头文件以 C 结构体的形式描述了上面提到的那些头,它也包含动态节,重定位节,和符号表的结构体。
N-bit 架构(N=32,64,ElfN 代表 Elf32 或 Elf64,uintN_t 代表 uint32_t 或 uint64_t)使用了下面这些数据类型:
数据类型 | 说明 |
---|---|
ElfN_Addr | 无符号程序地址,uintN_t |
ElfN_Off | 无符号文件偏移量,uintN_t |
ElfN_Section | 无符号节索引,uint16_t |
ElfN_Versym | 无符号版本符号信息,uint16_t |
Elf_Byte | unsigned char |
ElfN_Half | uint16_t |
ElfN_Sword | int32_t |
ElfN_Word | uint32_t |
ElfN_Sxword | int64_t |
ElfN_Xword | uint64_t |
(注意:BSD 的术语有点不一样。Elf64_Half 是 Elf32_Half 大小的两倍大,Elf64Quarter 用作 uint16_t。为了避免混淆,在下文中这些类型由显式的替换。)
文件格式定义的所有数据结构遵循相关类型的 “自然” 大小及对齐规则。如果有需要,4 字节对象的数据结构可以包含显式的填充以确保 4 字节对齐,来强制结构体大小为 4 的整数倍,等等。
ELF 文件头由类型 Elf32_Ehdr 或 Elf64_Ehdr 描述:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
ElfN_Addr e_entry;
ElfN_Off e_phoff;
ElfN_Off e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
} ElfN_Ehdr;
这些字段的含义如下:
e_ident 这个字节数组描述了如何来解释这个文件,其依赖的处理器或文件其余部分的内容。这个数组中的每一样东西都由以 EI_
为前缀的宏命名,且可能包含以 ELF
为前缀的宏值。这些宏有如下这些:
ABI 值 | 含义 |
---|---|
ELFOSABI_NONE | 与 ELFOSABI_SYSV 相同 |
ELFOSABI_SYSV | UNIX System V ABI |
ELFOSABI_HPUX | HP-UX ABI |
ELFOSABI_NETBSD | NetBSD ABI |
ELFOSABI_LINUX | Linux ABI |
ELFOSABI_SOLARIS | Solaris ABI |
ELFOSABI_IRIX | IRIX ABI |
ELFOSABI_FREEBSD | FreeBSD ABI |
ELFOSABI_TRU64 | TRU64 UNIX ABI |
ELFOSABI_ARM | ARM 架构 ABI |
ELFOSABI_STANDALONE | Stand-alone (embedded) ABI |
e_type 该结构体的这个成员描述了目标文件的类型:
类型值 | 含义 |
---|---|
ET_NONE | 一种未知类型 |
ET_REL | 可重定位文件,即 *.o 文件 |
ET_EXEC | 可执行文件 |
ET_DYN | 共享目标文件,即 *.so 文件 |
ET_CORE | core 文件,即 crash 时的核心转储文件 |
e_machine 该成员为单个文件指定所需的体系结构。比如:
机器值 | 含义 |
---|---|
EM_NONE | 未知机器类型 |
EM_M32 | AT&T WE 32100 |
EM_SPARC | Sun Microsystems SPARC |
EM_386 | Intel 80386 |
EM_68K | Motorola 68000 |
EM_88K | Motorola 88000 |
EM_860 | Intel 80860 |
EM_MIPS | MIPS RS3000 (big-endian only) |
EM_PARISC | HP/PA |
EM_SPARC32PLUS | SPARC with enhanced instruction set |
EM_PPC | PowerPC |
EM_PPC64 | PowerPC 64-bit |
EM_S390 | IBM S/390 |
EM_ARM | Advanced RISC Machines |
EM_SH | Renesas SuperH |
EM_SPARCV9 | SPARC v9 64-bit |
EM_IA_64 | Intel Itanium |
EM_X86_64 | AMD x86-64 |
EM_VAX | DEC Vax |
e_entry 这个成员给出了系统首次控制转移的目标虚拟地址,这将启动进程。如果文件没有关联的入口点,则这个成员的值为 0。
e_phoff 这个成员是程序头表(program header table)在文件中的字节偏移量。如果文件没有程序头表,则这个成员的值为 0。
e_shoff 这个成员是节头表(section header table)在文件中的字节偏移量。如果文件没有节头表,则这个成员的值为 0。
e_flags 这个成员为与文件关联的处理器特有标记。标记名的形式为 EF_machine_flag
。目前,还没有定义任何标记。
e_ehsize 这个成员是 ELF 头的字节大小。
e_phentsize 这个成员是文件的程序头表中一个项的字节大小;所有的项具有相同大小。
e_phnum 这个成员是程序头表中的程序头个数。这样 e_phentsize 和 e_phnum 的乘积给出了这个表的字节大小。如果文件没有程序头,则 e_phnum 的值为 0。
如果程序头表中项的个数大于等于 PN_XNUM (0xffff),则这个成员的值为 PN_XNUM (0xffff),而真实的程序头表中的项数由节头表中的初始项的 sh_info 成员给出。否则初始项的 sh_info 成员的值为 0。
可执行文件或共享目标文件的程序头表是一个结构体的数组,其中的每一个都描述了一个段或系统用于为执行做准备的其它信息。一个目标文件的段包含一个或多个节。程序头只对可执行文件和共享目标文件有意义。文件用 ELF 文件头的 e_phentsize 和 e_phnum 成员描述它自己的程序头大小。根据具体的架构,ELF 程序头用类型 Elf32_Phdr 或 Elf64_Phdr 描述:
typedef struct {
uint32_t p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
uint32_t p_filesz;
uint32_t p_memsz;
uint32_t p_flags;
uint32_t p_align;
} Elf32_Phdr;
typedef struct {
uint32_t p_type;
uint32_t p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
} Elf64_Phdr;
32 位和 64 位程序头的主要区别在于 p_flags 成员在结构体中的位置。
p_type 结构体的这个成员表示这个数组元素描述的是何种类型的段,或如何解释数组元素的信息。
p_offset 这个成员表示这个段的第一个字节从文件开始位置处的偏移量。
p_vaddr 这个成员表示这个段的第一个字节在内存中的虚拟地址。
p_paddr 在物理地址是相对寻址的系统上,这个成员保留用作段的物理地址。在 BSD 下这个成员未使用,且必须是 0。
p_filesz 这个成员表示段在文件镜像中的字节大小。它可能是 0。
p_memsz 这个成员表示段在内存镜像中的字节大小。它可能是 0。
p_flags 这个成员表示与段相关的标记的位掩码:
文本段通常具有标记 PF_X 和 PF_R。数据段通常具有标记 PF_X,PF_W 和 PF_R。
p_align 这个字段的值是段在内存和文件中的对齐方式。可加载的进程段对 p_vaddr 和 p_offset 必须具有一致的值,为页大小的模。值为 0 和 1 表示不需要对齐。否则,p_align 应该是个正值,2 的指数,且 p_vaddr 应该等于 p_offset,模 p_align。
文件的节头表让我们可以定位文件所有的节。节头表是 Elf32_Shdr 或 Elf64_Shdr 结构体的数组。ELF 文件头的 e_shoff 成员给出了节头表到文件开始位置处的字节偏移量。
e_shnum 成员为节头表包含的项数。
e_shentsize 的值为每个项的字节大小。
节头表索引是这个数组的下标。一些接头表索引被保留:初始项和 SHN_LORESERVE 及 SHN_HIRESERVE 之间的索引。初始项用于 e_phnum,e_shnum 和 e_strndx 的 ELF扩展,在其它情况下,初始项中的每个字段被设置为 0。目标文件不具有如下这些特殊索引的节:
SHN_UNDEF 这个值标记一个未定义的,丢失的,不相关的,或其它无意义的节参考。
SHN_LORESERVE 这个值表示保留的索引的下界。
SHN_LOPROC, SHN_HIPROC 大于 [SHN_LOPROC, SHN_HIPROC] 范围的值被保留用于处理器特有语义。
SHN_ABS 这个值指定了对应引用的绝对值。比如,一个符号相对于节号 SHN_ABS 定义则具有绝对值,且不受重定位影响。
SHN_COMMON 相对于这个节定义的符号是通用符号,比如 FORTRAN COMMON 或未分配的 C 外部变量。
SHN_HIRESERVE 这个值表示保留的索引范围的上界。系统保留 SHN_LORESERVE 和 SHN_HIRESERVE 之间的范围,包括。节头表不包含这些保留索引的项。
节头的结构如下:
typedef struct {
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
} Elf32_Shdr;
typedef struct {
uint32_t sh_name;
uint32_t sh_type;
uint64_t sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
uint64_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint64_t sh_addralign;
uint64_t sh_entsize;
} Elf64_Shdr;
32 位和 64 位节头没有真正的差异。
sh_name 这个成员表示节的名称。它的值是到节头字符串表节的索引,给出了一个以 null 结尾的字符串的位置。
sh_type 这个成员给节的内容和语义做了分类。
SHT_NULL 这个值把节头标记为 inactive。它没有与之关联的节。该节头的其它成员具有未定义值。
SHT_PROGBITS 该节持有由程序定义的信息,其格式和含义完全由程序决定。
SHT_SYMTAB 这个节是符号表。典型地,SHT_SYMTAB 为链接编辑提供了符号,尽管它可能也用于动态链接。
作为一个完整的符号表,它可能包含许多不需要动态链接的符号。目标文件也可以包含一个 SHT_DYNSYM 节。
SHT_STRTAB 这个节是一个字符串表。一个目标文件可以有多个字符串表节。
SHT_RELA 这个节包含具有显式的附加的重定位项,例如,目标文件的 32 位类的类型Elf32_Rela。一个目标文件可以有多个重定位节。
SHT_HASH 这个节是符号哈希表。参与动态链接的目标文件必须包含一个符号哈希表。一个目标文件可能只有一个哈希表。
SHT_DYNAMIC 这个节包含用于动态链接的信息。一个目标文件可以只有一个动态节。
SHT_NOTE 这个节包含 notes(ElfN_Nhdr)。
SHT_NOBITS 这种类型的节在文件中不占用空间,但它与 SHT_PROGBITS 类似。尽管这种节不包含数据,但其 sh_offset 成员包含概念上的文件偏移量。
SHT_REL 这个节包含不具有显式的附加的重定位偏移,例如,目标文件的 32 位类的类型 Elf32_Rel。一个目标文件可以有多个重定位节。
SHT_SHLIB 该节是保留的,但具有未指定的语义。
SHT_DYNSYM 该节包含动态链接符号的最小集合。一个目标文件也可以包含一个 SHT_DYNSYM 节。
SHT_LOPROC, SHT_HIPROC [SHT_LOPROC, SHT_HIPROC] 范围内的值是为处理器特有语义保留的。
SHT_LOUSER 这个值是为应用程序保留的索引范围的下界。
SHT_HIUSER 这个值是为应用程序保留的索引范围的上界。SHT_LOUSER 和 SHT_HIUSER 之间的节类型可以用于应用程序,而不会与当前或未来系统定义的节类型冲突。
sh_flags 节支持描述杂项属性的一位标志。如果给 sh_flags 设置了一个标记位,则这个节的属性为 “on”。否则,属性为 “off” 或不应用。未定义的属性设置为 0。
SHF_WRITE 该节包含的数据在进程执行期间应该可写。
SHF_ALLOC 该节在进程执行期间占用内存。有些控制节不驻留在目标文件的内存映像中。对于那些节来说这个属性为 off。
SHF_EXECINSTR 该节包含可执行的机器指令。
SHF_MASKPROC 这个掩码中包含的所有位为处理器特有语义保留。
sh_addr 如果该节出现在进程的内存镜像中,则这个成员为该节的第一个字节应该所处的地址。否则,这个成员包含 0。
sh_offset 这个成员的值为这个节中的第一个字节到文件开始位置处的字节偏移量。一种节类型,SHT_NOBITS,在文件中不占用空间,但它的 sh_offset 成员定位了在文件中概念上的位置。
sh_size 这个成员的值为该节的字节大小。除非节的类型为 SHT_NOBITS,则该节在文件中占有 sh_size 字节。SHT_NOBITS 类型的节可以具有非 0 的大小,但它在文件中不占用空间。
sh_link 该成员为节头表索引链接,其解释依赖于节类型。
sh_info 这个成员包含额外的信息,其解释依赖于节类型。
sh_addralign 一些节具有地址对齐限制。如果一个节包含双字,则系统必须为整个节确保双字对齐。即,sh_addr 的值必须全等于 0,模 sh_addralign 的值。只能是 0 和 2 的正整数幂。值为 0 或 1 意味着该节没有对齐限制。
sh_entsize 一些节包含固定大小的项的表,比如符号表。对于这样的节,该成员给出了每一项的字节大小。如果该节不包含固定大小的项的表则该成员包含 0。
各种各样的节包含有程序和控制信息:
.bss 本节包含未初始化但会占用程序的内存镜像空间的数据。根据定义,系统在程序开始运行时以0 初始化数据。
本节的类型为 SHT_NOBITS。属性类型为 SHF_ALLOC 和 SHF_WRITE。
.comment 这个节包含版本控制信息。这个节的类型为 SHT_PROGBITS。没有属性类型。
.ctors 本节包含指向 C++ 构造函数的已初始化指针。本节的类型为 SHT_PROGBITS。属性类型为 SHF_ALLOC 和 SHF_WRITE。
.data 本节包含用于程序内存镜像的已初始化数据。本节的类型为 SHT_PROGBITS。属性类型为 SHF_ALLOC 和 SHF_WRITE。
.data1 本节包含用于程序内存镜像的已初始化数据。本节的类型为 SHT_PROGBITS。属性类型为 SHF_ALLOC 和 SHF_WRITE。
.debug 本节包含符号调试的信息。内容未指定。本节的类型为 SHT_PROGBITS。不使用任何属性类型。
.dtors 本节包含指向 C++ 析构函数的已初始化指针。本节的类型为 SHT_PROGBITS。属性类型为 SHF_ALLOC 和 SHF_WRITE。
.dynamic 本节包含动态链接信息。本节的属性将包含 SHF_ALLOC 位。SHF_WRITE 位是否被置位依赖于处理器。本节的类型为 SHT_DYNAMIC。参考上面的属性。
.dynstr 本节包含动态链接所需的字符串,最常见的是与符号表项关联的表示名字的字符串。本节的类型为 SHT_STRTAB。用到的属性为 SHF_ALLOC。
.dynsym 本节包含动态链接符号表。本节的类型为 SHT_DYNSYM。用到的属性为 SHF_ALLOC。
.fini 本节包含用于进程终止代码的可执行指令。当程序正常退出时,系统安排执行本节的代码。本节的类型为 SHT_PROGBITS。用到的属性为 SHF_ALLOC 和 SHF_EXECINSTR。
.gnu.version 本节包含版本符号表,一个 ElfN_Half 元素的数组。本节的类型为 SHT_GNU_versym。用到的属性类型为 SHF_ALLOC。
.gnu.version_d 本节包含版本符号定义,一个 ElfN_Verdef 结构的表。本节的类型为 SHT_GNU_verdef。用到的属性类型为 SHF_ALLOC。
.gnu.version_r 本节包含元素需要的版本符号,一个 ElfN_Verneed 结构的表。本节的类型为 SHT_GNU_versym。用到的属性类型为 SHF_ALLOC。
.got 本节包含全局偏移表。本节的类型为 SHT_PROGBITS。属性是特定于处理器的。
.hash 本节包含符号哈希表。本节的类型为 SHT_HASH。用到的属性为 SHF_ALLOC。
.init 本节包含用于进程初始化代码的可执行指令。当程序开始运行时,系统在调用主程序的入口点之前安排执行本节的代码。本节的类型为 SHT_PROGBITS。用到的属性为 SHF_ALLOC 和 SHF_EXECINSTR。
.interp 本节包含程序解释器的路径名。如果程序具有包含该节的段,则该节的属性将包含 SHF_ALLOC 位。否则,SHF_ALLOC 位将为 off。本节的类型为 SHT_PROGBITS。
.line 本节包含符号调试的行号信息,其描述了程序源码和机器码之间的对应关系。内容未指定。本节的类型为 SHT_PROGBITS。没有用到任何属性类型。
.note 本节包含各种 notes。本节的类型为 SHT_NOTE。没有用到任何属性类型。
.note.ABI-tag 本节用于声明期望的 ELF 镜像的运行时 ABI。它包含操作系统名和它的运行时版本。本节的类型为 SHT_NOTE。只用到了 SHF_ALLOC 属性。
.note.gnu.build-id 本节用于包含唯一标识 ELF 镜像内容的 ID。具有相同 build ID 的不同文件应该包含相同的可执行内容。参考 GNU 链接器 (ld (1)) 的 –build-id 选项来了解更多细节。本节的类型为 SHT_NOTE。只用到了 SHF_ALLOC 属性。
.note.GNU-stack 本节用于声明栈属性的 Linux 目标文件中。本节的类型为 SHT_PROGBITS。唯一使用的属性是 SHF_EXECINSTR。本节向 GNU 链接器表示目标文件需要一个可执行栈。
.note.openbsd.ident OpenBSD 本地可执行文件通常包含这个节来标识它们自己,以使内核在加载文件时可以绕开任何兼容性 ELF 二进制仿真测试。
.plt 本节包含过程链接表。本节的类型为 SHT_PROGBITS。属性是特定于处理器的。
.relNAME 本节包含如下面所述的重定位信息。如果文件具有包含重定位的可加载段,本节的属性将包含 SHF_ALLOC 位。否则,该位为 off。按照惯例,“NAME” 由重定位应用的节提供。这样 .text 的重定位节通常的名字将为 .rel.text。本节的类型为 SHT_REL。
.relaNAME 本节包含如下面所述的重定位信息。如果文件具有包含重定位的可加载段,本节的属性将包含 SHF_ALLOC 位。否则,该位为 off。按照惯例,“NAME” 由重定位应用的节提供。这样 .text 的重定位节通常的名字将为 .rela.text。本节的类型为 SHT_RELA。
.rodata 本节包含只读数据,典型地用于进程镜像的非可写段。本节的类型为 SHT_PROGBITS。用到的属性为 SHF_ALLOC。
.rodata1 本节包含只读数据,典型地用于进程镜像的非可写段。本节的类型为 SHT_PROGBITS。用到的属性为 SHF_ALLOC。
.shstrtab 本节包含节名。本节的类型为 SHT_STRTAB。不使用任何属性类型。
.strtab 本节包含字符串,最常见的是与符号表项关联的表示名字的字符串。如果文件具有一个包含符号字符串表的可加载段,本节的属性将包含 SHF_ALLOC 位。否则,此位将是 off 的。本节的类型为 SHT_STRTAB。
.symtab 本节包含符号表。如果文件具有一个包含符号表的可加载段,本节的属性将包含 SHF_ALLOC 位。否则,此位将是 off 的。本节的类型为 SHT_SYMTAB。
.text 本节包含 “text”,或程序的可执行指令。本节的类型为 SHT_PROGBITS。用到的属性为 SHF_ALLOC 和 SHF_EXECINSTR。
字符串表包含以 null 结尾的字符序列(复数),通常称为字符串(复数)。目标文件使用这些字符串表示符号和节名。使用字符串的地方通过字符串在字符串表节内的索引来引用。第一个字节,其索引为 0,被定义为包含一个 null 字节 (’\0’)。类似地,字符串表的最后一个字节被定义为包含一个 null 字节,以确保所有的字符串均以 null 终止。
目标文件的符号表包含定位和重定位一个程序的符号定义和引用的信息。符号表索引是到这个数组的下标。
typedef struct {
uint32_t st_name;
Elf32_Addr st_value;
uint32_t st_size;
unsigned char st_info;
unsigned char st_other;
uint16_t st_shndx;
} Elf32_Sym;
typedef struct {
uint32_t st_name;
unsigned char st_info;
unsigned char st_other;
uint16_t st_shndx;
Elf64_Addr st_value;
uint64_t st_size;
} Elf64_Sym;
32 位和 64 位版本具有相同的成员,只是顺序不同。
st_name 这个成员包含到目标文件的符号字符串表的索引,其包含表示符号名称的字符。如果该值非 0,则它表示给出符号名称的字符串表索引。否则,符号没有名称。
st_value 这个成员给出了与符号关联的值。
st_size 许多符号具有关联的大小。如果符号不具有大小则该成员包含 0,否则是一个未知大小。
st_info 这个成员指定了符号的类型和绑定属性:
有一些宏可以用于打包和解包绑定和类型字段:
st_other 这个成员定义了符号可见性。
这些宏用于提取可见性类型:
ELF32_ST_VISIBILITY( other ) 或 ELF64_ST_VISIBILITY( other )
st_shndx 每个符号表项均是根据某些节 “定义” 的。该成员包含相关的节头表索引。
重定位是把符号引用和符号定义连接起来的过程。可重定位文件必须具有描述如何修改它们的节内容的信息,这样使得可执行文件和共享目标文件可以为进程的程序镜像包含正确的信息。重定位项就是这些数据。
不需要附加的重定位项:
typedef struct {
Elf32_Addr r_offset;
uint32_t r_info;
} Elf32_Rel;
typedef struct {
Elf64_Addr r_offset;
uint64_t r_info;
} Elf64_Rel;
需要附加的重定位项:
typedef struct {
Elf32_Addr r_offset;
uint32_t r_info;
int32_t r_addend;
} Elf32_Rela;
typedef struct {
Elf64_Addr r_offset;
uint64_t r_info;
int64_t r_addend;
} Elf64_Rela;
r_offset 这个成员给出了采取重定位行动的位置。对于一个可重定位文件,该值是从节的开始位置处到被重定位影响到的存储单元的字节偏移量。对于可执行文件或共享目标文件,该值是被重定位影响到的存储单元的虚拟地址。
r_info 该成员给出了必须执行重定位的符号表索引和应用的重定位的类型。重定位类型是特定于处理器的。当 text 引用了一个重定位项的重定位类型或符号表索引,它意味着应用 ELF[32|64]_R_TYPE 或 ELF[32|64]_R_SYM,分别地,到项的 r_info 成员的结果。
r_addend 这个成员指定了一个常量附加用于计算将被存储进可重定位字段中的值。
.dynamic 节包含了一系列包含与动态链接相关的信息的结构体。d_tag 成员控制 d_un 的解释。
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn _DYNAMIC[];
typedef struct {
Elf64_Sxword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
extern Elf64_Dyn _DYNAMIC[];
d_tag 这个成员可以具有下面的任何值:
d_val 该成员表示具有各种解释的整数值。
d_ptr 该成员表示程序虚拟地址。当解释这些地址时,实际的地址应当基于原始文件值和内存基地址计算得出。文件不包含修正这些地址的重定位项。
_DYNAMIC 包含 .dynamic 节中的所有动态数据结构的数组。它是由链接器自动填充的。
ELF notes 允许附加任意的信息给系统使用。它们主要用于核心转储文件(e_type 为 ET_CORE),但许多项目定义了它们自己的扩展集合。比如,GNU 工具链使用 ELF notes 从链接器向 C 库传递信息。
Note 节包含一系列 notes(参考下面的 struct 定义)。每个 note 后跟名称字段(其长度由 n_namesz 定义),然后是描述符字段(其长度由 n_descsz 定义),且其开始地址 4 字节对齐。由于它们的任意长度,两个字段都没有在 note 结构中定义。
解析两个连续的 notes 应该阐明它们在内存中的布局的例子:
void *memory, *name, *desc;
Elf64_Nhdr *note, *next_note;
/* The buffer is pointing to the start of the section/segment */
note = memory;
/* If the name is defined, it follows the note */
name = note->n_namesz == 0 ? NULL : memory + sizeof(*note);
/* If the descriptor is defined, it follows the name
(with alignment) */
desc = note->n_descsz == 0 ? NULL :
memory + sizeof(*note) + ALIGN_UP(note->n_namesz, 4);
/* The next note follows both (with alignment) */
next_note = memory + sizeof(*note) +
ALIGN_UP(note->n_namesz, 4) +
ALIGN_UP(note->n_descsz, 4);
记住 n_type 的解释依赖于由 n_namesz 字段定义的命名空间。如果 n_namesz 字段没有设置(比如,为 0),则有两个 notes 集合:一个用于核心转储文件,另一个用于所有其它 ELF 类型。如果命名空间未知,则工具通常也将 fallback 到这些 notes 集合。
typedef struct {
Elf32_Word n_namesz;
Elf32_Word n_descsz;
Elf32_Word n_type;
} Elf32_Nhdr;
typedef struct {
Elf64_Word n_namesz;
Elf64_Word n_descsz;
Elf64_Word n_type;
} Elf64_Nhdr;
n_namesz 名称字段的字节长度。在内存中内容将紧跟在这个 note 后面。名称以 null 终止。比如,如果名称是 “GNU”,则 n_namesz 将被设置为 4。
n_descsz 描述符字段的字节长度。在内存中内容将紧跟在名称字段后面。
n_type 依赖于名称字段的值,这个成员可以具有下面的任何值:
核心转储文件 (e_type = ET_CORE)
Notes 被所有核心转储文件使用。这些是与操作系统和架构高度相关的,且通常需要与内核,C 库,和调试器紧密配合的。当命名空间为默认值时(例如,n_namesz 将被设置为0),或者当命名空间未知时使用回退。
n_name = GNU
GNU 工具链使用的扩展。
默认的/未知命名空间(e_type != ET_CORE)
当命名空间为默认(比如,n_namesz 将被设置为0)时,或当命名空间未知而回退时使用。
https://man7.org/linux/man-pages/man5/elf.5.html
可重定位目标文件的结构及其链接过程
动态链接过程
可执行文件加载及其执行过程
一个简单的 ELF 文件解析器:
#include
#include
#include
#include