我希望制作一个跟踪其他软件功能调用的软件 它应该是从“运行时”中调用哪个函数。
示例:
int main ()
{
a ();
b ();
c ();
return 0;
}
a ()
{
d ();
e ();
}
b ()
{
e ();
f ();
}
假设我希望在C中为C写这个,我应该如何在运行时(从第一次调用开始)跟踪调用 - 使用线程和没有线程?
提示?
答案 0 :(得分:8)
您可以在运行时通过静态分析执行此操作,也可以在运行时通过动态分析执行此操作。
在运行时只有两种方法可以做到这一点,它们都相当于检测代码:
修改(“仪器”)源代码,以便跟踪您想要的内容,例如修改 每个调用站点传递一个有效包含调用函数名称的附加参数,以及每个函数条目以记录该函数的条目(例如,有效地调用函数名称)和调用函数名称。检测过程需要从检测点,回到源文件和行构建交叉引用,以便以后报告。
您可以使用Program Transformation System (PTS)以完全自动化的方式执行此操作,该Branch Coverage for Arbitrary Languages可以解析源代码以生成AST,修改AST,然后重新生成源。通常,PTS会允许您将更改写为表单的源级模式,“如果您看到 this ,则将其替换为 ”。 (不,正则表达式不能这样做)。
如何在我的技术论文{{3}}中找到这一点的扩展示例。本文展示了PTS中的内容,然后展示了典型的仪器转换。
对于像C这样的语言来说,“正确”完成此操作可能很困难,因为您需要一个完整的C解析器作为基础,这本身就是一项重大的技术壮举。最好以完整的形式获得这样的野兽(一些PTS有这个,考虑Concinnelle或DMS)或者你永远不会去做仪器部分。 (另一个答案表明,GCC内置了很多这种仪器功能)。
合理地说,额外仪器的执行成本非常适中(10-30%)。这个概念已被用于实现测试覆盖工具和时序分析器,它们通过跟踪动态调用集来运行,这似乎正是你想要的。
修改运行代码的执行引擎以跟踪所需的详细信息。鉴于您希望使用C程序执行此操作,执行引擎本质上是底层指令集。
由于您无法合理地修改处理器芯片本身,因此您必须:
修改目标代码(使用与程序转换相当的对象代码,请参阅前面的段落了解概念,或参见PIN和Valgrind等工具了解实现细节),
或编写某种聪明的指令集解释器,在收到调用指令时收集所需的信息(由于其解释性质,这可能相当慢)。
实际指令集的实现因指令集本身(英特尔的X86指令集很大),目标文件格式等而变得复杂。不要指望这条路径比源仪表路径更容易;只是期望它有一套不同的问题。
一个标准问题是知道要用什么;目标代码空间充满了不属于感兴趣的应用程序的代码(库,系统例程......),因此收集测试覆盖率数据并不感兴趣。为此,您需要一个符号表(对)来说明什么是应用程序代码,什么不是。您还需要能够从名称跟踪到原始源文件中的某个点, 为了报告结果。
目标代码检测的另一个难点是很难判断已经内联的函数foo是否已被执行(“覆盖”)。源检测方案没有这个问题,因为foo在被编译/内联之前得到检测,因此检测也会内联。
处理线程是一个正交问题。在您记录事实 X-calls-Y 的每个地方,您只需附加从OS获得的线程ID(或其等效)T,以生成 X-calls-Y-在-T 即可。从您的问题中不清楚为什么要这样做。
您可以决定跳过运行时实现的所有麻烦,并构建/获取可以生成调用图的静态分析器。使用Clang或DMS可以获得这些C语言。自己建造它可能很困难;现在你需要一个完整的C解析器,完整的数据流和点到分析,以及调用图构造。细节不适合本段。
答案 1 :(得分:7)
这非常依赖于平台。你需要的是做或多或少做调试器的工作。
我接近这个的方式(当我不得不调试在新架构上构建gdb所需的工具时,我实际上实现了所有这一切):
读取要调试的程序的符号表。你在评论中说Linux,所以要开始你需要一个读取ELF文件或阅读ELF规范并自己实现的库。
使用ptrace
系统调用在函数main
创建断点。如果你很幸运,你的系统有一个ptrace
工具来创建断点并将记账保存在内核中。如果没有,您需要为您的cpu架构找出断点指令并自己实现断点。
接下来,您需要弄清楚动态加载程序中的调试挂钩,以便了解何时加载共享库。您还需要库的符号表。
现在您已经拥有了所有符号(不可否认,这是在您的程序运行一段时间之后,因为它必须加载动态库,但我们假装程序从main
开始)创建断点在你从符号表中得到的所有功能。
让程序运行。每次遇到断点时,反向查找符号表中的指令指针。将函数的名称保存到文件或要保存的位置。您现在可以跟踪函数调用。
或者只是使用调试器。 gdb可能可以编写脚本来执行类似的操作。
答案 2 :(得分:4)
我很抱歉这些不是开源工具,但我过去曾将它们用于研究,并且它们支持您所需的功能,因此您可能会获得一些使用它们的提示。
在Linux上,尝试查看Pin的工作原理。
在Windows上,查看Detours。
答案 3 :(得分:3)
我会使用GCC
中内置的检测功能为您执行源代码检测。
以下是教程的链接:http://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/
基本上你告诉程序在每个函数调用之前调用一个instrumentation-function并给出两个地址作为参数。 您自己提供了该仪器功能,并使用from-address和to-address调用它。 from-address告诉你从哪里调用to-address的函数。
然后,您需要将地址转换为函数名称(可以在源代码文件中给定行号)。
通过使用GNU binutils工具addr2line
,您可以将地址和地址转换为行号,因为二进制文件是使用调试信息(gcc -g ...
)编译的。
使用这种方法开始使用仪器非常快。
修改强>
如果您手边没有源文件,只有二进制文件,您可以尝试将二进制文件拆解或解析为程序集。这样,您就可以将程序分成跳转到/来自的逻辑块。
如果没有源代码,函数名称将无法轻松恢复,因此您可能只能跟踪先前已将程序划分为的逻辑块中的跳转。
这是几个调试器做AFAIK的原因。
我认为OllyDbg(仅限Windows)和IDA pro可以提供可视化流程图排序,或显示彩色块图或类似的东西。
在没有源代码的情况下,您真的需要努力工作,而如果您手头有源代码,那么您可以通过各种方式完全按照自己的意愿行事。仅在write code -> compile -> execute
之间添加一个步骤,使其变为write code -> insert instrumentation -> compile -> execute
。
答案 4 :(得分:1)
费力的方法是将printf作为函数的第一行和最后一行插入。或者使用调试器。 问题可能是将所有变量设置为0 / null的调试器,"解决"代码中的错误。
我的解决方案是 using ctrace