Трассировка изменений в переменные автоматически - PullRequest
6 голосов
/ 27 октября 2010

Я отлаживаю программу на C (GCC и GDB в Linux и Visual Studio в Windows), которая дает разные результаты на двух разных архитектурах.Я хотел бы сравнить выполнение для каждой архитектуры, отслеживая изменения в значениях, хранящихся в переменных, чтобы найти различия.

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 ]

7 голосов
/ 01 ноября 2010

Я бы написал скрипт для автопилота отладчика.Я не знаю, есть ли ожидаемый на Windows (это возможно), но это отличный инструмент для написания сценариев, которые интерактивные инструменты автопилота.Сценарий будет выглядеть примерно так: <pre> #!/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" }</p> <pre><code>"Program exited normally" { exit; } }

}

Я бы изменил «main» на функцию, в которой, по вашему мнению, программа работает неправильно.Вы также можете вставить любые другие команды отладчика, например, распечатать строку, на которой находитесь, перед печатью переменных;здесь я использую "info localals", которая печатает все локальные значения.Очевидно, что вам нужно сохранить этот вывод в файл для анализа.Expect довольно прост в изучении, и весь синтаксис основан на tcl.

4 голосов
/ 31 октября 2010

Есть несколько факторов, которые необходимо учитывать в неявном решении:

  • C едва ли знает тип.Например, если вы использовали предложение @Jens Gustedt об инструментальных функциях, у вас все еще остается проблема определения, что именно вы смотрите в стеке, и как правильно распечатать значения.IIRC, инструментальные функции не дадут вам прототип будущей функции и не предоставят удобную структуру с указателями на эти значения.Это было бы больше похоже на шаблоны C ++.Вам нужно было бы написать функцию входа / выхода, которая бы знала о прототипах всех функций и обладала внутренними знаниями о точном соглашении о вызовах и механизмах упаковки для ваших переменных.
  • Вам необходимо скомпилировать с помощью отладкисимволы, чтобы определить, какая память была связана с переменной, определенной в вашем коде, и когда эти переменные изменяются.
  • Более сложные типы не имеют стандартного способа печати (например, структуры),даже в C ++.Вам понадобятся функции печати, которые соответствуют какому-либо интерфейсу.
  • Указатели и массивы неопределенной длины будут очень сложны в обращении.Вам нужно проверить наличие указателей NULL и заранее знать размеры массива, чтобы правильно печатать их значения.

Я пытался сделать что-то похожее на ваш "printf при входе в функцию" в Существующий проект Я работаю над.Это полезно для важных функций , но я получил гораздо больше от , утверждая, что значения находятся в ожидаемых диапазонах при входе в функцию и выходе.Вот записи header и source , которые демонстрируют, как значительно упростить печать строк, функций и другой полезной информации вокруг вашей ручной трассировки.Вы заметите, что многие из моих типов имеют соответствующую функцию *_valid() и утверждения, касающиеся их использования.Как только моя программа запускается с треков (что, я уверяю вас, довольно часто во время отладки) запускает подтверждения, и я могу проанализировать ситуацию с помощью трассировки стека.

Вы также можете найти thisответ полезен как в отношении сложности выполнения всего этого с одним C, так и в отношении того, как обойти это с помощью макросов.

Лучше всего, если вам нужно сделать это неявно, через GDB, предположительно, вы можете написать сценарии для анализа изменений после каждой инструкции, а также разумно определять и печатать типы, используя отладочную информацию, предоставленную -g.

1 голос
/ 05 ноября 2010

Если вы хотите кроссплатформенность, printf - ваш друг.Немного поработав с grep, мы выясним, куда присваиваются переменные.Кроме того, я думаю, что ваши усилия будут лучше всего потрачены на выяснение того, как сократить цикл редактирования / компиляции * 2 / run_tests * 2 / diff, а также на размышления о том, где делать двоичные разбиения.Вернувшись назад, мне пришлось решить более или менее ту же проблему, но с добавленной сложностью иметь неполного переводчика языка в середине.Возможность быстрого запуска обеих версий и различий в выводе делала это очень разумной проблемой.

1 голос
/ 02 ноября 2010

Очевидный вопрос заключается в том, является ли ваша программа (и все библиотеки, которые она вызывает) действительно независимой от архитектуры или даже детерминированной (например, у вас может быть нестабильная сортировка или даже где-то потоки), или если у вас просто есть неинициализированная переменная где-то.

Предполагая, что вы полагаете, что ваша программа полностью детерминирована, и вам действительно нужна трассировка данных по каждому назначению, способ сделать это можно с помощью системы преобразования программ . Такой инструмент принимает шаблоны «если вы видите это, замените их», используя синтаксис поверхности вашего целевого языка (в данном случае, C). Вы управляете своей программой, используя правила преобразования, чтобы автоматически находить все места, где требуется инструментарий, и вставляете их туда, компилируете и запускаете инструментированную программу, а затем выбрасываете инструментированную версию, получив ваш след.

Наш инструментарий реинжиниринга программного обеспечения DMS может сделать это. То, что вы хотите сделать, это заменить каждое назначение комбинацией назначения и 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 -> заменяется этим , если somecondition . Кавычки на самом деле являются мета-кавычками и позволяют встроить C-синтаксис в язык правил. \ Это побег из синтаксиса C обратно в правила языка.

