Linux第九讲:动静态库

server/2025/3/29 3:12:38/

Linux第九讲:动静态库

  • 1.静态库的制作 && 什么是库
    • 1.1静态库生成
  • 2.动态库
    • 2.1动态库生成
    • 2.2静态链接和动态链接的区别
    • 2.3解决策略
      • 2.3.1将我们写的动态库拷贝至系统
      • 2.3.2建立软链接
      • 2.3.3LD_LIBRARY_PATH
      • 2.3.4ldconfig方案:配置/etc/ld.so.conf.d/
    • 2.4几个结论输出
  • 3.使用外部库 -- 感兴趣尝试
  • 4.目标文件
  • 5.ELF文件
    • 5.1ELF文件格式
    • 5.2ELF从形成到加载轮廓
      • 5.2.1ELF形成可执行
      • 5.2.2ELF可执行文件加载
      • 5.2.3地址和偏移量之间的关系
  • 6.理解链接与加载
    • 6.1静态链接
    • 6.2ELF加载与进程地址空间
      • 6.2.1虚拟地址/逻辑地址
      • 6.2.2重新理解虚拟地址空间
    • 6.3动态链接与动态库加载
      • 6.3.1进程如何看待动态库 && 共享库
      • 6.3.2动态链接
        • 6.3.2.1我们的可执行程序被编译器动了手脚
        • 6.3.2.2动态库中的相对地址
        • 6.3.2.3程序如何和库进行映射
        • 6.3.2.4程序怎么进行库函数的调用
        • 6.3.2.5全局偏移量表(GOT)
        • 6.3.2.6库间依赖 && plt(延迟绑定)
  • 7.总结

1.静态库的制作 && 什么是库

无论是ubunto操作系统,还是centos操作系统,都有对应的静态库和动态库,下面我们通过静态库的制作来了解一下什么是库:

在这里插入图片描述
如果需要链接的.h文件不在当前路径下呢?我们该如何进行链接?:
在这里插入图片描述

系统只能在特定的路径进行查找,我们当然可以将自己实现的库,保存在自动查找的路径上:
在这里插入图片描述
我们进行一个小总结:

gcc main.c -I 头⽂件路径 -L 库⽂件路径 -l mymath
-L: 指定库路径
-I: 指定头文件搜索路径
-l: 指定库名

1.1静态库生成

libmyc.a:mystdio.o mystring.oar -rc $@ $^
mystdio.o:mystdio.cgcc -c $<
mystring.o:mystring.cgcc -c $<.PHONY:output
output:mkdir -p lib/includemkdir -p lib/mylibcp -f *.h lib/includecp -f *.a lib/mylibtar czf lib.tgz lib.PHONY:clean
clean:rm -rf *.o libmyc.a lib lib.tgz

这样,当别人想要使用自己实现的库进行链接时,我们make output一下,就可以将我们的lib目录或者是打包的文件发送,我们使用tar -xzvf archive.tgz就可以进行解包了:

//解包操作
tar -xzvf lib.tgz //静态库链接操作
gcc -o usercode usercode.c -I lib/include/ -L ./lib/mylib/ -lmyc

然后就可以形成可执行文件了!

2.动态库

2.1动态库生成

libmyc.so:mystdio.o mystring.ogcc -shared -o $@ $^
mystdio.o:mystdio.cgcc -fPIC -c $<
mystring.o:mystring.cgcc -fPIC -c $<.PHONY:output
output:mkdir -p lib/includemkdir -p lib/mylibcp -f *.h lib/includecp -f *.so lib/mylibtar czf lib.tgz lib.PHONY:clean
clean:rm -rf *.o libmyc.so lib lib.tgz

动态库的生成与静态库的生成有所不同,我们需要注意一下。这时我们何上面那样,进行打包,解包,然后gcc进行链接,这时出现了问题:
在这里插入图片描述
那么这就出现问题了:我已经将链接哪个库,在哪里找到该库等信息全都告诉你了,为什么还是执行不了呢?为什么静态库没有这个问题呢?
我们可以使用ldd指令查看可执行文件的链接状态,可以看到,确实是找不到目标动态库了
在这里插入图片描述

2.2静态链接和动态链接的区别

原因是:
1.静态库:程序在编译链接时就已经把库的代码和数据拷贝到了可执行文件中,所以可执行程序的执行就不需要静态库的存在了
2.动态库:程序在执行时才去链接动态库的代码和数据
3.而且,你是将路径告诉了gcc,gcc知道了从哪里获取库,获取哪一个库,但是系统不知道呀!执行可执行文件,是系统执行的呀!

2.3解决策略

那么动态库究竟要怎么进行链接呢?:

