美文网首页程序员
3、静态链接

3、静态链接

作者: eesly_yuan | 来源:发表于2017-02-23 22:13 被阅读58次

    上一篇说到链接主要包括地址和空间分配、符号决议、重定位这三部分,本篇文章将针对这三部分进行一个详细说明

    链接过程

    ld a.o b.o -e main -o ab
    

    链接过程实际上将多个目标文件.o的同类段进行合并处理合并成一个elf的执行文件,这个过程中涉及到一个问题就是不同目标文件的段如何进行合并?

    • 直观感受可以直接按序合并(导致输出文件出现很多段,由于段对齐可能导致很多的空间浪费或者碎片,这里指的空间是执行程序加载后的虚拟地址控制)
    • 或者采用相似段合并在一起的方法,实际情况下,通常采用后者,如下图所示
      链接过程
      采用上述方式的链接器,其链接过程通常可以分为两步
    • 1、空间和地址分配
      扫描所有目标文件的段属性、长度、位置等,将相似段合并设置在执行文件的对应段位置、长度和属性并建立合并前后的映射关系,即重定位信息。同时收集所有的目标文件的符号信息和符号引用信息,统一放到一个全局符号表中,即符号信息
    • 2、符号解析和重定位
      根据第一步获取到的重定位信息符号信息对输出文件中的符号进行重定位,即解析和符号地址(这里指虚拟地址)调整等,后面将详细介绍。
    符号解析和重定位

    链接之前,目标文件中对其他模块中符号的引用的位置通常填入一个临时的假地址,包括两种类型

    • 1、全0,即绝对地址指令
    • 2、相对地址指令,通常是相对于调用指令的下一条指令的偏移量

    而链接时重定位就是找到被引用符号在最终输出文件的地址,利用该地址对这些需要调整的临时假地址进行修正。下面分析一下实现这个过程中涉及的两个问题

    • 1、如何知道哪些地址需要被调整?
      通常在目标文件中存在一个段,内部保存着该目标文件引用外部符号的信息,通常称之为重定位表,这也是.o文件为什么被叫做可重定位文件。当然在使用可重定位表的过程中就已经在进行符号解析了,获取需要重定位符号后,在全局符号表中进行查找,并进行后续的地址修正。
    • 2、如何进行地址修正?
      针对前面的两种类型的假地址,修正方式如下
      1、绝对地址方式,修正值=假地址+符号实际地址(在全局符号表中的地址),通常为0+符号实际地址
      2、相对地址方式,修正值=假地址+符号实际地址-被修正的位置
      上面那个计算公式可以采用如下方式进行推导
      修正值+下一条指令的位置=符号实际地址
      假地址+下一条指令的位置=被修正的位置
    common块机制

    链接通常允许弱符号(定义未初始化的全局变量)机制,即允许同一个符号定义在多个文件中,由于链接过程中并不涉及符号的类型,只知道符号所占空间的大小,这将导致链接器进行符号选择的问题,链接器通常采用common块机制:最终选择占用空间最大的那个弱符号,或者说给该符号分配最大的空间
    具体的关于多个目标文件中强弱符号的选择规则如下

    • 1、两个或者两个以上的强符号,出错
    • 2、一个强符号,一个或者多个弱符号,选择强符号
    • 3、多个弱符号,common机制,简单来说就是类型位置,导致分配大小未知,稳妥的方式就是选最大的。
    C++相关问题

    c++由于其语言特性需要编译链接器进行支持,这里主要说明两方面

    • 重复代码消除
      这里以模板导致的代码重复和虚函数表导致的代码重复问题为例介绍目前的常用解决方法

    1、假设有模板类M,文件a.cpp使用并实例化了M,文件b.cpp也使用并实例化了相同类型的M,那么在a.o和b.o的代码段中都将存在M实例的代码,这两部分代码是完全相同的,因此出现了代码冗余的问题(空间浪费、寻址易出错、运行效率低)。
    针对这个问题目前比较主流的做法是:将每个模板的实例代码都放在一个单独的段里面,这个段里面就只包含这一个模板实例,并且按照一定的方式对这种段进行命名,这样当两个.o文件都包含M代码实例时,就可以区分这些相同的代码段并进行去重操作,这种类型的段在gcc中被称之为link once。
    ·
    2、对于一个有虚函数的类会有一个与之相对应的虚函数表,编译器会在用到该类的多个目标文件中包含该虚函数表的代码,造成代码重复。
    针对这个问题的解决方式与上面的方式类似

    • 全局构造和析构
      C++的全局构造函数是在main函数之前执行的,析构函数是在全局析构函数中执行的。对应glibc下程序的启动运行流程如下
    _start->init->main->fini
    

    与上面对应的elf中存在两个段.init和.fini,存放在这两个段中的代码将分别在上述init阶段和fini阶段执行,将全局构造和析构分别放入上述段也就可以实现全局对象的正常构造和析构了。

    静态库链接

    一般一种语言开发都会附带一个语言库,这些库通常是对系统调用的包装,例如linux下glibc库。通常这些语言库都包含两个版本:静态库和动态库,这里先主要介绍静态库。静态库实际上是一组目标文件的集合,linux下可以利用ar命令将多个目标文件打成一个静态库也可以查看一个静态库中包含哪些目标文件。编译时使用静态库链接方式,链接器只会将静态库中使用到的目标文件链接到最终的输出文件中

    链接过程控制

    上述介绍到链接实际是地址空间分配和符号重定位,针对这个链接过程,链接器提供了一定的控制方式

    • 1、命令行参数ld -e -o等
    • 2、将链接指令存放在目标文件中,比如vc++通常把链接指令存放在pe目标文件的.drectve段
    • 3、使用链接控制脚本,默认情况下ld的链接脚本存在/usr/lib/ldscripts/下,可以通过-T参数指定自定义的链接脚本,ld链接脚本的语法这里就暂不介绍了。
    ld -T mylink.scripts
    

    相关文章

      网友评论

        本文标题:3、静态链接

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