跟踪变量自动变化

时间:2010-10-27 09:24:34

标签: c debugging compiler-construction

我正在调试一个C程序(Linux中的GCC和GDB以及Windows中的Visual Studio),它在两种不同的体系结构上提供不同的结果。我想通过跟踪变量存储在变量中来比较每个体系结构的执行情况,以便找出差异。

file main.c, line 234. Variable A changes from 34 to 23
file main.c, line 236. Variable B changes from 0 to 2
.....
etc.

是否可以指示编译器检测此效果,而无需使用printf语句手动乱丢源?

11 个答案:

答案 0 :(得分:7)

我会写一个脚本来自动导航调试器。我不知道Windows上是否有预期(可能是),但它是编写自动驾驶交互式工具脚本的绝佳工具。该脚本将类似于:

#!/usr/bin/expect
set progname [lindex $argv 0]
spawn gdb $progname
expect "(gdb)"
send "break main\n"
expect "(gdb)"
send "run\n"
while 1 {
    expect {
    "(gdb)" {
        send "info locals\n"
        expect "(gdb)"
        send "next\n"
    }

"Program exited normally" {
    exit;
}
}

"Program exited normally" { exit; } }

我会将“main”更改为您认为程序出错的函数。您还可以插入所需的任何其他调试器命令,例如在打印变量之前打印出您所在的行;在这里我使用“信息本地”打印出所有本地值。显然,您需要将此输出保存到文件进行分析。 Expect非常容易学习,语法都基于tcl。

答案 1 :(得分:4)

在隐式解决方案中需要考虑几个因素:

  • C几乎不知道类型。例如,如果您使用了@Jens Gustedt关于仪器功能的建议,那么您仍然需要确定您在堆栈上看到的确切内容,以及如何正确地打印出值。 IIRC,仪器功能不会为您提供即将发布的函数的原型,也不会提供一个方便的结构,指向这些值。这更类似于C ++模板。您必须编写一个知道所有函数原型的进入/退出函数,并且内部知道您的变量的精确调用约定和打包安排。
  • 您需要使用调试符号进行编译,以便确定哪些内存与代码中定义的变量相关联,以及何时修改这些变量。
  • 更复杂的类型没有标准的打印方式(例如结构),即使在C ++中也是如此。您需要具有符合某种界面的打印功能。
  • 指针和不确定长度的数组将很难处理。您需要测试NULL指针,并提前知道数组大小以正确打印它们的值。

我在existing project正在努力尝试类似于你在函数入口处的“printf”。这对important functions有用,但我已经远离asserting values are in the expected ranges at function entry并退出了。这是日志记录headersource,它们演示了如何大大简化手动跟踪周围的行,函数和其他有用信息的打印。您会注意到我的许多类型都有相应的*_valid()函数,以及关于它们使用的断言。一旦我的程序在轨道上运行(我在调试过程中保证你很频繁),断言将触发,我可以通过堆栈跟踪分析情况。

您可能还会发现this answer对于单独使用C进行此操作的难度以及如何使用宏来解决此问题非常有用。

如果您需要隐式执行此操作,最好的选择是通过GDB,可能您可以write scripts分析每条指令后的更改,并使用-g提供的调试信息智能地确定和打印类型。 / p>

答案 2 :(得分:1)

Valgrind可以自动跟踪所有商店,但标准工具不能轻易提供明确的跟踪。您必须编写自己的valgrind工具,例如通过修改cachegrind来跟踪所有Ist_Store指令。

唉,valgrind在Windows上不起作用。

答案 3 :(得分:1)

gdb和Visual Studio的调试器都支持监视 - 变量。这就像一个断点,但是当变量的值发生变化时,程序会破坏代码中的某个位置。如果将值更改为与已有的值完全相同,您可能会发现它们的行为完全不同(不确定是否存在任何差异,但可能存在差异)。

另外,如果您正在查看局部变量,您可能会发现两个调试器的行为存在差异。一个调试器可能会忘记返回的函数中的任何监视变量,而另一个调试器可能每次都会重新调用它们,而另一个调试器可能最终只能看到实际的内存位置,而不关心内存被重新用作什么。我不确定这些调试器在这方面是如何工作的,因为我不习惯看局部变量(我不确定我是否曾经看过局部变量)。

您可能想要做的是在初始化这些变量的位置设置一个断点,逐步完成初始化,然后设置对变量的监视并让程序运行。您可以记录更改(手动或{至少使用gdb}脚本),并查看两个程序在行为上的分歧。

您可能会发现,某种类型的大小在一种类型上是不同的,您使用的是未初始化的内存(并且未初始化的内存在两个系统上设置不同),或者某些数据结构填充可能正在改变某些内容。如果它更复杂的话,不是像API函数错误而你没有检查它,或者某个或两个编译器给你的一些警告,你忽略了它,那么它很可能很难找到。 / p>

