LWN:看一下动态链接过程!

本文详细介绍了Linux系统中动态链接程序的工作原理,包括链接过程、重定位、地址空间设置、PLT的使用以及延迟链接策略。着重探讨了动态链接器如何为程序准备地址空间,以及性能优化措施,如地址空间布局随机化和PLT重写。
摘要由CSDN通过智能技术生成

关注了就能看到更多这么棒的文章哦~

A look at dynamic linking

By Daroc Alden
February 13, 2024
Gemini translation
https://lwn.net/Articles/961117/

动态链接程序是现代 Linux 系统的关键组件,负责设置大多数进程的地址空间。虽然随着最初推动动态链接的因素变得越来越没有那么重要了,使得静态链接二进制文件变得越来越流行,但是动态链接仍然是默认设置。本文会介绍一下动态链接程序为执行程序做准备工作时的采取的步骤。

调用链接程序(linker)

当 Linux 内核受命执行一个程序时,它会查看文件头以确定其是哪种类型的程序。然后,内核查阅自身针对 shell 脚本和本机二进制文件的内置规则,以及 binfmt_misc 设置(这是允许用户注册自定义程序解释器的一个内核特性)以确定如何处理该程序。以 “#!” 开头 的文件被标识为作为该行其余部分中指定的解释器的输入。此功能就是让内核显得可以直接执行 shell 脚本的原因 — 实际上它执行的是一个解释器,并将脚本作为参数传递。另一方面,以 “\x7fELF”开头的文件被识别为 ELF 文件。内核首先查看该文件是否包含 程序标头 中的 PT_INTERP 元素。若存在,则此元素表明该程序是动态链接的。

PT_INTERP 元素还指定程序期望哪个动态链接程序进行链接 — 又称解释器,令人困惑。在 Linux 中,动态链接程序通常存储在特定体系结构的路径下,例如 /lib64/ld-linux-x86-64.so.2. 不允许链接程序本身动态链接,以避免无限嵌套。找到动态链接程序后,内核会以与其他任何静态链接可执行文件相同的 方式 设置其初始地址空间,并给它一个指向要执行的程序的打开的文件描述符。

重定位

动态链接程序的工作是对进程的地址空间进行安排,使其既包含主可执行文件本身,还包含该文件所依赖的所有库。对于现代可执行文件来说,这几乎肯定会加载 位置无关代码(position-independent code),这种代码设计用于从非固定基本地址运行。许多代码段仍然需要知道内存的不同部分(section)的位置,因此位置无关可执行文件包含一份 “重定位(relocations)”列表:动态链接程序需用内存中各种组件的实际地址来修补的二进制文件中特定位置。在大多数程序中,大部分重定位都是对 全局偏移表 (GOT) 的修补,其中包含指向全局变量或已加载的段(section),还有链接时常量(link-time)的指针。

允许动态链接程序将不同的组件加载至不同的地址能够起到几个作用。最早的位置无关代码形式存在于使用基址寄存器访问内存地址的体系结构中,使得一个程序的多个副本能够以不同的地址驻留在内存中。动态地址转换(dynamic address translation)的发明使得该动机变得无关紧要了。现代工具链针对 地址空间布局随机化 (ASLR) 使用了这个灵活功能,允许动态链接程序向所选位置添加一个随机值。静态链接带有位置无关代码的程序(包括动态链接程序本身)都将会加载到内核选择的随机地址上。因此,动态链接程序的第一个主要任务是读取它自己的程序标头并对其本身进行重定位。此过程包括用全局结构和函数的位置,来修补后续代码 — 在进行这些重定位之前,链接器会小心不从其他编译单元调用任何函数或访问任何全局变量。

动态链接器进行重定位后,就可以调用其特定平台的函数来执行特定操作系统的设置。在 Linux 中,它调用 brk() 来设置进程的数据段,包括为其自身状态分配一些空间,并通知 malloc() 将分配放在新分配的空间中(它与程序最终的堆是分开的)。此时, malloc() 引用了一个临时实现,该实现甚至无法释放内存,该实现会在动态链接器识别并对所需依赖项应用重定位时来使用。

完成依赖项识别的第一步是设置链接映射(link map) — 即由 dlinfo() 使用的记录信息结构。完成后,动态链接器会找到内核映射的 vDSO,一个共享对象包括可以在无需切换至内核的情况下为某些系统调用服务的代码,这是一种常用于 gettimeofday() 系统调用的技术。动态链接器将 vDSO 放入链接映射中,以便处理其他依赖项的链接时所使用的同一代码可以处理调用 vDSO 的共享对象。

到此,似乎可以实际读取和链接程序所依赖的共享对象了,但还需要完成一个步骤。用户可以通过在 LD_PRELOAD 环境变量中指定共享对象来在运行时覆盖函数。通过这种方式覆盖函数可以用于许多目的,例如,对应用程序进行调试、使用备用分配器(例如 Boehm-Demers-Weiser 型保守垃圾回收器 或 jemalloc)、或使用诸如 libfaketime 之类的工具来伪造该应用程序的时间和日期。动态链接器首先解析预加载库,这样在以后链接库时,它可以直接向它们发送已覆盖的函数定义。

