云计算百科
云计算领域专业知识百科平台

【Linux指南】动静态库系列(五):动态库运行报错排查:为什么编译通过,运行却找不到 .so

文章目录

    • 一、先复现问题
    • 二、编译期找库和运行期找库不是一回事
      • 1. 编译链接阶段
      • 2. 程序运行阶段
    • 三、用 ldd 查看动态依赖
    • 四、动态链接器默认去哪里找库
    • 五、解决方案一:拷贝到系统库路径
    • 六、解决方案二:在系统路径建立软链接
    • 七、解决方案三:设置 LD_LIBRARY_PATH
    • 八、解决方案四:配置 ld.so.conf.d 并执行 ldconfig
    • 九、解决方案五:编译时写入 rpath
    • 十、五种方案对比
    • 十一、为什么静态库没有这个问题
    • 十二、几个常用排查命令
      • 1. ldd
      • 2. file
      • 3. readelf -d
      • 4. ldconfig -p
    • 十三、一次完整排查流程
    • 十四、总结

上一篇我们已经能制作并链接动态库了。很多同学第一次做动态库时,会遇到一个非常典型的问题:

gcc main.c -I./include -L./lib -lmyc -o main

编译链接明明成功了,但一运行:

./main

却报错:

error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory

或者用 ldd 查看时看到:

libmyc.so => not found

这个问题非常关键,因为它说明动态库有两个阶段都要“找库”:编译链接阶段要找,程序运行阶段还要再找。本文就专门把这件事讲透。 image.png

一、先复现问题

假设我们的目录结构如下:

shared_lib_demo/
├── include/
│ └── my_math.h
├── lib/
│ └── libmyc.so
└── main.c

main.c 调用了 libmyc.so 中的函数。

编译命令:

gcc main.c -I./include -L./lib -lmyc -o main

这条命令可能成功生成 main。

但是执行:

./main

可能报错:

./main: error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory

这时候很多人会困惑:

编译的时候不是已经用 -L./lib 告诉 gcc 库在哪里了吗?
为什么运行的时候还说找不到?

答案是:

-L 只影响编译链接阶段,不负责程序运行阶段。

二、编译期找库和运行期找库不是一回事

动态库有两个重要阶段。

1. 编译链接阶段

命令:

gcc main.c -I./include -L./lib -lmyc -o main

这时参与工作的主要是编译器和链接器。

参数含义:

-I./include 告诉编译器去哪里找头文件
-L./lib 告诉链接器去哪里找库文件
-lmyc 告诉链接器链接 libmyc.so 或 libmyc.a

只要链接器在 ./lib 下找到了 libmyc.so,它就可以生成可执行程序。

2. 程序运行阶段

当你执行:

./main

此时不再是 gcc 在工作,而是操作系统加载程序,并由动态链接器负责加载依赖的 .so。

Linux 下常见动态链接器是:

/lib64/ld-linux-x86-64.so.2

或者 Ubuntu/Debian 上类似:

/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

动态链接器并不会自动记住你编译时写过的 -L./lib。所以如果它的搜索路径里没有 ./lib,运行时就找不到 libmyc.so。

这就是问题本质。

三、用 ldd 查看动态依赖

ldd 可以查看一个可执行程序依赖哪些动态库:

ldd ./main

如果库找不到,会看到:

linux-vdso.so.1
libmyc.so => not found
libc.so.6 => /lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2

其中:

libmyc.so => not found

说明可执行程序确实记录了对 libmyc.so 的依赖,但动态链接器在运行搜索路径中没有找到它。

如果库能找到,则可能显示:

libmyc.so => ./lib/libmyc.so

或:

libmyc.so => /usr/local/lib/libmyc.so

所以排查动态库运行问题时,第一步通常就是:

ldd ./main

四、动态链接器默认去哪里找库

