1. 什么是库
- 所有的库,本质都是源文件对应的 .o 文件
- 动静态库中不要包含 main 函数
- 静态库: .a[linux]、.lib[windows]
- 动态库:.so[linux]、.dll[windows]
2. 静态库
- 静态库(.a):程序在链接的时候把代码链接到可执行文件中,程序运行的时候不再需要静态库
- 编译默认使用动态链接库,gcc的 -static 强制设置链接静态库
2.1 静态库生成
- 静态库命名必须遵守 libxxx.a
ar -rc libmyc.a *.o # 把所有 .o 文件打包成 xxx.a
2.2 静态库使用
# 场景1:头文件和库文件安装到系统路径下
gcc main.c -lmystdio
# 场景2:头文件和库文件和我们的源文件在同一个路径下
gcc main.c -L . -lmymath
# 场景3:头文件和库文件有自己的独立路径
gcc main.c -I 头文件路径 -L 库文件路径 -lmymath
- 选项(可以不留空格)
- -L:指定库路径
- -I(大写):指定头文件搜索路径
- -l(小写):指定库名
- 库文件名称和引入库的文件
- 去掉前缀 lib 和 后缀 .a / .so ,如 libc.a -> c
3. 动态库
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省磁盘空间
3.1 动态库生成
# 1. 生成位置无关的目标文件 .o
gcc -c -fPIC test.c add.c ...
# 2. 生成动态库
gcc -shared -o libmy.so *.o
- shared:表示生成共享库模式
- fPIC:产生位置无关码
- 库文件名称和引入库的文件
- 去掉前缀 lib 和 后缀 .a / .so ,如 libc.so -> c
3.2 动态库的使用
gcc main.c -I 头文件路径 -L 库文件路径 -lmymath
- 选项(可以不留空格)
- L:指定库路径
- I(大写):指定头文件搜索路径
- l(小写):指定库名
3.3 库运行搜索路径
- 问题:编译时用 -L 能找到库生成可执行文件,但是系统不知道导致运行时找不到对应库文件
- 解决方法:
- 拷贝 .so 文件到系统共享库路径下,一般指 /usr/lib、/usr/local/lib、/lib64 或者开篇指明的库路径
- 向系统库路径下建立同名软连接
- 更改环境变量(临时,常用):LD_LIBRARY_PATH
- ldconfig方案:配置 /etc/ld.so.conf.d/,使用ldconfig更新
3.4 总结
- 结论1:gcc/g++默认使用动态库,想用静态链接只能提供静态库并加 -static
- 结论2:在linux系统下,默认情况安装的大部分库,默认都优先安装的是动态库
- 结论3:库:应用程序 = 1:n
- 结论4:vs不仅仅形成可执行程序,也能形成动静态库
4. 目标文件
-
.o/.obj:可重定位目标文件
![![[编译和链接过程.png]]](https://i-blog.csdnimg.cn/direct/8fcb90610f0f462e8a3065ad55b88e42.png)
-
目标文件(.o文件)是一个二进制文件,文件格式是 ELF,是对二进制代码的一种封装
-
动静态库、可执行程序、可重定位目标文件都是 ELF 格式的
5. ELF文件
- ELF文件:
- 可重定位目标文件:即 xxx.o 文件
- 可执行文件:即可执行程序
- 共享目标文件:即 xxx.so 文件(静态库是 xxx.o 的打包压缩包,不属于)
- 内核转储:存放当前程序的执行上下文,用于dump信号触发
- ELF文件组成
- ELF头:描述文件的主要特性
- 程序头表:列举所有有效的段和属性
- 节头表:包含对节的描述
- 节:ELF文件的组成单位,包含特定类型的数据
- 代码节(.txt):保存机器指令,是程序的主要执行部分
- 数据节(.data):保存已初始化的全局变量和局部静态变量
![![[XXX.o的ELF格式.png]]](https://i-blog.csdnimg.cn/direct/4e0b33c9ed0d46f3bb873e3d09825562.png)
6.ELF从形成到加载轮廓
6.1 ELF形成可执行
- step1(前置步骤):将多份 C/C++ 源代码,翻译成目标 .o 文件 + 动静态库(ELF)
- step2:将多份 .o 文件 section 进行合并
![![[ELF形成可执行文件.png]]](https://i-blog.csdnimg.cn/direct/0f22b11f7ce249888cf7506b2886aa7f.png)
6.2 ELF可执行文件加载
- 一个 ELF 会有多种不同的 Section,在加载到内存的时候,也会进行 Section 合并,形成 segment
- 合并原则:相同属性,比如:可读,可写,可执行,需要加载时申请空间等
- 为什么要将 section 合并成为 segment?
- 为了减少页面碎片,提高内存效率
- 链接视图 – 对应节头表 Section header table
- 静态链接的时候一般关注的是链接视图
- 执行视图 – 对应程序头表 Program header table
- 如何加载可执行文件
- 告诉操作系统哪些模块可以被加载进内存
- ELF HEADER
- 主要目的是定位文件的其他部分
7. 链接与加载
7.1 静态链接
- 研究静态链接,本质就是研究 .o 是如何加载的
- 在 .o 文件中,不知道调用函数的地址,所以在汇编时,编译器只是将函数的跳转地址先暂时设为0
- 链接的时候这个地址会修正
- 多个 .o 合并之后,在最终的可执行程序中,就知道函数对应的地址了
- 静态链接就是把库中的 .o 进行合并,并修正他们的地址
- 链接过程中会涉及到对 .o 中外部符号进行地址重定位
![![[静态链接的过程.png]]](https://i-blog.csdnimg.cn/direct/644c863f889f465b834232dfa3214074.png)
7.2 ELF加载与进程地址空间
7.2.1 虚拟地址/逻辑地址
- 一个ELF程序,在没有被加载到内存的时候就有地址
- ELF的逻辑地址(起始地址+偏移量)认为是起始地址0
- 进程 mm_struct、vm_area_struct 在刚创建的时候数据从哪里来?
- 从 ELF 各个 segment 来,每个 segment 都有自己的起始地址和自己的长度,用来初始化内核结构中的 [start,end] 等范围数据秒另外再用详细地址填充页表
- 虚拟地址机制,不光要OS支持,编译器也要支持
7.2.2 理解进程地址空间
![![[重新理解进程地址空间.png]]](https://i-blog.csdnimg.cn/direct/9bbb2f4f1b2e44919bdcfc42614d759d.png)
- 磁盘里的 ELF 存的是虚拟地址;加载到内存后,内核建立虚拟地址到物理地址的映射;CPU 永远用虚拟地址,由 MMU 转成物理地址去执行;mm_struct 和 vm_area_struct 管理这个映射关系,真正的映射写进页表
7.3 动态链接与动态库加载
- 库函数调用
- 1.被进程看到:动态库映射到进程的地址空间
- 2.被进程调用:在进程的地址空间中进行跳转
7.3.1 进程如何看到动态库
- 进程通过虚拟地址看到动态库
![![[进程如何看到动态库.png]]](https://i-blog.csdnimg.cn/direct/02862bbe08784f8c903880cf4793b2a6.png)
7.3.2 进程间如何共享库的
- 动态库中的代码不会重复出现了
![![[进程间如何共享库的.png]]](https://i-blog.csdnimg.cn/direct/3f00d281e82d4cc3ae06858ccd60c341.png)
7.3.3 动态链接
7.3.3.1 概要
- 静态链接会将编译产生的所有目标文件,连同用到的各种库,合并形成一个独立的可执行文件
- 静态链接的最大问题在于生成的文件体积大,并且相当耗费内存资源
- 动态链接将连接的整个过程推迟到了程序加载的时候
- 将需要共享的代码提取出来,保存成一个独立的动态链接库,等到程序运行的时候再将它们加载到内存,不但可以节省空间,还因为同一模块在内存中只需要保留一份副本,可以被不同的进程共享
7.3.3.2 可执行程序
- _start 函数会调用动态链接器的代码来解析和加载程序所依赖的动态库(sharedlibraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址
- 环境变量和配置文件:指定动态库搜索路径
7.3.3.3 怎么进行库函数调用
- 访问库中任意方法,只需要知道库的起始虚拟地址+方法偏移量,即可定位库中的方法
- 整个调用过程,是从代码区跳转到共享区,调用完毕再返回代码区,整个过程完全在进程地址空间中进行
7.3.3.4 全局偏移量表GOT
![![[库函数调用过程.png]]](https://i-blog.csdnimg.cn/direct/02ec65ef38a44ed780f9140984440fed.png)
- 每个进程的每个动态库有独立的 GOT 表,所有进程间不能共享 GOT 表
- 在调用函数的时候会先查表,然后根据表中的地址来进行跳转,这些地址在动态库加载的时候会被修改为真正的地址
7.3.3.5 库间依赖
- 库也会调用其他库!库之间是有以来的
- 库中也有 GOT
- 库在被调用到时才对函数进行重定位,延迟绑定(PLT)
- 动态链接实际上将链接的整个过程,从编译(准确来说是汇编)时推迟到了程序的运行时
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_86525813/article/details/159174590



