动态链接与静态链接

作者: 羊角包 | 来源:发表于2019-02-27 17:12 被阅读7次
    • 什么是静态链接
    • 如何实现静态链接
    • 静态链接的优点与缺点
    • 什么是动态链接
    • 如何实现动态链接
    • 动态链接的优点与缺点
    • SO文件格式简析
    • 根据SO文件格式进行静态反编译

    静态链接

    一段代码从文本编辑器上产生到最终能够在机器上运行,经历了非常多的阶段,概括而言,至少包含了以下几个阶段:

    • 编译: 编译器通过词法分析,语法分析,语义分析等,将一段代码翻译成汇编语言
    • 汇编:将汇编语言翻译成机器指令
    • 链接:解决符号之间的重定位问题
    • 装载:将可执行文件加载到内存

    静态链接就是在装载之前,就完成所有的符号引用的一种链接方式。静态链接的处理过程分为2个步骤:

    1. 空间与地址的分配。扫描所有的目标文件,合并相似段,收集当中所有的符号信息。
    2. 符号解析与重定位。调整代码位置。
    链接前和链接后

    如何可得,在完成静态链接之后,可执行文件中代码段、数据段等的虚拟地址已经确定,即当此可执行文件被载入到内存后,代码段的起始位置就在0000000000400400的位置。

    静态链接的优缺点

    • 优点: 简单
    • 缺点:
      • 浪费内存空间。在多进程的操作系统下,同一时间,内存中可能存在多个相同的公共库函数。
      • 程序的开发与发布流程受模块制约。 只要有一个模块更新,那么就需要重新编译打包整个代码。

    为了解决以上2个问题,就诞生了动态链接。

    动态链接

    基本思想就是将对符号的重定位推迟到程序运行时才进行。

    只要推迟到运行时进行符号的重定位,就能解决静态链接的两个缺点。

    对于第一个缺点:在运行时重定位,如果在运行过程中调用了公共库函数或者其他模块的函数,系统只需要在内存中维护一份公共库代码即可,只要将不同应用程序对公共库函数的调用地址设置成相同即可。

    对于第二个缺点:理论上只要将需要替换的模块更新,无需将整个应用程序打包。

    动态链接的实现

    对于静态链接来说,系统只需要加载一个文件(可执行文件)到内存即可,但是在动态链接下,系统需要映射一个主程序和多个动态链接模块,因此,相比于静态链接,动态链接使得内存的空间分布更加复杂。

    不同模块在内存中的装载位置一定要保证不一样。

    装载时重定位

    对于每一个模块,对于代码中符号的绝对引用,都需要加上一个基地址偏移量。(比如一个模块中存在一个绝对地址的引用A,它假定的是模块可以被加载到0x1000的地方,但是系统在加载该位置已经被其他模块占用了,系统选择将它加载到0x4000的地方,那么对绝对地址A的引用就需要被修改为A+0x3000,即修改代码)。

    但是它也有缺点,因为不同进程可能使用同一个模块,但是对于不同进程,代码段是不能共享的,因此,多个进程还是没有办法共享一个模块。因为不能修改模块当中的代码,一旦一个进程将共享模块的绝对地址修改了,其他进程此时调用就一定会报错。

    解决这个问题也有办法,在Windows中使用的就是装载时重定位,但是在Linux下使用的是另外一种方式。

    地址无关代码

    一个模块的代码部分是共享的,但是数据部分是每个进程一个副本的。因此,地址无关代码的基本思想就是将代码段中的绝对地址引用剥离出来放到数据段中,以保证代码指令不变。在Android系统下进行SO文件的编译,默认就是产生地址无关代码。

    因此程序执行的流程就变成了:当模块A调用模块B的某个方法的时候,会从模块A的数据段部分找到模块B中函数地址,然后进行函数调用。

    动态链接的优点与缺点

    优点: 解决了静态链接的缺陷,更适应现代的大规模的软件开发

    缺点:1. 结构复杂 2.引入了安全问题,这也是我们能够进行PLT HOOK的基础

    SO文件格式

    ELF头表

    ELF头表记录了ELF文件的基本信息,包括魔数,目标文件类型(可执行文件,共享库文件或者目标文件),文件的目标体系结构,程序入口地址(共享库文件为此值为0),然后是section表大小和数目,程序头表的大小和数目,分别对应的是链接视图和执行视图。

    Section表

    Section表记录了每一个Section的基本信息,名称,类型,字节数,虚拟地址偏移和文件偏移。文件偏移指的是在ELF文件中,Section距离ELF文件起始位置的字节数,而虚拟地址偏移指的是当此section被加载到内存中后,该Section距离ELF起始位置的字节数。由于有些section只存在于文件中,而不会被系统加载到内存中,因此虚拟地址偏移可能为0.

    程序头表

    程序头表是装载视图下,系统进行segment解析的入口,它给出了每一个segment的类型,文件偏移,虚拟地址偏移和对齐等。通过对程序头表的遍历,我们可以得到ELF文件所有的segment。

    重定位表

    静态链接下需要对符号的引用进行重定位,并且ELF文件中对符号的引用可能出现在代码段,也可能出现在数据段,因此重定位表分为了代码段重定位表和数据段重定位表,分别记录引用的符号名和所在的偏移地址。因此,重定位表的格式就是记录符号名和重定位地址的数组。

    .dynamic段

    .dynamic段是动态链接中最重要的段,它记录了和动态链接有关的段的类型,地址或者数值。指向了与动态链接相关的段

    .got段

    位于数据区,就是上文所述为了实现位置无关代码将对绝对地址的引用抽离出来存放的集合

    .plt段

    在介绍.rel.plt段之前,对比静态链接与动态链接我们需要知道.plt段的作用。我们会发现,动态链接将所有的重定位操作延迟到加载时处理,那么就难以避免的会降低程序执行的效率,试想,如果有1000个对外部模块的函数引用,动态链接器就需要先解决这1000个函数引用,然后才开始执行程序。为此,链接器为了提升效率,采取了这样一种策略:仅当函数被调用时,才会唤醒动态链接器解决重定位问题。

    .plt段就是为了实现这种策略增加的段。增加了.plt段之后,代码段中的地址就不再指向.got段而是指向了.plt段,再由.plt段指向.got段,具体过程如下:

    .plt段是包含了若干数目的代码片段组成的段,代码段中的地址指向对应函数的.plt代码段,代码片段的第一行代码就是间接调用.got表中对应函数地址,但是此时,.got表中的地址指向的是.plt中代码片段的第二行代码,而第二行以后的代码作用就是调用动态链接器处理.got表中的地址。当再次调用此函数时,.plt中代码段第一行代码就可以正确的跳入函数地址执行相应的函数了。

    但是,在Android平台下,由于兼容性的限制,并没有采用这种"延迟加载"的特性,所以一开始加载,.got表中的地址就是真实的函数调用地址。但是,Android下的ELF文件仍然保留了.plt表这种结构。

    根据SO文件结构进行静态反编译

    静态反编译是指对未载入内存控件的SO文件进行的反编译,在这种情况下,主要是针对SO文件的链接视图进行关键参数上的修改,使得静态反编译工具不能够正常的工作。

    根据SO文件的格式,在ELF头表中会表明Section表的位置和Section表格的大小。绝大部分静态反编译工具是依据SO文件的链接视图还原出SO文件内容的,那么在这种情况下,我们只需要修改ELF头表中与SECTION表相关的2个参数,就能使得工具反编译SO文件失效。但是SO文件在系统载入内存之后,是使用的执行视图对SO文件进行解析,因此不会影响SO文件运行时。

    相关文章

      网友评论

        本文标题:动态链接与静态链接

        本文链接:https://www.haomeiwen.com/subject/kdwfuqtx.html