关于 ELF 格式文件的笔记

关于 ELF 格式文件的笔记

ELF 的全称是 Executable and Linkable Format,直接翻译过来就是可执行的和可链接的格式,在 Linux / Android 中,他们的可执行文件也是 ELF 格式,执行的程序可能还会依赖别的函数库,在 Andorid 中我们称为 native 库,通常分为静态库(以 .a 结尾,该库不会再依赖其他的库)和动态链接库(以 .so 结尾,全称 Shared Object,在程序运行时动态链接加载到内存中,它本身还有可能依赖其他库)。无论是静态库还是动态库他们也都是 ELF 格式的文件,根据它的命名就很好理解可以执行文件就是 Executable 的,而在运行过程中动态加载的库就是 Linkable 的。学习和理解 ELF 格式的文件对理解 Linux 操作系统和其对应的程序进程执行的方式有很大的帮助,对崩溃的方法栈回溯和 PLT Hook 也有全新的认识。

我自己在 C 语言和 Linux 系统方面也是小白,也看了别人介绍的文章,经常看得云里雾里的,这次我来记录一下最近学习到的东西,可能有的不对,谨慎参考。

C 编译过程

在这之前我们先来看看 C 的源代码是如何编译成可执行文件的,先看图,我再解释。

我们通常是有多个源码文件,但是只有一个源码文件以 main() 函数作为程序执行的入口函数,在 main() 函数中通常他会依赖其他源码文件中的代码(可能还会依赖上面讲到静态库和动态库),当然其他源码文件可能也有互相的依赖。在 C 语言中通过头文件来表示这种依赖关系(无论是库还是不同源码文件的依赖),通常在头文件中会定义一些结构体,全局变量和未实现的方法等等,而源码文件中就是方法的的真正实现,在开始编译开始前就需要先处理这些头文件,编译器会把源代码中依赖的头文件全部合并到源码文件中去,这个过程中还可以执行一些宏脚本来控制这个合并过程,这个过程称为 预处理

在预处理完成后,编译器会根据合并后的源代码,在编译成汇编代码,这个过程称为编译

接着我们还需要对上一个步骤生成的汇编代码再编译,生成对应设备能够执行的机器码指令,生成的文件是以 .o 结尾的文件,它的文件格式也是 ELF 的,它是一个可链接的文件类型,这个过程称为汇编

讲道理汇编后的的 .o 文件中已经是机器码了,是不是就可以直接执行了?emmm,讲道理没有依赖的话确实可以直接执行了。不过大部分情况下都有依赖关系,前面不是说到了吗 .o 文件是一个可链接类型的文件,这个时候 Linker 就会把上述所有源码生成的 .o 文件链接合并成一个可执行的文件,这个可执行文件就会以 main() 函数的地址作为执行的入口地址,最后输出的文件就是可以单独执行的程序了,这个过程称为 链接

上面图中的编译过程包含我上面描述的预处理,编译,汇编等过程。

程序运行

上面编译出来的可执行程序就一定能够正常的运行吗?不一定,因为我们上面有提到可能还会有依赖的库(静态库和动态链接库),如果我们的设备上缺少这些库也就会报错,后续我们以动态链接库分析。

还记得我还是学生的时候就经常去下载一些盗版游戏玩,有的时候就能遇到 "缺少 xxxx.dll "的问题,.dll 文件就是 Windows 系统上的动态链接库,然后我们就去网上去下载缺少的库文件,然后根据教程把它放在特定的位置,然后游戏就可以正常运行啦。

参考下图:

我们想象一下没有动态链接库的依赖程序会变成什么样?每个程序中都有都有一些非常通用的逻辑,比如说求平方根、基础图形渲染和声音播放等等很多程序都会用到的逻辑,那么每个程序都包含他们用到的很通用的代码,那么他们的可执行文件就非常大,当程序运行时这些庞大的执行文件就会被加载到内存中,由于每个程序都这个干就会导致内存的占用就特别多。但是我们仔细想想,每个程序用到的基础逻辑都是一样的,也就是我们的内存中占用了大量的重复的基础逻辑,这是对内存的严重浪费。

