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

作者:egoist2023日期:2025/10/2

🌟 各位看官好,我是egoist2023

🌍 Linux == Linux is not Unix !

🚀 今天来学习Linux的指令知识,并学会灵活使用这些指令。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享更多人哦!

目录

进程如何看到动态库

进程间如何共享库

动态链接

编译器对可执行程序动手脚

动态库相对地址

程序如何和库具体映射

程序怎么进行库调用

全局偏移量表GOT

库间依赖(看看即可)

总结


进程如何看到动态库

为什么之前不谈进程如何看到静态库 ?

没必要。静态库就是将 .o 打了包,会把自己的代码实现拷贝到自己的可执行程序里。程序一旦运行时就跟静态库无关了,就看不到静态库也不需要看到。

前面我们用ldd命令可以查看一个可执行程序所依赖的动态库:

结论一 : 让我们自己的程序跑起来,除了要加载myexe本身,也要加载myexe依赖的库文件!!

那么进程是如何看到对应的动态库文件的?库文件也要加载到物理内存,要建立虚拟到物理映射关系,那么就可以通过虚拟地址空间找到需要的物理内存.

结论二 : 通过自己的虚拟地址空间中的共享区看到的

结论三:介于栈和堆中间的共享区就是用来存放动态库的

进程间如何共享库

一个进程可以看到一个库,那么能不能多个进程看到一个库呢?如何做到多个进程看到同一个库的?

结论四 : 每一个进程把要的库映射到自己的虚拟地址空间中,即多个进程能看到同一个库.这种方式不需要重复加载代码和数据在物理内存中,有效节省内存空间.

动态库的本质:通过地址空间映射,对公共代码进行去重!!!

动态链接

动态链接其实远比静态链接要常用得多,而这里myexe可执行程序依赖libc.so是什么呢?

是C语言的运行时库,里面提供了常用的标准输入输出文件字符串处理等功能.

为什么编译器默认不使用静态链接呢?

我们前面说过静态链接实际上会合并形成一个独立的可执行文件.虽然它不需要额外的依赖就能运行,照理来说应该更方便才对?

静态链接最⼤的问题在于⽣成的⽂件体积⼤,并且相当耗费内存资源。随着软件复杂度的提升,我们的操作系统也越来越臃肿,不同的软件就有可能都包含了相同的功能和代码,显然会浪费⼤量的硬盘空间。

这个时候,动态链接的优势就体现出来了,我们可以将需要共享的代码单独提取出来,保存成⼀个独立的动态链接库,等到程序运行的时候再将它们加载到内存,这样不但可以节省空间,因为同⼀个模块在内存中只需要保留⼀份副本,可以被不同的进程所共享。

原理:动态链接又是如何进行的呢?

⾸先要交代一个结论,动态链接实际上将链接的整个过程推迟到了程序加载的时候。比如我们去运行⼀个程序,操作系统会⾸先将程序的数据代码连同它⽤到的⼀系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,操作系统会根据当前地址空间的使⽤情况为它们动态分配⼀段内存。当动态库被加载到内存以后,一旦它的内存地址被确定,我们就可以去修正动态库中的那些函数跳转地址了。

编译器对可执行程序动手脚

在C/C++程序中,当程序开始执行时,实际上并不会直接跳转到 main 函数。有一个程序的入口点是 _start ,这是⼀个由C运行时库(通常是glibc)或链接器(如ld)提供的特殊函数。

在 _start 函数中,会执行一系列初始化操作,这些操作包括:

1.设置堆栈:为程序创建⼀个初始的堆栈环境。

2.初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段。

3.动态链接:这是关键的⼀步 , _start 函数会调用动态链接器的代码来解析和加载程序所依赖的动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址。

动态链接器:

  • 动态链接器(如ld-linux.so)负责在程序运行时加载动态库。
  • 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。

环境变量和配置⽂件:

  • Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置⽂件(如/etc/ld.so.conf及其⼦配置⽂件)来指定动态库的搜索路径。
  • 这些路径会被动态链接器在加载动态库时搜索。

缓存文件:

  • 为了提高动态库的加载效率,Linux系统会维护⼀个名为/etc/ld.so.cache的缓存文件。
  • 该⽂件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会⾸先搜索这个缓存文件。

