程序的链接

可重定位目标文件和可执行目标文件都是机器语言目标文件,前者由单个模块生成,后者是多个模块组合而成

两者都是按照一定的格式以二进制字节序列构成的一种目标文件,包括代码节(.text),只读数据节(.rodata),已初始化全局数据节(.data)和未初始化全局数据节(.bss)

1.目标文件的格式

目标代码: 指编译器和汇编器处理源代码后所生成的机器语言目标代码

目标文件:指包含目标代码的文件

Windows:PE格式,可移植可执行(Portable Executable,简称PE)

Linux等类UNIX:ELF格式,可执行可链接(Executable and Linkable Format,简称ELF)

2.链接的好处

  • 模块化:①一个程序可以分成很多源程序文件②可构建公共函数库,如数学库,标准C库等(代码重用,提高开发效率)
  • 效率高:①只需重新编译被修改的源程序文件,然后重新链接②无需包含共享库所有代码,只需包含所调用函数的代码

3.链接的步骤

静态链接器将多个可重定位目标文件合成一个可执行目标文件

当启动一个可执行目标文件执行时,首先会通过某种方式调出常驻内存的名为加载器(loader)的操作系统程序来处理。例如,任何UNIX程序的加载执行都是通过调用execve系统调用函数来启动加载器的。加载器解析可执行ELF文件中的程序头表信息,对虚拟内存地址空间进行分页并初始化页表项内容,加载时,只读代码段和可读写数据段对应的页表项都被初始化为未缓存页,即有效位为0,并指向磁盘中可执行目标文件中适当的地方。因此,程序加载过程中,实际上并没有真正从磁盘上加载代码和数据到主存,而是仅仅创建了只读代码段和可读写数据段对应的页表项。只有在执行代码的过程中发生了缺页异常,才会真正从磁盘加载代码和数据到主存。这种方式,使得我们可以运行那些远大于我们实际物理内存的程序,何程序都不需要一次性加载完所有指令和数据,只需要加载当前需要用到就行了

链接的本质:合并相同的“节”

第1步:符号解析

目的:将每个符号引用与一个确定的符号定义建立关联

编译器将所有符号存放在可重定位目标文件的符号表中

符号表是一个结构数组,每个表项包含符号名、长度和位置等信息

第2步:重定位

将多个代码段与数据段分别合并为一个单独的代码段和数据段,确定每个定义的符号在虚拟地址空间中的绝对地址,将可执行文件中符号引用处的地址修改为重定位后的地址信息

可见,重定位细分为3步:合并相关.o文件—>确定每个符号的地址—>在指令中填入新地址

4.动态链接

共享目标文件(share object file),也称为共享库文件,是一种特殊的可重定位目标文件。里面记录了相应的代码、数据、重定位表和符号表信息,能在可执行目标文件装入或运行时被动态地装入内存并自动被链接,该过程称为动态链接,由一个称为动态链接器的程序来完成

动态链接有两个重要特性

共享性:共享库中的代码段在内存中只有一个副本

动态性:共享库只在使用它的程序被加载或被执行时才加载到内存被动态链接

动态链接中有个重要概念:位置无关代码(Position-Independent Code,PIC)

共享库代码在内存中的位置可以是不确定的 ,即使共享库代码的长度发生变化,也不影响调用它的程序,引入PIC后,链接器无需修改代码即可将共享库加载到任意地址运行

地址相关的代码,比如绝对地址代码、利用重定位表的代码等等,都是地址相关的代码。你回想一下我们之前讲过的重定位表。在程序链接的时候,我们就把函数调用后要跳转访问的地址确定下来了,这意味着,如果这个函数加载到一个不同的内存地址,跳转就会失败

PLT(Procedure Linkage Table)与GOT(Global Offset Table)

如果要使用延迟绑定(Lazy Binding)技术,就需要用到PLT。若不用延迟绑定技术,只用GOT就可以实现位置无关代码,只不过实现开销更大

在动态链接对应的共享库,我们在共享库的 data section 里面,保存了一张全局偏移表。虽然共享库的代码部分的物理内存是共享的,但是数据部分是各个动态链接它的应用程序里面各加载一份的。所有需要引用当前共享库外部的地址的指令,都会查询 GOT,来找到当前运行程序的虚拟内存里的对应位置。而 GOT 表里的数据,则是在我们加载一个个共享库的时候写进去的

不同的进程,调用同样的共享库函数,各自 GOT 里面指向最终加载的动态链接库里面的虚拟内存地址是不同的

这样,虽然不同的程序调用的同样的动态库,各自的内存地址是独立的,调用的又都是同一个动态库,但是不需要去修改动态库里面的代码所使用的地址,而是各个程序各自维护好自己的 GOT,能够找到对应的动态库就好了

下图中说GOT表里的内容是运行时计算出来的,非编译时确定的,意思是用了延迟绑定技术(延迟到第一次函数调用时对其地址进行重定位,重定位的实质就是填入准确地址)

若不使用延迟绑定技术,那么GOT表里的内容就是就是在加载时通过动态链接得到的

GOT表位于共享库自己的数据段里。GOT表在内存里和对应的代码段位置之间的偏移量,始终是确定的。这样,我们的共享库就是地址无关的代码,对应的各个程序只需要在物理内存里面加载同一份代码。而我们又要通过各个可执行程序在加载时,生成的各不相同的GOT表,来找到它需要调用到的外部变量和函数的地址

可见,共享库函数并不是通过预先已经确定好的函数名称来调用,而是利用当时它在内存里面的动态地址来调用

位置无关代码生成的共享库,只要在内存里面保留一份就好了。这样,我们不仅能够做到代码在开发阶段的复用,也能做到代码在运行阶段的复用

总结:

GOT,PLT是每个可执行文件都会有的 ,调用某个共享库的函数时,GOT用来存储经计算后的函数地址,PLT存的是另外的指令,意思就是:got表里没有该函数地址就发起地址计算,并存好,如果有了,就直接操纵指令跳转

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值