动态分析方法
这里我描述一些动态分析方法。
动态方法实际上运行程序以确定调用图。
与动态方法相反的是静态方法,它们试图在不运行程序的情况下仅从源代码中确定它。
动态方法的优点:
- 捕获函数指针和虚拟 C++ 调用。这些在任何非平凡软件中都存在大量。
动态方法的缺点:
- 您必须运行程序,这可能会很慢,或者需要您没有的设置,例如交叉编译
- 只有实际调用的函数才会显示。例如,某些函数可能会根据命令行参数被调用或不被调用。
KcacheGrind
https://kcachegrind.github.io/html/Home.html
测试程序:
int f2(int i) { return i + 2; }
int f1(int i) { return f2(2) + i + 1; }
int f0(int i) { return f1(1) + f2(2); }
int pointed(int i) { return i; }
int not_called(int i) { return 0; }
int main(int argc, char **argv) {
int (*f)(int);
f0(1);
f1(1);
f = pointed;
if (argc == 1)
f(1);
if (argc == 2)
not_called(1);
return 0;
}
使用方法:
sudo apt-get install -y kcachegrind valgrind
# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c
# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main
# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234
你现在位于一个包含许多有趣性能数据的GUI程序中。
在右下角,选择“调用图”选项卡。这将显示一个交互式调用图,与其他窗口中的性能指标相关联,当你单击函数时。
要导出图形,请右键单击它并选择“导出图形”。导出的PNG看起来像这样:
![](https://istack.dev59.com/s7dYK.webp)
从中我们可以看到:
- 根节点是
_start
,它是实际的ELF入口点,并包含glibc初始化样板
f0
、f1
和f2
按预期彼此调用
pointed
也显示出来了,即使我们使用函数指针调用它。如果我们传递了命令行参数,它可能不会被调用。
not_called
没有显示出来,因为它在运行中没有被调用,因为我们没有传递额外的命令行参数。
valgrind
的好处在于它不需要任何特殊的编译选项。
因此,即使你没有源代码,只有可执行文件,也可以使用它。
valgrind
通过运行你的代码通过轻量级“虚拟机”来实现。这也使得执行速度与本地执行相比极其缓慢。
如图所示,还可以获得每个函数调用的时间信息,这可以用于对程序进行分析,这很可能是此设置的原始用例,不仅仅是查看调用图:如何在Linux上对C++代码进行分析?
在Ubuntu 18.04上测试。
gcc -finstrument-functions
+ etrace
https://github.com/elcritch/etrace
-finstrument-functions
添加回调,etrace解析ELF文件并实现所有回调。
然而,我不幸地无法让它工作:为什么“-finstrument-functions”对我无效?
声称的输出格式为:
\
| \
| | \
| | | \
| | | \
| | | \
| | | \
| | | \
| | \
| | | \
| | \
| | \
| | | \
| | | \
| | \
| | | \
| | | \
除了特定的硬件跟踪支持之外,这可能是最有效的方法,但缺点是您必须重新编译代码。