【链接装载与库】动态链接(下)

动态链接

=上篇=

延迟绑定 (PLT)

动态链接的确有很多优势,比静态链接要灵活得多,但它是以牺牲一部分性能为代价的。主要原因是动态链接下对于全局和静态的数据访问都要进行复杂的GOT定位,然后间接寻址;对于模块间的调用也要先定位GOT, 然后再进行间接跳转,另外一个原因是动态链接的链接工作在运行时完成,即程序开始执行时,动态链接器都要进行一次链接工作。我们将在这一节介绍优化动态链接性能的一些方法。

  • 延迟绑定实现

在动态链接下,程序模块之间包含了大量的函数引用,会耗费不少时间用于解决模块之间的函数引用的符号查找以及重定位。
如果一开始就把所有函数都链接好实际上是一种浪费。所以ELF采用了一种叫做延迟绑定的做法,基本的思想就是当函数第一次被用到时才进行绑定

ELF 使用PLT(Procedure Linkage Table) 的方法来实现
当我们调用某个外部模块的函数时,如果按照通常的做法应该是通过GOT。 PLT为了实现延迟绑定,在这个过程中间又增加了一层间接跳转。调用函数并不直接通过GOT跳转,而是通过一个叫作PLT项的结构来进行跳转。每个外部函数在PLT中都有一个相应的项,比如bar()函数在PLT中的项的地址我们称之为 bar@plt。

bareplt:
jmp *(baraGOT)
push  n
push  moduleID
jump  _dl_runtime_resolve
  1. 第一条指令是一条通过GOT间接跳转的指令,跳转到 bar(), 实现函数正确调用。
    但是为了实现延迟绑定,链接器在初始化阶段并没有将 bar()的地址填入到该项,而是将上面代码中第二条指令 “push n”的地址填入到bar@GOT中,
  2. 第二条指令将一个数字n 压入堆栈中,这个数字是bar这个符号引用在重定位表“rel.plt” 中的下标。
  3. 接着又是一条push指令将模块的ID 压入到堆栈,然后跳转到 _dl_runtime_resolve。 这实际上就是在实现我们前面提到的 lookup(module,function)这个函数的调用:先将所需要决议符号的下标压入堆栈, 再将模块ID压入堆栈,然后调用动态链接器的_dl_runtime_resolve()函数来完成符号解析和重定位工作。_dl_runtime_resolve()在进行一系列工作以后将bar()的真正地址填入到bar@GOT中。

一旦bar()这个函数被解析完毕,当我们再次调用bar@plt 时,第一条jmp指令就能够跳 转到真正的bar()函数中

上面我们描述的是 PLT 的基本原理,PLT真正的实现要比它的结构稍微复杂一些。ELF将GOT拆分成了两个表叫做“.got”和“.got.plt”。其中“.got”用来保存全局变量引用的地址,“.got.plt”用来保存函数引用的地址,另外“.got.plt”还有一个特殊的地方是它的前三项 是有特殊意义的,分别含义如下
在这里插入图片描述

● 第一项保存的是“.dynamic” 段的地址,这个段描述了本模块动态链接相关的信息,我 们在后面还会介绍“.dynamic”段。
● 第二项保存的是本模块的 ID。
● 第三项保存的是 _dl_runtime_resolve的地址。

动态链接相关结构

动态链接情况下,可执行文件的装载与静态链接情况基本 一样。首先操作系统会读取可执行文件的头部,检查文件的合法性,然后从头部中的“Program Header”中读取每个 “Segment”的虚拟地址、文件地址和属性,并将它们映射到进程虚拟 空间的相应位置,这些步骤跟前面的静态链接情况下的装载基本无异。
但是在动态链接情况下,操作系统还不能在装载完可执行文件之后就把控制权交给可执行文件,因为我们知道可执行文件依赖于很多共享对象。这时候,可执行文件里对于很多外部符号的引用还处于无效地址的状态,即还没有跟相应的共享对象中的实际位置链接起来。 所以在映射完可执行文件之后,操作系统会先启动一个动态链接器
在Linux 下,动态链接器ld.so实际上是一个共享对象,操作系统同样通过映射的方式 将它加载到进程的地址空间中。操作系统在加载完动态链接器之后,就将控制权交给动态链接器的入口地址;当所有动态链接工作完成以后,动态链接器会将控制权转交到可执行文 件的入口地址,程序开始正式执行。

  • “.interp”段

