%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 - [[#|❓静态库与动态库有何区别]] - [[#使用示例|❓如何创建、使用动/静态库?]] - [[#链接时的静态库声明顺序 🚨|❓链接时命令行中对静态库的声明顺序有何要求?]] - [[#共享库的 "位置无关特性" 与 "延迟绑定"|❓共享库的 "位置无关特性" 是指什么?如何实现的?]] - [[#共享库中函数的 "延迟绑定"|❓共享库函数地址的 "延迟绑定" 是指什么?如何实现的?]] - [[#全局偏移量表 GOT]] - [[#过程链接表 PLT]] #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** - 当存在多个进程链接到同一个共享库时,共享库在每个进程中的内存地址是否都相同? - ❓ 如何查看 & 设置动态库的 "运行时搜索路径" ? # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # 静态库与动态库总结 "库" 是一组**预编译的代码和数据集合**,封装成了**库文件**,**可供其他程序使用**(库自身无法独自运行)。 库文件根据**链接方式和用法**分为两种:「**==静态库==**」和 「**==动态库==**」 | | Unix-like 下后缀 | Windows 下后缀 | 链接时代码嵌入 | 程序运行时依赖库文件 | 代码共享 | | ---------------- | ------------- | ----------- | ------- | ---------- | ---- | | **静态库** | `.a` | `.lib` | ✔️ | ❌ | ❌ | | **动态库**(**共享库**) | `.so` | `.dll` | ❌ | ✔️ | ✔ | ![[02-开发笔记/03-计算机基础/编译与链接/静态库与动态库#^squbw5]] ### 使用静态库、动态库的优缺点 也即 "静态链接"、 "动态链接" 的优缺点,参见 [[02-开发笔记/03-计算机基础/编译与链接/链接#「静态链接」与「动态链接」|链接-「静态链接」与「动态链接」]] ![[02-开发笔记/03-计算机基础/编译与链接/链接#^w3s7wr]] ### 使用示例 ##### 静态库 ```shell # 创建静态库 g++ -c addvec.cpp mutvec.cpp ar rcs -o libvector.a addvec.o mutvec.o # 使用静态库 g++ -o prog main.cpp -L. -lvector # 方式一 g++ -o prog main.cpp ./libvector.a # 方式二 ./prog # 运行程序 ``` ##### 动态库 ```shell # 创建共享库(动态链接库) g++ -shared -fpic -o libvector.so addvec.cpp mutvec.cpp # 使用共享库 g++ -o prog main.cpp -L. -lvector # 链接阶段引入动态库的重定位信息及符号信息. export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH # 设置动态链接器在运行时搜索动态库的路径 ./prog # 运行程序, 将在运行时动态链接到共享库 ``` <br><br><br> # 静态库 > 静态库被设计于应用在 "**静态链接**" 的场景。 在 Linux 下,**静态库**以称为 "**==存档==" (archive)** 的文件格式保存(后缀`.a`),本质上即是**将一组==可重定位目标文件==进行打包**,其中具有一个特殊头部描述各个成员目标文件的大小和位置。 ## 创建静态库 使用 `ar rcs <libname>` 命令可将一组 "**可重定位目标文件**" 打包生成**静态库**。 ![img|395](_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-A531B64C631DD3864C379A63420A299C.png) > [!example] 创建静态库示例 > > - `gcc -c foo.c bar.c` :编译生成 "**可重定位目标文件**" > - `ar rcs libmylib.a foo.o bar.o` :将**多个可重定位目标文件**打包成 "**静态库**" 文件 `libmylib.a` > [!info] Linux 库的命名规范 > > Linux 下要求**动/静态库的命名格式**必须为:**`lib[your_library_name][扩展名]`**, > 其中 **lib 为前缀**,中间是**库名**,**扩展名为`.a`或 `.so`**。 > > > [!NOTE] 链接器通过 `-l` 指定库名时,需省略 `lib` 前缀和`.a` 或 `.so` 后缀,链接器会自动补上。 > ^4pmzpt <br> ## 使用静态库 ![image-20230912154651065|666](_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-2B3E4017CA6F0FD3E1EE977CBA337508.png) 程序开发时,通常使用一个静态库需要有: 1. **静态库对应的`.h` ==头文件==** (由库提供者提供,头文件中包含静态库中的**函数&变量等声明**) 2. **静态库文件本身(`.a`/`.lib` 文件)** 开发者通过**引入静态库对应的头文件**来**使用静态库中定义的符号**,然后再在链接时**由链接器完成对静态库文件的链接**。 > [!example] Linux gcc/g++ 编译时使用静态库: > > - **方式一**(**直接指定静态库文件**):`gcc -o myprogram main.c ./libmylib.a` > > ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-8EC2B0934CB2D20CB6105B7A51CDD2F2.png|286]] > > > - **方式二**( 通过 `-L`指定库搜索路径 & `-l` 指定库名):`gcc main.c -o myprogram -L. -lmylib` > > ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-A49CF7807ACFA87D150ABC058524ED33.png|283]] > > 注:`-l` 指定库名时**需省略前缀 `lib` 以及后缀 `.a` 或`.so`** > > [!example] Visual Studio 下使用静态库: > > ![image-20230912155214347|525](_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-4EC379386BFD8DB6DDF3A4105C48158B.png) > <br><br><br> # 动态库 | 共享库 > 动态库被设计于应用在 "**动态链接**" 的场景,但也可被 "**静态链接**"。 Linux 下的**共享库**(也称**动态库**)是 ELF 目标文件的一种,供其他程序**在运行时==链接==**,**其可被加载到任意的内存地址**。 共享库的特点: 1. **动态库==独立==于可执行文件**,由**可执行文件==在运行时通过 "动态链接器" 完成链接和加载==**。 2. **代码共享**: 1. 动态库中的代码和数据不会嵌入到可执行文件中,**所有引用该库的可执行文件链接/共享同一份 `.so` 文件** 2. **动态库的==在内存中只会存在一份 "代码段" 实例==**,**所有引用该共享库的进程==共享动态库的代码段==**,但各自持有一份 **"数据段"** 的副本。 > [!info] **动态库** > > - 在 Unix-like 系统(如 Linux )下称为 **==共享库==** 或**共享目标文件**(Shared Objects File), > - 在 Windows 下称为 **==动态链接库==**(DLL)。 > ^squbw5 > [!NOTE] ❓<font color="#c0504d">为什么需要动态库?</font> > > ![image-20230912160019353|842](_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-C7EB7985F643DFC5BC662E9AFB90722E.png) > > 静态库存在如下缺点: > > - 会导致空间浪费 > - 由于静态库内嵌于程序中,**若多个程序使用相同的静态库,相当于静态库中代码将在内存中存在多份拷贝**。 > - 对程序的更新、部署和发布不便 > - 如果静态库更新了,**所有使用它的应用程序要使用新的静态库版本,都需要重新编译、发布**。 > > 使用动态库的优点:动态库不会内嵌于可执行文件,而是在程序运行时才被动态载入,因此: > > - **不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例**,规避了空间浪费问题。 > - **当程序需要使用新版本的动态库时,只需替换库文件即可**。 > <br> ## 动态库的 "部分链接" 与 "完全链接" - 「**部分链接**」:在链接阶段,**链接器需要==复制动态库中的一些重定位和符号表信息==**,从而**保证程序==在运行时==能够解析对动态库中代码和数据的引用**,此过程为 "部分链接",由 "**链接器**" 完成。 - 「**完全链接**」:程序**在运行时链接到动态库**,称之为 "**完全链接**"。该过程由 "**==动态链接器==**"完成,根据**环境变量 `LD_LIBRARY_PATH`** 指定的路径去**搜索程序所需链接的动态库**。 > [!NOTE] 完全链接的过程 > > **加载器加载 "部分链接" 的可执行文件**,当发现该程序中**包含一个动态链接器的路径名**后,加载器**加载和运行该 "动态链接器"**,由**动态链接器**完成对动态库的加载和重定位任务(**重定位可执行程序中所有对动态库中符号定义的引用**),此后将控制传递给应用程序。 > 自此,共享库的内存位置固定,且在程序执行的整个过程中不会改变。 <br> ## 动态库的创建 Linux 下 gcc 编译器通过命令 `gcc -shared -fpic` 指定**创建共享库文件**。 - `-shared`:指示生成 "**共享库文件**",而非可执行文件。 - `-fpic`:指示编译器生成 "**位置无关的代码**" > [!example] 创建共享库:`gcc -shared -fpic -o libvector.so addvec.cpp mulvec.cpp` > > ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-F476CD734C4EAFC4E6160BD7102E9D7C.png|447]] > ![[05-工具/GNU 工具/gcc 编译器#^69vo2q]] ![[02-开发笔记/03-计算机基础/编译与链接/静态库与动态库#^4pmzpt]] <br> ## 动态库的使用 ### Linux gcc/g++ 编译时使用动态库 **在下述链接过程中,链接器仅==复制了动态库中的一些重定位和符号表信息==,使得程序在==运行时能够解析对动态库中代码和数据的引用==,但链接器没有将任何动态库的代码或数据嵌入到可执行文件中。** - **方式一**(**直接指定动态库文件 j**):`g++ -o prog main.cpp ./libvector.so` ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-AF6BB3663350F67C3B560F3BC31302E8.png|267]] ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-0F6EE742A557E4BF84D4426B0083C1DC.png|744]] - **方式二**( 通过 `-L`指定**链接时**库搜索路径 & `-l` 指定库名 & 通过 `LD_LIBRARY_PATH` 指定**运行时库搜索路径**): - `gcc main.c -o myprogram -L. -lmylib` - `export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH` ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-A49CF7807ACFA87D150ABC058524ED33.png|283]] ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-867BE2AA01E4835938EB963347F109C2.png|774]] ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-297EC71AF7DC870EDF425BD452D410EB.png|774]] > [!NOTE] 方式一与方式二的区别 > > - 方式一中,直接给出了**动态库的所在路径**,因此**程序运行时,动态链接器==会直接按此路径==加载动态库**。 > - 方式二中,gcc 选项 `-L` 指定的是 **==链接时对 `-l` 所列出的库的搜索路径==**,而不是 "**动态库的运行时加载路径**"。因此,程序运行时将由**动态链接器**去**指定的库搜索路径(`LD_LIBRARY_PATH`)**下查找动态库,以完成链接。 > <br> ### Windows 下使用动态库 动态链接库的使用需要库的开发者提供生成的 `.lib` 文件和 `.dll` 文件,或者只提供 `.dll` 文件。 **使用时只能使用 `.dll` 中导出的函数**,未导出的函数只能在 `.dll` 内部使用。 Dll 的调用有**显式连接**和**隐式连接**两种: - **隐式连接**:需要三项—— `.h` 头文件,**动态库的引入库文件 `.lib`**,**动态库本身 `.dll`** ; - **显式连接**:只需要 `.dll` 文件即可。 **动态库文件**中包含实际代码和数据,而**引入库文件**中包含 **==动态库导出的函数和变量的符号名==**。 ![image-20230912150754399|656](_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-D4C6AB4AC97C41FE4E0F5145BF30CF95.png) > [!NOTE] 关于 **引入库 `.lib` 文件** (可选,显式链接时只需 DLL 即可) > > - 编译链接时,**对动态库只需链接该 `.lib` 文件**,**程序运行时据此加载所需的 DLL**。 > - 该文件只在编译时用到,程序发布时只需包含动态库 `.dll` 文件。 ###### 动态库的引入库 lib 与静态库 lib 的区别 动态库的引入库与静态库同为 `.lib` 后缀文件,但实质不同。 - 静态库本身包含实际执行代码、符号表等,,编译链接时,静态库中的函数和数据会被复制并**包含在最终生成的可执行文件**中。 - 引入库**只包含了动态库的地址符号表**等,确保程序找到对应函数的基本地址信息,实际执行代码位于动态库 `dll` 中,只在需要调用时才加载。 <br><br> ## 动态库的运行时搜索路径 程序**在运行时链接到动态库**的过程由 "**动态链接器**" 完成,动态链接器会在**环境变量 `LD_LIBRARY_PATH`** 指定的路径下**搜索程序所需链接的动态库**[^5]。 #### 配置动态库的运行时搜索路径 #TODO <br><br><br> # 共享库的 "位置无关特性" 与 "延迟绑定" - 共享库的 "**==位置无关特性==**" ——**共享库==可以被加载到任意的内存地址==中**,**程序中对==共享库中符号引用的实际地址==将在 "运行时" 由动态链接器解析得到**。 - 共享库函数地址的 "**==延迟绑定==**" —— **动态链接器对共享库中==函数运行时地址==的解析会直到程序 "==首次调用==该函数" 时才完成**。 为支持上述两点特性,链接器: - 引入了 "**==全局偏移量表==(Global Offset Table, ==GOT==)**" 以实现支持共享库的 "**位置无关特性**"。 - 引入了 "**==过程链接表==((Procedure Linkage Table,==PLT==))** " 以实现对共享库函数地址的 "**延迟绑定**"(需结合 GOT 表完成) > [!note] 未动态链接到动态库时,不需要 GOT 和 PLT 表 > > 如果程序未使用动态库,不需要动态链接,则链接阶段不会生成这两个表,因为**所有符号的地址在链接时就已经确定,不需要在运行时解析符号**。 #### 查看 GOT 和 PLT 表 ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-80F8B18E48F74367941EB2CC699BADBB.png|654]] <br> ## 全局偏移量表 GOT 「**全局偏移量表 GOT**」:用于存储**共享中==全局变量和函数==** 的==**运行时内存地址**==,实现**支持共享库的 "位置无关" 特性**。 GOT 表中**每个条目对应一项==共享库中的全局符号==(函数或全局变量)**,条目用以存储 "**该符号在运行时的内存地址**": - 在链接阶段,程序中 "**对共享库中符号的引用**" 都会重定向为 "**==对该表中对应条目的引用==**",对 GOT 表**条目所指地址**进行**间址访问**。 - 在运行时加载阶段,每个条目**由 "动态链接器" 填入==条目对应符号的运行时内存地址==**。 GOT 是个数组,其中**每个条目占 8 字节**。 > [!NOTE] ❓既然 GOT 表中存了共享库中 "函数" 的地址,为什么还需要 PLT 而不是直接跳转? > > 目的在于实现 "**延迟绑定**"。 > <br> ## 过程链接表 PLT 「**==过程链接表== PLT**」:存储了**进行函数调用的相关指令**,程序中对 "**共享库中函数的符号引用**" 都会重定位为对"**==该表中对应条目的引用==**",该表**结合 GOT 表** 用于实现 **==延迟绑定函数地址==**。 PLT 表是个数组,其中每个条目是 **16 字节的==代码 (指令)==**: - `PLT[0]` 是特殊条目,**跳转到动态链接器**。 - 其余每个条目**负责调用**一个 "**具体函数**"。 <br> ## 位置无关特性的实现原理 基于 **GOT 表** 和 **PLT 表**,采取以下机制: - 在**链接**阶段, **链接器** 对 "**使用了共享库的可执行文件**" 采取以下处理: 1. 在其 **"数据段""起始处**创建 "**==全局偏移量表 GOT==**",同时**为 GOT 中每个条目**生成一个对应的 "**重定位记录**", 供**动态链接器**在运行时**填入各条目对应符号的运行时内存地址** 2. 在其 "**代码段**" 中创建 "**==过程链接表 PLT==**"。 3. 将可执行文件中所有对 "**==共享库中全局变量==**" 的符号引用**重定位到 ==GOT 对应条目处==**; 4. 将可执行文件中所有对 "**==共享库中函数==**" 的符号引用**重定位到 ==PLT 对应条目处==**。 - 在**运行时加载**阶段,**动态链接器**完成 **==运行时符号解析==**——对 **GOT 表中的条目进行重定位,==填入符号的运行时内存地址==**。 - 注:对于 "**函数地址**" 的解析会推迟到 "**程序第一次调用该函数时**",即 "**延迟绑定**",需结合 PLT 表完成。 ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-66707AE0A0595D4C91B8536D7125FE0A.png|674]] > [!important] ❓<font color="#c0504d">GOT 表是如何支持 "位置无关代码" 的?</font> > > **GOT 表位于可执行程序的数据段起始处,与符号引用之间的相对距离是固定的**, > 因此在链接阶段可确定该 "**相对地址**", 从而**将==对 "符号" 的引用==重定位为==对 "GOT 表中条目" 的引用==**。 > > 而 "**GOT 表中条目**" 存储了**共享库中==全局变量和函数==的==运行时内存地址==**,因此**可供程序在运行时通过 GOT 表获取符号的实际内存地址**。 > [!NOTE] 在链接阶段,所有**对 "共享库中全局符号" 的引用**会被重定位为如下形式 > > 对共享库中变量的访问: > 例如 `mov offset(%rip), %rax`,其中`offset(%rip)` 是**该符号在 GOT 表中条目的位置**,采用了 "**==相对地址==**"。 > 在运行时,存入 `%rax` 中的是 "**变量在运行时的内存地址**",因此访问该变量还需一次内存访问:`(%rax)`。 > > > > > 对共享库中函数的访问还需要依赖于 PLT 表,会略微复杂一些。 <br> ## 共享库中函数的 "延迟绑定" 「**延迟绑定**」:直到程序**首次调用共享库中的==函数==** 时才**解析该函数的运行时内存地址**,并**填入到 GOT 表**中,此后**调用则通过 GOT 表直接获取函数地址**。 作用:减少程序启动时的加载时间 <br> ### 延迟绑定的实现原理 > ❓<font color="#c0504d">延迟绑定是如何实现的?</font> 首先,链接阶段会将**对 "共享库中函数" 的符号引用** 重定位为 "**==该函数在 PLT 表中的对应条目==**" , 即一条函数调用指令 `call func` 的实际过程将是:`call PLT[..]`,其中 **PLT 表中对应条目的首条指令为 `jmp *GOT[...]`**, 即**跳转到==该函数在 GOT 中对应条目所指向的地址处==**。 "**延迟绑定**" 体现在: 每个函数在 GOT 表中的条目**初始值** 为 "**==该函数对应的 PLT 条目中的第二条指令地址==**", 其作用大致为 `call PLT[0]`,即**启动动态链接器来解析函数的运行时内存地址,并将其填入(更新)到 GOT 条目中**。 因此,**当==首次调用函数==时,动态链接器会==将函数的运行时内存地址填入 GOT 中对应条目==**, 而在后续调用中,通过 `call PLT[..]` => `jmpq *GOT[..]` 就会直接完成函数调用。 > [!example] 延迟绑定示例 [^7](P491) > ![[_attachment/02-开发笔记/03-计算机基础/编译与链接/静态库与动态库.assets/IMG-静态库与动态库-2CA2681B2717D01B156271FE21B025DA.png|666]] <br><br><br> # 参考资料 # Footnotes [^3]: [C++静态库与动态库的区别?](https://blog.csdn.net/dd_hello/article/details/81782934) [^4]: [C语言 Windows下使用gcc制作静态库与动态库](https://blog.csdn.net/TIME_LEAF/article/details/115333179) [^5]: [linux下 GCC编译链接静态库&动态库 ](https://www.cnblogs.com/thechosenone95/p/10605172.html) [^6]: 《程序员的自我修养:链接、装载与库》 [^7]: 《深入理解计算机系统》