4.调用 __libc_start_main :一旦动态链接完成, _start 函数会调用 __libc_start_main(这是glibc提供的一个函数). __libc_start_main 函数负责执行⼀些额外的初始化工作,比如设置信号处理函数、初始化线程库(如果使用了线程)等。

5. 调用 main 函数:最后, __libc_start_main 函数会调用程序的 main 函数,此时程序的执行控制权才正式交给用户编写的代码。

6. 处理 main 函数的返回值:当 main 函数返回时, __libc_start_main 会负责处理这个返回值,并最终调用 _exit 函数来终⽌程序.

上述过程描述了C/C++程序在 main 函数之前执⾏的⼀系列操作,但程序员通常只需要关注 main 函数中的代码,而不需要关心底层的初始化过程。了解这些底层细节有助于更好地理解程序的执行流程和调试问题。

动态库相对地址

动态库,静态库,可执行,.o等,都是ELF格式的文件 --> 编译器要对它们进行ELF格式编址:

动态库为了随时进行加载,为了支持并映射到任意进程的任意位置,对动态库中的方法,统一编址,采用相对编址的方案进行编制的(其实可执⾏程序也⼀样,都要遵守平坦模式,只不过exe是直接加载的)。

逻辑地址 = 起始地址 + 偏移量的方式.

由于有了平坦模式的加持,即起始地址是从0开始的.所以对于相对编址和绝对编址二者是等价的:

  • 相对编址:偏移量
  • 绝对编址:全0到全f

逻辑地址 = 0 + 偏移量 = 相对编址

程序如何和库具体映射

  • 动态库也是一个文件,要访问也是要被先加载,要加载也是要被打开的
  • 让我们的进程找到动态库的本质:也是⽂件操作,不过我们访问库函数,通过虚拟地址进行跳转访问的,所以需要把动态库映射到进程的地址空间中

程序怎么进行库调用

  • 库已经被我们映射到了当前进程的地址空间中
  • 库的虚拟起始地址我们也已经知道了
  • 库中每⼀个方法的偏移量地址我们也知道
  • 所以:访问库中任意方法,只需要知道库的起始虚拟地址+方法偏移量即可定位库中的方法
  • 而且:整个调用过程,是从代码区跳转到共享区,调⽤完毕在返回到代码区,整个过程完全在进程地址空间中进行的.

将库函数替换为起始虚拟地址,这种工作由谁来做啊?

就是前面说的动态链接器和OS:尤其是在动态加载时,进行地址重定向!

全局偏移量表GOT

注意:也就是说,我们的程序运行之前,先把所有库加载并映射,所有库的起始虚拟地址都应该提前知道.然后对我们加载到内存中的程序的库函数调⽤进⾏地址修改,在内存中二次完成地址设置(这个叫做加载地址重定位)

等等,修改的是代码区?不是说代码区在进程中是只读的吗?为什么能修改?当我要修改代码区时,页表会通过权限拒绝进行修改。那这是怎么回事呢?

所以:动态链接采用的做法是在 .data (可执行程序或者库自己)中专门预留一片区域用来存放函数的跳转地址,它也被叫做全局偏移表GOT,表中每⼀项都是本运⾏模块要引用的⼀个全局变量或函数的地址。

因为.data区域是可读写的,所以可以⽀持动态进行修改

  1. 由于代码段只读,我们不能直接修改代码段。但有了GOT表,代码便可以被所有进程共享。但在不同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上,就是每个进程的每个动态库都有独⽴的GOT表,所以进程间不能共享GOT表。
  2. 在单个.so下,由于GOT表与 .text 的相对位置是固定的,我们完全可以利⽤CPU的相对寻址来找到GOT表。
  3. 在调⽤函数的时候会⾸先查表,然后根据表中的地址来进⾏跳转,这些地址在动态库加载的时候会被修改为真正的地址。
  4. 这种⽅式实现的动态链接就被叫做 PIC 地址⽆关代码 。换句话说,我们的动态库不需要做任何修改,被加载到任意内存地址都能够正常运⾏,并且能够被所有进程共享,这也是为什么之前我们给编译器指定-fPIC参数的原因,PIC=相对编址+GOT。

库间依赖(看看即可)

  • 不仅仅有可执行程序调用库
  • 库也可以调用其他库!!因为库之间也能存在依赖.那么如何做到库和库之间互相调⽤也是与地址⽆关的呢?
  • 库中也有.GOT , 和可执行⼀样!这也就是为什么⼤家为什么都是ELF的格式!

