1、在linux中,可执行文件的格式是ELF,Gcc编译出来的目标文件也是ELF格式。目标文件中除了有编译后的机器指令代码、数据等,还包括链接时所需要的一些重要信息,比如重定位表、符号表、调试信息、字符串表等。目标文件将这些信息按不同的属性,以“段”的形式存储。例如,程序源代码编译后的机器指令经常被放在代码段(.text)里,全局变量和局部静态变量数据经常放在数据段(.data)。除了.text段 和 .data段,ELF中可能还存在其他的段,比如,.bss段、.comment段、.debug段、.rodata段、.symtab段(符号表)、 .rel.text段(重定位表)等等。
2、整个链接过程主要大致分为两步:
第一步,空间与地址分配。扫描所有的输入目标文件,获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。第二步,符号解析与重定位。使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。3、符号解析与重定位 依赖的重要信息
重定位表链接器在处理目标文件时,须要对目标文件中某些部位进行重定位,即代码段 和 数据段中那些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个须要重定位的代码段或数据段,都会有一个相应的重定位表。
重定位表里记录了目标文件中要重定位的地方,每个要被重定位的地方叫一个重定位入口。每个重定位入口可以用一个二元组来描述——(r_offset,r_info)。其中,r_offset是重定位入口的偏移,对于可重定位文件来说,这个值是该重定位入口所要修正的位置的第一个字节相对于段起始的偏移。r_info用来指示重定位入口的类型和涉及的符号,它决定了用什么方式、将哪个符号的地址进行重定位。
符号表链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说,符号值就是它们的地址。
4、符号解析 & 符号地址确定
每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件中的符号。重定位的过程中,每个重定位的入口都是对一个符号的引用。当链接器需要对某个符号的引用进行重定位时,它就要明确这个符号的目标地址。这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。
链接的第一步是扫描和空间分配,链接器按照前面介绍的空间分配方法进行空间和地址的分配,这一步完成后,输入文件中的各个段在链接后的虚拟地址就已经确定了。又因为各个符号在段内的相对位置是固定的,所以此时各个符号的地址也已经是确定的了,只不过链接器需要给每个符号加上一个偏移量(段基址),使它们能够调整到正确的虚拟地址。
5、重定位 & 指令修正方式
链接器在完成地址和空间分配之后就已经可以确定所有符号的虚拟地址了,那么链接器就可以根据符号的地址对每个需要重定位的指令进行地址修正。
不同的处理器指令对于地址的格式和寻址方式都不一样,例如,对于32位x86平台下的ELF文件的重定位入口 所修正的指令寻址方式只有两种:
绝对近址32位寻址相对近址32位寻址对应的重定位修正方法分别是:
绝对寻址修正 S + A相对寻址修正 S - ( P - A)其中,
A 是 保存在被修正位置的值P 是 被修正的位置(相对于段开始的偏移量或者虚拟地址)S 是 符号的实际地址
参考资料:
1、《程序员的自我修养--链接、装载与库》,俞甲子,石凡,潘爱民,电子工业出版社。