为 C/C++ 编写检测探查器的最简单方法是什么?

What's the easiest way to write an instrumenting profiler for C/C++?

本文关键字:最简单 方法 是什么 探查 检测 C++      更新时间:2023-10-16

我见过一些像Pin和DynInt这样的工具,它们可以进行动态代码操作,以便在不需要重新编译的情况下插入代码。这些似乎是一个简单问题的重量级解决方案:从程序中检索准确的函数调用数据。

我想写一些东西,这样在我的代码中,我可以写

void SomeFunction() {
  StartProfiler();
  ...
  StopProfiler();
}

执行后,检索关于在StartProfiler()StopProfiler()(整个调用树)之间调用了什么函数以及每个函数花费了多长时间的数据。

最好我也能读出调试符号,以获得函数名而不是地址。

这里有一个关于我发现的解决方案的有趣提示。

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("%dn", total_calls);
}

foo.s:

.globl mcount
mcount:
  movl  _total_calls(%rip), %eax
  addl  $1, %eax
  movl  %eax, _total_calls(%rip)
  ret

用CCD_ 8编译。结果:

$ ./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
  ...