Linux 动态链接原理

Linux动态链接技术原理与优化实践 动态链接是现代操作系统的核心机制,通过将库函数绑定推迟到运行时,显著提升内存利用率和维护效率。其技术原理涉及ELF格式解析、延迟绑定和符号查找等关键过程,在Linux中通过PLT/GOT机制实现高效函数调用。工程实践中,动态链接支持插件化架构和热更新等场景,但也需注意版本冲突和性能损耗问题。通过LD_DEBUG工具链和perf火焰图可分析加载性能,结合RELRO加固和数字签名能有效提升安全性。针对glibc等基础库的优化,以及Nginx等应用的模块化设计,都深度依赖动态链接技术。 阅读详情
Linux动态连接原理 
注意:
以下所用的连接器是指,ld,
而加载器是指ld-linux.so;
1  GOT表;
GOT(Global Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数的地址。可以用GOT表来间接引用全局变量、函数,也可以把GOT表的首地址作为一个基准,用相对于该基准的偏移量来引用静态变量、静态函数。由于加载器不会把运行模块加载到固定地址,在不同进程的地址空间中,各运行模块的绝对地址、相对位置都不同。这种不同反映到GOT表上,就是每个进程的每个运行模块都有独立的GOT表,所以进程间不能共享GOT表。
在x86体系结构上,本运行模块的GOT表首地址始终保存在�x寄存器中。编译器在每个函数入口处都生成一小段代码,用来初始化�x寄存器。这一步是必要的,否则,如果对该函数的调用来自另一运行模块,�x中就是调用者模块的GOT表地址;不重新初始化�x就用来引用全局变量和函数,当然出错。
这两段话的意思是说,GOT是一个映射表,这里的内容是此段代码里面引用到的外部符号的地址映射,比如你用到了一个printf函数,在这里就会有一项假设是1000,则就像这样的:

.Got
符号                        地址

Printf                      1000

………

这样的话程序在运行到printf的时候就寻找到这个地址1000从而走到其实际的代码中的地方去。
但是这里存在一个问题,因为printf是在共享库里面的,而共享库在加载的时候是没有固定地址的,所以你不知道它的地址是1000还是2000?怎么办呢?
于是引入了下面的表plt,这个表的内容是什么呢?请看下面:

2,  PLT表;
PLT(Procedure Linkage Table)表每一项都是一小段代码,对应于本运行模块要引用的一个全局函数。以对函数fun的调用为例,PLT中代码片断如下:
.PLT
fun:  jmp *fun@GOT(�x)
pushl $offset
jmp .PLT0@PC
其中引用的GOT表项被加载器初始化为下一条指令(pushl)的地址,那么该jmp指令相当于nop空指令。
用户程序中对fun的直接调用经编译连接后生成一条call [email]fun@PLT 指令,这是一条相对跳转指令(满足浮动代码的要求!),跳到.PLTfun 。如果这是本运行模块中第一次调用该函数,此处的jmp等于一个空指令,继续往下执行,接着就跳到PLT[email]0。该PLT项保留给编译器生成的额外代码,会把程序流程引入到加载器中去。加载器计算fun的实际入口地址,填入fun@GOT表项。图示如下:
user program
--------------
call fun@PLT
|
v
DLL             PLT table                loader
--------------   --------------   -----------------------
fun:           <-- jmp*fun@GOT  --> change GOT entry from
|             $loader to $fun,
v             then jump to there
GOT table
--------------
fun@GOTloader
第一次调用以后,GOT表项已指向函数的正确入口。以后再有对该函数的调用,跳到PLT表后,不再进入加载器,直接跳进函数正确入口了。从性能上分析,只有第一次调用才要加载器作一些额外处理,这是完全可以容忍的。还可以看出,加载时不用对相对跳转的代码进行修补,所以整个代码段都能在进程间共享。
上面的话是什么意思呢?
拿我们上面举的例子,printf在got表里面对应的地址是1000,而这个1000到底以为着什么呢?

PLTfun:  jmp *fun@GOT(�x)
1000: pushl $offset
jmp .PLT0@PC
你可以看到所谓1000就是它下面的这个地址,也就是说在外部函数还没有实现连接的时候,got表里面的内容其实是指向下一条指令的,于是开始执行了plt表里面的内容,于是这个段里面的内容肯定包括计算当前这个函数的实际地址的内容,于是求得实际地址添入got表,假设地址为0x800989898
于是got表里面的内容就应该这样的:
Printf                        0x800989898
………………..
这样当下一次调用这个printf的时候就不需要再去plt表里面走一遭了。
这里需要提一下的是,查找printf的地址实际上就是递归查找当前执行的程序所依赖的库,在她们export的符号表里面寻找,如果找到就返回,否则,报错,就是我们经常看到的undefined referenc to XXXXX.

3,  代码段重定位前提。

代码段本身是存在于只读区域的,所以理论上它是不可能在运行的时候重新修改的,但是这就涉及一个问题,如何保证Got表的正确使用,因为每一个进程都有自己的got表,而共享库完全同时被许多个进程使用的,于是在每个函数的入口都有这样的语句:

call L1
L1:  popl �x
addl $GOT+[.-.L1], �x
.o:  R_386_GOTPC
.so: NULL
上述过程是编译、连接相合作的结果。编译器生成目标文件时,因为此时还不存在GOT表(每个运行模块有一个GOT表,一个PLT表,由连接器生成),所以暂时不能计算GOT表与当前IP间的差值,仅在第三句处设上一个 R_386_GOTPC重定位标记而已。然后进行连接。连接器注意到GOTPC重定位项,于是计算GOT与此处IP的差值,作为addl指令的立即寻址方式操作数。以后再也不需要重定位了。
这样做的好处是目的是什么呢?
就是在函数内部引用外部符号的时候能够正确的转到适当的地方去。
4,  变量、函数引用
当引用的是静态变量、静态函数或字符串常量时,使用R_386_GOTOFF重定位方式。它与GOTPC重定位方式很相似,同样首先由编译器在目标文件中设上重定位标记,然后连接器计算GOT表与被引用元素首地址的差值,作为leal指令的变址寻址方式操作数。代码片断如下:
leal .LC1@GOTOFF(�x), �x
.o:  R_386_GOTOFF
.so: NULL
当引用的是全局变量、全局函数时,编译器会在目标文件中设上一个R_386_GOT32重定位标记。连接器会在GOT表中保留一项,注上 R_386_GLOB_DAT重定位标记,用于加载器填写被引用元素的实际地址。连接器还要计算该保留项在GOT表中的偏移,作为movl指令的变址寻址方式操作数。代码片断如下:
movl x@GOT(�x), �x
.o:  R_386_GOT32
.so: R_386_GLOB_DAT
需要指出,引用全局函数时,由GOT表读出不是全局函数的实际入口地址,而是该函数在PLT表中的入口.PLTfun。这样,无论直接调用,还是先取得函数地址再间接调用,程序流程都会转入PLT表,进而把控制权转移给加载器。加载器就是利用这个机会进行动态连接的。
 注意:这里讨论的是变量函数的引用,不是函数的直接调用,而是函数,变量的地址的取得,如果是函数的话,取得的实际上是plt里面的地址,于是最终还是没能逃过加载器的协助。
5,  直接调用函数
如前所述,浮动代码中的函数调用语句会编译成相对跳转指令。首先编译器会在目标文件中设上一个R_386_PLT32重定位标记,然后视静态函数、全局函数不同而连接过程也有所不同。
如果是静态函数,调用一定来自同一运行模块,调用点相对于函数入口点的偏移量在连接时就可计算出来,作为call指令的相对当前IP偏移跳转操作数,由此直接进入函数入口,不用加载器操心。相关代码片断如下:
call f@PLT
.o:  R_386_PLT32
.so: NULL
如果是全局函数,连接器将生成到.PLTfun的相对跳转指令,之后就如前面所述,对全局函数的第一次调用会把程序流程转到加载器中去,然后计算函数的入口地址,填充fun@GOT表项。这称为R_386_JMP_SLOT重定位方式。相关代码片断如下:
call f@PLT
.o:  R_386_PLT32
.so: R_386_JMP_SLOT


如此一来,一个全局函数可能有多至两个重定位项。一个是必需JMP_SLOT重定位项,加载器把它指向真正的函数入口;另一个是GLOB_DAT重定位项,加载器把它指向PLT表中的代码片断。取函数地址时,取得的总是GLOB_DAT重定位项的值,也就是指向.PLTfun,而不是真正的函数入口。


进一步考虑这样一个问题:两个动态连接库,取同一个全局函数的地址,两个结果进行比较。由前面的讨论可知,两个结果都没有指向函数的真正入口,而是分别指向两个不同的PLT表。简单进行比较,会得出"不相等"的结论,显然不正确,所以要特殊处理。

注意:
一个是必需JMP_SLOT重定位项,这里指的就是直接调用函数的情况;
另一个是GLOB_DAT重定位项,这里指函数地址引用的情况;
6,  数据段的重定位
在数据段中的重定位是指对指针类型的静态变量、全局变量进行初始化。它与代码段中的重定位比较起来至少有以下明显不同:一、在用户程序获得控制权(main函数开始执行)之前就要全部完成;二、不经过GOT表间接寻址,这是因为此时�x中还没有正确的GOT表首地址;三、直接修改数据段,而代码段重定位时不能修改代码段。
如果引用的是静态变量、函数、串常量,编译器会在目标文件中设上 R_386_32重定位标记,并计算被引用变量、函数相对于所在段首地址的偏移量。连接器把它改成R_386_RELATIVE重定位标记,计算它相对于动态连接库首地址(通常为零)的偏移量。加载器会把运行模块真正的首地址(不为零)与该偏移量相加,结果用来初始化指针变量。代码片断如下:
.section .rodata
.LC0: .string "Ok\n"
.data
p:     .long .LC0
.o:  R_386_32 w/ section
.so: R_386_RELATIVE


如果引用的是全局变量、函数,编译器同样设上R_386_32重定位标记,并且记录引用的符号名字。连接器不必动作。最后加载器查找被引用符号,结果用来初始化指针变量。对于全局函数,查找的结果仍然是函数在PLT表中的代码片断,而不是实际入口。这与前面引用全局函数的讨论相同。代码片断如下:
.data
p:       .long printf
.o:  R_386_32 w/ symbol
.so: R_386_32 w/ symbol
7,  总结:

下表给出了前面讨论得到的全部结果:
.o                          .so
--------------------------------------------------------------------------
|装载GOT表首地址        R_386_GOTPC     NULL
代码段|-----------------------------------------------------
重定位|引用变量函数地址静态  R_386_GOTOFF    NULL
|                 全局  R_386_GOT32     R_386_GLOB_DAT
|-----------------------------------------------------
|直接调用函数     静态  R_386_PLT32     NULL
|                    全局  R_386_PLT32     R_386_JMP_SLOT
------|-----------------------------------------------------
数据段|引用变量函数地址 静态  R_386_32 w/sec  R_386_RELATIVE
重定位|                 全局  R_386_32 w/sym  R_386_32 w/sym
Linux下动态链接的实现原理及编程实例 动态链接器的主要任务是在程序加载时查找和加载所需的共享库,并将其链接到程序代码中。当一个程序需要调用某个函数时,动态链接器会在预定义的共享库搜索路径中查找相应的库文件,并将其加载到内存中。在Linux系统中,动态链接器负责查找和加载所需的共享库,并将其链接到程序代码中。在这个示例中,动态链接器在程序加载时自动查找并加载了所需的printf函数的实现。动态链接的原理及实现方式在Linux系统中被广泛应用,本文将详细介绍动态链接的原理,并提供一个简单的编程示例来演示其用法。 阅读详情

相关推荐

Linux动态链接器的运作原理与应用

本文深入探讨了Linux系统中的动态链接器机制,包括动态链接器的启动方式、它如何找到并加载共享库,以及在解析库依赖时遵循的路径规则。文章还介绍了环境变量和编译器选项如何影响库的加载,以及动态链接器支持的命令行选项和特定字符串扩展。

weixin_42589700的博客 349

GOT表,PLT表,代码段重定位,数据段重定位--Linux动态连接原理

转载地址:http://blog.youkuaiyun.com/lmh12506/article/details/6801630  Linux动态连接原理 注意: 以下所用的连接器是指,ld, 而加载器是指ld-linux.so; 1,  GOT表; GOT(Global Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数的地址。可以用GOT表来间接引用全局变量、函数

yaoyaowugui的专栏 4126

Linux 动态链接器/加载器的内部工作原理

本文深入探讨了 Linux 系统中动态链接器/加载器的工作机制。我们从 /lib/ld.so 和 /lib/ld-linux.so 等关键文件的职能开始,逐步分析了动态链接过程中的关键文件,如 /etc/ld.so.cache 和 /etc/ld.so.preload,以及它们如何影响库文件的搜索和加载。此外,还提到了 ELF 格式支持的版本要求,以及与动态链接相关的其他实用工具和文档。

weixin_28840811的博客 424

GOT表和PLT表

GOT表和PLT表 文章目录GOT表和PLT表前言一、GOT表二、PLT表总结 前言 程序对于外部函数的调用需要在生成可执行文件时将外部函数链接到程序中,(C语言应该讲过)链接的方式分为静态链接和动态链接。 静态链接得到的可执行文件包含外部函数的全部代码,动态链接得到的可执行文件中并不包含外部函数的代码,而是运行时将动态链接库(若干外部函数的集合)加载到内存的某个位置,再在发生调用时去链接库定位所需的函数。 怎么理解呢,这里打个比喻,静态链接是给你一件成品武器,调用时直接整个武器都已经组装好了到你手里,动

qq_52126646的博客 4260

泛微E10 eBuilder低代码平台避坑指南:新手最容易犯的5个错误及解决方案

本文针对泛微E10 eBuilder低代码平台的新手开发者,总结了5个最常见错误及解决方案,包括表单联动配置、流程设计权限、动作流时序、数据加工类型转换和权限管理过度配置等问题。通过实战经验和代码示例,帮助开发者避开常见陷阱,提升开发效率。

weixin_30919429的博客 540

ArcGIS利用模型构建器批量导出nc文件中的不同波段数据

ArcGIS利用模型构建器批量导出nc文件中的不同波段数据

Yuanbaokk的博客 7257

Got表之导入表hook

github地址:https://github.com/shineygs/GotHook 代码量不多,几乎每行代码都有注释。 这里我们通过program header table,先找到.dynamic段,也是动态链接中最重要的结构段,保存了动态链接器所需要的基本信息,比如依赖哪些共享对象,动态链接符号表,重定位表等。然后在.dynamic段遍历找到,动态符号表,字符串表以及重定位表,下面来看一下...

随手笔记 1022

手把手教你实现阿克曼ROS小车的运动控制

本文详细介绍了如何从零开始实现阿克曼ROS小车的运动控制。内容涵盖硬件搭建、STM32底层PID速度与舵机控制、ROS层阿克曼运动学模型解析,以及上下位机通信协议设计。通过手把手的代码示例和调试步骤,帮助机器人爱好者与开发者掌握将高级速度指令转化为小车具体动作的全流程,实现流畅的阿克曼转向控制。

weixin_29193259的博客 200

linux 库全局变量_Linux下动态链接实现原理

微信公众号:二进制人生专注于嵌入式linux开发。更新日期:2020/06/06,内容整理自网络。转自:https://www.linuxidc.com/Linux/2015-03/114572.htm,二进制人生将其涉及的代码改成了arm平台(基于海思开发板),并且增加了关于.rel.text节的介绍,和plt函数跳板的介绍,以及重定位介绍的补充。目录符号重定位链接时符号重定位(静态链...

weixin_39656174的博客 266

封神台靶场,为了小芳,冲之——cookie注入

第二个靶场 打开网站 找一下注入点 一般在新闻这种里面点开看看 先判断是否存在注入 有waf 尝试了很多方法绕过都没成功,/and/,aandnd,AnD等一系列都没绕过去,乌鸡鲅鱼 删掉id=169 发现 然后判断cookie注入 在搜索框输入 Javascript:alert(document.cookie=”id=”+escape(‘169’)); 然后弹窗了(其中 “id=” 取决URL中的id= escape中的数值也是URL中id=的值)返回刚刚的页面,把ID删除后一样能正常显示。说明服务器能

Wubuqing的博客 656

【瑞芯微px30】【RTL8363】【一】驱动移植-网卡驱动概述

linux rtl8363驱动移植

weixin_34799243的博客 1180

重定位类型分析(2)

本节分析R_386_GOTPC,R_386_GOT32,R_386_GLOB_DAT和R_386_PLT32 PIC位置无关代码,使用-fPIC参数就可以编译得到 >>>>>>>>>>>>>>>>>R_386_GOTPC 看看第一个重定位符号的重定位信息 汇编代码:        call    .L3 //.L3地址压栈.L3:      

ruixj的专栏 3619

36、T5L 迪文屏C51开发之绘制动态曲线

T5L 迪文屏C51开发之绘制动态曲线1、介绍2、例程功能简介3、GUI界面设计3、C51程序设计 1、介绍   本文将利用DGUS Tool软件中的【动态曲线控件】来绘制曲线效果,在T5L迪文屏中总共支持8个通道的曲线,而曲线数据是需要缓冲区的,如果 8 通道曲线同时使用,0x1000-0x4FFF 将作为曲线缓冲区地址,此时该部分变量地址不能被其他控键使用,其他控键地址使用范围为0x5000-0xFFFF,当某曲线通道不被使用时,那么其所对应的缓冲区地址可以做其他用途,曲线通道具体的地址分布如下图所示:

嵌入式羊圈 3133

[linux仓库]深入解析Linux动态链接与动态库加载:理解背后的原理与技巧

本文探讨了Linux动态库的工作原理及其与进程的交互机制。文章首先指出静态库会在编译时被直接合并到可执行文件中,而动态库则通过共享区映射到进程地址空间,使多个进程可以共享同一库文件,节省内存资源。动态链接由动态链接器在程序运行时完成,通过全局偏移量表(GOT)实现地址重定向,并采用延迟绑定技术提高效率。动态库采用相对编址方案,支持灵活加载到任意内存位置,其ELF格式与可执行程序相同,便于库间互相调用。文章还解析了程序从_start到main函数的初始化过程,包括动态链接、堆栈设置等底层细节。

egoist2023的博客 2381

【异常检测】【EfficientAD】论文简单梳理与代码实现

EfficientAD作为较近的一篇异常检测网络,主要还是从S-T网络入手,对齐进行了相关改进。修改特征提取器引入自动编码器hard loss以及惩罚项。

胡侃有料的博客 2859

两组相机(或三维点)对齐方法介绍及实现代码(求解相似变换,包含旋转R、平移t、尺度s)

  这个变换用于将生成的稀疏重建场景对齐到真值上。由于SfM(Structure from Motion,运动恢复结构)求解的结果是尺度未定的,所以重建场景与真值之间不仅会相差一个旋转与平移,还会存在尺度缩放。待求解的问题可以描述为:已知一组相机(个数为m)的位姿真值Rgt、tgt和预测位姿Rpred、tpred,现要将预测值对齐到真值上。它们之间会相差一个相似变换Msimilarity,包括尺度s、旋转量R、平移量t,可以根据如下步骤进行求解:    1 求解旋转R   首先计算所有m个相机的位姿差异总和

zeeq的博客 3926

Linux动态链接与动态库加载:从原理到实战,一文吃透底层逻辑

本文深入解析Linux动态链接与动态库加载机制。动态链接将符号解析推迟到运行时,通过GOT/PLT机制实现地址无关性和延迟绑定优化。进程通过mmap系统调用将动态库映射到虚拟地址空间,多个进程共享同一物理内存中的库代码。动态链接器ld-linux.so负责加载依赖库、解析符号并完成地址重定位。相比静态链接,动态链接节省内存、便于更新但启动稍慢。理解动态链接原理有助于解决库依赖问题、优化程序性能,是Linux系统开发的重要基础。

心向往之,行必能至 601

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Linux动态链接机制及原理

动态链接VS静态链接: 动态链接从命名中可以看出动态是关键, 那这个动态体现在哪呢? 静态链接中的静态体现在哪呢?, 我们知道代码需要经过几个步骤才会编译成机器认识的符号, 代码一般经过预编译, 编译, 汇编, 链接然后形成可执行程序或者动态库, 这几个步骤的作用分别如下: 预编译: 将代码进行整理,#include,#define,注释的代码等等... 编译:将预编译后的文件进行语法分析, 词法分析, 语义分析及优化等 汇编:变成机器可以执行的指令 链接:将目标文件及库一起链接成exe文件,链接过程主要

yuyixiong的博客 2235

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Linux链接原理,Linux下动态链接实现原理

符号重定位讲动态链接之前,得先说说符号重定位。C/C++ 程序的编译是以文件为单位进行的,因此每个 c/cpp 文件也叫作一个编译单元(translation unit), 源文件先是被编译成一个个目标文件, 再由链接器把这些目标文件组合成一个可执行文件或库,链接的过程,其核心工作是解决模块间各种符号(变量,函数)相互引用的问题,对符号的引用本质是对其在内存中具体地址的引用,因此确定符号地址是编译...

weixin_32821533的博客 303

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

linux下动态链接实现原理

转载地址:http://www.cnblogs.com/catch/p/3857964.html 符号重定位 讲动态链接之前,得先说说符号重定位。 c/c++ 程序的编译是以文件为单位进行的,因此每个 c/cpp 文件也叫作一个编译单元(translation unit), 源文件先是被编译成一个个目标文件, 再由链接器把这些目标文件组合成一个可执行文件或库,链接的过

james zhang的博客 1588

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

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

行潇 384

Linux “ 编译 “实战:动态链接库让代码动起来的核心原理

本文介绍了在Linux系统中使用gcc/g++编译器将代码转换为可执行程序的过程。

Miun123的博客 1804

linux 下动态链接实现原理

符号重定位 讲动态链接之前,得先说说符号重定位。 c/c++ 程序的编译是以文件为单位进行的,因此每个 c/cpp 文件也叫作一个编译单元(translation unit), 源文件先是被编译成一个个目标文件, 再由链接器把这些目标文件组合成一个可执行文件或库,链接的过程,其核心工作是解决模块间各种符号(变量,函数)相互引用的问题,对符号的引用本质是对其在内存中具体地址的引用,因此确定符号地址是...

weixin_34025051的博客 149
上一篇: 全局偏移量表和过程链接表
下一篇: 802.11帧结构
crazy_programmer_p
博客等级 码龄12年 21粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值