dladdr 返回的 dli_fname 不是绝对路径,因其直接取自 ELF 的 DT_SONAME 或加载时原始路径名,未经解析;真实磁盘路径需结合 /proc/self/maps 匹配 dli_fbase 地址范围后提取第6列,或按 RPATH/LD_LIBRARY_PATH/ld.so.cache 顺序查找。

dladdr 返回的 dli_fname 为什么不是绝对路径?
调用 dladdr 获取函数地址时,dli_fname 字段常返回相对路径(如 "libfoo.so")、空字符串,甚至指向 /proc/self/map_files/... 这类伪路径——根本原因在于:它直接抄的是内存映射段的 PT_INTERP 或 DT_SONAME 字段,或动态链接器加载时记录的原始路径名,而非解析后的磁盘真实路径。
真正需要的“完整绝对路径”,得自己查:dladdr 只负责定位符号归属的共享对象映射基址,不负责路径规范化。
实操建议:
- 先调用
dladdr拿到dli_fbase(模块起始地址),再遍历/proc/self/maps,匹配该地址范围对应的那一行,提取第6列(即磁盘路径); - 若第6列为
[vdso]、[vvar]等内核映射,跳过;若为空或以/tmp/、/dev/shm/开头,说明是运行时生成的匿名映射,无对应文件; - 对
LD_LIBRARY_PATH或RPATH加载的库,/proc/self/maps中的路径已是解析后的真实绝对路径,无需再realpath()——但要注意符号链接是否被展开。
如何从 dli_fname 安全还原为磁盘上真实存在的绝对路径?
不能直接对 dli_fname 调 realpath():它可能是 SONAME(如 "libz.so.1"),也可能是编译时硬编码的相对路径(如 "../lib/libbar.so"),还可能根本不在当前工作目录下。
立即学习“C++免费学习笔记(深入)”;
正确做法是模拟动态链接器的查找逻辑,但只走关键路径:
- 若
dli_fname本身是绝对路径(以"/"开头)且access(dli_fname, F_OK) == 0,直接使用; - 否则,按顺序检查:
DT_RUNPATH/DT_RPATH(可用readelf -d your_binary | grep -E "(RUNPATH|RPATH)"查)、LD_LIBRARY_PATH环境变量各路径、/etc/ld.so.cache(用ldconfig -p | grep $soname辅助)、/lib和/usr/lib; - 注意:C++ dlopen 的 handle 不会暴露这些元信息,所以必须在目标函数所在模块加载前,用
readelf或objdump提前解析其动态段,或改用dl_iterate_phdr配合getauxval(AT_PHDR)手动读取程序头获取DT_RUNPATH。
反混淆:如何把 __ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKSt7__cxx1112basic_stringIS4_S5_T1_E 还原成可读函数签名?
C++ 符号名在 ELF 中默认是 mangling 后的,dladdr 的 dli_sname 就是这种形式。标准库不提供跨平台 demangle 接口,但 GNU libstdc++ 和 libc++ 都导出了 __cxa_demangle。
关键细节:
-
__cxa_demangle第一个参数不能是const char*字面量——它内部会修改缓冲区,必须传入堆分配或足够大的栈缓冲; - 返回值为
char*,需用free()释放(即使你传了自定义缓冲区,只要__cxa_demangle分配了新内存,就归它管); - 若返回
nullptr,常见原因是符号格式非法(比如传了 C 函数名)或内存不足;此时应 fallback 到原dli_sname; - 注意:Clang 编译的目标若用
-fno-rtti,部分类型信息丢失,demangle 结果可能不完整(如省略模板参数)。
示例片段:
char *demangled = __cxa_demangle(dli_sname, nullptr, nullptr, &status);
if (status == 0 && demangled) {
printf("demangled: %s\n", demangled);
free(demangled);
} else {
printf("raw: %s\n", dli_sname);
}
为什么在 LD_PRELOAD 注入的库中调 dladdr 常失败或返回空?
因为 LD_PRELOAD 库在主程序 _start 之前就被加载,而 dladdr 依赖的内部数据结构(如 _dl_loaded 链表)可能尚未完全初始化,或其符号表未被 glibc 的符号解析器标记为“可查询”。
更隐蔽的问题是:glibc 对 LD_PRELOAD 库的 DT_DEBUG 段处理特殊,dladdr 在早期阶段查不到其 struct link_map 入口。
绕过方法:
- 不要在
__attribute__((constructor))函数里急着调dladdr,延迟到第一次实际函数调用时再查; - 改用
dl_iterate_phdr+ 手动遍历程序头,结合dladdr1(需传RTLD_DL_LINKMAP)获取struct link_map*,再从中读l_name; - 如果只为获取自身路径,最稳的方式是读
/proc/self/exe(对主程序)或/proc/self/maps中匹配当前libxxx.so名字的那一行——前提是你的库名不含通配符且没被重命名加载。
反混淆和路径解析都依赖准确的模块边界识别,而 LD_PRELOAD 场景下这个边界最容易漂移。宁可多查一次 /proc/self/maps,也不要信 dli_fname 的第一眼结果。


