系统中哪个才是动态链接器呢,它的位置由谁决定?
是由ELF可执行文件决定。在动态链接的ELF可执行文件中,有一个专 门的段叫做“interp”段,“interp”的内容很简单,里面保存的就是一个字符串,这个字符串就是可执行文件所需要的动态链接器的路径。操作系统在对可执行文件的进行加载的时候,它会去寻找装载该可执行文件所需要相应的动态链 接器,即“.interp”段指定的路径的共享对象。

Linux 下,可执行文件所需要的动态链接器的路径几乎都是 “/lib/ld-linux.S0.2”, 其他的*nix 操作系统可能会有不同的路径。在 Linux的系统中,/lib/ld-linux.so.2通常是一个软链接, 比如在我的机器上,它指向/ib/ld-2.6.1.so,这个才是真正的动态链接器。

我们也可以用这个命令来查看一个可执行文件所需要的动态链接器的路径

readelf -l a.out | grep interpreter
  • “.dynamic”段

动态链接ELF中最重要的结构应该是“.dynamic”段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的 位置、共享对象初始化代码的地址等。

  • 动态符号表

为了完成动态链接,最关键的还是所依赖的符号和相关文件的信息。
为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有一个叫做动态符号表的段用来保存这些信息,这个段的段名通常叫做“.dynsym” 。与“.symtab”不同的是,“.dynsym”只保存了与动态链接相关的符号,对于那些模块内部的符号,比如模块私有变量则不保存。很多时候动态链接的模块同时拥有 “.dynsym” 和“ .symtab”两个表,“.symtab” 中往往保存了所有符号,包括“ .dynsym” 中 的符号。

与“ .symtab”类似,动态符号表也需要一些辅助的表,比如用于保存符号名的字符串表。静态链接时叫做符号字符串表“ .strtab”, 在这里就是动态符号字符串表“.dynstr”; 由于动态链接下,我们需要在程序运行时查找符号,为了加快符号的查找过程,往往还有辅助的符号哈希表 (“.hash”) 。

  • 动态链接重定位表

在动态链接中,导入符号的地址在运行时才确定,所 以需要在运行时将这些导入符号的引用修正,即需要重定位。
动态链接的可执行文件使用的是PIC 方法,但这不能改变它需要重定位的本质。PIC 模式的共享对象也需要重定位。

在前面“静态链接”中分析过的目标文件的重定位十分类似, 唯一有区别的是目标文件的重定位是在静态链接时完成的,而共享对象的重定位是在装载时 完成的。在静态链接中,目标文件里面包含有专门用于表示重定位信息的重定位表,比如 “.rel.text”表示是代码段的重定位表,“.rel.data”是数据段的重定位表。

动态链接的文件中,也有类似的重定位表分别叫做“.rel.dyn”和“.rel.plt”, 它们分别相当于“ .rel.text”和“.rel.data"。"rel.dyn”实际上是对数据引用的修正,它所修正的位置 位于“.got”以及数据段;而“.rel.plt”是对函数引用的修正,它所修正的位置位于“got.plt”。
在这里插入图片描述

当动态链接器需要进行重定位时,它先查找“printf”的地址,“printf”位于libc-2.6.1.s0。 假设链接器在全局符号表里面找到“printf”的地址为0x08801234, 那么链接器就会将这个 地址填入到“got.plt”中的偏移为0x000015d8 的位置中去,从而实现了地址的重定位

  • 动态链接时进程堆栈初始化信息

站在动态链接器的角度看,当操作系统把控制权交给它的时候,它将开始做链接工作, 那么至少它需要知道关于可执行文件和本进程的一些信息。
这些信息往往由操作系统传递给动态链接器,保存在进程的堆栈里面。我们在前面提到过,进程初始化的时候,堆栈里面保存了关于进程执行环境和命令行参数等信 息。事实上,堆栈里面还保存了动态链接器所需要的一些辅助信息数组

动态链接的步骤和实现

