为 C/C++ 编写检测分析器的最简单方法是啥?
Posted
技术标签:
【中文标题】为 C/C++ 编写检测分析器的最简单方法是啥?【英文标题】:What's the easiest way to write an instrumenting profiler for C/C++?为 C/C++ 编写检测分析器的最简单方法是什么? 【发布时间】:2012-09-03 05:40:13 【问题描述】:我见过一些工具,例如 Pin 和 DynInst,它们可以进行动态代码操作,以便在无需重新编译的情况下检测代码。这些似乎是解决看似简单的问题的重量级解决方案:从程序中检索准确的函数调用数据。
我想写一些东西,在我的代码中,我可以写
void SomeFunction()
StartProfiler();
...
StopProfiler();
在执行后,检索有关在 StartProfiler()
和 StopProfiler()
(整个调用树)之间调用了哪些函数以及每个函数花费了多长时间的数据。
最好我也可以读出调试符号,以获取函数名称而不是地址。
【问题讨论】:
如果您可以使用 Linux,请尝试使用 callgrind(来自 valgrind)来获取调用树。它将动态翻译和检测您的程序,并以树格式显示所有函数调用(+check kcachgrind GUI)。还有一些系统范围的分析器能够绘制调用树(如 linux pref 或 google-perftools),但它们的调用图仅在某个采样间隔(例如每 1 毫秒)获取并且不准确。 我在 OS X 上,但我认为(部分)valgrind 最近已经移植到那里? valgrind 仅适用于 OSX 10.6/10.7; 10.8 支持有限。此外,callgrind 没有“START”/“STOP”宏(只有--instr-atstart=no
和 callgrind_control 实用程序),它会在不转储完整调用跟踪的情况下绘制树的摘要(但在内部它有一个)。此外,如果您想跟踪所有调用(大多数答案只需要脚本和 gdb),请检查此线程 ***.com/questions/311840/…。我通常使用这个解决方案:blog.superadditive.com/2007/12/01/…
【参考方案1】:
这是我发现的解决方案的一个有趣提示。
gcc(和 llvm>=3.0)在编译时有一个-pg
选项,传统上它是为了支持 gprof。当您使用此标志编译代码时,编译器会在每个函数定义的开头添加对函数 mcount
的调用。您可以覆盖此函数,但您需要在汇编中执行此操作,否则您定义的 mcount
函数将通过调用 mcount
进行检测,并且您将在 main
之前快速耗尽堆栈空间被调用。
这是一个小小的概念证明:
foo.c:
int total_calls = 0;
void foo(int c)
if (c > 0)
foo(c-1);
int main()
foo(4);
printf("%d\n", total_calls);
foo.s:
.globl mcount
mcount:
movl _total_calls(%rip), %eax
addl $1, %eax
movl %eax, _total_calls(%rip)
ret
用clang -pg foo.s foo.c -o foo
编译。结果:
$ ./foo
6
main
为 1,foo
为 4,printf
为 1。
这是 clang 为 foo
发出的 asm:
_foo:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl %edi, -8(%rbp) ## 4-byte Spill
callq mcount
movl -8(%rbp), %edi ## 4-byte Reload
...
【讨论】:
为什么要在汇编中这样做,当我们可以用 C 语言编写它并放入单独的源文件时,该文件将在没有 -pg 的情况下进行编译。 这是个好主意 :) 另一种可能性是用 C 语言编写它并编写一个小的汇编 shim,它会在生成的callq mcount
之后跳转到函数中。以上是关于为 C/C++ 编写检测分析器的最简单方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
在 Win/Linux 上使用 C/C++ 为大页面分配内存的最简单方法是啥? [关闭]
在 .NET 应用程序中查看所有调用的最简单方法(分析/检测)