链接时的重定位与符号解析

一:符号解析

1.符号表:

由汇编器构造,使用编译器输出到汇编语言.s文件中的符号

(1)Local符号与global符号

任何带有static属性声明的全局变量或者函数的模块都是私有的,即都是local属性的(local属性符号链接器看不到,链接器只关注global符号)

(2)伪节

每个符号都被分配到目标文件的某个节。但是有三个特殊的伪节,它们在节头部表中是没有条目的的:ABS代表不该被重定位的符号;UNDEF代表未定义的符号,也就是在本目标模块中引用,但是在其他地方定义的符号;COMMON表示还未被分配位置的未初始化的数据目标。只有可重定位目标文件中有这些伪节,可执行目标文件中是没有的。

COMMON和.bss的区别很细微,目前GCC版本根据以下规则来将可重定位目标文件中的符号分配到COMMON与bss中

COMMON:未初始化的全局变量

Bss:未初始化的静态变量,以及初始化为0的全局或静态变量

(3) 符号解析:

链接器解析外部符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。也就是从符号引用处去找符号定义处在符号表中。

对于引用与定义在同一模块中的局部符号的解析最明了,编译器只允许每个模块中的每个局部符号有一个定义。局部静态变量也会有本地链接器符号,编译器也会确保它的名字唯一。

对于全局符号的引用解析:当编译器遇到一个不是在当前模块中定义的符号时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用的符号的定义,就输出一条错误信息(无法解析的外部符号)并终止

(4)强弱符号:

编译时,编译器向汇编器输出每个全局符号,或是强或是弱,汇编器把这个信息隐含地编码在可重定位目标文件的符号表中。函数和已初始化的全局变量是强符号,未初始化的全局变量为弱符号。

  多重定义的符号名的处理规则:不允许有多个同名的强符号。如果一个强符号和多个弱符号同名选择强符号。多个弱符号同名从若符号中任意选择一个

  COMMON段:采用的原因就是当编译器翻译某个模块遇到一个弱全局符号,不确定其他模块中是否也定义,一方面如果有其他定义不确定使用那个,所以编译器将它放到COMMON让链接器决定。另一方面当他初始化为0,他就是一个强符号,他就是唯一的,所以编译器就直接分配到.bss段。另外静态符号的构造必须唯一,就自然放在.data或者.bss段

二:重定位

(在符号解析之后的步骤,在重定位中将合并输入模块,并为每个符号分配运行时地址)

1.重定位有两步:

(1)重定位节和符号定义:

合并相同类型的节,然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。这一步完成,程序中的每条指令和全局变量都有唯一的运行时内存地址了。

也就是指令段与代码段中全局、静态变量、函数入口以及指令的运行时内存地址都已确定

 

注意:

.bss段在目标文件和可执行文件中并不占用文件的空间,但是它在装载时占用地址空间(占用的是虚拟地址空间)

静多态:编译器间确定函数入口地址,准确知道调用的是哪个函数  运行时底层call的是函数的入口地址

 

运行时加载数据段、指令段、只读数据段 但是不会加载符号表

 

一般函数在重定位第二步时就会在其引用位置进行重定位写入。

虚函数入口地址只有在链接时才能确定,所以链接完成之后虚函数引用处的重定位并没有写入。而且它的入口地址在符号表中,但是并不会加载到内存中,所以用虚表保存虚函数的入口地址,由于运行时才会有对象生成,空间的开辟,才能知道指针调用的是基类还是父类中的虚函数才能确定函数的地址是什么,所以就需要提前保存下来

 

动多态:编译时不知道入口地址,运行时才能知道函数的入口地址  运行时call的是寄存器

 

因为动多态的调用发生条件是指针调用虚函数,且必须依赖完整对象的调用,在运行时对象才会开辟内存才会有vfptr,所以Vfptr指向虚表是在运行时,更准确时机是在构造函数第一行代码之前

 

父类中的虚函数会使子类中同名同参的函数也变为虚函数,因为只能使用父类指或引用指向派生类对象,所以用父类指针调动发生动多态时,是因为不知道调用的是子类还是父类中的虚函数,只有在运行时才能知道。所以将虚函数的入口地址保存在虚表中。当运行时从虚表中拿出需要的函数入口地址,这也就是动多态。符号表中是有虚函数的入口地址但是符号表不会加载到内存中,而运行时只会加载数据段、指令段、只读数据段等。所以就将虚表存放在只读数据段中

 

虚函数表在编译期间就已经确定(即编译期间虚表合并,也就是编译时就将所有虚函数的入口地址放在虚表中,但是编译器间并不知道发生动多态的地方的指针调用的虚函数是哪个,也就不知道其函数的入口地址

 

虚函数重定位时,由于不知道调的是哪个函数,所以在重定位第二步的  重定位段中的符号引用时  也就是在引用处重定位并不能确定 使用的是哪个函数的地址  也就是这个位置的引用地址并没有 运行时就需要去找  本来加载到内存中时那个引用的位置就是空的  所以他就把虚函数类的函数入口地址存起来 方便在之后使用

 

(2)重定位节中的符号引用:

这一步中,链接器修改代码节和数据节中对于每个符号的引用,使得他们指向正确的运行时地址。 这一步链接器依赖于可重定位目标模块中称为重定位条目的数据结构。

 

重定位条目:未链接完成时一个模块中对于其他模块的全局变量或函数位置并不知道,用重定位条目告诉链接器在将目标文件合并成可执行文件时如何让修改这个引用

你可能感兴趣的:(基础)