暗色模式

Linux 动态链接库查找机制:ld.so 到底按什么顺序找库,以及为什么不能动 /lib 下的 .so

技术教程
2026-10-06
16
0
本文要点
  • 报错不是「文件丢了」,是「名字搜不到」:14432 字节的库文件明明在 /tmp/ldso-demo/libs 里,程序照样报 error while loading shared libraries(exit=127);LD_LIBRARY_PATH 一指过去,同一个二进制立刻能跑
  • 查找顺序固定,且可以打印出来:DT_RPATH → LD_LIBRARY_PATH → DT_RUNPATH → /etc/ld.so.cache → 内置目录。只差 RPATH/RUNPATH 的两个二进制,LD_DEBUG=libs 打出的搜索阶段顺序正好相反
  • 找不到一个库的代价是几十次 open:cache 命中时 /bin/ls 只开 3 次;不在 cache 里的名字,glibc 把每个内置目录展开成 19 个候选,4 个目录 76 次,叠加 RPATH 与 LD_LIBRARY_PATH 后实测 114 次
  • ldd 不是魔法,是个 bash 脚本:file /usr/bin/ldd = Bourne-Again shell script,第 103 行就是 add_env="LD_TRACE_LOADED_OBJECTS=1 …";自己设这个变量,输出和 ldd 一字不差
  • 永远不要动 /lib 下的真实 .so:这台机器 /usr/bin + /usr/sbin 共 1377 个二进制,129 个依赖 libselinux.so.1(含 mv、cp、ls),静态链接的只有 busybox 一个。挪走它,连「改回来」的那条 mv 都会在 main 之前死掉——本文用 /tmp 副本完整重演了这个死局和它的解法

Linux 动态链接库查找机制:ld.so 到底按什么顺序找库,以及为什么不能动 /lib 下的 .so

error while loading shared libraries: libxxx.so.1: cannot open shared object file: No such file or directory——这条报错几乎所有 Linux 用户都见过,但大多数人对它的理解停留在「装一下就好了」。

这个理解有两个盲区:一是文件在磁盘上不等于找得到,ld.so 只认它自己搜索路径里的名字;二是它发生在程序自己的 main 之前,程序没有机会做任何事——「缺库」不是功能坏了,是根本没启动。这两点平时无所谓,出事的时候(尤其你去动 /lib 的时候)就是生与死的区别。

本文在 Ubuntu 22.04 LTS 演示机(glibc 2.35-0ubuntu3.15,内核 5.15.0-30-generic)上把这套机制实测一遍:造报错、看查找顺序、拆开 ldd 的真身,最后一章复盘一次真实事故——有人把 /lib 下的一个 .so 改名,机器就此失联。所有涉及「坏依赖」的演示都在 /tmp/ldso-demo/ 下用 patchelf(apt 装的 0.14.3)改出来的副本完成,没有动过任何系统文件。

先造一个报错:这条信息到底在说什么

patchelf --add-needed 可以给一个二进制加一条不存在的依赖,比手工编译更直接:

mkdir -p /tmp/ldso-demo/libs
cp /bin/ls /tmp/ldso-demo/ls-broken
patchelf --add-needed libghostdemo.so.1 /tmp/ldso-demo/ls-broken
readelf -d /tmp/ldso-demo/ls-broken | grep NEEDED
/tmp/ldso-demo/ls-broken /tmp/ldso-demo/libs 2>&1
echo "exit=$?"
cp /lib/x86_64-linux-gnu/libutil.so.1 /tmp/ldso-demo/libs/libghostdemo.so.1
echo "--- 库文件现在就在磁盘上 ---"
ls -l /tmp/ldso-demo/libs/libghostdemo.so.1
/tmp/ldso-demo/ls-broken /tmp/ldso-demo/libs 2>&1
echo "--- 告诉 ld.so 去哪找,立刻能跑 ---"
LD_LIBRARY_PATH=/tmp/ldso-demo/libs /tmp/ldso-demo/ls-broken /tmp/ldso-demo/libs 2>&1
echo "--- 诊断口径:ldd 报的 not found ---"
ldd /tmp/ldso-demo/ls-broken 2>&1 | grep -E 'ghost|not found'