动态链接的步 骤基本上分为3步:

  1. 先是启动动态链接器本身
  2. 然后装载所有需要的共享对象
  3. 最后是重定位和初始化
  • 动态链接器自举

我们知道动态链接器本身也是一个共享对象,但是事实上它有一些特殊性。
动态链接器本身不可以依赖于其他任何共享对象;其次是动态链接器本身所需要的全局和静态变量的重定位工作由它本身完成。

这种具有一定限制条件的启动代码往往被称为自举,动态链接器入口地址即是自举代码的入口

  • 装载共享对象

完成基本自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,我们可以称它为全局符号表。然后链接器开始寻找可执行 文件所依赖的共享对象。读取相应的ELF 文件头和“.dynamic”段,然后将它相应的代码段和数据段映射到进 程空间中。如果这个 ELF 共享对象还依赖于其他共享对象,那么将所依赖的共享对象的名 字放到装载集合中。如此循环直到所有依赖的共享对象都被装载进来为止。

  • 全局符号介入与地址无关代码

当上面的步骤完成之后,链接器开始重新遍历可执行文件和每个共享对象的重定位表, 将它们的GOT/PLT中的每个需要重定位的位置进行修正。

重定位完成之后,如果某个共享对象有 “init”段,那么动态链接器会执行 “init”段中的代码,用以实现共享对象特有的初始化过程,比如最常见的,共享对象中的C++的全局/静态对象的构造就需要通过“.init”来初始化。

当完成了重定位和初始化之后,所有的准备工作就宣告完成了,所需要的共享对象也都已经装载并且链接完成了,这时候动态链接器就如释重负,将进程的控制权转交给程序的入 口并且开始执行。

显式运行时链接

支持动态链接的系统往往都支持一种更加灵活的模块加载方式,叫做显式运行时链接, 有时候也叫做运行时加载。 也就是让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载。这种运行时加载在理论上也是很容易实现的。而且一般的共享对象不需要进行任何修改就可以进行运行 时装载,这种共享对象往往被叫做动态装载库 其实本质上它 跟一般的共享对象没什么区别,只是程序开发者使用它的角度不同。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/185286.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python高级语法----深入理解Python协程

文章目录 什么是协程?Python中的协程基本示例协程和事件循环总结Python协程是一种非常强大的并发编程概念,让你能够高效地处理多任务。协程在Python中的使用已经变得越来越流行,特别是在异步编程中。本文将用通俗易懂的语言来介绍协程的概念,并提供实际的代码示例和执行结果…

javascript 操作mysql数据库

目录 一:Javascript访问MYSQL 二:JavaScript中操作Mysql数据库实例 一:Javascript访问MYSQL 1、下载MYSQL的ODBC连接 2、在JS中建立ODBC连接如下: var con new ActiveXObject("ADODB.Connection"); con.Connection…

JS加密/解密之你是否真的明白xss