当我们把这些通用的逻辑独立出来,单独编译成一个个动态链接库,然后把他们放在操作系统的特定目录下,我们的可执行程序就不需要包含这部分代码,当我们的程序加载到内存中后,系统就会根据我们程序的声明去查找这些库,然后把这些库加载到内存中供我们使用,这样不就是太酷啦。在 Linux 中会把动态加载到内核空间,然后映射到应用进程的地址上,如果还有别的进程也需要这个库,由于加载过了就不会再重复加载,只需要把地址映射上去就好了,也就是所有的进程都共享同一份内存中的代码,这也许就是叫做 Shared Object 的原因吧,通过这样的方式也大大降低了代码对于内存的占用。

在调用动态链接库的方法最主要的一步就是如何找到我们所调用的方法对应的代码的地址,在 ELF 文件中定义了很多的信息来帮助我们找到我们所需要方法的地址,我们继续来看看 ELF 文件中有哪些信息。

ELF 文件基本结构

主要结构包含 ELF 头,Program 头表(段表),Section (节),Section 头表(节表), 其中最最最重要的就是 SectionSection 中包含了我们代码中的所有的信息,比如机器码指令,字符表,符号表(也就是我们定义的变量,方法名等等),动态链接信息(依赖的动态链接库和如何找到相关方法的信息等等)。

查看 ELF 我使用的是 readelf 工具,你也可以找找其他工具。

ELF 头

text 复制代码
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              DYN (共享目标文件)
  系统架构:                          AArch64
  版本:                              0x1
  入口点地址:               0x19280
  程序头起点:          64 (bytes into file)
  Start of section headers:          246208 (bytes into file)
  标志:             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         27
  Section header string table index: 26

这里面的内容很简单描述了基本文件类型,入口程序位置等等,重要的信息是段表头和节表头中 Item 的数量,表头大小和开始的位置,根据这些信息我们就能够很容易地获取段表头和节表头。

节和节头

先看看节表头:

text 复制代码
节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.androi[...] NOTE             0000000000000238  00000238
       0000000000000098  0000000000000000   A       0     0     4
  [ 2] .note.gnu.bu[...] NOTE             00000000000002d0  000002d0
       0000000000000024  0000000000000000   A       0     0     4
  [ 3] .dynsym           DYNSYM           00000000000002f8  000002f8
       0000000000002910  0000000000000018   A       8     1     8
  [ 4] .gnu.version      VERSYM           0000000000002c08  00002c08
       000000000000036c  0000000000000002   A       3     0     2
  [ 5] .gnu.version_r    VERNEED          0000000000002f74  00002f74
       00000000000000e0  0000000000000000   A       8     7     4
  [ 6] .gnu.hash         GNU_HASH         0000000000003058  00003058
       0000000000000acc  0000000000000000   A       3     0     8
  [ 7] .hash             HASH             0000000000003b24  00003b24
       0000000000000db8  0000000000000004   A       3     0     4
  [ 8] .dynstr           STRTAB           00000000000048dc  000048dc
       0000000000002451  0000000000000000   A       0     0     1
  [ 9] .rela.dyn         RELA             0000000000006d30  00006d30
       0000000000008178  0000000000000018   A       3     0     8
  [10] .rela.plt         RELA             000000000000eea8  0000eea8
       0000000000000c18  0000000000000018  AI       3    22     8
  [11] .rodata           PROGBITS         000000000000fac0  0000fac0
       0000000000003b7c  0000000000000000 AMS       0     0     16
  [12] .gcc_except_table PROGBITS         000000000001363c  0001363c
       00000000000003bc  0000000000000000   A       0     0     4
  [13] .eh_frame_hdr     PROGBITS         00000000000139f8  000139f8
       000000000000117c  0000000000000000   A       0     0     4
  [14] .eh_frame         PROGBITS         0000000000014b78  00014b78
       00000000000046fc  0000000000000000   A       0     0     8
  [15] .text             PROGBITS         0000000000019280  00019280
       000000000001ef10  0000000000000000  AX       0     0     16
  [16] .plt              PROGBITS         0000000000038190  00038190
       0000000000000830  0000000000000000  AX       0     0     16
  [17] .data.rel.ro      PROGBITS         00000000000399c0  000389c0
       0000000000002ea0  0000000000000000  WA       0     0     8
  [18] .fini_array       FINI_ARRAY       000000000003c860  0003b860
       0000000000000010  0000000000000000  WA       0     0     8
  [19] .init_array       INIT_ARRAY       000000000003c870  0003b870
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .dynamic          DYNAMIC          000000000003c878  0003b878
       0000000000000250  0000000000000010  WA       8     0     8
  [21] .got              PROGBITS         000000000003cac8  0003bac8
       00000000000000b8  0000000000000000  WA       0     0     8
  [22] .got.plt          PROGBITS         000000000003cb80  0003bb80
       0000000000000420  0000000000000000  WA       0     0     8
