CSAPP-链接器

静态连接

构造可执行文件的时候,链接器所完成的任务:

1.符号解析

每个符号对应于一个函数,一个全局变量或者静态变量,符号解析的目的是把每个符号引用正好和一个符号定义关联起来。

2.重定位

把每个符号定义和另一个内存位置关联起来,然后重定位这些节,修改所有对这些符号的引用。


目标文件有三种形式


1.可重定位目标文件:eg: prog.o

1.包含二进制代码和数据,编译的时候可以和其他可重定位目标文件合并起来,创建一个可执行目标文件。


2.每个.o文件由对应的.c文件生成


3.每个.o文件代码和数据地址都从0开始
image

2.可执行目标文件:eg: a.out

1.直接可以复制到内存中执行


2.代码和数据地址为虚拟地址空间中的地址

3.共享目标文件(*.so)

3.特殊的可重定位目标文件,能在装入或者运行的时候被装入到内存并且自动被链接


符号和符号表

1.被模块m定义并能够被其他模块引用的全局符号。全局连接器的符号对应于非静态的C函数和全局变量。

2.被其它模块定义而且被模块m引用的全局符号,这些符号叫做外部符号,对应于在其它模块中定义的非静态C函数和全局变量。

3.只能被模块m定义和引用的局部符号,对应于带static的C函数和全局变量。

————————————————————分割线————————————————————————-

Coursera 听课笔记

hello.c->预处理(cpp)->hello.i(ASCII中间文件)->编译器(cc1)->汇编语言文件->(汇编器)->可重定位目标文件(main.o)->链接->可执行目标文件

预处理命令

gcc -E hello.c -o hello.i

cpp hello.c > hello.i

处理源文件中以#开头的预编译指令,包括:
1.删除#define 并且展开所定义的宏
2.处理所有条件预编译指令
3.插入头文件到#include 处,可以用递归方式进行处理
4.删除所有的注释”//“和“/ /”
5.保留所有#pragma编译指令
6.添加行号和文件名标识,方便编译的时候编译器产生调试用的行号信息

编译

把预处理文件进行此法分析,语法分析,语义分析并且优化

编译命令

gcc -S hello.i -o hello.s


gcc -S hello.c -o hello.s


但是得到的.s 汇编代码文件,机器仍然无法识别

汇编

.s 文件称为汇编语言源程序


汇编程序用来把汇编语言源程序转换为机器指令序列


汇编指令和机器指令一一对应,前者是后者的符号表示,他们都属于机器级指令,所构成程序称为机器级代码


gcc -c hello.s -o hello.o


gcc -c hello.c -o hello.o


汇编结果是一个可重定位的目标文件

链接

把多个可重定位目标文件合并来生成可执行目标文件命令

gcc -static -o myporc main.o test.o

链接器的由来:

用符号表示跳转位置,无需修改jmp指令的跳转目标

汇编语言出现:

用助记符表示操作码 jmp mov


用符号表示位置 L0


用助记符表示寄存器


最终进行汇编和链接

链接,先确定L0的地址,然后在jmp指令中填入L0的地址

高级编程语言的出现

子程序起始地址和变量初始地址是符号定义


调用子程序和使用变量是符号的引用

链接操作的步骤

1.确定符号引用关系(符号解析)


2.合并相关.o文件


3.确定每个符号的地址


4.在指令中填入新的地址
(2.3.4都属于重定位)

使用链接好处

模块化


1.一个程序可以分成很多源程序文件


2.可以构建共享函数库


效率高


1.空间上无需包含共享库所有代码


2.源文件当中无需包含共享库所有代码
例如只要直接调用printf()函数,无需包含其源码
可执行文件和运行时的内存只需包含所调用函数的代码,不需要包含整个共享库

1
2
3
4
5
6
7
8
9

main.c
int buf[2] ={1,2};
void swap();

int main(){
swap();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
swap.c
extern int buf[];
int *bufp0 = &buf[0];
static int *bufp1;

void swap(){
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
局部变量temp 分配在栈中,不会在过程(函数)被引用,因此不是符号定义
可执行文件生成 -O2 2级优化 -g 生成调试信息 -o 目标文件名
链接本质:合并相同的节

系统代码: .text


系统数据: .data


未初始化的静态变量: .bss


未初始化的全局变量 COMMON


链接就是要把不同可重定位目标文件中相同的节合在一起,放在可执行目标文件当中

程序头表

目标文件的格式

目标代码:编译器和汇编器处理源代码后生成的机器语言目标代码
目标文件:包含目标代码的文件
静态连接库文件可以由若干个可重定位目标文件组成

ELF(Executable and Linkable Format)

两种视图


1.链接视图


2.执行视图

image

可执行目标文件 的节(section)是ELF文件中具有相同特征的最小可处理单位,例如 .text:代码,.data节数据,.rodata只读数据,.bss未初始化的数据

可重定位目标文件
由不同的段(Segment)组成,描述节如何映射到存储段中,多个节可以映射到同一个段中,例如可以合并
.data节和.bss节,映射到一个可读可写的数据段中。

链接视图——可重定位目标文件格式

image

image


补充: 节头表包含每个节的节名,偏移以及大小。


从ELF头读取节头表,再在节头表中读取节的信息

C语言规定

未初始化的全局变量和局部静态变量默认初始值为0

把.bss 与 .data分开的好处

data节中存放具体的初始值,需要占磁盘空间
.bss无需存放初始值,只要说明.bss每个变量将来在执行的时候占用多少字节就行
因此.bss实际上不占用磁盘空间,提高了磁盘空间利用率



所有 未初始化的全局变量和局部静态变量都被汇总到.bss节中,通过专门的“节头表”来说明应该为.bss节预留多大的空间


.bss 最初是UA-SAP汇编程序中所用的一个伪指令,为符号预留一块内存空间。

image

ELF头占52个字节
Elf_Half 占2个,e_ident 16个, 其他的占4个

image

在链接视图中,虚拟地址都为0,因为其并不会执行

执行视图–可执行目标文件

IMGAE

总结

1.可执行目标文件是ELF的执行视图,由不同的段组成

2.可重定位目标文件是ELF格式的链接视图,由不同的节组成

3.两种目标文件的数据都是二进制表示的补码形式

4. 链接的本质:

把指定的若干个可重定位目标模块的代码和数据分别合并,以生成一个只读代码段和一个可读可写数据段

5. 链接器主要功能:

符号解析以及重定位

6.符号解析的含义

就是对每个需要链接的模块中的符号引用找到对应的符号定义,把符号引用与每一个唯一的符号定义进行绑定。

7.重定位

把合并后的只读代码段以及可读可写数据段中符号引用处的引用位置重新填上新的数值,用来指向绑定的定义符号。