ELF
ELF文件全名为Executable and Linking Format files,即可执行与可链接格式文件,用于可执行文件、目标代码、共享库、可重定位文件、核心转储等类型的文件。
下面以64位ELF文件为例。
ELF文件结构
ELF文件由ELF头部、程序头部表或节头部表(或两者)组成。ELF 头部位于文件的零偏移处,程序头部表和节头部表在文件中的偏移量在 ELF 头部中定义。这两个表格描述了文件其余部分的特性。在linux/C环境下,
1 | |
头文件可以将ELF的文件结构获取为结构体的形式,这个结构体是4字节对齐的。ELF的基本数据类型如下(u代表unsigned):
Elf64_Addr 无符号程序地址,uint64_t
Elf64_Off 无符号文件偏移,uint64_t
Elf64_Section 无符号节索引,uint16_t
Elf64_Versym 无符号版本符号信息,uint16_t
Elf_Byte unsigned char
Elf64_Half uint16_t
Elf64_Sword int32_t
Elf64_Word uint32_t
Elf64_Sxword int64_t
Elf64_Xword uint64_t
ELF头部
ELF头部的结构体定义如下。
1 | |
e_ident
e_ident记录了一些基础信息。
EI_MAG0:魔数第 0 字节,必须为 ELFMAG0 (0x7f)
EI_MAG1:第 1 字节,’E’
EI_MAG2:第 2 字节,’L’
EI_MAG3:第 3 字节,’F’
EI_CLASS:第 5 字节,标识架构:
ELFCLASSNONE:无效
ELFCLASS32:32 位架构
ELFCLASS64:64 位架构
EI_DATA:第 6 字节,数据编码:
ELFDATANONE:未知
ELFDATA2LSB:小端
ELFDATA2MSB:大端
EI_VERSION:第 7 字节,ELF 规范版本:
EV_NONE:无效
EV_CURRENT:当前版本
EI_OSABI:第 8 字节,目标操作系统/ABI:
ELFOSABI_NONE / ELFOSABI_SYSV
ELFOSABI_HPUX、ELFOSABI_NETBSD、ELFOSABI_LINUX、ELFOSABI_SOLARIS 等
EI_ABIVERSION:第 9 字节,ABI 版本。ABI全程Application Binary Interface,应用程序二进制接口。
EI_PAD:填充起始,填充的值为0.
EI_NIDENT:e_ident 数组大小
e_type
e_type标注了目标文件的类型,包括
ET_NONE An unknown type.
ET_REL A relocatable file.
ET_EXEC An executable file.
ET_DYN A shared object.
ET_CORE A core file.
e_machine
这个字段标注了运行这个文件所需要的架构。
e_version
标注了文件版本
e_entry
系统转交控制权给 ELF 中相应代码的虚拟地址。如果没有相关的入口项,则这一项为0。
e_phoff
程序头部表在文件中的偏移(字节),若无则为0。
e_shoff
节头部表在文件中的偏移(字节),若无则为0。
e_flags
目前未定义,为0。
e_ehsize
ELF头部大小,单位为字节。
e_phentsize
该成员以字节为单位保存文件程序头表中一个条目的大小,所有条目大小相同。
e_phnum
程序头部表中的条目数。若 ≥ PN_XNUM (0xffff),则此处为 PN_XNUM,实际数目在节头部表初始条目的 sh_info 中。
e_shentsize
节头部表中每个条目的大小(字节)。每个条目大小都相等。
e_shnum
节头部表中的条目数。若 ≥ SHN_LORESERVE (0xff00),则此处为 0,实际数目在初始节头部的 sh_size 中。
e_shstrndx
节名字符串表所在节在节头部表中的索引。若 ≥ SHN_LORESERVE,则此处为 SHN_XINDEX (0xffff),实际索引在初始节头部的 sh_link 中。
程序头部(Phdr)
程序头部表是一个结构体数组,每一项描述一个段(segment)。仅对可执行文件和共享对象有意义。
1 | |
p_type
段类型:
PT_NULL:未使用
PT_LOAD:可加载段
PT_DYNAMIC:动态链接信息
PT_INTERP:解释器路径
PT_NOTE:注释信息
PT_SHLIB:保留
PT_PHDR:程序头部自身
PT_LOPROC / PT_HIPROC:处理器专用
PT_GNU_STACK:GNU 扩展,控制栈状态
p_offset
段在文件中的偏移
p_vaddr
段在内存中的虚拟地址
p_paddr
物理地址(通常未使用),该字段仅用于物理地址寻址相关的系统中。
p_filesz
段在文件中的大小
p_memsz
段在内存中的大小
p_flags
段标志:
PF_X:可执行
PF_W:可写
PF_R:可读
p_align
段对齐方式。该成员保存了段在内存和文件中对齐所依据的值。可加载的进程段必须使 p_vaddr 和 p_offset 在模页大小下同余。值为 0 或 1 表示不需要对齐。否则,p_align 应为 2 的正整数次幂,且 p_vaddr 应等于 p_offset 模 p_align。
节头部 Shdr
节头部的结构体定义如下。
1 | |
sh_name
节名在节头部字符串表中的索引
sh_type
节类型:
SHT_NULL:无效
SHT_PROGBITS:程序定义的数据
SHT_SYMTAB:符号表
SHT_STRTAB:字符串表
SHT_RELA:带加数的重定位表
SHT_HASH:哈希表
SHT_DYNAMIC:动态链接信息
SHT_NOTE:注释
SHT_NOBITS:无文件内容(如 .bss)
SHT_REL:无加数的重定位
SHT_DYNSYM:动态符号表
SHT_LOPROC / SHT_HIPROC:处理器专用
sh_flags
节标志:
SHF_WRITE:可写
SHF_ALLOC:运行时占用内存
SHF_EXECINSTR:包含可执行指令
SHF_MASKPROC:处理器专用
sh_addr
如果该段出现在进程的内存映像中,则该成员保存段第一个字节应处的地址。否则为 0
sh_offset
保存了从文件开头到段第一个字节的偏移字节数
sh_size
节大小,以字节为单位保存
sh_link、sh_info
链接信息,含义依赖节类型
sh_addralign
地址对齐要求
sh_entsize
表中条目大小(如符号表)
常见节类型
.bss:未初始化数据(SHT_NOBITS)
.comment:版本控制信息
.ctors / .dtors:C++ 构造/析构函数指针
.data / .data1:已初始化数据
.debug、.line:调试信息
.dynamic:动态链接信息
.dynstr:动态链接字符串表
.dynsym:动态符号表
.fini:进程终止代码
.init:进程初始化代码
.got:全局偏移表
.hash:符号哈希表
.interp:解释器路径
.note、.note.ABI-tag、.note.gnu.build-id、.note.GNU-stack 等
.plt:过程链接表
.rel* / .rela*:重定位信息
.rodata / .rodata1:只读数据
.shstrtab:节名字符串表
.strtab:字符串表
.symtab:符号表
.text:可执行指令
字符串表与符号表
字符串表节存放以空字符结尾的字符序列,通常称为字符串。符号表保存了定位和重定位程序符号定义及符号引用所需的信息。
1 | |
字段含义如下:
st_name:名称索引
st_value:符号值
st_size:大小
st_info:类型和绑定(STT_*、STB_*)
类型:STT_NOTYPE、STT_OBJECT、STT_FUNC、STT_SECTION、STT_FILE
绑定:STB_LOCAL、STB_GLOBAL、STB_WEAK
st_other:可见性(STV_DEFAULT、STV_INTERNAL、STV_HIDDEN、STV_PROTECTED)
st_shndx:所在节索引
重定位条目
重定位是将符号引用与符号定义连接起来的过程。 可重定位文件必须包含描述如何修改其节内容的信息,从而使得可执行文件和共享目标文件能够为进程的程序映像保存正确的信息。重定位条目就是这些数据。
1 | |
参数含义:
r_offset:重定位位置
r_info:符号表索引 + 重定位类型
r_addend:加数(仅 Rela)