Linux 库制作与原理

库制作与原理

这份文档讲解Linux下静态库、动态库的制作、使用,ELF目标文件格式、编译链接、程序加载、虚拟地址空间、动态链接原理,配套gcc选项、Makefile示例、调试命令。

一、库基础概念

库是预编译、可复用的二进制代码;Linux分静态库(.a)动态库(.so) ;Windows对应.lib.dll

  • 头文件.h:提供函数声明,给编译器看;
  • 库文件:提供函数二进制实现,给链接器使用。

1.静态库(.a)

  1. 原理 :编译链接阶段,把库里面的.o目标文件拷贝合并进可执行程序;程序运行不再依赖.a文件
  2. 制作步骤
    1. gcc -c xxx.c得到.o目标文件(只编译不链接)
    2. ar -rc libxxx.a *.o归档打包,ar是归档工具。Makefile示例给出完整构建脚本。
  3. 使用
    • -I:指定头文件搜索路径;
    • -L:指定库文件搜索路径;
    • -lxxx:链接库(去掉lib前缀、.a后缀);
    • -static:强制使用静态链接。

静态库链接完成后,删除.a,程序依旧正常运行。

2.动态库(共享库 .so)

  1. 原理编译时只记录依赖,不拷贝代码;运行时由操作系统把.so加载进内存 。多个进程内存可以共享同一份动态库代码,节省磁盘、内存空间;程序运行必须存在.so文件。

  2. 制作

    1. 编译生成位置无关代码:gcc -fPIC -c xxx.c
    2. 生成动态库:gcc -shared -o libxxx.so *.o

    -fPIC生成位置无关代码,让库可以加载到内存任意地址。

  3. 编译使用 :同样用-I-L-l编译找库路径≠运行找库路径

  4. 动态库运行查找(重点易错)
    编译时-L只是告诉链接器 去哪里找库;程序运行时动态链接器会另外搜索库 ,找不到会报not found
    解决4种方案:

  5. .so复制到系统库目录/usr/lib/lib64

  6. 创建软链接指向库;

  7. 设置环境变量LD_LIBRARY_PATH(临时生效);

  8. 修改/etc/ld.so.conf.d/配置文件,执行ldconfig更新系统库缓存,永久生效。

  9. 调试命令:ldd 可执行程序查看程序依赖哪些动态库,是否找到。

gcc默认优先动态链接;找不到同名.so才会用静态库。

二、目标文件与ELF格式

.o目标文件、可执行程序、.so动态库、core转储文件,全部属于ELF格式

ELF提供两个视图:

  1. 链接视图(节section,节头表Section Header Table) :编译链接阶段使用,细粒度划分代码数据。
    • .text:程序机器指令代码;
    • .data:已经初始化的全局、静态变量;
    • .rodata:只读常量,字符串字面量;
    • .bss:未初始化全局/静态变量,只预留内存,不占文件大小;
    • .symtab符号表:记录函数、变量符号;UND代表未定义符号
    • .got/.got.plt全局偏移表,用于动态链接。
  2. 执行视图(段segment,程序头表Program Header Table):操作系统加载程序到内存使用。链接器会把属性相同(可读、可写、可执行)的多个section合并为一个segment,减少内存页碎片,方便设置内存权限。

工具命令

  • readelf -h 文件:查看ELF头部信息;
  • readelf -S 文件:查看所有section节;
  • readelf -l 文件:查看segment段;
  • objdump -d 文件:反汇编查看代码段机器指令。

三、静态链接过程

  1. 源文件经过gcc -c编译得到若干.o目标文件。每个.o内部调用外部函数时,不知道外部函数真实地址,call指令地址填0,依靠重定位表后期修正。符号表里标记UND未定义符号。
  2. 链接器工作:
    • 把各个.o.text.data等section合并;
    • 根据重定位表,修正函数调用地址(静态重定位,编译链接阶段完成地址修正);
    • 把静态库里面需要的.o提取出来,一并合并;
  3. 输出独立可执行文件,不再依赖原来的.o和静态库。

四、进程虚拟地址空间 & ELF加载

  1. ELF可执行文件本身就带有虚拟地址,不是加载到内存才分配地址。
  2. 操作系统加载可执行程序:读取ELF程序头表,按照segment信息,映射到进程虚拟地址空间;内核task_struct、mm_struct、vm_area_struct结构体描述进程地址空间布局(代码段、数据段、堆、栈、内核空间)。
  3. 物理内存和进程虚拟地址靠页表映射。

五、动态链接原理(GOT / PLT / PIC)

动态链接把符号地址解析工作推迟到程序运行时

  1. PIC位置无关代码:动态库不能写死绝对内存地址,全部用相对寻址。
  2. GOT全局偏移表 :存函数、变量真实地址;GOT所在段是可读写;代码段.text只读,不能直接修改机器指令,修改地址全部改GOT表。每个进程拥有自己GOT。
  3. PLT过程链接表,延迟绑定(懒加载)
    • 程序刚启动,不解析全部动态库函数;
    • 第一次调用库函数:PLT桩代码调用动态链接器解析符号,把真实地址回填GOT;
    • 后续再次调用该函数:直接查GOT跳转,不再解析,提升性能。
  4. 动态链接器ld‑linux‑x86‑64.so:程序启动_start入口,调用动态链接器加载所有依赖.so库,完成符号重定位,之后调用__libc_start_main,才执行用户的main函数。
  5. 动态库被mmap映射进进程虚拟地址空间;物理内存一份库,可以映射到多个不同进程虚拟地址,实现代码共享。

六、gcc编译选项汇总

选项 作用
-c 只编译,不链接,输出.o目标文件
-I(大写i) 头文件.h搜索目录
-L(大写L) 库文件(.a/.so)搜索目录(编译链接阶段)
-lxxx(小写L) 链接libxxx库,去掉lib前缀和后缀
-fPIC 生成位置无关代码,制作动态库必须
-shared 生成动态共享库.so
-static 强制静态链接

七、重要命令清单

  1. ar -rc:生成静态库;ar -tv查看静态库内部.o
  2. ldd:查看可执行/动态库依赖;
  3. readelf:解析ELF头、节、段;
  4. objdump -d:反汇编看机器指令;
  5. ldconfig:刷新系统动态库缓存。
相关推荐
liulilittle1 小时前
Linux AI 开发环境搭建实录
linux·ai·llm·agent·hyper-v·dev·opencode
高亦真2 小时前
今天是学习嵌入式的第39天
linux·学习·算法
AR-26710-3 小时前
Linux Day8——FHS文件系统层次
linux
qq_297670873 小时前
Ubuntu 打开系统应用老弹密码?三步彻底关闭免密
linux
杨云龙UP3 小时前
DB2 HADR 主备切换实战:SQL1639N 认证问题、备库只读与双向 Takeover
linux·运维·数据库·db2·主备切换·角色互换
半仙白桑3 小时前
内核篇第一讲:linux创建进程
linux·fork·创建进程
吴声子夜歌3 小时前
Shell编程实例——bash的配置与自定义(二)
linux·运维·shell
风笙9783 小时前
[内存管理] page数据结构
linux