终端截图:ls-broken 的 NEEDED 列表第一条是 libghostdemo.so.1;直接运行报 error while loading shared libraries 并 exit=127;把 14432 字节的库文件放进目录后直接运行仍然报错;LD_LIBRARY_PATH 指过去后输出 libghostdemo.so.1 正常执行;ldd 显示 libghostdemo.so.1 => not found

拆开这段输出:

  • exit=127。程序一行代码都没执行——动态链接发生在 main 之前,由加载器完成,失败就直接退出。
  • 文件在磁盘上不等于找得到。libghostdemo.so.1 明明躺在 /tmp/ldso-demo/libs 里(14432 字节),程序照样报 No such file or directory,因为 ld.so 只认自己的搜索结果,不会扫磁盘。
  • 修复只需要一个环境变量。LD_LIBRARY_PATH 一指过去,同一个二进制立刻正常——问题从头到尾都只在「搜索路径」这一层。
  • ldd 的口径和运行时一致:libghostdemo.so.1 => not found,用的就是同一套查找逻辑。

ld.so 是程序的第 0 号依赖

先回答「谁在做查找」。内核加载 ELF 程序时只做一件事:读 PT_INTERP,把里面指的程序解释器映射进内存,把控制权交给它。之后解析 NEEDED、搜库、重定位,全是这个解释器(ld.so)的事。

readlink -f /bin
readlink -f /lib
readlink -f /lib64
readelf -l /bin/ls 2>/dev/null | grep -m1 'Requesting'
ls -l /lib64/ld-linux-x86-64.so.2
readelf -d /bin/ls | grep -E 'NEEDED|RUNPATH|RPATH'

实测输出:

