程序的链接

链接的概念

链接器使得分离编译成为可能,开发人员各自开发自己负责的部分,然后将自己负责的源码单独编译成可重定位目标文件,再通过链接器将各自编译的可重定位目标文件链接到一起,成为一个可执行目标文件。
这样,程序员A在自己模块中定义的变量、函数可能被其他程序员在自己的模块中引用,程序员A也可能引用其他程序员定义的变量或者函数。那么链接器就需要将在不同文件中定义的符号、引用的全局变量、全局函数进行匹配。这个过程就是重定位。
在一个模块中定义的非静态局部变量在程序运行时在栈中被管理,这部分变量不是链接器关心的部分。

符号解析

有哪些符号

在每个可重定位目标模块m中都有一个符号表,它包含m定义和引用的符号的信息,在链接器的上下文中,有三种符号:

  • 由模块m定义并能够被其他模块引用的全局符号。即非静态的函数和全局变量。
  • 由其他模块定义并被模块m引用的全局符号。这些符号相对于m来说为外部符号,即被其他程序员定义的全局函数、全局变量。
  • 只被模块m定义和引用的静态局部符号。对应于被static修饰的函数和全局变量。

如何记录这些符号

符号表存在于每个可重定位目标模块的.systab节中。在ELF格式的可重定位目标文件中,为每一个上述的符号都维护了一个在符号表中的条目。
符号表是由汇编器构造的,链接器根据这个符号表中的每个符号的信息来完成对所有符号的重定位。
使用GNU READELF程序可以查看符号表。
几个典型的符号信息:

  • name表示某一符号条目在当前符号表中的字节偏移,指向符号的以null结尾的字符串名字。
  • value是符号在可重定位文件中的地址。实际上是距定义目标的节的起始位置的偏移。比如在main.c中定义了函数sum,sum这个符号定义在可重定位文件的.text节,那么sum条目的value值就是sum相对于.text节起始位置的偏移地址。
  • size是目标的字节大小
  • type表示符号的类型,通常要么是数据要么是函数。
  • binding表示符号是全局还是局部

符号解析过程

对于那些引用和定义都在同一个模块中完成的符号来说,符号解析是非常简单的。只需要在当前模块的.systab中将符号地址填入到对符号的引用处即可。
对于全局符号的解析则较为麻烦。当编译器遇到一个不是在当前模块中定义的符号时,它会先假设该符号在别的模块中被定义。即生成以恶一个链接器符号表条目,将符号表对应的条目中的Ndx设置为UND,然后交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用符号的定义,那么就输出一条错误信息并终止。

编译器如何解析多重定义的全局符号

在编译时,编译器向汇编器输出每个全局符号,或者是强或者是弱。汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。
对多重定义符号的处理规则:

  • 不允许有多个同名的强符号
  • 如果有强符号和弱符号同名,那么选择强符号
  • 如果有多个弱符号,则随机选择一个弱符号。

重定位

当符号解析完成以后,链接器就知道了每一个输入目标模块中代码节和数据节的大小。重定位主要是两个步骤:合并输入模块,并为每个符号分配运行时地址。

  • 重定位节和符号定义 在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址。
  • 重定位节中的符号引用 在编译时,对于每个进行符号引用的指令,编译器并不知道引用对象的最终位置。因此编译器会生成一条重定位条目,来告诉链接器在这里进行重定位。重定位条目里包含了如何进行重定位的信息。

重定位条目

重定位条目里包含了需要被修改的引用的节偏移,标识被修改引用对象应该指向的符号,以及重定位时需要进行偏移调整的量。

链接器根据重定位条目里的信息,将引用对象的运行时地址填入引用字段。这样就形成了最终的可执行目标文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值