有没有一种方法可以比较两次不同的C/C++程序运行结果?

8

我正在调试这个程序,它是从即将毕业的博士生那里继承来的。该程序接受几个文本文件并对其进行处理。我遇到的问题是一个段错误,因为程序尝试访问尚未初始化的数组。我想知道是否有任何调试工具可以让您运行程序并比较程序运行过程中两条不同的路径。虽然我可以手动检查程序,但我不愿意这样做,因为它相当大,并且我还没有完全掌握它。我一直在使用GDB和Valgrind(以及使用g++ - wall显示警告),这就是我取得进展的方式。但是否有任何软件可以让您执行上述操作,甚至只是逐步执行程序。


1
我不知道有类似的东西。在代码中添加“跟踪”代码(例如,在每个函数的开头/结尾,或者至少在一些更重要的函数中)是帮助调试此类问题的一种方法。 - Mats Petersson
4
我想知道你询问的原因——如果Valgrind已经指出了未初始化的数据,为什么你还需要进一步了解?你现在需要知道什么更多的信息来解决问题呢? - RichieHindle
2
Gdb会逐步执行程序。输入“break main”,“next”,然后只需不断按回车键即可。 - luser droog
@luserdroog:逐步执行一个读取莎士比亚所写的每个单词的程序可能会非常乏味,最后还可能崩溃... ;) - Mats Petersson
2
覆盖率分析工具可以告诉您执行了哪些代码行。我想,不同覆盖率分析工具的输出可能会告诉您采取了哪些不同的分支。 - Yakk - Adam Nevraumont
显示剩余4条评论
3个回答

5
这些建议是特定于GCC的。您可以使用gcov覆盖工具,获取程序已执行部分及频率的详细记录。您需要向GCC传递一些特殊选项,以生成适当的仪器和输出,供gcov处理。

--coverage 此选项用于编译和链接为覆盖分析而仪器化的代码。该选项是-fprofile-arcs -ftest-coverage(编译时)和-lgcov(链接时)的同义词。有关这些选项的更多详细信息,请参阅文档。

然后,在执行程序时,会生成一些分析和覆盖数据。然后,您可以调用gcov来分析该输出。下面是从上面链接中获取的输出示例:
         -:    0:Source:tmp.c
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
         -:    3:int main (void)
         1:    4:{
         1:    5:  int i, total;
         -:    6:
         1:    7:  total = 0;
         -:    8:
        11:    9:  for (i = 0; i < 10; i++)
        10:   10:    total += i;
         -:   11:
         1:   12:  if (total != 45)
     #####:   13:    printf ("Failure\n");
         -:   14:  else
         1:   15:    printf ("Success\n");
         1:   16:  return 0;
         -:   17:}

如果您想实现自己的仪器来记录程序的调用历史,您可以在GCC上使用-finstrument-functions及其相关选项。生成入口和出口函数的插桩调用,在函数进入后和退出前,使用当前函数的地址和其调用站点调用以下分析函数。(在某些平台上,__builtin_return_address无法超出当前函数工作,因此调用站点信息可能无法对分析函数可用。)
      void __cyg_profile_func_enter (void *this_fn,
                                     void *call_site);
      void __cyg_profile_func_exit  (void *this_fn,
                                     void *call_site);

第一个参数是当前函数起始地址,可以在符号表中精确查找。

在C++中,这些钩子的实现应该声明为extern "C"。你可以实现这些钩子来记录每次调用函数时的信息。你无法得到函数名称,但可以使用objdumpaddr2line后处理指针。


4

我认为你选择像GDB和valgrind这样的工具是正确的方向。

GDB可以脚本执行程序并查看调用栈,在segfault发生时可以在该位置设置断点并使用不崩溃的参数再次运行程序,然后比较两者的差异。

使用valgrind,它实际上是一套工具(http://valgrind.org/info/tools.html),您可以使用callgrind和kcachegrind取得一些成功。Callgrind为您提供调用图,而kcachegrind (http://kcachegrind.sourceforge.net/cgi-bin/show.cgi/KcacheGrindIndex) 允许您对其进行可视化。我在对大型C代码库进行性能分析时都使用了它们。

另一个可以帮助您的工具是Fenris (http://lcamtuf.coredump.cx/fenris/whatis.shtml),它还可以打印出您代码的调用图。阅读您的要求时,我认为Fenris最接近,因为它还允许您“可视化”所采取的代码路径。


问题是Fenris目前还不支持我的CPU :( - higgs241

3

GDB可以让您逐行“步进”执行程序。以下是一些提示:

  1. 只需在main函数处打断点(键入b main),按下n + Enter键以执行当前行并继续到下一行。
  2. 如果要进入函数(即进入被调用的函数并从那里继续执行),请按s + Enter键。
  3. 键入p + 变量名以打印出该值(非常好,可以告诉您该变量是否已初始化,提示……)
  4. 如果您正在命令行中运行GDB并想要一个GUI包装器,请使用Emacs。只需键入emacs program.c,然后键入Alt + x,再键入gdb。输入可执行文件的名称,然后按Enter键。现在您可以看到更多代码,并仍然使用gdb命令进行调试。

我打算试一下,目前正在编写一个Python脚本,可以让我在gdb中对每个程序进行建模,并比较输出,然后我会查看并确定输出分歧的地方。 - higgs241
很酷,随时跟进! - jh314
@higgs241 你可以看一下这个 gdb/python 脚本,它在这些情况下对我很有用 https://gitorious.org/misc-gdb-stuff/misc-gdb-stuff/trees/master/misc_gdb/lockstep - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接