2.3.1将我们写的动态库拷贝至系统

 sudo cp ./lib/mylib/libmyc.so /lib64/

系统会自动在/lib64/路径下查找链接需要的动态库,当我们执行拷贝操作之后,就能够直接执行可执行文件了!:
在这里插入图片描述

2.3.2建立软链接

sudo ln -fs /home/test/LinuxClass/class15/zhangsan/lib/mylib/libmyc.so /lib64/libmyc.so

在这里插入图片描述
建立软连接,也可以找到需要链接的动态库,这个方法也是比较常用的方法

//取消建立链接
sudo unlink /lib64/libmyc.so

2.3.3LD_LIBRARY_PATH

操作系统运行程序,要查找动态库,也会在该环境变量中查找动态库:
在这里插入图片描述

//将我们的动态库的路径导入到该环境变量中
export LD_LIBRARY_PATH=/home/test/LinuxClass/class15/zhangsan/lib/mylib:$LD_LIBRARY_PATH
$LD_LIBRARY_PATH:保留原有的LD_LIBRARY_PATH值,以便在添加新路径的同时,不覆盖已有的路径。

这样就可以直接执行了:
在这里插入图片描述
需要注意的是:该环境变量在每次退出之后都会清空,因为它是内存级别的环境变量,所以我们可以将路径写到配置文件中,保证可以永久使用该动态库:
在这里插入图片描述

2.3.4ldconfig方案:配置/etc/ld.so.conf.d/

这个比较麻烦,我们只了解即可:
在这里插入图片描述

2.4几个结论输出

1.gcc/g++默认使用的是动态库,当我们既有静态库、又有动态库时,默认进行动态库链接,如果没有进行配置,就会出现报错!如果我们非要静态链接,在代码后面加上-static即可,但是一旦加上了-static,就只能进行静态链接,也就是说,如果只有动态库存在的情况下,加上-static,就不考虑动态链接的情况了
2.在Linux系统下,我们默认install、yum等操作,默认都优先安装动态库!
3.可能会有多个应用程序使用同一个库,也就是库:应用程序 = 1:n
4.vs在下载的过程中,想要编译C/C++代码,我们需要按照教程点击C++桌面开发等内容进行下载,其实下载的就是C/C++的标准库!所以说vs既可以形成可执行程序,也就是exe文件,也可以形成动静态库!只是需要配置罢了!感兴趣的可以询问大模型

3.使用外部库 – 感兴趣尝试

使用指南: link
我们首先需要安装ncurses,这是一个图形库,可以在Linux下展示一些有趣的图形界面:

// 安装 
// Centos
$ sudo yum install -y ncurses-devel// ubuntu
$ sudo apt install -y libncurses-dev

4.目标文件

.o/.obj文件被称为目标文件,其实叫做可重定位目标文件,它可以与其它文件进行链接,生成可执行程序
在这里插入图片描述
而为什么要将.c文件先生成.o文件之后,再将所有的.o文件与库进行链接,才能生成可执行程序呢?为什么不是将.c文件直接与库进行链接呢?:
1.先将.c文件编译为.o文件,这样当一份.c文件做出修改之后,只需要将这一份的.c文件再次编译成.o文件,再进行链接,就可以生成可执行程序了。对此,编译器其实做了很多隐藏的任务,当我们只对一份源文件进行修改,make之后,其实只有修改的那一份源文件进行了编译!
2.源文件并不是ELF格式,下面我们会讲什么是ELF文件格式

5.ELF文件

我们先说一下常见的ELF文件:
1.可重定向文件:.o文件
2.可执行文件:.exe文件
3.共享库文件:.so .a文件
ELF文件其实就是将源文件以一定的格式放入到二进制文件中

5.1ELF文件格式

ELF文件的格式如下:
在这里插入图片描述
我们现在只需要了解:
1.ELF文件,对于相同属性的代码和数据,放在一个section(节)中
2.代码节(.txt):保存机器指令,是程序的主要执行部分
3.数据节(.data):保存已初始化的全局变量和静态局部变量

5.2ELF从形成到加载轮廓

5.2.1ELF形成可执行

.o文件和库文件都是ELF文件,那么形成可执行文件的具体操作是什么?:
在这里插入图片描述
更多的细节操作我们不追究

5.2.2ELF可执行文件加载

我们先对ELF做出一个详细的了解:
在这里插入图片描述
所以ELF可执行文件怎么加载到内存的,我们已经简单了解了,下面会详细讲解加载到内存的全部操作的,我们先来看一些问题:

1.为什么要将section合并为segment:

