探索动态日志模块的实现
最初的目标是创建一个通用的日志模块, 它具有基本的日志输出功能并支持重定向. 这样, 如果需要更换日志模块, 可以轻松实现.
最初的构想是通过函数重定向, 即使用 dlsym
来重定向所有函数以实现打印功能.
然而, 这种方法引发了一个问题, 即无法正确获取文件名和行号信息, 同时也受到流式打印问题的影响.
因此, 我开始思考是否有一种方法可以在运行时动态获取文件名和行号信息.
目前, 思路有些混乱导致不太愿意深入研究. 于是将这些思考和学习记录下来, 以备将来深入学习之用.
寻找解决方案
在明确了需要实现的任务后, 我发现了一个 GitHub 项目, 实现了我所需的功能.
以下是我从该项目中获得的知识点, 有助于理解如何在运行时动态获取文件名和行号的机制:
-
__cyg_profile_func_enter(void* callee, void* caller)
和__cyg_profile_func_exit(void* callee, void* caller)
这两个函数是在所有函数的入口和出口上增加的回调函数, 用于跟踪函数调用.
它们只在使用
extern "C"
并带有__attribute__((no_instrument_function))
属性的情况下才会生效.在编译时, 需要使用参数
-finstrument-functions -rdynamic -ldl
来启用它们. -
dladdr(address, info)
处理地址信息, 并将栈的信息返回给
info
(类型为Dl_info
的结构体), 从而获取函数调用栈的相关信息. -
abi::__cxa_demangle(__mangled_name, __output_buffer, __length, __status)
用于处理文件名信息, 以使其更易读.
-
BFD(Binary File Descriptor)库和 GNU 二进制工具集
BFD库的主要目标是提供一个通用的接口, 允许开发者读取、分析、修改和生成各种不同格式的二进制文件
而无需关心文件格式的细节. 在这里, 它的主要作用是获取函数栈的信息.
使用之前, 需要执行
bfd_init
函数进行初始化. -
bfd_openr(filename, target)
这个函数用于打开一个二进制文件以进行读取操作, 返回一个 BFD 对象.
-
bfd_close(abfd)
用于识别和验证输入的二进制文件的格式是否受支持.
-
bfd_get_symtab_upper_bound(abfd)
用于计算符号表的上限大小, 以便在分析二进制文件时为符号表分配足够的内存空间.
-
bfd_canonicalize_symtab
用于规范化符号表中的符号, 通常包括去除重复的符号、按名称排序符号等操作.
规范化符号表可以提高后续符号解析和分析操作的效率, 并确保符号表中的数据是一致和可预测的.
-
_Unwind_Backtrace(func, argue)
通常用于获取当前线程的函数调用堆栈的信息, 包括每个函数调用的返回地址和其他相关信息.
这些函数与异常处理机制紧密相关, 通常在实现自定义异常处理或其他与异常相关的功能时使用.
-
_Unwind_GetIPInfo
用于获取当前程序执行的位置, 通常指向当前函数中的某一行代码.
-
bfd_find_nearest_line
在给定的地址
pc
处查找最近的源代码行号信息, 并将结果存储在传入的参数地址中.这些信息包括源代码文件名、包含地址的函数名称以及源代码中的行号.