【图片+代码】:Linux 动态链接过程中的【重定位】底层原理

目录

  • 动态链接要解决什么问题?
    • 矛盾:代码段不可写
    • 解决矛盾:增加一层间接性
  • 示例代码
    • b.c
    • a.c
    • main.c
    • 编译成动态链接库
  • 动态库的依赖关系
  • 动态库的加载过程
    • 动态链接器加载动态库
    • 动态库的加载地址分析
  • 符号重定位
    • 全局符号表
    • 全局偏移表GOT
    • liba.so动态库文件的布局
    • liba.so动态库的虚拟地址
    • GOT表的内部结构
    • 反汇编liba.so代码

在上一篇文章中,我们一起学习了Linux系统中 GCC编译器在编译可执行程序时,静态链接过程中是如何进行符号重定位的。

为了完整性,我们这篇文章来一起探索一下:动态链接过程中是如何进行符号重定位的。

老样子,文中使用大量的【代码+图片】的方式,来真实的感受一下实际的内存模型。

文中使用了大量的图片,建议您在电脑上阅读此文。

关于为什么使用动态链接,这里就不展开讨论了,无非就几点:
  1. 节省物理内存;
  2. 可以动态更新;

动态链接要解决什么问题?

静态链接得到的可执行程序,被操作系统加载之后就可以执行执行。

因为在链接的时候,链接器已经把所有目标文件中的代码、数据等Section,都组装到可执行文件中了。

并且把代码中所有使用的外部符号(变量、函数),都进行了重定位(即:把变量、函数的地址,都填写到代码段中需要重定位的地方),因此可执行程序在执行的时候,不依赖于其它的外部模块即可运行。

详细的静态链接过程,请参考上一篇文章:【图片+代码】:GCC 链接过程中的【重定位】过程分析

也就是说:符号重定位的过程,是直接对可执行文件进行修改。

但是对于动态链接来说,在编译阶段,仅仅是在可执行文件或者动态库中记录了一些必要的信息。

真正的重定位过程,是在这个时间点来完成的:可执行程序、动态库被加载之后,调用可执行程序的入口函数之前。

只有当所有需要被重定位的符号被解决了之后,才能开始执行程序。

既然也是重定位,与静态链接过程一样:也需要把符号的目标地址填写到代码段中需要重定位的地方。

矛盾:代码段不可写

问题来了!

我们知道,在现代操作系统中,对于内存的访问是有权限控制的,一般来说:

代码段:可读、可执行;
数据段:可读、可写;

如果进行符号重定位,就需要对代码进行修改(填写符号的地址),但是代码段又没有可写的权限,这是一个矛盾!

解决这个矛盾的方案,就是Linux系统中动态链接器的核心工作!

解决矛盾:增加一层间接性

David Wheeler有一句名言:“计算机科学中的大多数问题,都可以通过增加一层间接性来解决。”

解决动态链接中的代码重定位问题,同样也可以通过增加一层间接性来解决。

既然代码段在被加载到内存中之后不可写,但是数据段是可写的。

在代码段中引用的外部符号,可以在数据段中增加一个跳板:让代码段先引用数据段中的内容,然后在重定位时,把外部符号的地址填写到数据段中对应的位置,不就解决这个矛盾了吗?!

如下图所示:

理解了上图的解决思路,基本上就理解了动态链接过程中重定位的核心思想。

示例代码

我们需要3个源文件来讨论动态链接中重定位的过程:main.ca.cb.c,其中的a.cb.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();
}

代码说明:

  1. 定义了 2 个全局变量:一个静态,一个非静态;
  2. 定义了 3 个函数:

func_a2是静态函数,只能在本文件中调用;
func_a1和<

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值