ELF (Executable Linkable Format,wiki chs)是Linux参考COFF(Common Object File Format)规范而定义的可执行文件格式。
可执行文件、共享目标文件(*.so)、目标中间文件(又称可重定位文件,*.o)、核心转储文件(Core Dump File)都是ELF文件。
按位数可分为:elf32和elf64;支持cpu架构有:aarch64(即:arm64)、arm等。
ELF可能按照不同的字节序(Byte Order)来存储。例如:elf32-bigarm是大端(Big-endian)存储;elf64-littleaarch64是小端(Little-endian)存储。
与COFF一样,ELF也是基于段(Segment,有时也被叫节Section)的结构。其文件结构如下:
ELF头(ELF Header):ELF魔数、文件机器字节长度、数据存储方式(大小端)、版本、运行平台、ABI版本、ELF重定位类型、硬件平台及版本、入口地址、程序头入口和长度、段表的位置和长度、段的数量。
段表(Section header table):描述了ELF各个段的基本信息,如:每个段的段名、段的长度、在文件中偏移、读写权限及段的其他属性。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。
段的类型:
常量 | 值 | 含义 |
SHT_NULL | 0 | 无效段 |
SHT_PROGBITS | 1 | 程序段、代码段、数据段都是这种类型 |
SHT_SYMTAB | 2 | 该段的内容为符号表 |
SHT_STRTAB | 3 | 该段的内容为字符串表 |
SHT_RELA | 4 | 重定位表,包含重定位信息 |
SHT_HASH | 5 | 符号表的哈希表 |
SHT_DYNAMIC | 6 | 动态链接信息 |
SHT_NOTE | 7 | 提示性信息 |
SHT_NOBITS | 8 | 表示该段在文件中没有内容。如:bss段 |
SHT_REL | 9 | 该段包含了重定位信息 |
SHT_SHLIB | 10 | 保留 |
SHT_DNYSYM | 11 | 动态链接的符号表 |
段的标志位表示该段在进程虚拟空间中的属性。包括:是否可写(SHF_WRITE),是否需要分配空间(SHF_ALLOC)及是否可执行(SHF_EXECINSTR)。
常见的段如下:
段名 | 类型 | 标志位 | 说明 |
.text | SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
代码段 存放程序的可执行代码 |
.data .data1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
数据段 存放如下内容: ① 显示初始化为非0的局部静态变量 ② 显示初始化为非0的全局变量 |
.rela.text .rel.text |
SHT_RELA |
代码段重定位表 存在于目标中间文件(又称可重定位文件,*.o)中,用于链接时重定位(为静态重定位) |
|
.rela.data .rel.data |
SHT_RELA |
数据段重定位表 存在于目标中间文件(又称可重定位文件,*.o)中,用于链接时重定位(为静态重定位) |
|
.rodata .rodata1 |
SHT_PROGBITS |
SHF_ALLOC |
只读数据段 存放如下内容: ① 字符串常量 ② 全局const变量 |
.bss | SHT_NOBITS |
SHF_ALLOC + SHF_WRITE |
bss段 这个段在程序运行时,在内存中会被清零 在ELF文件中不占用空间 存放如下内容: ① 未初始化的全局变量 ② 未初始化的局部静态变量 ③ 显示初始化为0的局部静态变量 ④ 显示初始化为0的全局变量 |
.tdata | SHF_ALLOC + SHF_WRITE |
用来保存线程局部存储的初始化数据。 默认情况下,每次进程启动新的线程时,系统会产生一份.tdata副本。 |
|
.tbss | SHF_ALLOC + SHF_WRITE |
用来保存线程局部存储的未初始化数据。 默认情况下,每次进程启动新的线程时,系统会产生一份.tbss副本,并将它的内容初始化为0。 |
|
.data.rel.ro | SHF_ALLOC(SHF_WRITE) | 保存的是程序的只读数据,与.rodata类似,唯一不同的是它在重定位时会被改写,然后将会被置为只读。 | |
.dynamic | SHT_DYNAMIC |
SHF_ALLOC + SHF_WRITE 注:在某些系统,可能是只读的,没有SHF_WRITE标志位 |
动态链接信息 ① 依赖于哪些共享对象 ② 动态链接符号表(.dynsym)的位置 ③ 动态链接重定位表(.rela.dyn)的位置 ④ 共享对象初始化代码的地址 |
.plt | SHF_ALLOC + SHF_EXECINSTR |
用于支持延迟绑定,这些指令是用来计算外部函数的最终地址 |
|
.got | SHF_ALLOC + SHF_WRITE |
全局偏移表 (Global Offset Table,GOT) 存放外部变量的地址 GOT表中的地址需要动态链接器在装载模块,进行地址重定位时进行填充。 在访问外部符号,可以先通过相对地址找到GOT表中相关的项,再从中取出最终地址 |
|
.got.plt | SHF_ALLOC + SHF_WRITE |
使用PLT(Procedure Linkage Table,过程链接表)来实现对外部函数延迟绑定(第一次用到时才进行绑定) 以此来提升程序的启动速度 第一项保存的是“.dynamic”段的地址 第N项保存的是运用plt段的指令计算得到的外部函数的最终地址(动态链接器重定位得到外部函数的地址后,会写入在这里) |
|
.rela.dyn .rel.dyn |
SHT_RELA | SHF_ALLOC |
.dynsym段的重定位表 对数据引用的修正,它所修正的位置存放在“.got”以及数据段 |
.rela.plt .rel.plt |
SHT_RELA | SHF_ALLOC |
.plt段的重定位表 对函数引用的修正,它所修正的位置存放在“.got.plt” |
.dynstr | SHF_ALLOC |
动态符号字符串表(Dynamic String Table) 存放动态链接符号的符号名 |
|
.dynsym | SHF_ALLOC |
动态符号表(Dynamic Symbol Table) ① 导入符号(Import Symbol) ② 导出符号(Export Symbol) 编译选项开启:-fvisibility=hidden -fvisibility-inlines-hidden,默认不导出so中的符号 |
|
.hash | SHT_HASH |
SHF_ALLOC |
动态符号哈希表 提升动态符号的查找效率 |
.comment | SHT_PROGBITS |
注释信息段 存放的是编译器版本信息,比如字符串:"GCC:(GNU)4.2.0" |
|
.init | SHF_ALLOC + SHF_EXECINSTR |
程序执行前的初始化代码,这些代码早于main函数被执行,多数被用作实现C++全局构造 |
|
.fini | SHF_ALLOC + SHF_EXECINSTR | 程序退出时执行的代码,这些代码晚于main函数执行,多数被用作实现C++全局析构 | |
.init_array | SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
包含一些程序或共享对象刚开始初始化时所须要执行的函数指针 |
.fini_array | SHT_PROGBITS | SHF_ALLOC + SHF_EXECINSTR | 包含一些程序或共享对象退出时须要执行的函数指针 |
.preinit_array | 保存的是早于初始化阶段执行的函数指针数组,这些函数会在.init_array的函数指针数组之前被执行 | ||
.ctors |
保存的是全局构造函数指针 |
||
.dtors |
保存的是全局析构函数指针 |
||
.interp | SHT_PROGBITS |
包含动态链接器的路径(如:/lib/ld-linux.so.2) interp是interpreter(解释器)的缩写 系统在对可执行文件进行加载时,会从该段寻找并装载该可执行文件所需的动态链接器。 |
|
.shstrtab | SHT_STRTAB |
段表字符串表(Section String Table) 用于存放各个段的名字 |
|
.note.GNU-stack | SHT_NOTE | 堆栈提示段 | |
.note.android.ide | SHT_NOTE | 记录使用的android ndk的版本 | |
.note.gnu.build-id | SHT_NOTE | 链接时的唯一标识 | |
.note.ABI-tag | SHT_NOTE | 指定程序的ABI | |
.eh_frame | SHT_PROGBITS |
c++异常处理相关的内容 异常时栈帧unwind信息(Exception Handling Frame unwinding information) Call Frame Information(CFI)信息,保存了函数的调用关系,可以用于异常发生后的栈回溯 编译时,带上-funwind-tables,可以去除该段 -fno-asynchronous-unwind-tables |
|
.eh_frame_hdr | SHT_PROGBITS | c++异常处理相关的内容 | |
.gcc_except_table | SHT_PROGBITS | 语言相关数据 | |
.jcr | Java类注册信息 | ||
.gnu.version |
符号版本相关 所有动态符号的版本信息,它和.dynsym是一一对应的 为0表示内部定义的local的符号,1表示内部定义的global符号 其他的值则来自于.gnu_version_d的Index和.gnu_version_r的Version,前者表示导出的符号版本信息,后者表示导入的符号版本信息 |
||
.gnu.version_d |
符号版本相关 版本定义信息 |
||
.gnu.version_r |
符号版本相关 |
||
.debug_str .debug_loc .debug_abbrev .debug_info .debug_ranges .debug_macinfo .debug_line .debug_aranges |
SHT_PROGBITS |
调试信息 ELF采用DWARF(Debug With Arbitrary Record Format)的标准调试信息格式来存放调试信息 |
|
.symtab | SHT_SYMTAB | 如果有其他装载的段用到该段,则有SHF_ALLOC标志位 |
符号表(Symbol Table) 保存链接时所需要的符号信息 |
.strtab | SHT_STRTAB | 如果有其他装载的段用到该段,则有SHF_ALLOC标志位 |
字符串表(String Table) 通常是符号表里的符号名所需要的字符串 注:目标中间文件(又称可重定位文件,*.o)也存在该段 |
readelf
aarch64-linux-android-readelf.exe所在目录在:<android-ndk> oolchainsaarch64-linux-android-4.9prebuiltwindows-x86_64in
aarch64-linux-android-readelf.exe -S libUE4.so // 输出libUE4.so段表中各个段的信息
There are 27 section headers, starting at offset 0xf2c1650: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align ;Size为段的长度;Offset为段在文件中偏移位置 [ 0] NULL 0000000000000000 00000000 ;第一个元素是无效的段描述符,类型为NULL 0000000000000000 0000000000000000 0 0 0 [ 1] .note.android.ide NOTE 0000000000000270 00000270 ;记录使用的android ndk的版本的段 0000000000000098 0000000000000000 A 0 0 4 [ 2] .note.gnu.build-i NOTE 0000000000000308 00000308 ;链接时的唯一标识 0000000000000024 0000000000000000 A 0 0 4 [ 3] .dynsym DYNSYM 0000000000000330 00000330 0000000000dee838 0000000000000018 A 8 1 8 [ 4] .gnu.version VERSYM 0000000000deeb68 00deeb68 000000000012935a 0000000000000002 A 3 0 2 [ 5] .gnu.version_r VERNEED 0000000000f17ec4 00f17ec4 0000000000000080 0000000000000000 A 8 4 4 [ 6] .gnu.hash GNU_HASH 0000000000f17f48 00f17f48 00000000003e61c8 0000000000000000 A 3 0 8 [ 7] .hash HASH 00000000012fe110 012fe110 00000000004a4d70 0000000000000004 A 3 0 4 [ 8] .dynstr STRTAB 00000000017a2e80 017a2e80 000000000326c716 0000000000000000 A 0 0 1 [ 9] .rela.dyn RELA 0000000004a0f598 04a0f598 0000000001a9a510 0000000000000018 A 3 0 8 [10] .rela.plt RELA 00000000064a9aa8 064a9aa8 00000000000041b8 0000000000000018 AI 3 22 8 [11] .rodata PROGBITS 00000000064adc80 064adc80 ; 只读数据段 0000000000b19d08 0000000000000000 AMS 0 0 64 [12] .gcc_except_table PROGBITS 0000000006fc7988 06fc7988 000000000005e32c 0000000000000000 A 0 0 4 [13] .eh_frame_hdr PROGBITS 0000000007025cb4 07025cb4 00000000003aed74 0000000000000000 A 0 0 4 [14] .eh_frame PROGBITS 00000000073d4a28 073d4a28 0000000000e29424 0000000000000000 A 0 0 8 [15] .text PROGBITS 00000000081fe000 081fe000 ; 代码段 0000000006464320 0000000000000000 AX 0 0 16 [16] .plt PROGBITS 000000000e662320 0e662320 0000000000002bf0 0000000000000000 AX 0 0 16 [17] .data.rel.ro PROGBITS 000000000e665000 0e665000 0000000000b40f88 0000000000000000 WA 0 0 16 [18] .fini_array FINI_ARRAY 000000000f1a5f88 0f1a5f88 0000000000000010 0000000000000008 WA 0 0 8 [19] .init_array INIT_ARRAY 000000000f1a5f98 0f1a5f98 0000000000002918 0000000000000008 WA 0 0 8 [20] .dynamic DYNAMIC 000000000f1a88b0 0f1a88b0 00000000000002b0 0000000000000010 WA 8 0 8 [21] .got PROGBITS 000000000f1a8b60 0f1a8b60 00000000000e3280 0000000000000000 WA 0 0 8 [22] .got.plt PROGBITS 000000000f28bde0 0f28bde0 0000000000001600 0000000000000000 WA 0 0 8 [23] .data PROGBITS 000000000f28e000 0f28e000 ; 数据段 0000000000033220 0000000000000000 WA 0 0 16 [24] .bss NOBITS 000000000f2c1240 0f2c1220 ; BSS段 该段在文件中不存在内容 000000000060aaa8 0000000000000000 WA 0 0 64 [25] .comment PROGBITS 0000000000000000 0f2c1220 ; 注释信息段 0000000000000328 0000000000000001 MS 0 0 1 [26] .shstrtab STRTAB 0000000000000000 0f2c1548 0000000000000104 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), p (processor specific)
aarch64-linux-android-readelf.exe -t libUE4.so // 输出libUE4.so段表中各个段的信息
aarch64-linux-android-readelf.exe -h libUE4.so // 输出libUE4.so的ELF头的信息
aarch64-linux-android-readelf.exe -l libUE4.so // 输出libUE4.so的program headers
aarch64-linux-android-readelf.exe -e libUE4.so // 等价于-h -l -S输出的信息
aarch64-linux-android-readelf.exe -d libUE4.so // 输出libUE4.so的dynamic段的信息
aarch64-linux-android-readelf.exe -r libUE4.so // 输出libUE4.so的重定位信息(.rela.dyn段和.rela.plt段)
aarch64-linux-android-readelf.exe --dyn-syms -D -W libUE4.so // 输出libUE4.so的dynsym段中所有符号(不截断) 注:Ndx为UND时,表示是一个导入符号
aarch64-linux-android-readelf.exe -s -W libUE4.so // 输出libUE4.so的.symtab段(调试符号)中所有符号(不截断)
aarch64-linux-android-readelf.exe --debug-dump=frames-interp libUE4.so // 输出libUE4.so的eh_frame段的信息
aarch64-linux-android-readelf.exe -V libUE4.so // 输出libUE4.so的符号版本信息
aarch64-linux-android-readelf.exe -x.data libc++_shared.so // 以16进制的方式输出libc++_shared.so的.data段内容 注:libc++_shared.so来自于strip后的<android-ndk>sourcescxx-stlllvm-libc++libsarm64-v8alibc++_shared.so
aarch64-linux-android-readelf.exe -p.shstrtab libtestSo.so // 以字符串的方式输出libtestSo.so的.shstrtab段内容
aarch64-linux-android-readelf.exe -c libssl.a // 输出静态库libssl.a中包括各个中间目标文件(*.o)中的符号信息
aarch64-linux-android-readelf.exe -n libUE4.so // 输出libUE4.so中所有note段的信息
aarch64-linux-android-readelf.exe -v // 输出readelf的版本信息
aarch64-linux-android-readelf.exe -H // 输出readelf的帮助信息
objdump
aarch64-linux-android-objdump.exe所在目录在:<android-ndk> oolchainsaarch64-linux-android-4.9prebuiltwindows-x86_64in
aarch64-linux-android-objdump.exe -v // 输出objdump的版本信息
aarch64-linux-android-readelf.exe -H // 输出objdump的帮助信息
aarch64-linux-android-objdump.exe -i // 输出支持的ELF的格式和架构(architecture)
aarch64-linux-android-objdump.exe -f libUE4.so // 输出libUE4.so的简要信息
aarch64-linux-android-objdump.exe -h libUE4.so // 输出libUE4.so段表的信息 注:不输出一些辅助性的段(如:符号表、字符串表、段名字符串表、重定位表等)
aarch64-linux-android-objdump.exe -x libUE4.so // 输出llibUE4.so整个头的信息 注:包括段表的信息
aarch64-linux-android-objdump.exe -p libUE4.so // 输出llibUE4.so的program headers和dynsym段基本信息
aarch64-linux-android-objdump.exe -d -s libUE4.so // 对libUE4.so中的代码段进行反编译,并以16进制的形式输出
aarch64-linux-android-objdump.exe -a libpython2.7.a // 列出.a静态库中所有的目标中间文件(*.o)
aarch64-linux-android-objdump.exe -t libUE4.so // 显示libUE4.so中的调试符号表
aarch64-linux-android-objdump.exe -T libtestSo.so // 输出libtestSo.so的动态链接符号表(dynsym段)
aarch64-linux-android-objdump.exe -T -C libtestSo.so // 输出libtestSo.so的动态链接符号表(dynsym段),并自动对c++符号名进行反修饰(Demangle)
aarch64-linux-android-objdump.exe -R libtestSo.so // 输出libtestSo.so的动态链接重定位信息(.rela.dyn段和.rela.plt段)
aarch64-linux-android-objdump.exe -R -C libtestSo.so // 输出libtestSo.so的动态链接重定位信息,并自动对c++符号名进行反修饰(Demangle)
aarch64-linux-android-objdump.exe -d libtestSo.so // 对libtestSo.so中的包含机器指令的段进行反汇编
aarch64-linux-android-objdump.exe -S libtestSo.so // 显示libtestSo.so源代码和机器指令的段的反汇编代码(包含-d参数功能)
aarch64-linux-android-objdump.exe -D libtestSo.so // 对libtestSo.so所有的段进行反汇编
aarch64-linux-android-objdump.exe -r testSo.o // 显示中间文件testSo.o中重定位信息
aarch64-linux-android-objdump.exe -s libtestSo.so // 以16进制的方式输出libtestSo.so的内容
// 输出libUE4.debug.so地址范围为[0x0000000008277724, 0x0000000008277850]的机器指令进行反汇编,并自动其中c++符号名进行反修饰(Demangle)
aarch64-linux-android-objdump.exe -d -C libUE4.debug.so --start-address=0x0000000008277724 --stop-address=0x0000000008277850
注1:[0x0000000008277724, 0x0000000008277850]的机器指令对应UAkGameplayStatics::SetState(UAkStateValue const*, FName, FName)函数
注2:可通过命令:aarch64-linux-android-nm.exe -n -C libUE4.debug.so | findstr "UAkGameplayStatics::SetState"来获取该函数的起始地址
// 输出libUE4.debug.so地址范围为[0x0000000008277724, 0x0000000008277850]的机器指令进行反汇编(配合源代码),并自动其中c++符号名进行反修饰(Demangle)
aarch64-linux-android-objdump.exe -S -C libUE4.debug.so --start-address=0x0000000008277724 --stop-address=0x0000000008277850 --include="D:svnMyGamePluginsWwiseSourceAkAudioPrivate"
注:可通过命令:aarch64-linux-android-addr2line.exe -e libUE4.debug.so 0000000008277724来获取地址为0000000008277724对应的源代码文件和行数,得到如下结果
E:/tiyan/MyGame/Plugins/Wwise/Source/AkAudio/Private/AkGameplayStatics.cpp:377
参考