当前位置: 首页 > news >正文

动态库和静态库详解

库其实就是个文件

下面是文件后缀

静态库:.a(linux)   .lib(windows)

动态库:.so(linux) .dll(windows)

静态库的制作

ar   -rc   libmystdio.a   my_stdio.o   my_string.o

ar是归档工具,rc表示replace和create,ar跟tar有点像,把.o文件打包到一块,其他啥也不干

ar  -tv  libmystdio.a 

t: 列出静态库中的⽂件 ,v:详细信息,查看静态库

动态库的制作

libmystdio.so:my_stdio.o my_string.o
        gcc -o $@ $^ -shared
%.o:%.c
        gcc -fPIC -c $<
制作动态库的目标文件必须是源文件-fPIC编译成的,pic意思是位置无关编码,pic编译和普通的编译有什么区别呢?
正常的编译如果你调用一个函数,编出来的指令是call 临时地址,那pic编译呢,call pc+临时偏移量,这个偏移量是目标函数和pc之间的相对距离。因为动态库的加载位置是共享区,起始位置不固定,所以不能用绝对地址
生成动态库的过程其实就是把pic编译的目标文件给链接起来,要加-shared选项,表示不是链接出一个可执行文件而是动态库

 

库的使用

库这个文件的用处,就是和其他目标文件一起链接得到可执行程序
引⼊库的名称:去掉前缀 lib ,去掉后缀 .so , .a ,如: libc.so -> c
gcc main.c -I 头⽂件路径 -L 库⽂件路径 -lmymath
先编译main.c成目标文件,再和mymath库链接,得到最后的同名可执行文件main

ELF文件

那些文件是ELF文件?

.o目标文件

.exe可执行文件

.so动态库文件

核心转储文件

ELF文件的格式

ELF(ELF header)

