要获取一个二进制文件通过dlopen()动态加载的库,你不能依赖ldd这类静态分析工具。因为dlopen()是在程序运行时才决定加载哪些库,这些库的路径甚至可以是动态拼接的,所以它们不会写在文件的依赖信息里。
你可以根据具体场景,从下面几种方法中选择一种。
方法一:运行时拦截与追踪 (最推荐)
这是最可靠的方法。通过拦截程序对dlopen的调用,你可以实时、准确地记录下所有动态加载的库。
1. 使用LD_PRELOAD技术 (通用)
LTTng 项目提供了一个名为liblttng-ust-dl.so的库,它的工作原理就是利用LD_PRELOAD环境变量,在目标程序启动时抢先注入自己,从而钩取 (hook) 所有的dlopen调用并打印出加载的库路径。
操作步骤:
# 安装 lttng-ust-tools (不同发行版包名可能略有不同) # 对于 Debian/Ubuntu: sudo apt install lttng-ust-tools # 对于 RHEL/CentOS: sudo yum install lttng-ust # 使用 LD_PRELOAD 运行你的程序 LD_PRELOAD=liblttng-ust-dl.so ./your_program当程序运行时,所有dlopen加载的库路径都会被追踪并输出。这是专业性能分析工具(如 OpenResty XRay)使用的同款方法。
仅仅设置LD_PRELOAD=liblttng-ust-dl.so是不够的,你还需要通过lttng命令启动一个追踪会话,才能看到记录下来的.so库信息。
liblttng-ust-dl.so这个库的作用是记录事件(比如dlopen的调用),但它本身不会直接把结果打印在屏幕上。你需要像一个“录音师”一样,用 LTTng 的工具去控制录制和读取最终的“磁带”。
完整的操作需要以下三个步骤:
🛠️ 操作步骤
你可以直接复制下面的完整命令块一次性执行:
# 1. 创建一个追踪会话 lttng create my-dlopen-session --output=/tmp/my-trace # 2. 启用必要的事件(这是最关键的一步!) # “这是一个来自用户空间( -u )的事件,我们想追踪它(lttng_ust_dl:dlopen)” lttng enable-event --userspace lttng_ust_dl:dlopen # 3. 启动追踪 lttng start # 4. 运行你的程序(此时,预加载的库会开始记录 dlopen 的细节) LD_PRELOAD=liblttng-ust-dl.so ./你的程序 # 5. 程序运行完毕后,停止追踪 lttng stop # 6. 销毁会话(这会触发将内存中的数据刷新到磁盘) lttng destroy👀 如何查看结果?
追踪完成后,原始数据是二进制格式,需要使用babeltrace2这个工具来将它转换成人类可读的文本。
babeltrace2 /tmp/my-trace如果一切顺利,你会看到类似下面的输出,其中path = "..."这一行就是你想要找的动态库路径:
[16:45:21.123456789] (+0.000000123) 主机名 lttng_ust_dl:dlopen: { cpu_id = 0 }, { baddr = 0x7f1234560000, memsz = 123456, flags = 1, path = "/home/user/test/libexample.so", # <-- 这就是你需要的库路径 has_build_id = 0, has_debug_link = 0 }💡 补充说明
关于
lttng_ust_dl:dlopen事件
从搜索结果来看,lttng_ust_dl:dlopen事件中path字段记录的就是dlopen()调用时传递的路径。官方手册也指出,虽然liblttng-ust-dl.so也能产生其他类型的事件,但对于追踪库加载路径这个需求,直接使用lttng_ust_dl:dlopen是最直观和可靠的。为何需要
lttng enable-event?
这是新手最容易遗漏的一步。liblttng-ust-dl.so就像一个“摄像机”,而lttng enable-event命令则是告诉 LTTng 系统:“请开始录制这台摄像机拍到的画面”。没有这个命令,即使摄像机在运行,也没有人在保存录像。
2. 使用strace(系统调用追踪)
strace可以追踪进程发起的系统调用。dlopen底层会调用open、openat等系统调用来读取动态库文件。
操作步骤:
# -e 指定只追踪 open, openat 系统调用,-f 跟踪子进程 strace -e open,openat -f ./your_program 2>&1 | grep "\.so"这个方法的优点是几乎所有 Linux 系统都自带,但输出会比较杂乱,需要进行过滤。
方法二:静态分析与启发式方法 (作为备选)
当无法动态运行程序时(例如在离线分析或逆向工程中),可以尝试以下方法,但它们只能作为线索,无法保证100%准确。
1. 使用strings命令
这是最简单但最不可靠的方法。strings命令可以从二进制文件中提取所有可打印字符序列。如果程序将动态库的路径以字符串常量形式写在代码中,它就能被提取出来。
操作步骤:
# -n 6 指定字符串最小长度为6,然后过滤出包含 .so 的行 strings -n 8 ./your_program | grep "\.so"局限性:如果库路径是运行时拼接的(例如lib+ version +.so),或者被加密/混淆了,这个方法就会失效。
方法三:检查/proc文件系统 (针对运行中进程)
如果你已经运行了一个进程(比如 PID 是 1234),可以直接查看它的内存映射表。
操作步骤:
cat /proc/1234/maps | grep "\.so"这个文件会列出进程当前加载的所有内存区域,包括所有直接链接和通过dlopen打开的共享库。这种方法简单直接且权威,因为它是系统内核给出的实际状态。
总结与对比
| 方法 | 核心命令/操作 | 优点 | 缺点 |
|---|---|---|---|
| 运行时拦截 (最推荐) | LD_PRELOAD=liblttng-ust-dl.so ./程序 | 100%准确,专为此设计,信息清晰 | 需要额外安装lttng-ust工具 |
| 系统调用追踪 | strace -e open ./程序 2>&1 | grep .so | 无需安装额外工具,通用性强 | 输出冗余,需要从大量日志中筛选 |
| 静态字符串提取 | strings ./程序 | grep .so | 无需运行程序,快速简单 | 只能发现硬编码路径,漏报率高 |
| 进程内存检查 | cat /proc/PID/maps | grep .so | 针对已在运行的进程,准确且直接 | 需要进程已经在运行 |
对于绝大多数的分析需求,优先推荐LD_PRELOAD方法,这是专门为解决此类问题而设计的。
参考:lttng-tools和lttng-ust包详解-CSDN博客