链接
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行
大致过程如下
- 预处理阶段:处理以 # 开头的预处理命令
- 编译阶段:翻译成汇编文件
- 汇编阶段:将汇编文件翻译成可重定位目标文件
- 链接阶段:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件
编译器驱动程序
链接的原因
- 模块化
- 效率
静态链接
- 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置
目标文件
- 可重定位目标文件(.o文件):可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件
- 可执行目标文件(.out文件):可以直接在内存中执行
- 共享目标文件(.so文件):一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接
可重定位目标文件
ELF 文件格式把各种信息,分成一个一个的 Section 保存起来
- .text Section,也叫作代码段或者指令段(Code Section),用来保存程序的代码和指令
- .data Section,也叫作数据段(Data Section),用来保存程序里面设置好的初始化数据信息
- .rodata:只读数据,例如字符串常量、const 的变量
- .bss:未初始化全局变量,运行时会置 0
- .strtab:字符串表、字符串常量和变量名
- .rel.text Secion,叫作重定位表(Relocation Table)。重定位表里,保留的是当前的文件里面未知的一些函数跳转地址,比如printf函数
- .symtab Section,叫作符号表(Symbol Table)。符号表保留了我们所说的当前文件里面定义的函数名称和对应地址的地址簿
符号和符号表
- 全局符号
- 外部符号
- 局部符号
/* ELF符号表条目 */
typedef struct {
int name; /* String table offset */
int value; /* Section offset,or VM address */
int size; /* Obiect size in bytes */
char type:4, /* Data,func,soction,or src file name (4 bits) */
binding:4; /* Local or global (4 bits) */
char reserved; /* Unused */
char section; /* Soction hoader index,ABS.UNDEF */
} Elf_Symbol;
符号解析
处理多重定义的全局符号
- 不允许有多个同名的强符号
- 如果强符号和弱符号同名,则选择强符号
- 如果多个弱符号同名,则随意选择一个
与静态库链接
链接器使用静态库解析引用
重定位
- 重定位节和符号定义
- 重定位节中的符号引用
重定位条目
- R_X86_64_PC32
- R_X86_64_32
重定位符号引用
可执行目标文件
加载可执行目标文件
动态链接共享库
- so文件
- ddl文件
在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中
在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享
多了两个 section,一个是.plt,过程链接表(Procedure Linkage Table,PLT),一个是.got.plt,全局偏移量表(Global Offset Table,GOT)
为了调用共享库里的函数,首先第一步调用PLT[x]
里面的代理代码,这个代理代码会在运行的时候找真正的函数
plt[x]->got[y](发现没有地址)->plt[0]->got[2](存了一个特殊的动态链接库ld-Linux.so,他会负责找到链接的函数)->将找到的地址存回got[y]
从应用程序中加载和链接共享库
JNI
位置无关代码
可以加载而无需重定位的代码称为位置无关代码
PIC数据引用
PIC函数调用
"延迟绑定"
库打桩机制
- 编译时打桩
- 链接时打桩
- 运行时打桩