前言:程序的链接最后一部分「动态链接」
这篇博客主要讲在第一次加载并运行时进行动态链接
大致过程如下:
如何使用动态链接
// main.c
void func1();
int main() {
func1();
return 0;
}
// func.c
#include <stdio.h>
void func() {
printf("func \n");
}
void func1() {
printf("func 1\n");
}
-
生成位置无关的共享库
gcc -c func.c
gcc -shared -fPIC -o func.so func.o
-
生成可重定位目标文件
gcc -c main.c
-
与动态链接库一起生成可执行目标文件
gcc -o main main.o ./func.o
-
运行
结果:
func 1
如果把 func.so
从文件夹中删除,那么就运行不了了。
要注意!系统自带的共享库函数是不用自己写进命令里面的,因为命令本身会自动补全
原理
原理正如图所示:
程序在第一次加载并运行时,才重定位链接共享库函数和数据
同样是和静态链接的可执行文件一样的方法运行,为什么动态链接的可执行文件在执行的时候会有动态链接器呢?
程序序头表中有一个特殊的段:INTERP,其中记录了动态链接器目录及文件
也就是说:
加载器发现在其程序头表中有 .interp 段,其中包含了动态链接器路径名 ld-linux.so,因而加载器根据指定路径加载并启动动态链接器运行。动态链接器完成相应的重定位工作后,再把控制权交给 myproc,启动其第一条指令执行
位置无关
之前提到了位置无关,只有共享库被链接成位置无关的代码和数据,链接器才无需修改代码即可将共享库加载到任意地址运行
一共有四种情况:(位置无关主要解决后两者的问题)
模块内的过程调用、跳转
调用或跳转源与目的地都在同一个模块,相对位置固定,只要用相对偏移寻址即可
无需动态链接器进行重定位
模块内数据访问,如模块内的全局变量和静态变量
- .data 节与 .text 节之间的相对位置确定,任何引用局部符号的指令与该符号之间的距离是一个常数
模块外的数据访问,如外部变量的访问
引用其他模块的全局变量,无法确定相对距离
在 .data 节开始处设置一个指针数组(全局偏移表,GOT),指针可指向一个全局变量
GOT 与引用数据的指令之间相对距离固定
编译器为 GOT 每一项生成一个重定位项(如 .rel 节...)
加载时,动态链接器对GOT中各项进行重定位,填入所引用的地址
简而言之:在 .data 节里面有一个 GOT 表,它的相对位置固定,在动态连接时把外部数据的地址填入里面,内部用时,用的是 GOT 表中的地址
模块外的过程调用、跳转
- 也可以用上述的办法,但是调用模块外的函数都要增加三条指令
- 可以使用延迟绑定的方法(留个坑,以后好好介绍)