程序的动态链接(2):地址无关代码

前言

动态库的一个主要目的就是允许多个正在运行的进程共享内存中的库代码,以节约内存资源。现代系统使用了一种称为地址无关代码(Position-Indepent Code, PIC)的技术来编译动态库,使用这种技术,可以将动态库加载到内存的任何位置而无需链接修改,所有进程都可以共享动态库中代码的单一副本。

地址无关代码

PIC的基本思想是将指令中那些需要进行重定位的部分剥离出来和数据部分放在一起,这样指令部分就可以保持不变,而数据部分在每个进程中都可以拥有一个副本。为了实现PIC,关键在于如何处理动态库中的各种符号引用。下列代码中涵盖了在动态库中会遇到到的各种符号引用类型:
在这里插入图片描述
通过对动态库中的符号引用进行分类,主要可分为四种情况:

  • 类型一:模块内部的数据访问
  • 类型二:模块内部的函数调用
  • 类型三:模块外部的数据访问
  • 类型四:模块外部的函数调用

通常,对于模块内部符号的引用,可以利用PC相对寻址,然后由静态链接在构造目标文件时进行重定位,就可以使之成为PIC;然而对动态库定义的外部函数以及对全局变量的引用,还需要编译器进行一些特殊的处理。现在围绕这些类型的引用,我们可以看看现代编译系统是如何为其生成PIC的代码。

模块内部的数据访问

动态库在被加载到内存时,库文件中任何一条指令与其要访问的模块内部数据之间的相对位置是固定的,因此使用当前指令地址加上固定的偏移量就能是想访问模块内部数据。x86_64体系结构下,数据寻址已经支持相对当前PC指针寄存器的寻址方式:
在这里插入图片描述

模块内部的函数调用

由于被调用的函数与调用者都处于同一个模块中,它们之间的相对位置是固定的,因此使用相对地址调用就可以解决问题。在现代系统中,模块内的函数调用都是相对地址调用或基于寄存器的相对调用,该种指令无需重定位:
在这里插入图片描述

模块外部的数据访问

由于模块间的数据访问目标地址要等到装载时才能决定,为了实现PIC,编译系统使用了一些新的数据结构:全局偏移表(Global Offset Table,GOT)。GOT放置在数据段中,因此对于每个进程都有独立的副本,表中存放了所有外部变量的地址,由动态链接器在加载模块的时候,通过查找外部变量的地址进行填充;当指令需要引用外部变量时,可以通过GOT中相对应的项间接引用。
在这里插入图片描述

GOT实现指令地址无关性的关键在于,模块在编译时,GOT相对于当前指令的偏移是固定的,并且每个外部变量在GOT中的偏移也是可以确定的。这里

模块外部的函数调用

模块间的函数调用原理可以使用与数据访问类似的实现,只需要在GOT表中存放目标函数的地址即可。

全局符号介入

一个共享目标文件里面的全局符号被另一个共享目标文件的同名全局符号覆盖的现象,称作共享对象全局符号介入。Linux下动态链接器处理全局符号介入问题的规则是这样的:当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略。

全局符号介入对PIC的影响

共享对象全局符号介入引入了这样的一个问题:如果在共享对象A中定义了一个全局符号global,共享对象B同样定义了global全局符号,并且在本模块中也使用了这个全局符号,那么在最后可执行文件依序加载A和B的时候,B中的相同符号就会被A覆盖掉,那么共享对象B和外部模块在访问符号时就会出现不一致。相同问题对于函数引用也有可能出现。

为了解决这个问题,一个思路就是将共享对象内定义的需要被外部模块引用的符号作为模块外部符号进行处理,包括全局变量和函数。如果使用更直白的表述就是,只有对于模块内使用static关键字进行修饰的符号(即文件内作用域)才视为模块内的符号进行处理,即类型一和类型二中使用的处理;而非static修饰的符号使用类型三和类型四中的情况进行处理。

共享模块的全局变量问题

当一个可执行文件引用了一个定义在共享对象的全局变量的时候,由于可执行文件不使用PIC技术,它会以访问普通数据的方式来引用这个全局变量。于是,在链接生成可执行文件的时候,就需要进行重定位工作。为了使链接过程可以正常执行,链接器会在创建可执行文件时,在其.bss段创建该变量的副本,并使用该副本的地址进行重定位。

ELF共享库在编译时,默认将定义模块内部的全局变量当作定义在其它模块的全局变量,使用GOT来实现变量访问。当共享库被装载时,某个全局变量在可执行文件中存在副本,则动态链接器会使用该副本的地址来填充GOT的对应表项。