动态链接器运行时查找 .so,常见来源包括:

  • 可执行文件中记录的 rpath/runpath。
  • 环境变量 LD_LIBRARY_PATH 指定的路径。
  • /etc/ld.so.cache 缓存中的路径。
  • 系统默认库目录,例如 /lib、/usr/lib、/lib64、/usr/lib64。
  • /etc/ld.so.conf 和 /etc/ld.so.conf.d/ 配置后经 ldconfig 生成的缓存路径。
  • 不同系统、不同链接选项下的精确顺序会涉及一些细节。初学阶段先抓住核心:

    运行时找库由动态链接器负责,和 gcc 编译时的 -L 不是一套机制。

    五、解决方案一:拷贝到系统库路径

    最直接的方法是把 .so 拷贝到系统默认库路径,例如:

    sudo cp libmyc.so /usr/local/lib/
    sudo ldconfig

    或者某些系统中使用:

    sudo cp libmyc.so /lib64/
    sudo ldconfig

    这样动态链接器就能从系统路径找到它。

    但这个方案不适合学习实验中随便乱用,因为:

  • 需要 root 权限。
  • 容易污染系统库目录。
  • 多版本库可能冲突。
  • 删除不干净会影响后续实验。
  • 所以学习阶段更推荐使用 LD_LIBRARY_PATH 或 rpath。

    六、解决方案二:在系统路径建立软链接

    如果真实库在某个自定义目录,可以在系统库路径中建立软链接:

    sudo ln -s /home/user/mylib/libmyc.so /usr/local/lib/libmyc.so
    sudo ldconfig

    这样系统路径下看起来有 libmyc.so,实际文件仍在原位置。

    软链接方案比直接拷贝更方便维护,但依然需要 root 权限,也需要注意不要污染系统目录。

    七、解决方案三:设置 LD_LIBRARY_PATH

    学习和临时测试最常用的是:

    export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
    ./main

    如果库就在当前目录:

    export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
    ./main

    LD_LIBRARY_PATH 是环境变量,用来告诉动态链接器额外搜索哪些路径。

    优点:

  • 不需要修改系统目录。
  • 适合临时测试。
  • 改动范围只影响当前 shell 及其子进程。
  • 缺点:

  • 关闭终端后可能失效。
  • 不适合作为严肃发布方案。
  • 设置不当可能导致加载到错误版本的库。
  • 比如你可以这样验证:

    ldd ./main
    export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
    ldd ./main

    前后对比 ldd 输出,会看到 libmyc.so 从 not found 变成具体路径。

    八、解决方案四:配置 ld.so.conf.d 并执行 ldconfig

    更系统化的方式是把库路径加入动态链接器配置。

    例如你的库在:

    /home/user/mylib/lib

    可以创建配置文件:

    sudo sh -c 'echo /home/user/mylib/lib > /etc/ld.so.conf.d/myc.conf'

    然后执行:

    sudo ldconfig

    ldconfig 会更新 /etc/ld.so.cache,动态链接器后续可以从缓存中找到这些库。

    这种方式适合系统级安装库。

    优点:

  • 配置持久有效。
  • 不需要每次设置环境变量。
  • 符合 Linux 系统管理习惯。
  • 缺点:

  • 需要 root 权限。
  • 不适合随意测试大量临时库。
  • 配置错误会影响系统范围。
  • 九、解决方案五:编译时写入 rpath

    还有一种方式:在生成可执行文件时,把动态库搜索路径写进可执行文件。

    例如:

    gcc main.c -I./include -L./lib -lmyc -Wl,-rpath=./lib -o main

    这里的:

    -Wl,-rpath=./lib

    意思是把 -rpath=./lib 传给链接器。

    这样运行时,动态链接器会根据可执行文件中记录的路径去找 libmyc.so。

    可以使用 readelf -d 查看动态段信息:

    readelf -d ./main | grep -E "RPATH|RUNPATH|NEEDED"

    可能看到:

    NEEDED Shared library: [libmyc.so]
    RUNPATH Library runpath: [./lib]

    这种方案适合把库和程序放在相对固定目录结构中的项目。

    十、五种方案对比

    方案是否持久是否需要 root适用场景风险
    拷贝到系统库路径 持久 需要 系统安装 污染系统目录
    系统路径建软链接 持久 需要 系统安装/版本管理 链接错误会影响系统
    LD_LIBRARY_PATH 临时 不需要 学习、测试、临时运行 容易加载错库
    ld.so.conf.d + ldconfig 持久 需要 正规系统级安装 配置影响全局
    -Wl,-rpath 随程序 不需要 root 工程发布、相对路径固定 路径写死需谨慎

    学习阶段建议:

    优先 LD_LIBRARY_PATH 或 rpath;
    不要随便把实验库丢进系统目录。

    十一、为什么静态库没有这个问题

    静态库 .a 在链接阶段已经把相关代码合并进可执行程序。

    所以链接完成后:

    rm libmyc.a
    ./main

    程序仍然能运行。

    动态库不同。可执行程序里没有完整库代码,只记录了对 libmyc.so 的依赖。运行时必须能找到 .so。

    所以:

    静态库问题主要发生在编译链接阶段;
    动态库问题既可能发生在编译链接阶段,也可能发生在运行加载阶段。

    十二、几个常用排查命令

    1. ldd

    查看动态依赖:

    ldd ./main

    重点看有没有:

    not found

    2. file

    查看文件类型:

    file ./main
    file ./lib/libmyc.so

    可以判断是不是 ELF、是不是 shared object。

    3. readelf -d

    查看动态段:

    readelf -d ./main

    重点看:

    NEEDED
    RPATH
    RUNPATH

    4. ldconfig -p

    查看缓存中有哪些库:

    ldconfig -p | grep myc

    如果你已经配置了 ld.so.conf.d 并执行了 ldconfig,但这里查不到库,就说明配置没有生效。

    十三、一次完整排查流程

    遇到动态库运行失败时,可以按这个顺序排查:

    1. ldd ./main,看是不是 libxxx.so => not found
    2. 确认 libxxx.so 是否真实存在
    3. 确认编译时 -L 指向的目录是否正确
    4. 确认运行时动态链接器能否找到该目录
    5. 临时设置 LD_LIBRARY_PATH 验证库本身是否可用
    6. 再决定使用 rpath、ldconfig 还是系统安装方案

    不要一上来就复制库到 /lib64。先定位问题发生在哪个阶段。

    十四、总结

    动态库最容易让人困惑的点是:

    编译期找库和运行期找库不是一回事。

    -L 只告诉链接器编译时去哪找库;程序运行时由动态链接器负责加载 .so,它有自己的搜索路径规则。

    常见解决方式包括:

    LD_LIBRARY_PATH
    ldconfig
    -Wl,-rpath
    系统库路径
    软链接

    下一篇开始,我们进入更底层的世界:.o、.so、可执行文件为什么都属于 ELF?链接器到底在这些文件里处理什么?

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux指南】动静态库系列(五):动态库运行报错排查:为什么编译通过,运行却找不到 .so
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!