您应该确保关闭所有优化,除非问题仅在启用优化时出现。

可能会帮助你很多的东西就是尝试在每台机器上对程序的各个部分进行单元测试。与大问题相比,发现和理解小问题的行为差异要容易得多。

答案 4 :(得分:1)

显而易见的问题是询问您的程序(以及它调用的所有库)是否真的独立于架构,甚至是确定性的(例如,您可能有一个非稳定的排序,甚至是某个地方的线程),或者你只是在某个地方有一个未初始化的变量。

假设您认为您的程序完全是确定性的,并且您确实希望对每个分配进行数据跟踪,那么实现此目的的方法是使用program transformation system。这样的工具接受“如果你看到这个,用那个”模式替换它,使用目标语言的表面语法(在这种情况下,C)。您使用转换规则对程序进行检测,以自动查找需要检测的所有位置并将其插入到那里,编译并运行检测程序,然后将已检测的版本扔掉并获得跟踪。

我们的DMS Software Reengineering Toolkit可以做到这一点。你想要做的是用赋值和printf的组合替换每个赋值。 用于实现此目的的DMS重写规则是:

        rule insert_tracing_printfs(l: lhs, e: expression): expression -> expression =
           " \lhs=\e " -> "print_and_assign_int(\tostring\(\lhs\),&\lhs,\e)" if int_type(lhs);

基本规则格式为 ifyouseethis - >如果某些条件,则替换此。引号确实是metaquotes,并且可以将C语法嵌入到规则语言中。 \ _是逃脱C语法 回到规则语言中。

这些规则对DMS生成的抽象语法树进行操作,这是解析(预处理的)C源代码的结果。对于 lhs e 的所有合法形式,此特定规则准确模式匹配源代码以获得精确语法 lhs = * e *。当它找到这样的匹配时,它将通过函数调用替换赋值 碰巧执行任务并打印出跟踪值。

\ tostring函数获取lhs树并生成与原始对应的源文本 表达;这可以通过DMS API轻松实现,以实现漂亮的AST。 int _type 函数询问DMS生成的符号表,以确定lhs的类型是否为 int

您需要一个规则,例如您要打印的每种数据类型。您还需要一个规则,用于您的程序使用的每种赋值语法(例如,“=”,“+ =”,“%=”...)。因此,基本数据类型和少数赋值语法类型表明您需要1-2个这样的规则。

您还需要相应的C函数来匹配各种数据类型:

        int print_and_assign_int(char* s, int* t, int v)
        { printf("Integer variable %s changes from %d to %d\n",s,*t,v);
          *t=v;
          return v;
        }

(如果你也想要文件和行号,你可以使用文件和行号的C预处理器宏将它们作为额外参数添加到打印功能。)

对于C语句,如:

           if (x=getc())
              { y="abc";
                p=&y;
              }

以这种方式完成的一组重写规则会自动生成类似的内容:

          if (print_and_assign_char("x", &x,getc()))
             { print_and_assign_charstar("y",&y,"abc");
               print_and_assign_ptrtocharstar("p",&p,&y);
             }

您必须决定如何打印指定的指针值,因为您必须假设它们没有等效的地址,因此您必须打印指针选择的值。只要你有空虚,就会让你陷入困境*;但是你可以打印出你对void *变量的了解,例如,是否为NULL,这仍然是有用的跟踪数据。

如果您经常进行这种调试,这可能都是值得的。恕我直言,你可能最好只是咬紧牙关并调试你的解决方案,因为我希望你会因为一些架构依赖而感到惊讶。

答案 5 :(得分:1)

如果你想跨平台,printf是你的朋友。使用grep进行一些工作会找到变量的分配位置。除此之外,我认为你的努力最好花在找出如何缩短你的编辑/编译* 2 / run_tests * 2 / diff循环并考虑二进制拆分的位置。

我回过头来时,我或多或少有同样的问题需要解决,但是中间有一个不完整的语言翻译器会增加复杂性。能够同时运行版本和快速输出使得它成为一个非常合理的问题。

答案 6 :(得分:0)

只要某些指令修改了变量,就可以告诉gdb中断。 IIRC的命令是“观察”。例如。 '观看A',或'观看*(int*)0x123456'

当某人使用'rwatch'读取时,你甚至可以告诉它。

答案 7 :(得分:0)

你可以告诉gcc乐器函数调用:-finstrument-functions。这不会让您了解分配的粒度,但如果您将一些基本功能打包到inline函数中,则会关闭。

答案 8 :(得分:0)