相关参考

  • 《程序员的自我修养——链接、装载与库》
  • 《深入理解计算机系统》
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言中创建动态链接库供C程序使用的步骤如下: 1. 写好要封装的C语言函数或者模块的代码,并将其保存为一个或多个C文件。 2. 编写一个头文件,将要使用的函数或者模块的声明放在该头文件中。 3. 打开终端或者命令提示符,使用GCC编译器将C文件编译为目标文件。命令如下: `gcc -c 文件名.c -o 输出的目标文件名.o` 这样会将C文件编译为目标文件,目标文件是二进制文件,用来存储编译后的机器码。 4. 将编译好的目标文件打包成动态链接库。动态链接库的命名规则为"libxxx.so",其中xxx为动态链接库的名字。命令如下: `gcc -shared 目标文件1.o 目标文件2.o -o libxxx.so` 这样会将目标文件打包成动态链接库。动态链接库是一个二进制文件,包含了函数或者模块的机器码。 5. 将生成的动态链接库文件放置到C程序的运行路径下,使用时可通过指定库的名字链接到该库。 6. 在C程序中引入头文件,并调用动态链接库中的函数或者模块。 以上是使用GCC编译器创建动态链接库供C程序使用的基本步骤。通过这种方式封装功能,可以实现代码的模块化和重用,提高程序的可维护性和可扩展性。 ### 回答2: 使用者使用动态链接库的主要目的是为了方便地重复使用某些功能代码。在C语言中,可以使用如下步骤来创建并使用动态链接库。 首先,创建动态链接库的源代码文件,该文件包含了要提供的功能代码。可以使用C语言编写这个文件,其中可能包含一些函数和全局变量等。 接下来,使用编译器将源代码文件编译成目标文件。在这个过程中,需要使用适当的编译选项来指定生成动态链接库而不是可执行文件。比如,在GCC编译器中,可以使用"-shared"选项来生成动态链接库。 然后,使用编译器将目标文件链接成动态链接库文件。这一步会生成扩展名为".so"(在Linux系统上)或".dll"(在Windows系统上)的文件。在该步骤中,需要提供一些额外的链接选项,以确保正确地生成动态链接库。 最后,使用者可以在自己的C语言程序中引用和使用动态链接库。可以通过在程序中包含相应的头文件并使用相关的函数和变量来调用动态链接库中的功能。 需要注意的是,使用者在编译和链接自己的程序时,需要指定动态链接库的位置和名称,以便在运行时正确加载和使用动态链接库。这可以通过编译选项和链接选项来实现。 总之,通过以上步骤,可以创建一个供C语言程序使用的动态链接库,并在程序中使用其中的功能代码。这样可以提高代码的复用性和可维护性,同时也便于程序的调试和更新。 ### 回答3: 编程语言C中的动态链接库(Dynamic Link Library,简称DLL)是一个可重用的代码和数据集合,可以在不同的程序中被调用。以下是创建动态链接库供C使用的基本步骤: 1. 编写C代码:首先,编写包含所需功能的C代码。将这些代码组织成一个或多个函数,这些函数可以是库的接口。 2. 创建头文件:创建一个头文件(.h文件),其中包含库的函数声明和必要的常量和类型定义。这个头文件将作为客户端程序动态链接库之间的接口。 3. 编译动态链接库:使用C编译器(例如gcc)将C代码编译成目标文件,使用以下命令生成位置无关的目标文件: ``` gcc -c -fPIC library.c -o library.o ``` 其中,`-c`选项表示只编译不链接,`-fPIC`选项表示生成位置无关代码,`library.c`是你的源代码文件名,`library.o`是生成的目标文件名。 4. 创建动态链接库:使用以下命令将目标文件创建为动态链接库: ``` gcc -shared -o liblibrary.so library.o ``` 其中,`-shared`选项表示生成动态链接库,`-o liblibrary.so`指定输出的库文件名为`liblibrary.so`。 5. 安装动态链接库:将生成的动态链接库文件(`liblibrary.so`)复制到系统的默认库目录(例如`/usr/lib`)。使用以下命令: ``` sudo cp liblibrary.so /usr/lib ``` 注意:根据操作系统和环境设置,可能需要提供管理员权限。 6. 使用动态链接库:在你的C程序中,通过包含头文件(步骤2)并使用函数声明来调用动态链接库中的函数。编译时需要链接动态链接库,可以使用以下命令: ``` gcc client.c -o client -llibrary ``` 其中,`client.c`是你的客户端程序代码文件名,`-llibrary`表示链接名为`liblibrary.so`的动态链接库。 这样,你就成功地创建了一个动态链接库,供其他C程序调用。在客户端程序中,只需要包含头文件并链接动态链接库,就能使用其中定义的函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值