
.a | .so |
动态链接器 (ld.so 或 ld-linux.so): 负责加载和链接动态库
动态段 (.dynamic): ELF文件中存储动态链接信息的部分
全局符号表 (Global Symbol Table): 用于符号解析的数据结构
程序启动 → 加载动态链接器 → 解析程序动态段 → 加载依赖库 → 符号解析 → 重定位 → 初始化 → 程序执行当执行一个动态链接的程序时,内核首先检查ELF文件的程序头表,找到解释器段(INTERP),该段指定了动态链接器的路径:如下图所示

内核加载动态链接器到内存,并将控制权移交给它。
动态链接器读取程序的动态段,获取所有依赖的共享库:

动态链接器按照特定顺序搜索库文件:
RPATH/RUNPATH: 编译时指定的库搜索路径
LD_LIBRARY_PATH: 环境变量指定的路径
/etc/ld.so.cache: 缓存的库配置
默认路径: /lib, /usr/lib 等
如下所示查看RPATH/RUNPATH

动态链接器使用mmap系统调用将库文件映射到进程的地址空间:
// 简化的映射过程void* addr = mmap(NULL, lib_size, PROT_READ|PROT_EXEC,MAP_PRIVATE, fd, offset);
不同的段(代码段、数据段)根据权限要求被映射到不同的内存区域:
代码段: 只读、可执行(通常开启ASLR)
数据段: 可读写(包含全局变量等)
这是动态链接的核心环节:
符号解析: 将未定义的符号引用绑定到具体的定义
重定位: 修改代码中的地址引用
// 可执行程序源代码#include<stdio.h>#include"third.h"intmain(int argc, char** argv){int result = sum(1, 6);printf("result = %d\n", result);return 0;}
// sum函数所在的动态库源代码#include"third.h"intsum(int a, int b){return a + b;}
// 编译可执行程序g++ -g -o main main2.cpp -I./ -L./ -lthird -Wl,--rpath=/home/candy/Desktop/Project/gdb
// 编译动态库g++ -fpic -g -shared -o libthird.so third.cpp


R_X86_64_RELATIVE: 库内部数据的相对地址调整
R_X86_64_JUMP_SLO: 对printf等外部函数的跳转槽需要填充

库的初始化函数按照特定顺序执行:
构造函数: 标记为__attribute__((constructor))的函数
.init_array段: 函数指针数组
.preinit_array段: 更早的初始化函数
// 示例:构造函数__attribute__((constructor))void init_function() {printf("Library initialized\n");}
.fini_array中的函数。
/etc/ld.so.conf: 额外的库搜索路径配置
/etc/ld.so.preload: 预加载的库列表
链接器脚本: 控制内存布局的脚本文件
3.3 查看动态库在进程的内存布局

注意:本文主要讲解借助动态链接器去装载链接库,当然也可以在程序中使用dlopen主动去加载动态库并取用其中的函数、变量进行使用!