动态分析方法
在这里,我描述了一些动态分析方法。
动态方法实际上运行程序以确定调用图。
与动态方法相反的是静态方法,它们试图仅从源代码中确定它而不运行程序。
动态方法的优点:
- 捕获函数指针和虚拟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如下所示:
从中我们可以看到:
- 根节点是
_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`对我不起作用?
声明的输出格式:
\-- main
| \-- Crumble_make_apple_crumble
| | \-- Crumble_buy_stuff
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | \-- Crumble_prepare_apples
| | | \-- Crumble_skin_and_dice
| | \-- Crumble_mix
| | \-- Crumble_finalize
| | | \-- Crumble_put
| | | \-- Crumble_put
| | \-- Crumble_cook
| | | \-- Crumble_put
| | | \-- Crumble_bake
除了特定的硬件跟踪支持以外,可能是最有效的方法,但缺点是必须重新编译代码。