// ...

节表头中描述了有哪些节,节的名字,类型,大小,地址等等。

不同的节存储了不同的重要信息,我简单列举一些:

  • .text:存储机器码指令
  • .dynstr 和 .shstrtab:字符表,存储程序中用到的字符串,比如方法名变量名,别的地方如果用到了 offset 来代表,在这个表中就能够查到对应的值,类似于 Java 中常量池中的字符串。
  • .dynsym: 符号表,也就是存储我们代码中定义的方法名,全局变量名等等。
  • .dynamic:动态链接表,存储动态链接库相关的信息,比如库的名字等等。
  • .got 和 .got.plt:和动态链接库的地址定位相关,目前我也没有看懂。

其他的我就先不列举了,大家可以去找找资料看看。

我这里以符号表为例子给大家看看:

less 复制代码
Symbol table '.dynsym' contains 438 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
// ...
    89: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread[...]@LIBC (2)
    90: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dl_iter[...]@LIBC (8)
    91: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread[...]@LIBC (2)
    92: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fwrite@LIBC (2)
    93: 000000000001abb8     8 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
    94: 0000000000012eda     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKb
    95: 0000000000012f02     2 OBJECT  GLOBAL DEFAULT   11 _ZTSs
    96: 000000000003c200    16 OBJECT  GLOBAL DEFAULT   17 _ZTIe
    97: 0000000000012fa4     3 OBJECT  GLOBAL DEFAULT   11 _ZTSDi
    98: 00000000000338d4     8 FUNC    GLOBAL DEFAULT   15 _ZNSt9exceptionD2Ev
    99: 000000000001305d    13 OBJECT  GLOBAL DEFAULT   11 _ZTSSt9bad_alloc
   100: 000000000001aef4    24 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   101: 0000000000012eec     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKc
   102: 0000000000012f0b     2 OBJECT  GLOBAL DEFAULT   11 _ZTSt
   103: 000000000003bf40    32 OBJECT  GLOBAL DEFAULT   17 _ZTIPl
   104: 000000000003c160    16 OBJECT  GLOBAL DEFAULT   17 _ZTIf
   105: 000000000001ac64    32 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   106: 000000000003396c     8 FUNC    GLOBAL DEFAULT   15 _ZNSt9type_infoD2Ev
   107: 000000000003bf90    32 OBJECT  GLOBAL DEFAULT   17 _ZTIPm
   108: 0000000000012f76     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKd
   109: 000000000003c250    16 OBJECT  GLOBAL DEFAULT   17 _ZTIg
   110: 0000000000012fb0    34 OBJECT  GLOBAL DEFAULT   11 _ZTSN10__cxxabiv[...]
   111: 000000000001303e    13 OBJECT  GLOBAL DEFAULT   11 _ZTSSt9exception
   112: 000000000001adc0    40 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   113: 000000000001b04c     8 FUNC    WEAK   DEFAULT   15 _ZdlPvRKSt9nothrow_t
   114: 000000000003baa8    24 OBJECT  GLOBAL DEFAULT   17 _ZTIN10__cxxabiv[...]
   115: 0000000000012ec0     2 OBJECT  GLOBAL DEFAULT   11 _ZTSv
   116: 000000000003bd50    16 OBJECT  GLOBAL DEFAULT   17 _ZTIh
   117: 000000000003c080    32 OBJECT  GLOBAL DEFAULT   17 _ZTIPn
   118: 0000000000012f7f     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKe
   119: 0000000000012ede     2 OBJECT  GLOBAL DEFAULT   11 _ZTSw
   120: 000000000003be90    16 OBJECT  GLOBAL DEFAULT   17 _ZTIi
   121: 000000000003c0d0    32 OBJECT  GLOBAL DEFAULT   17 _ZTIPo
   122: 0000000000012f6d     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKf
   123: 000000000001ac5c     8 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   124: 0000000000012ec9     3 OBJECT  GLOBAL DEFAULT   11 _ZTSDn
   125: 0000000000012e29    37 OBJECT  GLOBAL DEFAULT   11 _ZTSN10__cxxabiv[...]
   126: 000000000003bee0    16 OBJECT  GLOBAL DEFAULT   17 _ZTIj
   127: 0000000000012f38     2 OBJECT  GLOBAL DEFAULT   11 _ZTSx
   128: 0000000000012f88     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKg
   129: 00000000000338e4    16 FUNC    GLOBAL DEFAULT   15 _ZNKSt9exception[...]
   130: 000000000003399c    44 FUNC    GLOBAL DEFAULT   15 _ZNSt8bad_castD0Ev
   131: 000000000003c750    40 OBJECT  GLOBAL DEFAULT   17 _ZTVSt10bad_typeid
   132: 000000000001aeec     8 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   133: 000000000001b198     8 FUNC    WEAK   DEFAULT   15 _ZdlPvSt11align_[...]
   134: 0000000000012ef5     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKh
   135: 0000000000012f41     2 OBJECT  GLOBAL DEFAULT   11 _ZTSy
   136: 000000000003395c    16 FUNC    GLOBAL DEFAULT   15 _ZNKSt20bad_arra[...]
   137: 000000000001ac14     8 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   138: 000000000003bac0    24 OBJECT  GLOBAL DEFAULT   17 _ZTIN10__cxxabiv[...]
   139: 0000000000012f19     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKi
   140: 000000000003bf30    16 OBJECT  GLOBAL DEFAULT   17 _ZTIl
   141: 000000000001abe8     8 FUNC    GLOBAL DEFAULT   15 Java_com_tans_tm[...]
   142: 000000000001b1a0    12 FUNC    WEAK   DEFAULT   15 _ZdlPvmSt11align[...]
   143: 000000000003dfd0     8 OBJECT  GLOBAL DEFAULT   23 __cxa_terminate_[...]
   144: 0000000000012de3    34 OBJECT  GLOBAL DEFAULT   11 _ZTSN10__cxxabiv[...]
   145: 000000000003be00    32 OBJECT  GLOBAL DEFAULT   17 _ZTIPs
   146: 0000000000012f22     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKj
   147: 000000000003bf80    16 OBJECT  GLOBAL DEFAULT   17 _ZTIm
   148: 0000000000012f63     5 OBJECT  GLOBAL DEFAULT   11 _ZTSPKDh
   149: 000000000003be50    32 OBJECT  GLOBAL DEFAULT   17 _ZTIPt
   150: 000000000003c070    16 OBJECT  GLOBAL DEFAULT   17 _ZTIn
   151: 0000000000012fab     5 OBJECT  GLOBAL DEFAULT   11 _ZTSPKDi
   152: 0000000000013090    12 OBJECT  GLOBAL DEFAULT   11 _ZTSSt8bad_cast
   153: 0000000000012f2b     4 OBJECT  GLOBAL DEFAULT   11 _ZTSPKl
   154: 000000000003c0c0    16 OBJECT  GLOBAL DEFAULT   17 _ZTIo
   155: 0000000000012f98     3 OBJECT  GLOBAL DEFAULT   11 _ZTSDs
   156: 0000000000019a5c    16 FUNC    GLOBAL DEFAULT   15 _ZN19tMediaPlaye[...]