摘要:跨站脚本攻击(XSS)是当前Web应用程序中最常见的安全威胁之一。本文通过综合分析XSS攻击的原理和特点,提出了一系列全面的防御策略,包括输入验证和过滤、输出编码以及Content Security Policy(CSP&…

护眼灯买哪种好,五款热门专业护眼台灯推荐

护眼台灯的光照一般比较均匀,相比普通台灯,一般具有防蓝光、防频闪等功能,能够提供一个健康舒适的学习、生活灯光环境,建议选购内置智能感光模式的护眼台灯,以确保灯光亮度一直处于均衡状态,让眼睛更轻松。…

查看apk签名

cmd 命令: keytool -v -list -keystore "E:\xxx\release.jks"

浅谈蒙牛乳业有限公司变压器配电系统改造项目的应用

Application of power management system in transformer distribution system Renovation project of Inner Mongolia Meng Niu Dairy (Group) Co., Ltd. 摘要:本文介绍蒙牛乳业(当阳)有限公司低压系统改造电力监控系统,采用智能…

尚硅谷大数据项目《在线教育之实时数仓》笔记006

视频地址:尚硅谷大数据项目《在线教育之实时数仓》_哔哩哔哩_bilibili 目录 第9章 数仓开发之DWD层 P041 P042 P043 P044 P045 P046 P047 P048 P049 P050 P051 P052 第9章 数仓开发之DWD层 P041 9.3 流量域用户跳出事务事实表 P042 DwdTrafficUserJum…

11.9树的表示方法(孩子,父亲,孩子兄弟),树、森林的遍历,一些操作,决策树,前缀树

父亲表示法 优缺点:利用了树中除根结点外每个结点都有唯一的父节点这个性质,很容易找到树根,但是找孩子需要遍历整个线性表。 最近公共祖先 第一种方法,找路径然后比较 如果是搜索树,可以二分查找 不是,…

计算机网络期末复习-Part1

1、列举几种接入网技术:ADSL,HFC,FTTH,LAN,WLAN ADSL(Asymmetric Digital Subscriber Line):非对称数字用户线路。ADSL 是一种用于通过电话线连接到互联网的技术,它提供…

RabbitMQ集群

RabbitMQ概述 1.RabbiMQ简介 RabbiMQ是⽤Erang开发的,集群⾮常⽅便,因为Erlang天⽣就是⼀⻔分布式语⾔,但其本身并不⽀持负载均衡。支持高并发,支持可扩展。支持AJAX,持久化,用于在分布式系统中存储转发消…

excel中超级表和普通表的相互转换

1、普通表转换为超级表 选中表内任一单元格,然后按CtrlT,确认即可。 2、超级表转换为普通表 选中超级表内任一单元格,右键,表格,转换为区域,确定即可。 这时虽然已经变成了普通表,但样式没有…

vue3怎么获取el-form的元素节点

在元素中使用ref设置名称 在ts中通过从element-plus引入formInstance,设置formRef同名名称字段来获取el-form节点

flutter笔记:骨架化加载器

flutter笔记 骨架化加载器 - 文章信息 - Author: Jack Lee (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/article/details/134224135 【介绍】:本文介…

OpenCV 输出文本

PutText() 输出文本 OpenCV5 将支持中文字符的输出, 当前版本OpenCV4原生不支持, 可以使用Contrib包FreeType方式实现, 不过比较麻烦.为了省事, 也可以通过将Mat转成bitmap,然后使用GDI方式输出中文字符. 示例代码 /// <summary>/// OpenCV暂时不能支持中文字符输出,显示…

Qt 继承QAbstractListModel实现自定义ListModel

1.简介 QAbstractListModel是Qt框架中的一个抽象类&#xff0c;用于实现数据模型&#xff0c;用于在Qt的视图组件中展示和编辑列表数据。与QAbstractTableModel类似&#xff0c;它也是一个抽象类&#xff0c;提供了一些基本的接口和默认实现&#xff0c;可以方便地创建自定义的…

C++入门学习(4)引用 (讲解拿指针比较)

上期回顾 在学习完函数重载之后&#xff0c;我们可以使用多个重名函数进行操作&#xff0c;会发现C真的是弥补了好多C语言的不足之处&#xff0c;真的不禁感概一下&#xff0c;时代的进步是需要人去做出改变的&#xff0c;而不是一味的使用啊&#xff01;所以我们今天继续学一下…

浅析三维模型重建的地面控制点精度常见的几个问题及解决方法

浅析三维模型重建的地面控制点精度常见的几个问题及解决方法 在倾斜摄影三维模型重建过程中&#xff0c;地面控制点的精度是影响模型几何精度的关键因素之一。以下是常见的问题及相应的解决方法&#xff1a; 1、问题&#xff1a;地面控制点坐标测量误差较大。 解决方法&#…

《golang设计模式》第三部分·行为型模式-05-仲裁者/中介模式(Mediator)

文章目录 1. 概述1.1 作用1.2 角色1.3 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 仲裁者&#xff08;Mediator&#xff09;可以封装和协调多个对象之间的耦合交互行为&#xff0c;以减弱这些对象之间的耦合关联。 1.1 作用 将多个对象相互耦合的设计转变为所有对象…

【OpenCV实现图像:图像处理技巧之空间滤波】

文章目录 概要导入库空间过滤器模板展示效果分析与总结 概要 空间滤波器是数字图像处理中的基本工具之一。它通过在图像的每个像素位置上应用一个特定的滤波模板&#xff0c;根据该位置周围的相邻像素值进行加权操作&#xff0c;从而修改该像素的值。这种加权操作能够突出或模…