ELF 64 格式详解
本篇介绍
本篇详细介绍下ELF 64的文件格式。
ELF文件分类
- 可重定位文件(.o),包含代码和数据,但是代码和数据都没有指定绝对地址,需要链接其他目标文件来生成可执行文件或共享目标文件
- 共享目标文件(.so),包含代码和数据,以供链接器使用
- 可执行文件, 包含代码和数据,是可以执行运行的程序,代码和数据都有固定的地址
ELF文件内容
一个ELF文件需要包含以下部分:
- elf文件头,必须出现在elf文件的开头
- 节头表(Section header table), 重定位的文件(可重定位文件)必须包含,可加载的文件可选(共享目标文件,可执行文件)
- 段头表(Program header table),可加载的文件必选,重定位文件可选
- 段和节的实际内容,包括可加载的数据,符号表等
节头表和段头表其实分别是链接和加载的视图,结构大致如下:
image.png
ELF 64的数据类型定义如下:
image.png
ELF文件头格式
文件头格式如下:
image.png
可以找一个so,用 readelf -h 看看输出,结果可以完全对上:
image.png
对于MacBook M1 的设备可能没有readelf,objdump等命令,一个简单的方法是可以直接使用ndk中的命令,llvm-readelf, llvm-objdump等
接下来分别看下各个字段的含义:
e_ident
elf文件的标识,一共16个字节,各个字段的含义如下:
image.png
- e_ident[EI_MAG0] ~ e_ident[EI_MAG3] 是用来标识ELF文件的魔数,0x7f, 'E','L','F'
-
e_ident[EI_CLASS] 用来标识对应的ELF文件类别,可取的值如下:
image.png -
e_ident[EI_DATA] 用来区分字节序,可取的值如下
image.png - e_ident[EI_VERSION] 目标文件格式的版本,目前就是EV_CURRENT,也就是1
-
e_ident[EI_OSABI] 该文件的目标操作系统和ABI,可取的值如下
image.png - e_ident[EI_ABIVERSION] 该文件的目标ABI版本,如果兼容System V ABI 第三版,该字段应该是0
-
e_type 该文件的类型,可取的值如下
image.png - e_machine 标识目标架构
- e_version 文件格式的版本
- e_entry 程序入口的虚拟地址
- e_phoff 程序段头表在该文件内的偏移,单位是字节
- e_shoff 节头表在该文件内的偏移,单位是字节
- e_flags 包含处理器特定的标记
- e_ehsize ELF头的大小,单位是字节
- e_phentsize 程序段头表项的大小,单位是字节
- e_phnum 程序段头表项的数量
- e_shentsize 节头表项的大小,单位是字节
- e_shnum 节头表项的数量
- e_shstrndx 节头表中包含节名字的字符串表索引。
ELF节
节包含了ELF文件中除了文件头,程序段头表,节头表之外的所有内容。
节的索引中有几项是特殊的,比如如下几个:
image.png
可以实际看一下节的内容,通过readelf -S 命令就可以看到:
image.png
再看下节头表中项结构的定义,可以和输出的格式对上:
image.png
-
sh_name 节头名字在字符串表中的偏移,单位是字节。举一个例子,上图中第一项的名字是".note.android.ident",看看是如何计算的。
节头表的位置是1393984, 转成16进制 0xD4EBC0, 每项64字节,加上第0个保留项,那偏移就是0xD4EC00,具体偏移是0x0b
image.png
字符串表的索引是24,那偏移就是0xD4F1C0,内容如下:
image.png
按照上述的结构定义,可以看到sh_offset 的偏移是0xD4EADC, 再加上字符串偏移 0x0B,位置就是0xD4EAE7
image.png
这样就字符串位置计算出来了。
-
sh_type 节的类型,可选的值如下:
image.png -
sh_flags 当前节的属性,可选的值如下:
image.png -
sh_addr 该节在内存中的虚拟地址,如果不加载到内存中,地址是0
-
sh_offset 该节在文件中的偏移,单位是字节
-
sh_size 当前节在文件中占用的空间,唯一的例外是SHT_NOBITS,不占用文件空间
-
sh_link 当前节关联的节索引,用途如下所示
image.png -
sh_info 当前节的其他信息,用途如下
image.png -
sh_addralign 对齐参考,需要是2的幂
-
sh_entsize 如果节中包含表,该字段表中每项的大小,单位是字节
用于程序代码和数据的节如下:
image.png
用于文件信息的节如下:
image.png
字符串表
字符串节表包含用于节名字和符号名字的字符串,内部的字符串表是包含C格式的字符串,对外的索引就是对应字符串的起始位置偏移,单位是字节。
符号表
符号表结构如下:
image.png
- st_name 符号名字在符号字符串表中的偏移
-
st_info 符号的绑定属性和类型,高4比特是绑定属性,低4比特是符号类型,
绑定属性定义如下:
image.png
符号类型定义如下:
image.png image.png
- st_other 保留字段,保持是0就行
- st_shndx 定义当前符号的节索引,如果是未定义的,字段值是SHN_UNDEF,对于绝对符号的,值是SHN_ABS,common符号的,值是SHN_COMMON
- st_value 符号的地址,可能是绝对或相对的地址,对于可重定位的文件,值是定义该符号的节的相对偏移,对于可执行或可供行的文件,值是定义该符号的虚拟地址
- st_size 该符号对应的值的存储空间大小,如果未知,字段值是0
可重定位表
ELF 文件有2种重定位格式,"Rel"和"Rela", 前者较短,记录相对于符号原始值的偏移,后者是记录相对于特定字段的偏移。结构定义如下:
image.png
- r_offset 标识需要重定位的位置,对于可重定位文件,是从节开头到需要被重定位的存储位置的偏移量,对于可执行或共享库,是需要被重定位的存储位置的虚拟地址,单位都是字节
- r_info 包含符号表索引和重定向类型,符号表索引用于标识当前项在对应符号表中的符号,重定向类型是处理机指定的。
#define ELF64_R_SYM(i)((i) >> 32)
#define ELF64_R_TYPE(i)((i) & 0xf f f f f f f f L)
#define ELF64_R_INFO(s, t)(((s) << 32) + ((t) & 0xf f f f f f f f L))
- r_addend 计算重定向位置时候需要额外加的常数项
程序段头表
对于可执行和共享库,为了加载方便,用的视图是段,也就是内容一样,只是分类方式变化了。
可以先实际看下共享库的段表信息,readelf -l libtxffmpeg.so
可以看到,段是由节组成的,这是对于加载器,权限一样的,就可以合并到一块,方便内存的管理。
再看下段结构,可以和上图对得上:
image.png
-
p_type 段的类型,参考如下:
image.png
-
p_flags 段属性,高8比特是处理器专用,接下来8比特是环境变量专用
image.png -
p_offset 当前段相对于文件的偏移,单位是字节
-
p_vaddr 当前段在内存中的虚拟地址
-
p_paddr 保留项,用于物理寻址的系统
-
p_filesz 当前段在文件中的大小,单位是字节
-
p_memsz 当前段在内存中的大小,单位是字节
-
p_align 段的对齐约束,需要是2的幂
Note 节
SHT_NOTE的节或PT_NOTE的段,被编译器或其他用于用于存放一些特殊的信息,而这些信息可以被特定的工具所用,格式如下:
image.png
- namesz and name 记录用于该项所有者的名字长度和名字,name 包含一个C格式的字符串,并且按8字节对齐
- descsz and desc 该项的描述符长度和描述符信息,描述符信息需要 8字节对齐
- type 和该项所有者,信息解析者相关的一个值
动态段表
动态段表的实际内容如下:
image.png
结构定义如下:
image.png
-
d_tag 该项的类型,也会决定d_un的解析,具体取值如下:
image.png
- d_val 按整数值解析
- d_ptr 按虚拟地址解析
哈希表
使用哈希表可以加快动态符号表的查找速度。哈希表就是DT_HASH的节,看下实际例子的输出,命令是readelf --gnu-hash-table
:
内容比较多,忽略了一部分,接下来看下结构:
image.png
对应的哈希函数如下:
image.png
一个哈希表需要解决如何快速查找,如何解决冲突的问题。
看看hash 表如何快速查找,这儿用到了一个Bloom Filter, 本质上就是在查找前先用Bloom Filter判断下,如果结果是不在,那么就没必要查找了,如果是在,实际上也不一定在,就需要实际去查一下。
解决冲突时利用了chain数组,在查找符号时,如果Bloom Filter判断出在,然后就在bueket中对应索引位置看看,如果等于期望的字符串,那么直接返回,然后在chain数组同样索引位置拿到下一个需要查找的位置,然后递归查找下去,直到 chain中的值是STN_UNDEF。