如何获取link_map结构中的l_info、l_libname等扩展字段?
如何获取LinkMap结构体的扩展字段(如l_info)
问题背景
用dladdr()或dl_info获取的link_map结构体仅包含公开的标准字段(l_addr、l_name、l_ld、l_next、l_prev),但实际在glibc内部,这个结构体存在扩展字段(l_real、l_ns、l_libname、l_info、l_phdr等),这些字段会在libc初始化(比如setup_vdso阶段)后被填充,如何通过编程方式访问这些字段?
解决方法
1. 手动定义内部扩展版LinkMap结构体
glibc内部的link_map结构体定义在elf/link.h中,但这个是内部头文件,不对外暴露。你可以手动复制对应glibc版本的结构体定义到代码中(注意不同版本的字段偏移可能有变化,需匹配目标系统的glibc版本)。
以glibc 2.35为例的结构体定义:
#include <stdint.h> struct link_map_extended { uintptr_t l_addr; /* 共享对象加载的基地址 */ char *l_name; /* 共享对象加载时的绝对路径 */ void *l_ld; /* 共享对象的动态段 */ struct link_map_extended *l_next, *l_prev; /* 已加载对象的链表 */ /* 以下为扩展字段 */ struct link_map_extended *l_real; /* 符号所属的实际对象 */ struct namespace *l_ns; /* 该link_map所属的命名空间 */ const char *l_libname; /* 库名称(来自DT_SONAME) */ struct r_debug *l_r_debug; /* 指向r_debug结构的指针 */ struct link_map_extended *l_loader; /* 加载器的link_map */ ElfW(Phdr) *l_phdr; /* 程序头表指针 */ ElfW(Addr) l_phaddr; /* 程序头表的地址 */ size_t l_phnum; /* 程序头数量 */ uint32_t l_flags; /* 标志位 */ uint32_t l_nbuckets; /* 哈希桶数量 */ uint32_t l_symbias; /* 符号偏移量 */ const ElfW(Sym) *l_symtab; /* 符号表 */ const char *l_strtab; /* 字符串表 */ uint32_t l_chain; /* 哈希表链 */ uint32_t l_version; /* 版本号 */ struct dl_phdr_info *l_phdr_info; /* 指向dl_phdr_info结构的指针 */ struct link_map_extended *l_next_run; /* 运行顺序中的下一个对象 */ struct link_map_extended *l_prev_run; /* 运行顺序中的上一个对象 */ void *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM]; /* 动态段条目 */ };
之后将dladdr()获取的link_map指针强制转换为这个扩展结构体指针,即可直接访问l_info字段:
#include <dlfcn.h> #include <stdio.h> // 上面的struct link_map_extended定义放在此处 int main() { void *addr = &main; Dl_info info; if (dladdr(addr, &info)) { struct link_map_extended *lm_ext = (struct link_map_extended *)info.dli_fbase; // 示例:获取DT_SONAME条目 ElfW(Dyn) *soname_dyn = lm_ext->l_info[DT_SONAME]; if (soname_dyn) { const char *soname = lm_ext->l_strtab + soname_dyn->d_un.d_val; printf("SONAME: %s\n", soname); } } return 0; }
2. 用dl_iterate_phdr间接获取信息
如果不想依赖内部结构体定义,可以使用dl_iterate_phdr()遍历加载的共享库,通过程序头找到动态段,间接获取l_info对应的动态条目。
示例代码:
#include <link.h> #include <stdio.h> static int phdr_callback(struct dl_phdr_info *info, size_t size, void *data) { // 遍历程序头找到动态段 for (int i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) { ElfW(Dyn) *dyn = (ElfW(Dyn) *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr); // 遍历动态段条目 while (dyn->d_tag != DT_NULL) { if (dyn->d_tag == DT_SONAME) { const char *soname = (const char *)(info->dlpi_addr + dyn->d_un.d_val); printf("库路径: %s, SONAME: %s\n", info->dlpi_name, soname); } dyn++; } break; } } return 0; } int main() { dl_iterate_phdr(phdr_callback, NULL); return 0; }
3. 注意事项
- 结构体偏移依赖glibc版本:不同glibc版本的
link_map扩展字段可能有增减或偏移变化,直接强制转换可能引发内存访问错误,建议在目标系统上查看elf/link.h的实际定义。 - 兼容性有限:这种方法依赖glibc内部实现,不保证跨版本、跨发行版的兼容性,仅适合调试或特定场景使用。
- 编译需链接动态库:编译时需添加
-ldl选项,例如gcc -o test test.c -ldl。
内容的提问来源于stack exchange,提问作者Baba Rocks
相关产品推荐
相关产品推荐

