Chapter7 链接
1.为了构造可执行文件,链接器必须完成两个主要任务:
(1)符号解析:目的是将每个符号引用正好和一个符号定义关联起来
(2)重定位:编译期和汇编器生成从地址0开始的代码和数据节,通过把每个符号定义与一个内存位置关联起来,修改所有对这些符号的引用,使得它们指向这个内存位置
2.目标文件(*.o):一个以文件形式存放在磁盘中的字节序列,是按照特定的目标文件格式来组织的,Linux下为ELF,格式如下图所示:
3.ELF可重定位目标文件
如图所示,夹在ELF头和节头部表之间的都是节(section),主要有:
(1).text:已编译程序的机器代码(就是经过预处理->编译->汇编生成的代码的机器码)
(2).data:已初始化的全局和静态变量
(3).bss:未初始化的全局和静态变量。区分已初始化和未初始化主要是为了节省空间
(4).symtab:符号表,存放在程序中定义和引用的函数以及全局变量的信息
(5).rel.text/.rel.data:一个.text/.data中位置的列表,包含重定位信息(因为生成的目标文件是从地址0开始的,需要在运行时根据此列表修改为真实的地址)
3.符号表的类型
(1)全局符号:由模块m定义并被模块m引用的符号
(2)外部符号:由其他模块定义并被模块m引用的符号
(3)局部符号:只被模块m定义和引用的局部符号(c中为static变量和函数,要与局部变量区分开)
注意,.symtab中不包含对应于本地非静态程序变量的任何符号(局部变量的符号),这些符号在运行时在栈中被管理。有个例外,就是本地静态变量不在栈中管理,而是在.data或.bss为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号
4.符号表的条目(entry)
即.symtab节在目标文件中是如何被组织的,下面用代码来说明其格式:
typedef struct { int name; /*字节偏移,指向符号的字符串名字*/ char type:4; /*类型(数据或函数)*/ char binding:4; /*本地或者全局*/ char reserved; short secotion; /*被分配到目标文件的哪个节中*/ long value; /*距定义目标的节的起始位置的偏移*/ long size; }
用GNU READELF程序读取某个目标文件,结果如下:
Num: Value Size Type Bind Vis Ndx Name
8: 0000000000000000 24 FUNC GLOBAL DEFAULT 1 main
9: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 array
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sum
5.符号解析
链接器解析符号引用的方法是将对每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来:
(1)局部符号:对于和引用定义在相同模块中的局部符号的引用,符号解析简单明了,编译器只允许每个模块中每个局部符号有一个定义(如main、array)
(2)全局符号:当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号在其他模块中定义,生成一个链接器符号表条目,并把它交给链接器处理(如sum)