目录
- 动态链接要解决什么问题?
- 矛盾:代码段不可写
- 解决矛盾:增加一层间接性
- 示例代码
- b.c
- a.c
- main.c
- 编译成动态链接库
- 动态库的依赖关系
- 动态库的加载过程
- 动态链接器加载动态库
- 动态库的加载地址分析
- 符号重定位
- 全局符号表
- 全局偏移表GOT
- liba.so动态库文件的布局
- liba.so动态库的虚拟地址
- GOT表的内部结构
- 反汇编liba.so代码
在上一篇文章中,我们一起学习了Linux
系统中 GCC
编译器在编译可执行程序时,静态链接过程中是如何进行符号重定位的。
为了完整性,我们这篇文章来一起探索一下:动态链接过程中是如何进行符号重定位的。
老样子,文中使用大量的【代码+图片】的方式,来真实的感受一下实际的内存模型。
文中使用了大量的图片,建议您在电脑上阅读此文。
关于为什么使用动态链接,这里就不展开讨论了,无非就几点:
- 节省物理内存;
- 可以动态更新;
动态链接要解决什么问题?
静态链接得到的可执行程序,被操作系统加载之后就可以执行执行。
因为在链接的时候,链接器已经把所有目标文件中的代码、数据等Section
,都组装到可执行文件中了。
并且把代码中所有使用的外部符号(变量、函数),都进行了重定位(即:把变量、函数的地址,都填写到代码段中需要重定位的地方),因此可执行程序在执行的时候,不依赖于其它的外部模块即可运行。
详细的静态链接过程,请参考上一篇文章:【图片+代码】:GCC 链接过程中的【重定位】过程分析。
也就是说:符号重定位的过程,是直接对可执行文件进行修改。
但是对于动态链接来说,在编译阶段,仅仅是在可执行文件或者动态库中记录了一些必要的信息。
真正的重定位过程,是在这个时间点来完成的:可执行程序、动态库被加载之后,调用可执行程序的入口函数之前。
只有当所有需要被重定位的符号被解决了之后,才能开始执行程序。
既然也是重定位,与静态链接过程一样:也需要把符号的目标地址填写到代码段中需要重定位的地方。
矛盾:代码段不可写
问题来了!
我们知道,在现代操作系统中,对于内存的访问是有权限控制的,一般来说:
代码段:可读、可执行;
数据段:可读、可写;
如果进行符号重定位,就需要对代码进行修改(填写符号的地址),但是代码段又没有可写的权限,这是一个矛盾!
解决这个矛盾的方案,就是Linux
系统中动态链接器的核心工作!
解决矛盾:增加一层间接性
David Wheeler
有一句名言:“计算机科学中的大多数问题,都可以通过增加一层间接性来解决。”
解决动态链接中的代码重定位问题,同样也可以通过增加一层间接性来解决。
既然代码段在被加载到内存中之后不可写,但是数据段是可写的。
在代码段中引用的外部符号,可以在数据段中增加一个跳板:让代码段先引用数据段中的内容,然后在重定位时,把外部符号的地址填写到数据段中对应的位置,不就解决这个矛盾了吗?!
如下图所示:
理解了上图的解决思路,基本上就理解了动态链接过程中重定位的核心思想。
示例代码
我们需要3
个源文件来讨论动态链接中重定位的过程:main.c
、a.c
、b.c
,其中的a.c
和b.c
被编译成动态库,然后main.c
与这两个动态库一起动态链接成可执行程序。
它们之间的依赖关系是:
b.c
代码如下:
#include <stdio.h>
int b = 30;
void func_b(void)
{
printf("in func_b. b = %d \n", b);
}
代码说明:
定义一个全局变量和一个全局函数,被 a.c 调用。
a.c
代码如下(稍微复杂一些,主要是为了探索:不同类型的符号如何处理重定位):
#include <stdio.h>
// 内部定义【静态】全局变量
static int a1 = 10;
// 内部定义【非静态】全局变量
int a2 = 20;
// 声明外部变量
extern int b;
// 声明外部函数
extern void func_b(void);
// 内部定义的【静态】函数
static void func_a2(void)
{
printf("in func_a2 \n");
}
// 内部定义的【非静态】函数
void func_a3(void)
{
printf("in func_a3 \n");
}
// 被 main 调用
void func_a1(void)
{
printf("in func_a1 \n");
// 操作内部变量
a1 = 11;
a2 = 21;
// 操作外部变量
b = 31;
// 调用内部函数
func_a2();
func_a3();
// 调用外部函数
func_b();
}
代码说明:
- 定义了 2 个全局变量:一个静态,一个非静态;
- 定义了 3 个函数:
func_a2
是静态函数,只能在本文件中调用;func_a1
和<