Эти правила действуют на абстрактных синтаксических деревьях, сгенерированных DMS в результате анализа (предварительно обработанного) исходного кода на языке C. Этот конкретный шаблон правил точно соответствует исходному коду для точного синтаксиса lhs = * e * для всех допустимых форм lhs и e . Когда он находит такое совпадение, он заменяет присвоение вызовом функции. это происходит при выполнении задания, а также распечатывает значение вашей трассировки.

Функция \ tostring берет дерево lhs и генерирует исходный текст, соответствующий оригиналу выражение; это легко сделать с помощью DMS API для красивых печатных AST. Тип int _ Функция опрашивает сгенерированную 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 или нет, и это все равно будет полезным для трассировки.

Это может стоить хлопот, если вы много отлаживали. ИМХО, вам, вероятно, лучше просто прикусить пулю и отладить свой путь к решению, так как я ожидаю, что вы будете удивлены некоторой зависимостью от архитектуры.

1 голос
/ 02 ноября 2010

И отладчики gdb, и Visual Studio поддерживают watch -ing-переменные.Это похоже на точку останова, но вместо места в коде ваша программа ломается при изменении значения переменной.Вы можете обнаружить, что они не ведут себя точно так же, если значение изменяется на точно такое же значение, которое у него уже есть (не уверен, есть ли разница, но может быть).

Другое место, где вы можетенайти различия в поведении двух отладчиков, если вы смотрите локальные переменные.Один отладчик может забыть любые переменные наблюдения в функциях, которые возвращаются, в то время как другой может пересматривать их каждый раз, в то время как другой может просто закончить просмотр фактического расположения в памяти, не заботясь о том, как эта память была переопределена.Я не уверен, как эти отладчики работают в этом отношении, так как я не имею привычки наблюдать локальные переменные (я не уверен, что когда-либо наблюдал локальные переменные).

Что вы, вероятно, захотитеДля этого нужно установить точку останова, в которой вы инициализируете эти переменные, выполнить процедуру инициализации, а затем установить контроль переменных и позволить программе работать.Вы можете записать изменения (либо вручную, либо {по крайней мере, с помощью сценария gdb) и посмотреть, когда две программы расходятся в поведении.

Вы, вероятно, обнаружите, что размер одного типа отличается от одногочем другой, вы используете неинициализированную память (и неинициализированная память устанавливается по-разному в двух системах), или что некоторые дополнения структуры данных могут что-то менять.Если это более сложно, то это не что-то вроде ошибки API-функции и вы не проверяете ее, или какое-то предупреждение о том, что один или оба компилятора выдают вам, что вы игнорируете, тогда, вероятно, будет очень трудно найти.

Вы должны убедиться, что вы выключили все оптимизации, если только проблема не проявляется только при включенных оптимизациях.

Что-то, что, вероятно, вам сильно поможет, это попытка модульного тестированиякусочки программы на каждой машине.Гораздо легче обнаружить и понять различия в поведении небольших проблем, чем больших.

1 голос
/ 31 октября 2010

Valgrind может отследить все хранилища автоматически, хотя стандартные инструменты не всегда обеспечивают явную трассировку. Вы должны написать свой собственный инструмент valgrind, например, изменив cachegrind, чтобы отследить все инструкции Ist_Store.

Увы, valgrind не работает в Windows.

0 голосов
/ 02 ноября 2010

Во-первых, я предполагаю, что вы фактически используете один и тот же код в обоих случаях. В противном случае, начните искать, где код отличается.

Если ваша программа дает разные результаты на разных архитектурах, то есть вероятность, что вы не справляетесь с некоторыми вещами правильно. Первое, что я хотел бы сделать, - включить все возможные предупреждения компилятора и обратить на них внимание.

Если это ничего не даст, я бы попробовал инструмент статического анализа кода. Я использовал coverity (коммерческий продукт), но есть и другие . Эти инструменты иногда помогут вам найти ошибки в ваших программах, которые компиляторы не обнаружат.

Когда эти автоматические опции были исчерпаны, я мог бы попытаться сравнить все переменные во всей программе. В зависимости от размера вашей программы это может занять много времени.

0 голосов
/ 02 ноября 2010

Если вам известны строки, в которых изменяются переменные, которые вас интересуют, то вы можете использовать команду точки останова для простой трассировки:

Пример:

#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
0 голосов
/ 31 октября 2010

Я думаю, что программа ctrace может быть тем, что вы ищете; он был доступен в AT & T Unix (в те времена, когда AT & T владел Unix), но URL-адрес находится на странице руководства Sun Следовательно, вы можете найти его в других проприетарных версиях Unix (AIX, HP-UX, SCO); не понятно, что есть версия для Linux.

Библиотека CTrace в SourceForge - это совсем не одно и то же.

0 голосов
/ 30 октября 2010

Вы можете указать gcc вызовы функций прибора: -finstrument-functions. Это не поможет вам детализировать назначение, но закроется, если вы упакуете некоторые элементарные функции в inline функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...