程序头表 (Program header table)
节( Section
节头表 (Section header table)

1. 节头表(Section Header Table)​

  • 描述目标文件(.o)中的各个​​节(section)​​(如 .text.data.symtab 等),供​​静态链接器​​(ld)使用
  • 链接时,链接器会合并不同目标文件中的​​同名节​​(如将所有 .text 合并到代码段)成段。
  • ​​动态库/可执行文件可以没有节头表​​。

​2. 程序头表(Program Header Table,段头表)​

  • 描述可执行文件或动态库中的​​段(segment)​​(如代码段、数据段),供操作系统​​加载器​​(execve/ld.so)使用。
  • 加载时,操作系统根据程序头表将段映射到内存(如 LOAD 类型的段)
  • ​动态库和可执行文件必须有程序头表​​(否则无法加载),可以没有节头表。

​3. ELF 头

  • e_entry 字段​​:指定程序执行的起始地址(即 _start 函数的地址),加载后会将此地址赋给 ​​PC/RIP​​ 寄存器。

目标文件的节详解

1. 代码与数据节​

​节名​​作用​​常见属性​
.text存储​​代码​​(函数、指令)AX(可执行、只读)
.data存储​​已初始化的全局/静态变量​WA(可写、已初始化)
.bss存储​​未初始化的全局/静态变量​​(实际不占文件空间)WA(可写、未初始化)
.rodata存储​​只读数据​​(如字符串常量、const变量)A(只读)

链接后.text节和.rodata节会合并为代码段,.data节合并为数据段,.bss节合并为bss段


​2. 符号与重定位节​

​节名​​作用​​链接阶段关键性​
.symtab​符号表​​(函数、变量名及其地址/大小)✅ 静态链接必需
.rel.text.text节的​​重定位条目​​(修正代码中的地址引用)✅ 必需
.rel.data.data节的​​重定位条目​​(修正数据中的地址引用)

✅ 必需

符号表的作用:

1.记录符号定义,符号是变量还是函数,以及对应地址

2.标记未定义符号

3.辅助重定位

重定位表的作用:

目标文件中代码和数据中对符号(函数/变量)的引用地址是临时的。重定位表指导链接器如何将这些临时地址替换为​​最终的虚拟地址​​。


​3. 动态链接相关节​​(仅当启用-fPIC编译时存在)

​节名​​作用​​动态库必需​
.got全局偏移表(Global Offset Table)✅ 动态库需要
.plt过程链接表(Procedure Linkage Table)✅ 动态库需要

静态链接的​过程

​(1) 输入文件准备​
  • ​目标文件(.o)​​:含main的目标文件
  • ​静态库(.a)​​:本质是多个目标文件的打包(通过ar工具生成),例如:ar rcs libfoo.a foo.o bar.o # 将foo.o和bar.o打包为libfoo.a
​(2) 链接器(ld)的工作流程​
  1. ​符号解析(Symbol Resolution)​

    • 合并所有目标文件的符号表(.symtab),检查是否存在​​未定义符号​​(标记为U)。
    • ​若存在未定义符号​​:直接报错(如undefined reference to 'printf'),链接失败。
  2. ​节合并(Section Merging)​

    • 将同名节(如.text.data)从不同目标文件合并为​​段(Segment)​​:
      • 所有.text → 代码段(可执行)
      • 所有.data → 数据段(可读写)
      • 所有.bss → BSS段(未初始化数据,不占文件空间)
  3. ​分配虚拟地址(Address Assignment)​

    • 链接器为每个段分配​​最终的虚拟地址​​。例如:

      .text 0x400000 (代码段基地址) .data 0x600000 (数据段基地址)

  4. ​重定位(Relocation)​

    • 根据重定位表(.rel.text/.rel.data)修正代码和数据中对符号的引用:
      • ​修正代码中的地址​​:如call 0x0 → call 0x400100(函数foo的最终地址)。
      • ​修正数据中的指针​​:如mov rax, [0x0] → mov rax, [0x600020](变量var的地址)。
  5. ​生成可执行文件​

    输出一个完整的ELF可执行文件(a.out),包含程序头表(描述如何加载段到内存)。

动态链接的过程​

​1.生成可执行文件时的链接​

  • ​输入文件​​:
    • 目标文件(main.o
    • 动态库(.so,如 libc.so
  • ​静态链接器(ld)的操作​​:
    1. ​符号解析​​:检查 main.o 中的未定义符号(如 printf)是否在动态库的 .dynsym 中。
    2. ​生成 PLT/GOT​​:
      • 在可执行文件中创建 .plt 和 .got.plt 节。
      • 将对动态库函数的调用(如 call printf)改为 call printf@plt
    3. ​记录依赖关系​​:在可执行文件的 .dynamic 段中注明依赖的动态库(如 libc.so.6)。

​2. 运行时动态加载流程​

​(1) 加载可执行文件​
  • ​操作系统​​:
    1. 读取可执行文件的 ​​程序头表​​(Program Header Table),将代码段(.text)和数据段(.data)映射到进程的虚拟地址空间。
    2. 从ELF头中读程序入口,将 PC 寄存器设为 e_entry(入口地址,通常是 _start)。
​(2) 加载动态库​
  • ​动态链接器(ld.so)​​:
    1. 根据 可执行文件的.dynamic 段加载所有依赖的动态库(如 libc.so)到 ​​随机地址​​(ASLR)。
    2. 重定位got表的里变量的地址,函数地址不重定位,有另一套机制

got表和plt表说明

1.当动态库加载进地址空间,ld.so重定位got表中变量的地址,而代码段中对动态库中变量的访问,编译的指令为访问got表中目标变量的地址,如何做到,就是mov eax【pc+偏移量】,偏移量是pc到目标变量GOT条目的偏移​​

2.动态库加载进地址空间,动态链接器不会重定位got表中函数项,因为

在链接时会符号解析,如果main.o中使用了动态库的函数,那么调用函数的指令会从call printf被换成call printf@plt,去跳转plt表对应函数条目

printf@plt:
  jmp func@got ; 跳转got项
  pushq $index       ; 压入重定位表索引(如 printf 在 .rela.plt 中的位置)
  jmp .plt           ; 跳转到动态链接器(ld.so),重定位函数

plt表是在代码段,因为plt是不可更改的,而got在数据段,因为函数项是要动态重定位的,got表中函数项在编译时被编译成plt表中对应函数条目的push指令

第一次调用库函数时,call func@plt 跳转plt对应函数条目,跳转到got项,然后pushq 重定位表索引到队列,调用动态链接器去重定位库函数,将地址写进got,然后调用函数

第二次调用库函数,call func@plt  跳转plt表对应函数条目,跳转got项,然后直接调用函数

相关文章:

  • 从数学融智学视域系统地理解《道德经》:前三十七章,道法自然
  • C语言中的文件I/O
  • 单目视觉测量及双目视觉测量
  • 【软件安装】Windows操作系统中安装mongodb数据库和mongo-shell工具
  • 【AUTOSAR网络管理】T_NM_Timeout参数测试指南
  • 10G SFP+ 双纤光模块选购避坑指南:从SFP-10G-LRM到SFP-10G-ZR的兼容性与应用
  • 八股--SSM(2)
  • 【通用智能体】smolagents/open_deep_research:面向开放式研究的智能体开发框架深度解析
  • STM32 CubeMX时钟配置PWM信号输出
  • SOC-ESP32S3部分:8-GPIO输出LED控制
  • 辐射发射RE测试
  • 生成模型——扩散模型(Diffusion Model)
  • LoRA(Low-Rank Adaptation)
  • 从JDK 8到JDK 17的主要变化
  • 生成模型——Pix2Pix
  • 软件工程(八):UML类图的几种关系
  • 光流法(Optical Flow)
  • Python打卡训练营day30-库的导入
  • I.MX6ULL_Linux_系统篇(26) buildroot分析
  • 南京邮电大学《智能控制技术》期末抢救(上)
  • 专业网站建设阿里云/网站推广怎么做才有效果
  • 妈妈做愛网站/重庆seo建站
  • 珠海找工作哪个网站好/关键词排名优化易下拉软件
  • ubuntu配置wordpress/淘宝关键词优化工具
  • 微信微商城怎么做/搜索引擎优化的方法包括
  • 做外汇关注的网站/最新新闻播报