由于GOT表中的映射地址会在运⾏时去修改,我们可以通过gdb调试去观察GOT表的地址变化。在这⾥

我们只用知道原理 :使用gdb调试GOT

  • 由于动态链接在程序加载的时候需要对⼤量函数进⾏重定位,这⼀步显然是⾮常耗时的。为了进一步降低开销,我们的操作系统还做了⼀些其他的优化,⽐如延迟绑定,或者也叫PLT(过程连接表(Procedure Linkage Table))。与其在程序⼀开始就对所有函数进⾏重定位,不如将这个过程推迟到函数第⼀次被调⽤的时候,因为绝⼤多数动态库中的函数可能在程序运⾏期间⼀次都不会被使⽤到。

思路是:GOT中的跳转地址默认会指向⼀段辅助代码,它也被叫做桩代码/stup。在我们第⼀次

调⽤函数的时候,这段代码会负责查询真正函数的跳转地址,并且去更新GOT表。于是我们再次

调⽤函数的时候,就会直接跳转到动态库中真正的函数实现。

总结

本文探讨了Linux动态链接库的工作原理。动态库通过虚拟地址空间映射实现多个进程共享,有效节省内存空间。与静态链接不同,动态链接将链接过程推迟到程序加载时进行,由动态链接器负责库加载和地址重定位。文章详细介绍了程序启动流程(从_start到main函数)、动态库的相对编址原理,以及全局偏移表(GOT)在动态链接中的作用。还分析了库间依赖关系和延迟绑定(PLT)优化技术,展示了动态链接如何通过地址无关代码实现高效共享。这些机制共同构成了Linux动态链接的核心技术。


[linux仓库]深入解析Linux动态链接与动态库加载:理解背后的原理与技巧》 是转载文章,点击查看原文


相关推荐


Excel转PDF不分页
Bella_chene2025/10/2

将Excel转成PDF后,会发现存在分页的现象,理想是希望将一整个表格按实际情况缩放显示到PDF的一页上去 操作办法:打开excel表格,ctrl+P打开打印页面,点击页面设置,选择“将工作表调整为一页” 然后在右侧就可以看到效果,点击保存就可以了


ptyhon 基础语法学习(对比php)
come1123410/2/2025

