如何通过dladdr()函数编程打印源文件名及行号?
问题
我尝试使用dladdr()函数打印回溯信息,当前代码中的info.dli_fname仅显示ELF文件的名称。请问是否可以不借助addr2line或gdb工具,通过编程方式解析并打印出源文件名及代码行号?
代码:
void print_caller(void) { int rc = -1; Dl_info info = {0}; void *pc = __builtin_return_address(0); rc = dladdr(pc, &info); printf(" ==> %p: %s (in %s)\n", pc, info.dli_sname, info.dli_fname); }
输出:
$ ./a.out ==> 0x55a6b04a1589: foo2 (in ./a.out)
回答
可以实现,但需要直接解析ELF文件中的DWARF调试信息——这也是addr2line和gdb背后依赖的核心数据。dladdr()本身只能获取符号名称和ELF文件路径,无法提供行号和源文件信息,因为这些数据存储在DWARF专用段中,需要手动解析。
具体实现思路:
- 先通过
dladdr()拿到目标ELF文件的路径,打开文件后定位到DWARF相关段(比如.debug_info、.debug_line)。 - 将
__builtin_return_address返回的虚拟地址转换为ELF文件内部的相对偏移(减去ELF的加载基址)。 - 解析
.debug_line段,该段存储了地址与源文件、行号的映射表,遍历匹配即可找到对应地址的行号信息。
需要注意的细节:
- 编译程序时必须添加
-g参数生成DWARF调试信息,否则没有可解析的行号数据。 - 手动解析DWARF逻辑复杂,可借助轻量的DWARF解析库(比如
libdwarf)简化开发,避免从零实现解析逻辑。
以下是依赖libdwarf的简化示例:
#include <dwarf.h> #include <libdwarf.h> #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> void print_caller_with_line(void) { void *pc = __builtin_return_address(0); Dl_info info = {0}; if (!dladdr(pc, &info)) { fprintf(stderr, "dladdr failed\n"); return; } // 初始化libdwarf并打开目标ELF文件 Dwarf_Debug dbg; int res = dwarf_init_path(info.dli_fname, NULL, DW_GROUPNUMBER_ANY, DW_DLC_READ, NULL, NULL, &dbg); if (res != DW_DLV_OK) { fprintf(stderr, "dwarf_init_path failed\n"); return; } // 计算地址在ELF文件内的相对偏移(适配PIE可执行文件) Dwarf_Addr load_base = (Dwarf_Addr)info.dli_fbase; Dwarf_Addr addr_in_file = (Dwarf_Addr)pc - load_base; // 遍历编译单元查找对应行号 Dwarf_Unsigned cu_header_length, abbrev_offset, next_cu_header; Dwarf_Half version, address_size; while (dwarf_next_cu_header(dbg, &cu_header_length, &version, &abbrev_offset, &address_size, NULL, &next_cu_header, NULL) == DW_DLV_OK) { Dwarf_Line *line_buf; Dwarf_Signed line_count; if (dwarf_srclines(dbg, &line_buf, &line_count) == DW_DLV_OK) { for (int i = 0; i < line_count; i++) { Dwarf_Addr line_addr; const char *filename; Dwarf_Unsigned line_num; if (dwarf_lineaddr(line_buf[i], &line_addr) == DW_DLV_OK && dwarf_lineno(line_buf[i], &line_num) == DW_DLV_OK && dwarf_linesrc(line_buf[i], &filename, NULL) == DW_DLV_OK) { // 匹配目标地址所在的行区间 if (line_addr <= addr_in_file) { if (i == line_count - 1 || (dwarf_lineaddr(line_buf[i+1], &line_addr) != DW_DLV_OK || addr_in_file < line_addr)) { printf(" ==> %p: %s (in %s:%u)\n", pc, info.dli_sname, filename, line_num); break; } } } } dwarf_dealloc_lines(dbg, line_buf, line_count); } dwarf_next_cu_header_skip(dbg); } dwarf_finish(dbg); }
编译时需要链接libdwarf和dl库:gcc -g your_code.c -o a.out -ldl -ldwarf
这种方式无需调用外部工具,完全通过代码解析DWARF信息获取源文件和行号。
内容的提问来源于stack exchange,提问作者Kirubakaran
相关产品推荐
相关产品推荐