我认为程序ctrace可能就是你所追求的;它可以在AT& T Unix中使用(早在AT& T拥有Unix的时候),但URL是Sun的手册页。因此,您可以在Unix的其他专有版本(AIX,HP-UX,SCO)上找到它;目前尚不清楚是否有适用于Linux的版本。

SourceForge上的CTrace库根本不是一回事。

答案 9 :(得分:0)

如果您知道您感兴趣的变量所在的行,那么您可以使用breakpoint命令进行简单的跟踪:

示例:

#include <iostream>

int main(int, char **)
{
  for(int i = 0; i < 100; ++i)
  {
    std::cout << i << std::endl;
  }

  return 0;
}

编译此程序时如

c++ -g -o t1 t1.cpp

然后您可以使用这样的断点命令:

break 7
commands
print i
continue
end

生成简单的跟踪。这也适用于观察点(当变量改变状态时触发的断点)。

以下是示例gdb会话的日志:

$ gdb t1
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>                                
This is free software: you are free to change and redistribute it.                                           
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"                                   
and "show warranty" for details.                                                                             
This GDB was configured as "x86_64-linux-gnu".                                                               
For bug reporting instructions, please see:                                                                  
<http://www.gnu.org/software/gdb/bugs/>...                                                                   
Reading symbols from /tmp/t1...done.                                                                         
(gdb) break 7                                                                                                
Breakpoint 1 at 0x40087c: file t1.cpp, line 7.
(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>print i
>continue
>end
(gdb) set pagination off
(gdb) r
Starting program: /tmp/t1 

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$1 = 0
0

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$2 = 1
1

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$3 = 2
2

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$4 = 3
3

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$5 = 4
4

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$6 = 5
5

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$7 = 6
6

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$8 = 7
7

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$9 = 8
8

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$10 = 9
9

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$11 = 10
10

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$12 = 11
11

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$13 = 12
12

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$14 = 13
13

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$15 = 14
14

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$16 = 15
15

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$17 = 16
16

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$18 = 17
17

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$19 = 18
18

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$20 = 19
19

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$21 = 20
20

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$22 = 21
21

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$23 = 22
22

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$24 = 23
23

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$25 = 24
24

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$26 = 25
25

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$27 = 26
26

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$28 = 27
27

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$29 = 28
28

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$30 = 29
29

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$31 = 30
30

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$32 = 31
31

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$33 = 32
32

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$34 = 33
33

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$35 = 34
34

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$36 = 35
35

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$37 = 36
36

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$38 = 37
37

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$39 = 38
38

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$40 = 39
39

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$41 = 40
40

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$42 = 41
41

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$43 = 42
42

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$44 = 43
43

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$45 = 44
44

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$46 = 45
45

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$47 = 46
46

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$48 = 47
47

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$49 = 48
48

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$50 = 49
49

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$51 = 50
50

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$52 = 51
51

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$53 = 52
52

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$54 = 53
53

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$55 = 54
54

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$56 = 55
55

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$57 = 56
56

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$58 = 57
57

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$59 = 58
58

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$60 = 59
59

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$61 = 60
60

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$62 = 61
61

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$63 = 62
62

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$64 = 63
63

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$65 = 64
64

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$66 = 65
65

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$67 = 66
66

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$68 = 67
67

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$69 = 68
68

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$70 = 69
69

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$71 = 70
70

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$72 = 71
71

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$73 = 72
72

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$74 = 73
73

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$75 = 74
74

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$76 = 75
75

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$77 = 76
76

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$78 = 77
77

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$79 = 78
78

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$80 = 79
79

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$81 = 80
80

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$82 = 81
81

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$83 = 82
82

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$84 = 83
83

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$85 = 84
84

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$86 = 85
85

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$87 = 86
86

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$88 = 87
87

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$89 = 88
88

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$90 = 89
89

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$91 = 90
90

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$92 = 91
91

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$93 = 92
92

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$94 = 93
93

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$95 = 94
94

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$96 = 95
95

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$97 = 96
96

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$98 = 97
97

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$99 = 98
98

Breakpoint 1, main () at t1.cpp:7
7           std::cout << i << std::endl;
$100 = 99
99

Program exited normally.
(gdb) q

答案 10 :(得分:0)

首先,我假设您在两种情况下实际使用相同的代码。否则,请开始查看代码的不同之处。

如果您的程序在不同的体系结构上给出不同的结果,那么您可能没有以正确的方式处理某些事情。 我要做的第一件事就是打开所有可能的编译器警告并注意它们。

如果没有产生任何结果,我会尝试使用静态代码分析工具。我使用了coverity(商品),但也有others。 这些工具有时可以帮助您查找编译器未发现的程序错误。

当这些自动选项耗尽时,我可能会尝试比较整个程序中的所有变量。根据您的程序大小,这可能非常耗时。