preface
export LD_LIBRARY_PATH=...
LD是 Loader / Linker Dynamic 的缩写- 更准确地说,通常指 Linux 的 动态链接器(dynamic linker / loader)
LD_LIBRARY_PATH
可以理解成:
"动态链接器查找共享库(.so 文件)的路径"
例如:
bash
export LD_LIBRARY_PATH=/opt/mylib/lib
意思是:
告诉 Linux 的动态加载器:
运行程序时,除了系统默认目录,
还去
/opt/mylib/lib里面找.so动态库。
Linux 程序很多都会依赖:
text
libstdc++.so
libssl.so
libcuda.so
这些 .so 文件类似 Windows 的 .dll。
程序启动时:
- 内核启动程序
- 动态链接器(ld-linux.so)开始工作
- 它去找程序依赖的
.so - 找不到就报错:
text
error while loading shared libraries:
libxxx.so: cannot open shared object file
一、区分两个ld
Linux 里 "ld" 常见含义
1. ld 命令
Linux 还有一个真正叫:
bash
ld
的程序:
它是 GNU linker(链接器)。
例如:
bash
ld a.o -o app
负责把 .o 文件链接成可执行文件。
2. 动态加载器
比如:
text
/lib64/ld-linux-x86-64.so.2
这个也是 ld。
它负责:
- 加载动态库
- 解析符号
- 启动 ELF 程序
很多时候:
bash
LD_PRELOAD
LD_LIBRARY_PATH
这些环境变量都是给它用的。
常见 LD 环境变量
| 变量 | 作用 |
|---|---|
LD_LIBRARY_PATH |
额外动态库搜索路径 |
LD_PRELOAD |
强制预加载某个 .so |
LD_DEBUG |
输出动态链接调试信息 |
你还可以用:
bash
ldd 程序名
查看程序依赖哪些动态库。
例如:
bash
ldd /bin/ls
会显示:
text
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
这里的 ldd 其实也是 "list dynamic dependencies"。
二、loader(动态链接库)加载其他程序(例如库函数)的代码到本程序
很接近底层原理理解
动态加载器会把"其他共享库里的代码和数据"加载到当前进程的内存空间里,让本程序可以调用它们。
例如:
c
#include <stdio.h>
int main() {
printf("hello\n");
}
这几行代码,并没有 printf() 的真正代码实现。
真正代码在:
text
libc.so.6
里面。
当你运行程序时:
bash
./app
Linux 会:
1. 先启动动态加载器
例如:
text
/lib64/ld-linux-x86-64.so.2
2. 动态加载器读取 ELF 信息
它发现:
text
这个程序依赖 libc.so.6
可以用:
bash
ldd app
看到。
3. 把 libc.so.6 映射进当前进程内存
注意:
这里通常不是"复制代码"。
而是:
用 mmap 把共享库映射(map)到进程地址空间。
所以多个程序可以共享同一份物理内存里的 libc 代码。
这也是 .so 叫:
text
shared object
(共享对象)的原因。
4. 修正函数地址(动态链接)
程序里原本只有:
text
printf 在 libc 某处
但不知道真实地址。
动态加载器会:
- 找到 printf
- 得到真实内存地址
- 填入 GOT/PLT 等表
这样:
c
printf()
才能真正跳转到:
text
libc.so.6 里的 printf 实现
5. 才开始执行 main()
所以实际上:
text
main()
运行之前:
动态加载器已经做了很多工作。
你可以把它理解成:
text
你的程序 = 主程序
libc.so = 外挂代码模块
动态加载器负责:
- 找模块
- 装进内存
- 接上线
- 然后程序才能调用。
所以:
"加载其他程序(例如库函数)的代码到本程序"
核心方向是对的。
但更准确一点是:
加载共享库到"当前进程的地址空间",而不是把代码复制进可执行文件本身。