[音乐] 上一周我们介绍了ELF可重定位目标文件格式
和可执行目标文件格式,本周开始 我们介绍如何将多个可重定位模块
链接合并,以生成可执行目标文件 链接主要分两个步骤,符号解析和重定位
本周内容分5讲,包括符号及符号表 静态链接与符号解析,符号的重定位
可执行文件的加载,以及共享库和动态链接 下面我们开始第一讲,符号及符号表
本讲主要介绍符号的各种分类,符号表的结构 以及链接器对各类符号的解析规则
这个过程 有关链接操作的步骤,我们在前面简单介绍过
链接操作的工作,实际上是合并多个可重定位文件
那么要合并多个可重定位文件,第一步先要进行符号解析
因为这些可重定位目标文件当中,相应的这些符号
相互之间有引用关系,比如说这边 这条指令引用了这个模块里面定义的一个符号
然后这个里面的这条指令引用了本地的一个符号,这条指令
引用了这个模块里面的这个符号,相互之间它们有引用关系
所以这种引用关系,先要把它找出来,这个过程我们称为符号解析 第二步呢,就把它们进行合并。
合并的时候把代码和代码合并 数据和数据合并,合并以后
这些代码和数据呢,就把它们落实到一个存储空间里面
这个存储空间当中,有相应的地址,比如说存储空间,这是从0开始
然后这是1234,这样子,上去的一些地址,确定了一个存储空间的
地址,一旦确定了以后,那么这些定义的符号就会有值,这个值就确定了
这个确定的值,就是这个符号所代表的,比如说这个函数或者是
一段代码的话,它就是这段代码当中,第一条指令的首地址
就是这个符号的值,比如说p1这个函数,它的首地址在这个地方
那么p1的这个值,就是这条指令的地址 比如说操作数10,它存放在这个空间里面,存放在这个位置
这个10所存放的这个地址就是B的值,每一个定义的符号
它都是它代表的那个目标,存放在这个空间 当中的地址,就是这个定义符号的值
确定了这个值以后,引用它的那个地方就可以把这个值填上去
相应的引用它的这个指令当中,用到的定义符号的 地址可以填进去,这么填进去以后,所有的这些指令
就是一个确定的指令,执行的时候就能找到引用处的这个位置
取到相应的操作数,或者转移到相应的指令处去执行
这个过程我们称为重定位的过程,因此链接操作实际上 就是两个过程,一个是符号解析,一个是重定位
符号解析我们刚才讲过了,实际上就是 解决符号的引用和符号的定义之间的这个关联
因此首先我们要理解什么叫符号的定义,什么叫符号的引用
哪些符号是用来进行定义的符号,哪些符号呢是
进行引用的符号,比如说我们举以下几个例子,swap这个符号
是在我们前面讲的那个例子当中,在swap.c那个模块里面
定义的这个函数,很显然它是一个 定义,因为它后面加了一组大括号
这个一组大括号就是对swap这个函数名,进行的一个定义,因此它是一个定义的符号
在main.c里面有一条语句 这条语句是对swap函数的一个调用
这个很显然是对swap这个符号的一个引用 因此这边出现的这个符号,就是一个引用的符号
然后考虑这一个赋值语句,在这个赋值语句里面,前面有一个类型说明
对xp进行类型说明,因此这个xp,它应该是一个符号的定义
定义了xp这个符号,并且赋了初值,这个初值是引用x的
符号来进行定义的,所以显然这个符号x是一个引用的符号
xp是一个定义的符号,确定了整个程序当中 哪些符号是用来进行定义的,哪些符号是用来进行引用的
以后编译器呢就可以把这些定义的符合存放在一个符号表
里面,这个符号表就是我们前面讲的可重定位目标文件当中的这个节
在这个节里面可以把每一个符号,它的符号名
长度位置等等信息,每一个符号的这个属性
记录在一个表项里面,若干个表项就是一个数组
每个表项是一个结构类型,这样呢得到一个符号表 有关符号表的这个结构我们在后面会详细的介绍
然后对于符号的引用,编译器呢把它放在重定位节当中
比如说这个重定位节里面存放的是 text节这个代码部分重定位的信息
在这个节当中放的是data节当中的重定位信息
然后有了这两个节就是符号表节和重定位节以后
链接器就可以把符号的引用和符号的定义建立关联
比如说在这个例子当中 这个地方L0是一个符号的引用
这个地方的L0是一个定义的符号 对L0进行定义的,当我们进行解析以后
就对这个引用和这个定义就建立了关联,这个关联
是在重定位节里面建立的,它们之间的关联是在重定位节里面
和这个符号表里面进行关联的,关联好了以后就可以进行重定位,重定位的第一步
当然先要合并,我们前面的那个例子当中可以看出 多个代码段合并成单独的一个代码段
多个数据段合并成单独的一个数据段 也就是说在可执行文件里面只有单独的一个代码段和一个数据段
然后呢再把每个定义的符号再虚拟地址空间当中的绝对地址确定好
因为已经都合并好了相同的节合并成一个节,这样的话就确定了它的
位置,确定位置以后,每个定义的符号就有了值,这个值就是虚拟地址空间当中的
一个绝对地址,然后呢再把引用处的地址修改
为重新定位的这个地址信息,也就是修改为合并以后得到的绝对地址
这样的话,每条指令里面的信息就正确了,这时候这个重定位过程就结束了
那么得到的可执行文件就能够在CPU上直接执行 下面我们来举个例子,这是我们前面举的这个
main.c和swap.c的例子,在这个例子当中 哪些符号是定义符号,哪些符号是引用的符号
main.c这个里面,这个里面定义的符号很显然 main是一个定义的符号,它有大括号括起来的
所以是对这个函数的定义,大括号里面,然后呢buf是一个全局变量
说明成这个类型,所以它是一种定义
而swap也是一种定义,后面我们会讲到,它是一种对将要
调用的这个函数的一种原型说明,在前面只要给出类型的这种都是一种定义的符号
这种定义的符号实际上它在这个函数里面,它仅仅是说明是一个外部
定义,也就是说它这个里面是找不到它真正的定义的,我们后面会讲到,这是一种弱符号
然后我们再看swap.c当中 也有一个弱符号或者叫外部符号,它实际上
是在这个模块外面定义的一个符号,然后呢是这个
前面也有类型说明,所以它也是一种定义,这个也是一种定义
前面也有类型说明,有存储类型的说明和数据类型的说明
然后引用的地方我们可以看到,这个当然swap也是一种定义 然后符号的引用我们来看一下,在main点c里面
有一个swap这个过程调用或者叫函数调用这个肯定是一种引用,
然后呢在这个当中前面它是没有类型说明的
所以它直接就是引用,这个也是一种引用
这个也是引用,这个也是一种引用,实际上是对
这个符号进行引用,引用来对这个定义的符号进行赋值的。
这个也是一种引用对buf这个符号引用,来对这个符号进行赋值的。
下面呢这个也是一种引用,还有这个也是一种引用, 可以看出这个里面凡是一个符号前面
用来进行说明它的类型的这些都是一种定义,都是一种定义,
其它的都属于引用。
这个里面还有一个变量temp这个变量在这个地方
它也有类型说明,但是它是在一个函数 内部的一个说明,所以它是局部变量
应该是分配在栈里面的,我们在前面过程调用的时候讲过,所以它 不会被过程外部的变量或者是函数引用
所以它不是一种符号,它就是一个局部的变量它不出现在符号表里面。
这些符号我们说每一个模块它
都有一个符号表,这个符号表记录了在这个模块当中定义的
所有的符号,这些符号我们可以把它分成三种类型
链接器处理的三种符号,一种符号称为全局符号
就是在模块内部定义,并且是全局的 也就是说其它模块是可以引用的这种符号,
也就是说它前面一定是不带static这样的 说明的,这种不带static的这种函数
和不带static的这个全局变量都属于全局符号,
比如说刚才我们看到的main点c这个模块当中的
全局变量名buf就是一种全局符号。
还有一类符号叫外部定义的称为外部符号
比如说,在main点c里面我们有一个符号的定义swap
这个swap是在swap点c里面定义的,只不过我们在main点c里面要引用它,
为了引用它我们先要进行一个原型说明 因此它实际上是一种外部符号,然后在
swap点c里面也有一个extern说明的这个外部变量名
buf它也是一种外部符号,是指在其它某块定义
被本模块引用的全局符号,它是一种外部符号。
还有一种 是在本模块定义并且只能在本模块引用的这种局部符号
这种局部符号实际上就是在模块内 带static说明的这些都是本地的局部符号。
比如说我们刚才讲的这个模块里面 带static的这个变量名它就是一种本地的局部符号,
在swap点c外面是不能够用的,这个是个局部符号。
所有定义的符号可以分成这样三类,全局
外部和局部三种,这里要说明的这个局部符号
不是指程序当中的局部变量,局部变量是分配在栈中
的临时性的变量,链接器是不关心这种局部变量的。
而局部符号呢它是分配在静态 数据区的然后是在整个模块里面都可以使用的。
我们来看一下哪些是全局符号在这个例子里面,
刚才我们讲过了全局符号是说,是在本模块定义 外部模块可以使用的这些符号叫全局符号。
那么很显然这个main是在这个里面定义其它的 可以使用的可以调用的,这个buf也是在main点c
这个模块里面定义其它模块可以使用的。
对于swap点c来说它的全局符号就是 bufp0在本模块定义其它
模块使用还有swap也是本模块定义其它模块使用。
然后外部符号刚才我们已经讲过了,像这个swap
相对于main点c来说它就是一个外部符号,因为它是在另外一个
点c文件里面定义的符号,然后呢 在main点c里面定义的这个buf
实际上是一个全局符号而相对于
swap点c来说它就是一个外部符号,因为它是在另外一个模块定义 在本模块用的,所以它是一个外部符号。
然后局部符号的话,实际上只有这边这一个它是在本
模块定义并且本模块引用,因为它加了static这个属性, 表示它在这个模块外是不可以用的,只局部于
静态的局部于本模块,这就是局部符号。
我们讲了所有定义的符号都必须记录在 符号表里面,这个符号表就记录在这个节里面,
它是一个结构数组,用一个struct来定义每一个表项。
其中有符号的名称,这个符号的名称 当然是一个字符串,比如说我们刚才看到的这种swap
main buf等等,这些名称都是一个字符串,所有的字符串都是存放在这个表里面的
这里面有一个字符串的表,每个字符串有一个起始地址也就是一个偏移量,
这个偏移量就记录在这个字段里面,找到这个偏移量就 能够找到对应的字符串,也就是这个符号的名字然后呢,
这个字段描述的是这个符号它对应的节它的偏移量
或者是虚拟地址,如果是可执行文件的话那它就是一个虚拟地址。
如果是可重定位文件的话,它就是一个偏移量。
比如说如果这个符号是个函数名的话, 就说明是在text节当中的偏移量,如果这个符号
是个变量名,像刚才那样的buf这种变量名的话 那它就是在data节里面的起始位置,
或者是在bss节当中的起始位置,这个字段就是这
个符号对应的字节数,比如说这个符号是一个函数名的话
那么就是这个函数对应的那一个机器代码就是若干条指令
实现这个函数的所有的指令加起来的这个长度 就是它的size。
如果是个变量的话, 那么这个变量名表示的这个符号的话,它的这个字节数就是
这个变量的所占的长度,比如说int型的变量,
当然字节数就是4,short型的变量字节数就是2,也就是指函数大小或者 变量的长度。
后面的这个字段时间上它有8位,占了8位
这8位分成了2个4位,分别表示类型和绑定的属性。
前4位表示类型,后4位表示绑定的属性,符号的 类型可以是数据,也就是说如果是一个变量名那它就是个数据,
如果这个符号是一个函数名那它就是个函数,或者是源文件啊等等其它的。
绑定的属性呢是说明符号的类型,它是一个全局符号呢
还是一个局部符号还是一个弱符号,有关弱符号我们在后面会介绍。
最后一个字段给出的是符号对应目标所在节是什么?
也就是给出来的是一个索引值index,
也就是所有的节在节头表当中进行描述,节头表当中给出来的
是一个一个节的信息,每一个节当然有一个索引值,这个索引值就是记录在这个地方的,
比如说这个符号如果是一个函数名的话, 那么它对应的这个节就是text的节,text节的索引
比如说是1,在节头表里面是表示第一个节
比如说某一个符号是个变量名的话,说明它对应的这个变量的初始
值是在data节里面存放的,那么这个data节在节头表里面比如说是第三个节
那么它的index呢就是3,是这样子的。
这个字段还表示其它的情况,也就是说这个符号不是能 够确定所在节的话那么它就有可能是其它的情况,
比如说这个符号不应该被重定位,那么它就是一种情况通常 用ABS表示。
还有一种情况就是未定义的情况,就是说这个符号在点o里面它是没有被定义的, 所以它就用这个表示。
还有的呢是表示未初始化的数据,也就是说
这个符号是在bss节里面的,它是一个未定义的一个
数据,这个时候,就用COM来表示,这时候因为它没有进行初始化
所以它也就没有像偏移量啊 大小啊等等,这样的一些信息
所以在相应的字段里面,分别表示的是 对齐要求,在这个里面放,在size里面存放的是最小大小
在这个里面存放的就是对齐要求。
bss节 的总大小,我们在前面讲过,它是在节头表当中给出的 因为节头表当中会给出每一个节的这个size
当然,bss节的size就在这个里面给出,然后bss节里面每个变量的大小
就可以在这个符号表里面表示,因为这个符号表里面记录了每一个符号的情况
刚才我们讲过,这个里面 有一些定义的符号,比如说这边的buf swap、
main,这边的buf、 bufp0、 bufp1、
swap 这些都是定义的符号,所以我们可以看出在main.c里面定义了3个符号
在swap.c里面,定义了4个符号 因此等一会我们可以看到,对应于main.c的那个
可从定位文件,就是main.o里面,它的符号表里面应该有
这三个符号,要有记录,对于这个swap.c来说
它的这个swap.o里面的符号表
里面应该有这样四个符号,分别是 main.o,这个里面temp我们刚才讲过了,不是
一个定义的符号,它是一个局部变量。
我们记住这边有三个符号 这边有四个符号,我们来看一下,main.o里面
它最后三个条目,一共是十个条目,最后三个条目,八、 九、 十 当中给出了三个符号
给出了三个符号,刚才我们看到的是三个符号,这三个符号的信息说明 buf对应的目标是在第三个节当中
第三个节是data节,我们前面讲过了 第一个节是实际上是text的节,第二个是rodata
第三个是data,innext等于3的话,说明是data节 偏移量呢是0,也就是buf这个符号
在data节当中最开始,所以偏移量是0 那么它是一种全局符号。
前面我们讲过了,buf是在 main.o里面定义,其他的模块可以引用的,所以它是一种全局符号
并且呢它是一种数据,它定义的是一个变量,它的长度是八个字节
因为我们前面讲过,我们在这个里面定义的时候,buf是 [板书]
2,它有两个 数组元素,每个数组元素呢是占四个字节
回过头来看一下,buf是有2个数组元素,每个数组元素占四个字节
所以二四得八,一共八个字节。
里面已经进行了初始化了,这八个字节里面放的是1和2 所以我们在这可以看到,它有八个字节
然后我们看到main这个符号,它的Ndx是等于1 说明它是在第一个节.text的里面的
偏移量为0的那个地方的这个符号 也就是main这个函数包含的指令序列是
在text的节当中最开始的地方往后的一块区域 它是一种函数,而且是全局的函数
这些指令加起来的长度是有33个字节 第三个符号swap
前面我们讲过它实际上是一种外部定义的符号 因此它不在main.c里面进行定义
所以它在main.c里面是一个未定义的符号 因此呢不知道类型和大小,所以Notype
然后大小呢也不知道,size没有 而且呢是全局的,也就是说它是在其它模块定义的
整个模块可以相互引用的,所以它是一个全局符号Global
这是main.o里面的三个符号,刚才我们看到在
swap.o里面对应的swap.c里面我们也定义了4个符号 在这个里面一共记录了11个符号
最后4个符号呢是我们刚才看到的定义的这4个符号,这个里面我们可以看到,这个符号它是在
Data节当中,最初开始的地方进行定义 它是一种全局符号,并且呢是一种变量是一种数据
Size呢是4,因为我们知道它是一种指针类型的 所以Size是4,然后buf这个符号呢
是一个外部符号,所以它在本模块里面是没有定义的,也没有起始位置 没有定义,Size、
类型也不知道,但是呢知道它是一种全局变量 而swap呢是在swap.c里面定义的一个函数
所以呢它是全局变量,所有的函数都是全局变量
然后呢它是在.text这样的节里面,也就是它是一个函数嘛,所以是代码
而且呢,是在这个text的节当中最开始的地方开始定义的
然后整个的这个函数,所有包含的这个指令加起来的长度是36个字节
然后最后一个是未初始化的一个静态的变量
未初始化的所以类型是COM,因为前面加static是静态的
所以它是一个本地的局部变量,当然它是一个变量,所以是一个数据
它的长度是4个字节,因为它是指针类型所以它的长度是4
刚才前面我们讲过了,这个Size表示最小的长度
应该是4,刚才我们讲它是一个指针类型的,所以最小至少长度是4
然后呢是按4字节对齐的,因为这个是未初始化的在bss节里面的
所以它在value这个字段里面给出来的是对齐方式,这个是我们前面讲的那个例子当中
这两个模块包含的符号表里面的一些信息,这个刚才 我们讲过的,它是一种本地符号并且是未初始化的,没有地址的
所以是按4字节对齐,至少占4个字节,这个是符号表的信息举例
[音乐] [音乐]