如何追踪C/C++程序运行时被调用的函数与执行的语句?
C/C++ 自有代码执行流程追踪方案
1 专用追踪工具
- uftrace
专门用于用户态函数调用追踪的工具,刚好匹配你要替代strace/ltrace追踪自有代码的需求。使用前提就是你已经满足的-g -O0编译参数。
基础使用流程:
- 执行追踪:
uftrace record --filter=@/path/to/your/project/src ./你的程序 [程序参数],--filter参数可以只保留指定目录下的函数调用,自动过滤系统库、第三方库的调用 - 查看结果:
uftrace replay即可输出结构化的调用栈、函数执行时长、调用次数等信息,也支持导出为文本、JSON等格式
优势是性能开销极低,即使大型项目跑常规使用场景也不会有明显卡顿,支持函数级、行级追踪。
- gcc 内置插桩(
-finstrument-functions)
不需要额外安装工具,直接通过编译参数实现函数级追踪:
- 编译时追加参数:
-g -O0 -finstrument-functions,仅你自己编译的代码会被插桩,库函数不会被处理,天然过滤非自有代码 - 自行实现两个回调函数,在函数进入、退出时打印日志即可,示例代码如下:
#include <stdio.h> #include <dlfcn.h> void __cyg_profile_func_enter(void *this_fn, void *call_site) __attribute__((no_instrument_function)); void __cyg_profile_func_exit(void *this_fn, void *call_site) __attribute__((no_instrument_function)); void __cyg_profile_func_enter(void *this_fn, void *call_site) { Dl_info info; if (dladdr(this_fn, &info) && info.dli_sname) { printf("ENTER: %s\n", info.dli_sname); } } void __cyg_profile_func_exit(void *this_fn, void *call_site) { Dl_info info; if (dladdr(this_fn, &info) && info.dli_sname) { printf("EXIT: %s\n", info.dli_sname); } }
编译时链接-ldl即可,日志可以自行输出到指定文件。
- perf 工具
系统自带的性能分析工具也可以实现函数调用追踪:
执行perf record -g ./你的程序 [程序参数],运行结束后执行perf script即可输出完整的调用链,可通过符号过滤规则只保留自有项目的函数。
2 GDB 非阻塞追踪实现
你说的无需断点阻塞、仅步入自有代码、输出日志的需求完全可以通过GDB实现,具体操作如下:
- 提前准备GDB命令脚本,命名为
trace.gdb,示例内容:
# 关闭分页,避免输出阻塞 set pagination off # 配置日志输出路径 set logging file gdb_exec_trace.log set logging overwrite on set logging on # 配置跳过非自有代码路径,不会步入这些路径下的函数 skip -gfi /usr/* skip -gfi /lib/* # 如有第三方依赖库,也可以配置跳过,比如: # skip -gfi /path/to/third_party/* # 配置追踪点动作,触发时打印当前执行信息后自动继续,不会阻塞程序 define trace-action # 打印当前函数名、源码路径、行号、对应代码 frame continue end # 给自有项目下的所有函数设置非阻塞追踪点 # 替换下面的路径为你自己项目的源码根目录 rbreak /path/to/your/project/src/*:. commands trace-action end # 启动程序 run # 程序结束后退出GDB quit
- 执行命令启动追踪:
gdb -x trace.gdb --args ./你的程序 [程序参数] - 程序运行结束后,所有执行记录都会保存在
gdb_exec_trace.log中。
注意:如果需要语句级的执行追踪,可在上述脚本中补充步进到每一行的逻辑,不过性能开销会更高,建议只针对需要关注的模块开启。
3 方案选择建议
- 仅需要函数级调用关系,优先选择
uftrace,配置最简、性能最好,无需修改代码或编写脚本 - 需要自定义追踪逻辑(比如打印函数参数、返回值、上下文变量),优先选择gcc内置插桩方案
- 需要语句级执行记录,或不想安装额外工具,选择GDB追踪方案
内容的提问来源于stack exchange,提问作者Galaxy
相关产品推荐
相关产品推荐