非常棒!我们已经系统地对比了 Python 和 PHP 的所有主要数据类型。分类Python 类型对应 PHP 概念关键点基础intfloatstrintfloatstringPython 字符串格式化用f-string序列listtuple索引数组list可变(用[]),tuple不可变(用()映射dict关联数组键值对集合 (用{}集合set的结果元素唯一且无序特殊boolNoneboolnullFalse和None(首字母大写)这份表格可以作为您未来的速查手册。


音视频编解码全流程之用Extractor后Decodec
Everbrilliant8910/1/2025

本文介绍了音视频编解码流程中从媒体文件提取数据包后进行解码的两种实现方式:FFmpeg和MediaCodec。在FFmpeg部分,详细说明了交叉编译、数据包提取、解码器查找和分配、数据包解码等步骤,并提供了完整的代码实现。在MediaCodec部分,重点讲解了Extractor初始化、轨道选择、解码器初始化以及解码过程,同样包含详细代码示例。文章还对比了两种解码流程的异同,并展示了实际效果。所有代码均可在作者GitHub项目中查看。该系列文章为音视频开发提供了完整的流程参考,适合开发者学习音视频编解码技术。


【自动驾驶】自动驾驶概述 ⑤ ( 自动驾驶硬件概述 | 车载计算单元 IPC | 车辆线控系统 )
韩曙亮9/30/2025

一、车载计算单元 IPC1、车载计算单元 IPC 简介2、高性能计算3、高安全冗余4、高环境适应性二、车辆线控系统1、自动驾驶线控系统2、线控转向 ( Steer-by-Wire )3、线控制动 ( Brake-by-Wire )4、线控驱动 ( Throttle-by-Wire )5、线控换挡 ( Shift-by-Wire )6、线控悬挂 ( Suspension-by-Wire )7、线控系统举例说明


DeepSeek V3.1-Terminus、阿里 Qwen3-Max、ChatGPT Pulse 同周登场!| AI Weekly 9.22-9.28
AI信息Gap2025/10/3

卷,卷起来了! 📢 本周 AI 快讯 | 1 分钟速览🚀 1️⃣ 🚀 DeepSeek 发布 V3.1-Terminus :Agent 性能提升 28%,HLE 测试跃升全球第三,仅次于 Grok 4 和 GPT-5,SimpleQA 准确率达 96.8%。 2️⃣ 💰 阿里云栖大会七连发 :3800 亿 AI 基建投资起步,万亿参数 Qwen3-Max 对标 GPT-5,AIME 25 和 HMMT 数学测试满分 100 分。 3️⃣ 🖥️ Kimi 推出 OK Compu


UNIX下C语言编程与实践16-UNIX 磁盘空间划分:引导块、超级块、i 节点区、数据区的功能解析
迎風吹頭髮2025/10/4

一、UNIX 磁盘空间划分的核心逻辑:为何分为四个区域? UNIX 文件系统在格式化时,会将磁盘分区(如 /dev/sda1)划分为引导块(Boot Block)、超级块(Super Block)、i 节点区(Inode Area)、数据区(Data Area)四个连续的区域。这种划分并非随意设计,而是为了实现“系统启动-文件系统管理-数据存储”的完整功能链路,确保磁盘空间的有序管理和高效访问。 核心定位:四个区域各司其职且相互依赖——引导块负责“启动系统”,超级块负责“管理文件系统全局信息


Nginx 配置负载均衡(详细版)
1加1等于2025/10/6

本文详细介绍关于Nginx 配置负载均衡,包括配置文件结构、多种负载均衡策略、如何修改均衡策略以及其他一些重要的配置。 本文目录 一、、Nginx 配置负载均衡1. 配置文件结构 二、Nginx 负载均衡策略1. 轮询(`默认策略`)2. 加权轮询3. IP 哈希4. 最少连接 三、修改负载均衡策略四、Nginx 负载均衡其他配置1. 健康检查2. 会话保持3. 超时设置 一、、Nginx 配置负载均衡 1. 配置文件结构 Nginx 的负载均衡配置主要


Kubernetes核心技术与集群部署项目
企鹅侠客2025/10/7

从集群搭建到核心功能应用的完整流程,内容涵盖集群部署、核心组件、资源管理、安全机制、持久化、监控与应用交付等关键技术。首先介绍 Kubernetes 的架构与特性,深入讲解 kubeadm 与二进制两种集群搭建方式,包括 etcd 集群部署、Master 与 Node 组件安装、证书签发及高可用集群实现。核心技术部分系统解析 Pod 的运行机制、调度策略、健康检查与资源限制,讲解 Deployment、StatefulSet、DaemonSet、Job 等控制器的应用场景。配置管理方面介绍 Co


前端路由的秘密:手写一个迷你路由,看懂Hash和History的较量
良山有风来2025/10/9

你是不是也遇到过这样的场景?开发单页面应用时,页面跳转后刷新一下就404,或者URL里带着难看的#号,被产品经理吐槽不够优雅? 别担心,今天我就带你彻底搞懂前端路由的两种模式,手把手教你实现一个迷你路由,并告诉你什么场景该用哪种方案。 读完本文,你能获得一套完整的前端路由知识体系,从原理到实战,再到生产环境配置,一次性全搞定! 为什么需要前端路由? 想象一下,你正在开发一个后台管理系统。传统做法是每个页面都对应一个HTML文件,切换页面就要重新加载,体验特别差。 而前端路由让你可以在一个页面内实


汽车软件开发的质量和安全管理流程
NewCarRen2025/10/10

摘要 软件开发流程是智能车辆(联网车辆和自动驾驶车辆)的核心,必须精心管理。自动化与联网功能的开发分别通过功能安全和网络安全开发流程实现,且需遵循相关标准,这些标准规定了流程、最佳实践、危害、威胁及管理策略。通过改进软件开发流程,智能车辆的人体工程学性能将得到提升。本文阐述了如何通过软件开发来管理实现自动化与联网功能的流程,以及是否可能改变管理团队的策略与软件开发流程。 1、引言 智能车辆是一种能够从周围环境中获取信息,并对信息进行处理,从而实现自主安全行驶且不造成任何伤害的车辆。此外,智

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0