// ...   

数据有点多,里面描述了符号的类型,大小,对应的值等等信息。并不是所有的节都是这么容易看的,不同的节内容差别很大,比如代码相关的 .text 节就很抽象,它是机器码,通常需要反编译成汇编来看。

很多人都会把节和段搞混淆,在系统加载程序的过程中会为不同的节按照功能分类,同一类的节就分配到同一段空间中,这个分类就是段,也就是一个段包含若干的节,参考下图:

段表中描述了对节的映射,还定义了段的大小,我们看看段表:

text 复制代码
程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001f8 0x00000000000001f8  R      0x8
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000389c0 0x00000000000389c0  R E    0x1000
  LOAD           0x00000000000389c0 0x00000000000399c0 0x00000000000399c0
                 0x00000000000035e0 0x00000000000035e0  RW     0x1000
  LOAD           0x000000000003bfa0 0x000000000003dfa0 0x000000000003dfa0
                 0x0000000000000068 0x0000000000000b30  RW     0x1000
  DYNAMIC        0x000000000003b878 0x000000000003c878 0x000000000003c878
                 0x0000000000000250 0x0000000000000250  RW     0x8
  GNU_RELRO      0x00000000000389c0 0x00000000000399c0 0x00000000000399c0
                 0x00000000000035e0 0x0000000000003640  R      0x1
  GNU_EH_FRAME   0x00000000000139f8 0x00000000000139f8 0x00000000000139f8
                 0x000000000000117c 0x000000000000117c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x0
  NOTE           0x0000000000000238 0x0000000000000238 0x0000000000000238
                 0x00000000000000bc 0x00000000000000bc  R      0x4

 Section to Segment mapping:
  段节...
   00     
   01     .note.android.ident .note.gnu.build-id .dynsym .gnu.version .gnu.version_r .gnu.hash .hash .dynstr .rela.dyn .rela.plt .rodata .gcc_except_table .eh_frame_hdr .eh_frame .text .plt 
   02     .data.rel.ro .fini_array .init_array .dynamic .got .got.plt 
   03     .data .bss 
   04     .dynamic 
   05     .data.rel.ro .fini_array .init_array .dynamic .got .got.plt 
   06     .eh_frame_hdr 
   07     
   08     .note.android.ident .note.gnu.build-id 

最后

到这里算是对 ELF 有了一个基本的认识,关于具体如何定位动态链接库等等问题还需要继续的深入学习。

参考文章

blog.cloudflare.com/how-to-exec...

相关推荐
轩辰~几秒前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
雨中rain1 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
就爱学编程1 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
锋风Fengfeng1 小时前
安卓15预置第三方apk时签名报错问题解决
android
Bessssss1 小时前
centos日志管理,xiao整理
linux·运维·centos
s_yellowfish1 小时前
Linux服务器pm2 运行chatgpt-on-wechat,搭建微信群ai机器人
linux·服务器·chatgpt
豆是浪个1 小时前
Linux(Centos 7.6)yum源配置
linux·运维·centos
vvw&1 小时前
如何在 Ubuntu 22.04 上安装 Ansible 教程
linux·运维·服务器·ubuntu·开源·ansible·devops
我一定会有钱1 小时前
【linux】NFS实验
linux·服务器
Ven%1 小时前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip