ELF文件基础

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头(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”段的地址
第二项保存的是本模块的ID
第三项保存的是_dl_runtime_resolve()的地址

第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)

.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++异常处理相关的内容
.eh_frame_hdr SHT_PROGBITS   c++异常处理相关的内容
.gcc_except_table SHT_PROGBITS   语言相关数据
.jcr     Java程序相关
.gnu.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>\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin 

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 -D -W libUE4.so  // 输出libUE4.so的dynsym段和.symtab段(调试符号)中所有符号(不截断)

aarch64-linux-android-readelf.exe -x.data libtestSo.so  // 以16进制的方式输出libtestSo.so的.data段内容

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>\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin 

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:\svn\MyGame\Plugins\Wwise\Source\AkAudio\Private"

注:可通过命令:aarch64-linux-android-addr2line.exe -e libUE4.debug.so 0000000008277724来获取地址为0000000008277724对应的源代码文件和行数,得到如下结果

      E:/tiyan/MyGame/Plugins/Wwise/Source/AkAudio/Private/AkGameplayStatics.cpp:377

 

参考

程序员的自我修养

 

上一篇:吴章金: 《360度剖析Linux ELF》 新增 15 份实验材料,累计已达 70 份


下一篇:圆角三角形