深入理解计算机系统之链接(二)

符号表的深入理解

编译阶段编译器将符号输出到汇编语言.s文件中,然后汇编器由此构造符号表,存放在.symtab节。符号表是一个结构数组,下面是一个elf符号的数据结构

typedef struct {
    int name;             
    int value;
    int size;
    char type:4,
        binding:4;
    char reversed;
    char section;
} Elf_Symbol;

这里是详细的说明:
name是字符串表中的字节编译,指向符号的一null结尾的字符串名字,字符串表就是.strtab节,在上一篇博客中有介绍,它的内容包括在.symtab和在.debug中的符号表,以及节头部中节的名字。换句话说,字符串表就是以null结尾的字符串序列。

value是符号的地址,对于可重定位目标文件来说,value是距定义目标的节的起始位置的偏移,对于可执行目标文件,由于已经经过了重定位,所以该值是一个绝对运行时的地址。

size是目标的大小,单位是字节。

type是该条目的类型,一般来说要么是数据要么是函数。

binding表示符号是本地的还是全局的。
关于符号的数据结构先说这么多。

符号解析

链接器解析符号引用的方式是将每个引用与可重定位目标文件的符号表中一个确定的符号定义联系起来。对于那些和引用定义在相同模块的本地符号引用,符号解析是非常简单明了的。编译器只允许每个模块中每个本地符号有一个定义。
不过,对于全局符号的引用解析就比较麻烦。当编译器遇到一个在其他模块中定义的符号(函数名或变量名),它会假设该符号是在其他某个模块中定义的,生成一个链接器符号表的条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用的符号,它就输出一条错误信息。
比如以下的代码

void foo();
int main()
{
    foo();
    return 0;
}

那么编译器会没有障碍的运行,但是当链接器无法解析对foo的引用时就会终止。

对于全局符号的符号解析很棘手,还因为多个目标文件可能会定义相同的符号。C++和Java中采用的是对链接器符号破坏的方式解决这种问题,因为C++和Java都允许重载方法,这些方法在源代码中有相同的名字,却有不同的参数列表,那么链接器是如何区别这些不同的重载函数之间的差异呢?C++和Java中都能使用重载函数 是因为编译器将每个唯一的方法和参数列表组合编码成一个对链接器来说唯一的名字。这种编码的过程叫做破坏(mangling),而相反的过程叫做恢复。
C++和Java来使用兼容的破坏策略。一个被毁坏的类名字是由名字中字符的整数的数量,后面跟原始名字组成的。比如,类Foo被编码成3Foo。
方法被编码成原始方法名,后面加上_ _,加上被毁坏的类名,再加上每个参数的单个字母编码。

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

根据强弱符号的定义,Unix链接器使用下面的规则来处理多重定义的符号:
1.不允许有多个重名的强符号。
2.如果重名的符号中有一个强符号和多个弱符号,那么选择强符号。
3.如果有多个重名的弱符号,那么从中任意选一个。

其中2.3条经常会导致程序中出现一些奇怪的错误,要注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值