链接————csapp
一 基本概念
1.1 链接定义:将各种代码和数据收集并组合成一个单一的文件的过程。这个文件可以被加载到存储器并执行。
链接可以执行在编译时,也可以执行在加载时,也可以是运行时。现代操作系统中,连接是由链接器自动执行的。
1.2链接器意义:分离编译。即不用将一个大型应用程序组织为一个巨大的源文件,而是分割成更小的,易于管理的模块,独立修改编译他们。到那个改变一个模块时,只需重新编译它,再将他与之前的已编译的链接就可以了。
1.3学习连接器的意义:
—构造大型程序:构造大型程序时会遇到 缺少模块,缺少库,不兼容库版本引起链接错误。要解决这些你需要理解链接器的工作。
—避免一些编译错误:比如说后面讲的符号解析。
—理解语言的作用域规则:全局与局部变量的区别,static关键字。
—利用共享库
1.4编译器驱动程序
用户在需要时调用预处理器,编译器,汇编器,链接器。
一般c怨言程序:驱动程序先运行c预处理器(cpp),main.c —–> main.i 中间文件
然后,驱动程序运行c编译器(ccl),main.i ——> main.s 汇编
然后,编译驱动程序运行汇编器(as), main.s ——–> main.o 可重定位目标文件
最后,运行链接器(ld)将几个.o文件结合成可执行目标文件
在外壳中输入命令后外壳调用加载器,拷贝可执行文件到存储器,将控制转到程序开头。
二 静态链接
一组可重定位的目标文件及命令行参数输入到静态链接器输出可执行目标文件。可执行目标文件由各种不同的代码和数据节。
链接器的任务:
符号解析:将每个符号引用与定义联系起来。
重定位:编译器与汇编器生成从地址0开始的代码节和数据节,链接器把符号定义与一个存储器位置联系起来,然后修改这些符号引用,让他们只想存储器位置,实现重定位。
目标文件时字节快的集合,每个块是一个独立的逻辑单位,比如有的是代码,有的是数据。
目标文件:
可重定位目标文件:二进制代码和数据,可以与其他可重定问目标文件合并创建可执行目标文件
可执行目标文件:可直接诶拷贝到存储器并执行
共享目标文件:特殊的可重定位目标文件,在加载或运行时动态的加载到存储器中并链接。
三 可重定位目标文件(ELE)
ELE中的节:
ELE头
.text:已编译的机器码
.rodata:只读数据,,比如printf语句的输出格式串
.data:已初始化的c全局变量。局部变量在栈中,不在.data和.bss
.bss:未初始化的全局c变量,在目标文件中不占空间,区分已初始化的与未初始化的目的为了空间效率。
.symtab:符号表。此可重定位目标文件定义和引用的函数及全局变量信息。
.rel.text:.text节中的位置列表。当链接时需要修改。一般,引用外部函数及全局变量的指令需要修改,本地函数指令不需要修改。(不懂????????)
.rel.data:被此文件引用或定义的全局变量的重定位信息。
.debug:调试符号表。程序中定义的局部变量和类型定义,定义和引用的全局变量,原始c源文件。-g调用编译驱动程序时才会得到这张表
.line:原始c源文件的行号与.text中机器指令的映射。-g得到
.strtab:
节头部表:描述节位置与大小,每个节都有一个固定大小的条目,条目在节头部表里
四 符号与符号表
符号:
—由本文件定义并可被其他模块引用的全局符号
—其他文件定义被本文件引用的全局符号
—本文件定义并只在本文件内引用的本地符号
(本地非经太变量全在栈中)
符号表:(描述本文件中的使用的变量与函数的信息,这些信息是这些符号的定义,本地符号的话直接指向定义的节,引用其他文件符号的说明是引用的)
符号表条目数组的条目类型:
typedef struct{
int name;//字符串名字
intvalue;//符号的地址,据定义目标节的偏移值
int size;//目标大小
char type:4,//数据或函数
binding;4;//本地或全局
char reserved;//未用
char section;
}Elf_Symbol;
每个符号都和和目标文件的某一节关联,由section字段表示,该字段是到节头表的索引。有三个特殊的伪节在节头部表中没有条目:ABS不该被重定位的符号。UNDEF:未定义的符号,在本文件中引用,在其他文件中定义。COMMAN:还未分配位置的未初始化的。
五 符号解析:
将每个引用(不是本地定义的)与它输入的可重定位文件的 符号表(上面的那些条目集合)中的一个确定符号定义(符号在此文件中定义)联系起来。(在付号表中找到引用的符号,在其他文件的符号表中匹配这个符号(其他文件定义了这个符号))
对于引用与定义在同一个文件中很简单。编译器保证本地符号只有一个定义,拥有唯一名字。
而对于全局符号引用解析很麻烦,编译器遇到了这种情况,先假设在其他文件中定义的,编译器生成一个链接器符号表条目,并交给链接器处理。如果链接器找不到这个符号的定义,则输出错误。
链接器解析多重定义的全局符号:
编译器向汇编器输出每个全局符号的强弱信息,汇编器把此信息隐含编码在符号表里。函数和已初始化的全局时强,未初始化的是弱。
解析多重定义符号规则:
—不允许有多个强
—一强多弱,选强
—多弱,任选一个
后两个规则会导致一些意想不到且不易察觉的错误。
六 与静态库链接
将所有的目标模块打包成一个静态库。连接器构造可执行文件时,只拷贝静态库的需要的目标模块。
如果没有静态库,编译器开发人员向用户提供函数,可以让编译器自己解析这些函数,用户只需调用即可,但修改函数时,就需要新的编译器版本。另一种方法是将所有函数放在一个可重定位的文件中。但很大,而且许多用不到,浪费存储器空间。而改变一个函数,又要重新编译整个源文件。
静态库;将相关函数编译的目标模块,一个函数一个模块。封装成静态库。
链接器如何使用静态库解析引用:
从命令行左到右扫描重定位文件与存档文件。链接器维持一个可重定位文件集合E,未解析的符号U,前面输入文件中已定义的符号集合D;
—对于命令行的每个输入文件f,判断f时目标文件还是存档文件。目标文件加到E中,修改U,D。
—f是存档文件,匹配U中未解析的符号,若存档文件成员m定义了此符号,将m加到E中,修改U,D。对存档中的文件反复进行此过程,直到U,D不变。其他存档文件丢弃。
—完成扫描,U非空,输出错误。
这样,命令行输入存档的顺序很重要,不然会有错误。
七 重定位(赋存储器地址,改引用)
完成符号解析后:代码中的符号引用与符号定义联系起来。链接器直到输入目标模块的代码节与数据节的大小。开始重定位
重定位:重定位节和符号定义。相同类型节合并一块。给聚合节赋新地址,赋值给每一个单独的节及节定义的每个符号。
重定位节中的符号引用:修改代码节与数据节对符号的引用,使他们指向正确的运行时地址。依赖重定义条目。
重定位条目:汇编器生成目标模块,不知道数据和代码存在存储器中的什么位置,也不知道引用其他模块的符号的位置。汇编器遇到对最中位置未知的引用,就生成一个重定位条目,告诉链接器生成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中,已初始化的数据放在.rel.data中。
typedef struct{
int offset;//此符号在引用他的节中的偏移
int symbol:24,//被修改的引用应该指向的符号(????)
type:8;//如何修改
}Elf32_Rel;
重定位类型:
R_386_PC32:重定位一个使用32位PC相对地址的引用
R_386_32:重定位使用32位绝对地址的引用
重定位符号引用:
— 重定位PC相对引用
32位PC重定位,下一条地址是PC+32位引用;
引用定义地址=(本条地址+4)(解释:32位的,这是下一条地址)+x(最终的32位引用)
得到:x=引用定义地址-本条地址-4
—重定位绝对引用