1.减少页面碎片,提高内存使用效率:
磁盘是以块(4kb)为单位存储的,即使是1字节的数据,也需要一个块来存储,内存也是这样,也是按照块来存储的。假设.text部分占用4097个字节,一个页面可以存储4096个字节,所以.text部分就会占用两个页面,.init部分占用512字节,也就是1个页面,当进行合并时,.text部分会和.init部分合并,从而使页面使用率提高,减少了页面碎片。

2.我们怎么理解程序头表和节头表呢?我们从两个不同的视角来看:

1.链接视角 – 对应头节表(Section Header Table):
头节表中存储的是对节的描述,包括不同的节的地址,当进行链接操作时,需要知道各种的节地址,才能够执行链接操作
2.执行视角 – 对应程序头表(Program header table)
程序头表中存储的是所有的段对应的属性,当进程执行时,必须要告诉操作系统,如何完成内存的初始化,这时需要程序头表

3.symtable节 && Magic

在这里插入图片描述

5.2.3地址和偏移量之间的关系

在这里插入图片描述

6.理解链接与加载

有了上面的基础知识储备之后,我们看一下动静态库的链接和加载操作:

6.1静态链接

静态库的链接,其实就是将.o文件进行连接的过程,所以我们只需要研究.o文件的链接即可:
在这里插入图片描述
所以,.o文件被称为可重定向目标文件,其实就是在链接的过程中,会进行重定向操作!

6.2ELF加载与进程地址空间

6.2.1虚拟地址/逻辑地址

一个ELF文件,在汇编过程,就已经为可执行程序进行编址了,对于可执行程序来说,它包含代码段、数据段,其实也就是不同的segment,进程创建时,mm_struct和vm_area_struct的初始化数据就从Program Header Table中来

6.2.2重新理解虚拟地址空间

之前我们讲虚拟地址空间,知道了PCB、mm_struct、vm_area_struct,但是并没有深入到磁盘级别,我们今天重新理解一下:

在这里插入图片描述
在这里插入图片描述
所以,我们就知道了可执行程序执行的详细过程了!

6.3动态链接与动态库加载

6.3.1进程如何看待动态库 && 共享库

我们知道了进程如何看待可执行文件,但是动态库也是ELF文件,那么是如何看待动态库的,以及引入共享库的概念:
在这里插入图片描述

6.3.2动态链接

静态链接的缺陷在于:会将进程运行所需要的各种库,合并成一个独立的可执行文件,它不需要额外的依赖就可以运行。但是生成的文件体积大,当多个进程运行时,会造成多个相同库同时存在的情况。
而动态链接只需要在进程运行时存在一份库文件就可以实现进程的运行了!

6.3.2.1我们的可执行程序被编译器动了手脚

当我们反汇编我们的可执行程序后,会看到程序的真正入口 – _start:

0000000000400440 <_start>:400440:	31 ed                	xor    %ebp,%ebp400442:	49 89 d1             	mov    %rdx,%r9400445:	5e                   	pop    %rsi400446:	48 89 e2             	mov    %rsp,%rdx400449:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp40044d:	50                   	push   %rax40044e:	54                   	push   %rsp40044f:	49 c7 c0 d0 05 40 00 	mov    $0x4005d0,%r8400456:	48 c7 c1 60 05 40 00 	mov    $0x400560,%rcx40045d:	48 c7 c7 3d 05 40 00 	mov    $0x40053d,%rdi400464:	e8 b7 ff ff ff       	callq  400420 <__libc_start_main@plt>400469:	f4                   	hlt    40046a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)

在_start函数中,会执行一系列的初始化操作:
1.设置堆栈,为程序创建一个初始的堆栈环境
2.初始化数据段:将程序的数据段从初始化数据段复制到相应的内存位置,并清零未初始化的数据段
3.动态链接:这是今天要讲述的重要部分,要知道这个:_start函数会通过动态链接器提供的方法来解析和加载进程所依赖的动态库,处理所有的符号解析,让我们能够正确访问到库中的函数
4.调用__libc_start_main函数:动态链接完成之后,会调用该函数,负责执行一些额外的初始化工作,如信号处理等
5.main函数的调用:然后才会调用我们的main函数
6.处理main函数的返回值:当main函数返回时,__libc_start_main函数会处理该返回值,并最终调用_exit函数来终止进程

我们可以看一下动态链接器:
在这里插入图片描述

6.3.2.2动态库中的相对地址

动态库也是ELF,我们也可以理解为:起始地址(0) + 偏移量:
我们会在下面结合图片进行说明:

6.3.2.3程序如何和库进行映射

在这里插入图片描述

6.3.2.4程序怎么进行库函数的调用


上面对代码区进行修改的方法称为加载地址重定位,需要二次完成地址设置

6.3.2.5全局偏移量表(GOT)

在.data中,会被预留一块区域,用于存放函数的跳转地址,叫做全局偏移量表GOT,而.data区域是可读写的

在这里插入图片描述

6.3.2.6库间依赖 && plt(延迟绑定)

不仅仅只有可执行程序会调用库,库间也会进行库的调用,也就是库间依赖,而库也是ELF格式,也有.got,从而实现对其它库的调用!
但是库间链接需要对大量的函数进行重定位,比较耗时,所以就进行了延迟绑定(plt)的优化策略,思路是不一次性进行所有函数的重定位操作了,因为有的函数可能需要多次使用,从而进行多次的重定位操作,优化为在函数第一次执行时进行重定位操作,并且更新got表,再次进行函数调用时,只需要查找got表即可!:
在这里插入图片描述

7.总结

1.静态链接的出现,提高了程序的模块化水平,比如在企业中,各组完成的项目可以独立地进行测试,最后通过静态链接,就可以生成最终的可执行文件
2.静态链接会将编译产生的所有目标文件,和各种库合并,生成一个独立的可执行文件,其中我们会修改模块间函数的跳转地址,这叫做编译重定位
3.动态链接实际上是将链接的过程推迟到了程序加载的时候,当进程运行时,会将动态库的代码和数据加载到内存中,但是无论被加载到哪里,都要被映射到进程对应的地址空间,然后通过.got进行调用


http://www.ppmy.cn/server/179079.html

相关文章

HTTP长连接与短连接的前世今生

HTTP长连接与短连接的前世今生 大家好&#xff01;作为一名在互联网摸爬滚打多年的开发者&#xff0c;今天想跟大家聊聊HTTP中的长连接和短连接这个话题。 记得我刚入行时&#xff0c;对这些概念一头雾水&#xff0c;希望这篇文章能帮助新入行的朋友少走些弯路。 什么是HTTP…

基于深度学习的行人人脸识别系统的设计与实现

标题:基于深度学习的行人人脸识别系统的设计与实现 内容:1.摘要 随着安防、智能监控等领域的快速发展&#xff0c;行人人脸识别技术的需求日益增长。本研究旨在设计并实现一个基于深度学习的行人人脸识别系统。采用先进的深度学习算法&#xff0c;如卷积神经网络&#xff08;C…

uniapp笔记-swiper组件实现轮播图

思路 主要就是参考 swiper | uni-app官网 实现轮播图。 实例 新建一个banner.vue通用组件。 代码如下&#xff1a; <template><view>轮播图</view> </template><script> </script><style> </style> 随后在index.vue中导…

Android Compose 线性布局(Row、Column)源码深度剖析(十)

Android Compose 线性布局&#xff08;Row、Column&#xff09;源码深度剖析 一、引言 在 Android 应用开发的领域中&#xff0c;UI 布局是构建用户界面的核心工作之一。良好的布局设计不仅能提升用户体验&#xff0c;还能使应用在不同设备上保持一致的视觉效果。随着 Androi…

【linux】ubuntu 用户管理

目录 一、用户基本命令 1.1 添加新用户 1.2 切换用户 1.3 删除用户 1.4 修改用户密码 1.5 用户组的新建和删除 二、用户相关的文件 三、sudo权限的授权 3.1 sudo权限的授权 3.2 问题&#xff1a;sudo和su命令不能使用。 3.3 可以试试强制切换root。但是需要sudo可以…

第三章 | 初识 Solidity:开发环境搭建 第一个智能合约{介绍篇}

&#x1f4da; 第三章 | 初识 Solidity&#xff1a;开发环境搭建 & 第一个智能合约 ——从写下第一行代码&#xff0c;开启智能合约开发之旅&#xff01; ✅ 本章导读 前两章我们讲清了区块链和智能合约的基础原理&#xff0c;现在—— 是时候动手实战&#xff01; 你将完…

开发中常用的设计模式 用法及注意事项【面试题】

常见的设计模式&#xff1a;单例模式、工厂模式、观察者模式、发布-订阅模式、装饰器模式、策略模式、代理模式、模块模式等 React中的高阶组件&#xff08;装饰器模式&#xff09;、Vue的事件总线&#xff08;发布-订阅模式&#xff09; 一、 单例模式 (Singleton) 用途&…

[React 进阶系列] 组合组件 复合组件

[React 进阶系列] 组合组件 & 复合组件 今天写个人项目练手的时候搜到了一个比价有趣的实现&#xff0c;于是用了一下&#xff0c;发现这个 concept 不是特别的熟&#xff0c;于是上网找了下&#xff0c;返现了一个叫 复合组件(compound components) 的概念。搜索了一下后…