/usr/bin
/usr/lib
/usr/lib64
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
lrwxrwxrwx 1 root root 42 Sep  3 14:20 /lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 0x0000000000000001 (NEEDED)             Shared library: [libselinux.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

三个细节:

  • /bin、/lib、/lib64 都是指向 /usr 的符号链接(merged-usr 布局)。所谓「/lib 下的文件」实际在 /usr/lib 里——但改名的效果一模一样,因为查找走的是路径。
  • ls 的 NEEDED 只有两条,可 ldd /bin/ls 显示 4 个库。NEEDED 是直接依赖,ldd 展示的是闭包:libpcre2-8 是 libselinux 自己的依赖,被递归加载进来。
  • libc.so.6 本身也是被 ld.so 加载的——动态链接是「自举」的:内核只认解释器这一个文件,剩下全靠它。

查找顺序:LD_DEBUG=libs 把它打印出来

网上流传的顺序(RPATH → LD_LIBRARY_PATH → RUNPATH → cache → 默认目录)不用背,glibc 自带开关能原样打出来:

LD_DEBUG=libs /bin/ls >/dev/null 2>/tmp/ldso-demo/o1.log
echo "--- cache 命中时每个库只试一次 ---"
grep -A2 'find library=' /tmp/ldso-demo/o1.log | head -9
echo "cache 命中时 open 次数:$(grep -c 'trying file' /tmp/ldso-demo/o1.log)"
LD_LIBRARY_PATH=/tmp/ldso-demo/libs LD_DEBUG=libs /bin/ls >/dev/null 2>/tmp/ldso-demo/o2.log
echo "--- 加了 LD_LIBRARY_PATH:先在那里扑空,再回落 cache ---"
grep -A3 'find library=libselinux' /tmp/ldso-demo/o2.log | head -6
LD_DEBUG=libs /tmp/ldso-demo/ls-broken >/dev/null 2>/tmp/ldso-demo/o3.log
echo "--- 彻底找不到:cache 之后还要扫内置目录 ---"
echo "open 尝试次数:$(grep -c 'trying file' /tmp/ldso-demo/o3.log)"
grep -m1 'search path=' /tmp/ldso-demo/o3.log | grep -o '(system search path)'
tail -1 /tmp/ldso-demo/o3.log 2>&1

终端截图:cache 命中时 libselinux、libc、libpcre2-8 三个库各一次 search cache 与 trying file,open 次数为 3;设了 LD_LIBRARY_PATH 后先在 /tmp/ldso-demo/libs 试一次再回落 cache;找不到的库总共尝试 76 次,搜索路径标注为 (system search path),最后是报错行

cache 命中路径:三次 open

/bin/ls 的三个库都在 /etc/ld.so.cache 里,每个库的搜索只有两行:search cache=/etc/ld.so.cache → trying file=/lib/x86_64-linux-gnu/libselinux.so.1。cache 是张索引表,直接给出完整路径,一步命中。

cache 没有时:回落到内置目录,代价是几十次 open

libghostdemo.so.1 不在 cache 里,ld.so 只能扫内置目录。这台机器的内置目录是 4 个(/lib/x86_64-linux-gnu、/usr/lib/x86_64-linux-gnu、/lib、/usr/lib),而且每个目录都被展开成 19 个候选——前面加各种 CPU 特性子目录:

 1  <目录>/glibc-hwcaps/x86-64-v4
 2  <目录>/glibc-hwcaps/x86-64-v3
 3  <目录>/glibc-hwcaps/x86-64-v2
 4  <目录>/tls/haswell/avx512_1/x86_64
 …
19  <目录>          (目录本身排在最后)

4 个目录 × 19 = 76 次 open,全部失败之后才报错;把 RPATH 和 LD_LIBRARY_PATH 也算上(各 19 个候选),实测 114 次。展开数量与 glibc 版本和 CPU 有关,不用背——重点是:一个不存在的库名,代价是几十次无效的系统调用,这正是 cache 存在的意义。

RPATH 与 RUNPATH:一字之差,优先级完全不同

用 patchelf --force-rpath 和普通 --set-rpath 分别做两个二进制(依赖同一个假库,且库在 RPATH 和 LD_LIBRARY_PATH 两个位置都有),看 LD_DEBUG 打出来的搜索阶段顺序:

ls-rpath(DT_RPATH):
  (RPATH from file /tmp/ldso-demo/ls-rpath)
  (LD_LIBRARY_PATH)
  search cache=/etc/ld.so.cache
  (system search path)

ls-runpath(DT_RUNPATH):
  (LD_LIBRARY_PATH)
  (RUNPATH from file /tmp/ldso-demo/ls-runpath)
  search cache=/etc/ld.so.cache
  (system search path)

结论就是那张表:

优先级来源谁设置实测行为
1DT_RPATH旧式链接器 -Wl,-rpath(无 --enable-new-dtags)先于 LD_LIBRARY_PATH,且会被所有子进程继承
2LD_LIBRARY_PATH环境变量覆盖 cache,测试新库的正规手段
3DT_RUNPATH现代链接器默认只影响这个二进制自己,不传给子进程
4/etc/ld.so.cacheldconfig 生成命中则一步到位
5内置目录编译进 glibc/lib/x86_64-linux-gnu 等,永远最后兜底

日常最该记住的是第 2 行:LD_LIBRARY_PATH 能覆盖系统库。它是排查「新编的库到底有没有被加载」的最快手段,也是把系统搞挂的常见入口。

cache 从哪来:/etc/ld.so.conf 与 ldconfig

cat /etc/ld.so.conf
ls -1 /etc/ld.so.conf.d/
cat /etc/ld.so.conf.d/libc.conf
cat /etc/ld.so.conf.d/x86_64-linux-gnu.conf
ldconfig -p | wc -l
ldconfig -p | grep 'libselinux.so.1$'
ldconfig -p | grep -c ghostdemo
ls -l /etc/ld.so.cache

实测输出:

include /etc/ld.so.conf.d/*.conf

libc.conf
x86_64-linux-gnu.conf
# libc default configuration
/usr/local/lib
# Multiarch support
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu
318
    libselinux.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libselinux.so.1
0
-rw-r--r-- 1 root root 19920 Oct  1 06:52 /etc/ld.so.cache

要点:

  • /etc/ld.so.conf 只有一行 include /etc/ld.so.conf.d/*.conf,真正的目录列表在 conf.d 里:/usr/local/lib、/usr/local/lib/x86_64-linux-gnu、/lib/x86_64-linux-gnu、/usr/lib/x86_64-linux-gnu。
  • cache 里 318 行(316 个库),文件 19920 字节,修改时间 Oct 1 06:52——是上次 ldconfig 生成的快照,不是实时索引。装了新库没跑 ldconfig,cache 里就没有(grep -c ghostdemo 为 0 就是因为它只存在于 /tmp)。
  • ldconfig -p 只读缓存,无副作用,随便跑;但 ldconfig 本身(重建 cache)是系统级操作,别在生产的 /lib 上做实验。

ldd 不是魔法,它就是个 bash 脚本

很多人把 ldd 当成「系统直接知道每个程序依赖什么」的魔法。拆开看:

type ldd
file "$(type -p ldd)"
grep -n 'LD_TRACE_LOADED_OBJECTS' "$(type -p ldd)"
echo "--- ldd 做的事,一个环境变量就能复现 ---"
LD_TRACE_LOADED_OBJECTS=1 /bin/ls 2>&1
echo "--- 对照:ldd /bin/ls ---"
ldd /bin/ls 2>&1

终端截图:type ldd 显示 /usr/bin/ldd;file 显示它是 Bourne-Again shell script;第 103 行是 add_env="LD_TRACE_LOADED_OBJECTS=1…";LD_TRACE_LOADED_OBJECTS=1 /bin/ls 与 ldd /bin/ls 输出的库列表完全一致,只有 ASLR 地址不同

实测输出:

ldd is /usr/bin/ldd
/usr/bin/ldd: Bourne-Again shell script, ASCII text executable
103:add_env="LD_TRACE_LOADED_OBJECTS=1 LD_WARN=$warn LD_BIND_NOW=$bind_now"
--- ldd 做的事,一个环境变量就能复现 ---
    linux-vdso.so.1 (0x00007ffee98e0000)
    libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f03f3a24000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f03f37fb000)
    libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f03f3764000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f03f3a57000)
--- 对照:ldd /bin/ls ---
    linux-vdso.so.1 (0x00007ffcb43ce000)
    libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f14beb04000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f14be8db000)
    libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f14be844000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f14beb5b000)

ldd 是个 shell 脚本,它做的事只有一件:设 LD_TRACE_LOADED_OBJECTS=1,然后让加载器自己跑一遍并打印。两条命令的输出完全一致,只有地址列不同(ASLR,每次运行都变)。

理解了这一点,ldd 的三个边界就都解释得通了:

  • 它「运行」了目标程序。ldd 不是静态解析 ELF(那是 readelf -d 的事),它真的把加载过程跑了一遍。所以手册页明确建议不要对不可信的二进制用 ldd;来路不明的文件用 readelf/objdump。深度分析见 Linux 二进制分析:从 file 类型识别到 ELF 结构与 objdump 反汇编。
  • 它只回答「能不能找到」,不回答「符号齐不齐」。缺符号版本、dlopen 动态加载的库,ldd 都看不出来,要靠 LD_DEBUG=bindings、strace(见 strace 系统调用追踪:从卡死进程到缺失文件定位)或直接跑一遍看报错。
  • setuid 程序的 ldd 结果可能骗人。glibc 对特权程序启用 secure-execution 模式,会忽略 LD_LIBRARY_PATH 与 RUNPATH(见 ld.so(8) 的 Security 一节),但 ldd 在你的非特权环境里跑看不到这个限制——「ldd 说找得到、一跑就报错」是真实存在的场景。

排查清单

按这个顺序走,动态链接类问题基本一轮定位:

  1. ldd <程序> —— 先看哪个库 not found,把「程序坏了」缩小成「哪个库没找到」。
  2. ls -l <那个库的路径> —— 路径存在吗?不存在是搜索路径问题,存在还报错看下一步。
  3. LD_DEBUG=libs <程序> 2>&1 | grep -A3 'find library=<库名>' —— 直接看它按什么顺序搜了哪些目录。
  4. LD_LIBRARY_PATH=<库所在目录> <程序> —— 能跑通就只是路径问题;跑不通是库本身的问题(符号缺失、版本不符),换 LD_DEBUG=bindings。
  5. 长期方案用 DT_RUNPATH,或把库装进 conf.d 列出的目录后 ldconfig —— 不要用 LD_LIBRARY_PATH 做长期方案。

安全上记住一句就够:1377 个系统二进制里 129 个依赖 libselinux.so.1,静态链接的只有 busybox 一个。任何「挪开一个真实 .so 试试」的想法,都可能把整台机器变成下面这样。

真实事故复盘:为什么永远不要动 /lib 下的真实 .so

这不是假设性的警告。本文演示机所在的那台机器,曾经发生过一次事故:为了「临时绕开」一个 SELinux 相关的问题,把 /lib 下的 libselinux.so.1 用 mv 改了名——打算马上改回来。结果机器当场失去响应,SSH 再也连不上,重启也没用,最后只能从带外控制台进去处理。机制并不神秘,拆开看全是前面讲过的东西。

第一层:你以为改的是一个文件,其实是 129 个程序的命脉

readelf -d /usr/bin/mv | grep NEEDED
ldd /usr/bin/mv 2>&1 | grep -E 'selinux|acl|attr'
ls -l /lib/x86_64-linux-gnu/libselinux.so.1
dpkg -S /lib/x86_64-linux-gnu/libselinux.so.1
n=0; for f in /usr/bin/* /usr/sbin/*; do [ -f "$f" ] && readelf -d "$f" 2>/dev/null | grep -q 'libselinux.so.1' && n=$((n+1)); done; echo "libselinux_users=$n"
for f in /usr/bin/* /usr/sbin/*; do [ -f "$f" ] && echo x; done | wc -l
for f in /usr/bin/* /usr/sbin/*; do [ -f "$f" ] && file -b "$f" 2>/dev/null | grep -q 'statically linked' && echo x; done | wc -l

实测输出:

 0x0000000000000001 (NEEDED)             Shared library: [libselinux.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libacl.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libattr.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f156e6d3000)
    libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007f156e6c9000)
    libattr.so.1 => /lib/x86_64-linux-gnu/libattr.so.1 (0x00007f156e6c1000)
-rw-r--r-- 1 root root 166280 Mar 17  2022 /lib/x86_64-linux-gnu/libselinux.so.1
libselinux1:amd64: /lib/x86_64-linux-gnu/libselinux.so.1
libselinux_users=129
1377
1

mv 自己的 NEEDED 第一条就是 libselinux.so.1。全系统 1377 个二进制里 129 个依赖它——包括 mv、cp、ls 这些你以为「最基础」的命令,它们全是动态链接的;静态链接的只有 busybox 一个。

第二层:为什么「马上挪回来」救不了你

在 /tmp 里用副本把这个死局完整重演一遍(全程没动任何系统文件):

D=/tmp/ldso-demo
mkdir -p "$D/rescue"
cp /bin/mv "$D/mv-ghost"
patchelf --add-needed libghostdemo.so.1 "$D/mv-ghost"
cp /lib/x86_64-linux-gnu/libutil.so.1 "$D/rescue/libghostdemo.so.1"
LD_LIBRARY_PATH="$D/rescue" "$D/mv-ghost" --version 2>&1 | head -2
/bin/mv "$D/rescue/libghostdemo.so.1" "$D/rescue/libghostdemo.so.1.bak"
echo "--- 库不见了(只是改了名,还在 /tmp 里)---"
ls -l "$D/rescue" | cat
echo "--- 于是这个 mv 连自己都用不了了 ---"
LD_LIBRARY_PATH="$D/rescue" "$D/mv-ghost" --version 2>&1
echo "rc=$?"
LD_LIBRARY_PATH="$D/rescue" "$D/mv-ghost" 2>&1
echo "rc=$?"
echo "--- 救场:静态链接的 busybox 不依赖任何 .so ---"
file /usr/bin/busybox 2>&1 | cut -c1-120
ldd /usr/bin/busybox 2>&1
/usr/bin/busybox mv "$D/rescue/libghostdemo.so.1.bak" "$D/rescue/libghostdemo.so.1"
echo "restore_rc=$?"
LD_LIBRARY_PATH="$D/rescue" "$D/mv-ghost" --version 2>&1 | head -2

实测输出:

mv (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
--- 库不见了(只是改了名,还在 /tmp 里)---
total 16
-rw-r--r-- 1 root root 14432 Oct  3 05:00 libghostdemo.so.1.bak
--- 于是这个 mv 连自己都用不了了 ---
/tmp/ldso-demo/mv-ghost: error while loading shared libraries: libghostdemo.so.1: cannot open shared object file: No such file or directory
rc=127
/tmp/ldso-demo/mv-ghost: error while loading shared libraries: libghostdemo.so.1: cannot open shared object file: No such file or directory
rc=127
--- 救场:静态链接的 busybox 不依赖任何 .so ---
/usr/bin/busybox: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=166d9d43…
    not a dynamic executable
restore_rc=0
mv (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.

死局的逻辑就四步:

  • 库在的时候,这个 mv 一切正常(--version 输出 coreutils 8.32)。
  • 把库改个名(文件还在磁盘上,就差一个名字),mv 立刻报错,退出码 127;不带参数、加 --help 都一样死。报错发生在 main 之前,程序没有任何机会「降级运行」——这不是 mv 写得不好,是动态链接的设计:库必须在加载阶段全部就位。
  • 于是你唯一想做的动作——mv 改回来——用的恰恰是那个已经起不来的程序。修复工具本身被你要修的问题弄坏了,这就是「自我锁死」。
  • 救生索是静态链接的程序:busybox 不依赖任何 .so(ldd 直接说 not a dynamic executable),它的 mv 在坏状态下照样执行(restore_rc=0),文件一改回来,之前那个 mv 立刻复活。

套回真实事故:libselinux.so.1 一被改名,129 个二进制同时死掉,其中就包括所有能改文件名的工具;还能用的只剩 shell 内建命令(cd、echo 这类不加载外部程序的)和静态程序。而 sshd 拉起的登录 shell 里,你习惯敲的命令几乎全是外部程序——这就是「连不上、也修不了」的全部原因。

第三层:为什么连重启都救不回来

重启能解决「进程坏了」,解决不了「磁盘上的文件名就是错的」:改名是写进文件系统的持久化变更,重启后坏状态原样还在。更麻烦的是,开机早期的 systemd、udev 同样是动态链接的,机器可能连多用户模式都进不去。这类事故总是以「从带外控制台进救援模式」收场,原因就在这:只有绕开根文件系统上的动态链接环境,才能获得一组还能用的工具。

正确的做法

  • 改系统库的唯一正道是包管理器。libselinux.so.1 属于 libselinux1 包(dpkg -S 可确认),重装、降级、修复都走 apt。任何时候都不要用 mv/rm/chmod 直接操作 /lib、/usr/lib、/lib64 下的真实文件。
  • 需要「隔离一个库」时用不碰系统文件的手段:LD_LIBRARY_PATH 指向自己的目录、复制到 /tmp 再 patchelf 改副本、或者用容器。三条都能达到目的,而且全部可逆。
  • 动 /lib 之前先问「谁在用它」:ldd、readelf -d | grep NEEDED、dpkg -S。答案是「一百多个程序」时,这不是能手动改的东西。同时给重要机器留一条静态救生索:确认 busybox 在、是静态的,并确认带外控制台真的能进——事故里「最后的工具」平时毫无存在感,用上时就是全部。

小结

  • error while loading shared libraries 是加载阶段的失败,发生在 main 之前——程序没有「部分启动」这个状态,退出码通常是 127。
  • 文件在磁盘上 ≠ 找得到。ld.so 只认自己的搜索路径:DT_RPATH → LD_LIBRARY_PATH → DT_RUNPATH → /etc/ld.so.cache → 内置目录,用 LD_DEBUG=libs 就能原样打印。
  • cache 命中一次 trying file 就解决;不在 cache 里要扫内置目录,每个目录展开成 19 个候选,实测一个库失败的代价是 76 次 open,叠上 RPATH/LD_LIBRARY_PATH 后 114 次。
  • ldd 是个 bash 脚本,本体是 LD_TRACE_LOADED_OBJECTS=1;它只回答「能不能找到」,不回答「符号齐不齐」,对 setuid 程序还可能失真。
  • /lib 下的真实 .so 是 129 个二进制(含 mv/cp/ls)的共同命脉,静态救生索全场只有 busybox 一个。挪走它,连「改回来」的那条 mv 自己都起不来,而且是重启救不回来的持久化损坏。

相关阅读:

参考资料:

发表评论

暂无评论,快来抢沙发吧!