如何在Tracy中追踪整帧调用栈而无需逐函数添加ZoneScoped?
如何通过插桩让Tracy自动追踪整帧所有函数调用
当然可以,不需要手动给每个函数加ZoneScoped,利用编译器插桩功能结合Tracy的API就能实现整帧全函数调用追踪,以下是具体实现方案:
核心思路
借助编译器的函数插桩特性,在所有函数的入口/出口自动插入回调,再将这些回调与Tracy的Zone追踪API绑定,同时通过帧激活状态过滤,只记录你关心的帧范围内的函数调用。
具体步骤
1. 启用编译器插桩选项
根据你使用的编译器添加对应编译参数:
- GCC/Clang:添加
-finstrument-functions,该参数会让编译器在每个函数入口插入__cyg_profile_func_enter调用,出口插入__cyg_profile_func_exit调用。 - MSVC:使用
/Gh(生成函数进入钩子)和/GH(生成函数退出钩子)参数,对应触发_penter和_pexit函数。
2. 实现Tracy绑定的钩子函数
将编译器生成的钩子与Tracy的Zone API关联,同时通过帧状态控制只追踪目标帧:
#include <tracy/Tracy.hpp> #include <cstdint> // 仅在需要追踪的帧范围内启用插桩记录 bool g_isFrameActive = false; // GCC/Clang 版本钩子 extern "C" void __cyg_profile_func_enter(void* this_fn, void* call_site) { if (g_isFrameActive) { TracyZoneBegin((uintptr_t)this_fn, (uintptr_t)call_site); } } extern "C" void __cyg_profile_func_exit(void* this_fn, void* call_site) { if (g_isFrameActive) { TracyZoneEnd((uintptr_t)this_fn); } } // MSVC 版本钩子(需单独实现) #ifdef _MSC_VER extern "C" void __declspec(naked) _penter() { __asm { push ebp mov ebp, esp push ecx push edx call _penter_impl pop edx pop ecx pop ebp ret } } extern "C" void _penter_impl(void* this_fn, void* call_site) { if (g_isFrameActive) { TracyZoneBegin((uintptr_t)this_fn, (uintptr_t)call_site); } } extern "C" void __declspec(naked) _pexit() { __asm { push ebp mov ebp, esp push ecx push edx call _pexit_impl pop edx pop ecx pop ebp ret } } extern "C" void _pexit_impl(void* this_fn, void* call_site) { if (g_isFrameActive) { TracyZoneEnd((uintptr_t)this_fn); } } #endif
3. 控制帧的追踪范围
在你的帧循环中手动标记追踪的开始和结束:
while (isRunning) { // 开始追踪当前帧 g_isFrameActive = true; TracyFrameMarkStart(); // 可选,用于Tracy中标记帧开始 // 你的帧逻辑代码... // 结束当前帧追踪 TracyFrameMark(); // 标记帧结束 g_isFrameActive = false; }
4. 过滤无关函数(可选)
全量插桩会捕获包括标准库、系统函数在内的所有调用,可能导致追踪数据过载,你可以通过以下方式过滤:
- GCC/Clang:添加编译参数
-finstrument-functions-exclude-file-list=/usr/include,stdlib.h排除标准库文件;或者用-finstrument-functions-exclude-function-list=printf,malloc排除特定函数。 - MSVC:通过链接器脚本或在钩子函数中判断函数地址范围,只保留你自己代码段的函数调用。
5. 性能优化建议
全量插桩会带来一定性能损耗,建议:
- 仅在需要性能分析的构建中启用插桩(比如通过
TRACY_ENABLE宏控制)。 - 对高频调用的小型函数(如getter/setter),可以通过编译器参数或手动过滤排除,减少追踪开销。
内容的提问来源于stack exchange,提问作者Alkyone
相关产品推荐
相关产品推荐