现在,动态链接器已经具备完成程序地址空间排列所需的一切条件。从正在加载的程序开始,链接器在程序头中查找 DT_NEEDED 声明,这些声明表明程序依赖另一个共享对象。链接器会递归搜索这些 DT_NEEDED 声明,构建程序的任何传递依赖项所需的所有共享对象的列表。 DT_NEEDED 条目可以包含绝对路径,但也可以包含通过查阅 LD_LIBRARY_PATH 环境变量中的目录或一组默认目录来解析的相对路径。然后,动态链接器反向遍历这个依赖项的列表,这样,某一共享对象的依赖项在其加载之前就已经加载好了。

对于每个共享对象,链接器都会打开已解析的文件,将其加载到地址空间中新分配(并使用 ASLR 随机化处理过)的位置,然后执行共享对象头中列出的重定位集。然后,它将共享对象添加到链接映射中。

其中的例外是动态链接器自身。程序允许将动态链接器依赖为库,以提供诸如 dlinfo() 之类的功能,但是如果链接器在循环中间对自己应用重定位,它就会损坏,因此它将自身排除在依赖项列表之外。如上所述,它已经对自己应用了重定位。但是,链接器偶尔会使用可通过预加载库覆盖的函数。因此,一旦程序的所有依赖项都已放入地址空间,链接器就会对自己最后进行一次重定位,以便现在它可以引用其使用的任何函数的已覆盖版本。现在,动态链接器使用的 malloc() 实现从到此为止使用的简化实现切换到程序的其余部分使用的通用实现。

随着所有共享库的加载,动态链接器已经完成了大部分工作。但是,在跳转到主程序之前,它会设置 线程本地存储 (TLS),并执行由 C 库要求的任何初始化。然后,它把内核提供程序的参数状态及其环境恢复出来,并跳转到程序的入口点。

用户代码

人们可能会认为动态链接器的职责在主程序开始时就已结束,但事实并非如此。它不仅必须对运行时需要的 dlopen() 新共享对象的请求进行服务,而且还有一部分工作会在所加载程序的生命周期内运行:更新过程链接表 (PLT, Procedure Linkage Table)。

尽管程序可以简单地使用重定位来直接修补程序正文中的 CALL 指令,但是这样的“文本重定位(text relocation)”会导致两个性能问题。首先,由于所需的重定位数将取决于调用给定函数的次数(可能很大),因此将这些重定位最初应用于共享对象可能较慢。其次,由于文本重定位会弄脏包含程序可执行代码的内存页面,因此运行同一程序的不同进程不再能够共享相同的底层内存,从而增加了程序的内存使用情况。这些性能问题意味着动态链接器的维护者 普遍反对 进行文本重定位。

通过创建一个 PLT — 一个特殊的独立部分,其中包含对每个外部定义函数的间接引用,现代编译器和链接器可以解决这个问题。从程序内部调用这些函数会编译为对 PLT 的调用,然后 PLT 中包含一个跳转到外部函数的真实位置的指令。PLT 也可以直接使用文本重定位,但大多数体系结构改为通过第二个 GOT(从程序的正常 GOT 分离,在其自己称为 “.plt.got” 的 section 中)中存储的函数指针进行间接跳转。对于无法以紧凑方式对跳转到地址空间的任意部分进行编码的体系结构来说,分离函数指针很有用,但对于一项最终提升性能的技巧来说也很有用:延迟链接。

0297de188368f5ced399787b1524435b.png

与那些指向数据、在程序开始运行之前需要解析的重定位不同(因为动态链接器无法知道何时会访问它们),PLT 的 GOT 中的重定位不必立即应用。动态链接器最初用来自动态链接器自身的一个函数的地址来填充 PLT 的 GOT。此函数查询链接映射以确定有问题的外部符号位于何处,然后重写 PLT 的 GOT 的相应条目。仅对实际调用的外部函数执行链接,而且仅在程序启动后执行。对于大多数程序来说,这可以提高性能并使程序的初始启动速度更快。

可以使用 LD_BIND_NOW 环境变量,或通过使用 “-z now” 链接器选项编译程序来关闭延迟链接。就新发布的 glibc 版本 2.39 来说,glibc 的动态链接器还支持重写 PLT 元素以使用直接跳转而不是间接跳转,用在一些关注性能的系统上。报道 文章中公开宣称,引入 PLT 重写是出于安全动机,但 一位评论者 指出,前两种方法的存在表明 PLT 重写最有用之处在于作为性能调整设置。然而,有一个好处是可以禁用延迟链接:它允许 "迁移只读(Relocation Read-Only, RELRO)",这是一种安全缓解措施,这种情况下动态链接器在填入所有的 GOT(和 PLT 的 GOT)后将其重新映射为只读,从而阻止攻击覆盖它们以控制进程的控制流。

对于大多数程序而言,动态链接器基本上是不可见的,但它在建立每个进程的地址空间方面发挥了至关重要的作用。大多数程序员绝不会需要与它设置程序运行的具体过程打交道。但是,动态链接器明显的稳定性掩盖了其中大量令人意外的复杂性,这是为了确保能够有效准备程序以便执行。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

7cd8ff98d5f2db632f19c3d92a3fcecb.jpeg

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
应用背景为变电站电力巡检,基于YOLO v4算法模型对常见电力巡检目标进行检测,并充分利用Ascend310提供的DVPP等硬件支持能力来完成流媒体的传输、处理等任务,并对系统性能做出一定的优化。.zip深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值