Автоматически отслеживать изменения переменных
Я отлаживаю программу на 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 ответов
Я бы написал сценарий для автопилота отладчика. Я не знаю, доступен ли expect в windows (возможно, это так), но это отличный инструмент для написания скриптов, которые Autopilot interactive tools. Сценарий будет идти что-то вроде:
"Program exited normally" {
exit;
}
}
}
Я бы изменил "main" на функцию, где вы думаете, что программа идет не так. Вы также можете вставить любые другие команды отладчика, такие как печать строки, на которой вы находитесь перед печатью переменные; здесь я использую "info locals", который выводит все локальные значения. Очевидно, что вам нужно будет сохранить этот вывод в файл для анализа. Ожидать довольно легко учиться, и синтаксис основан на tcl.
в неявном решении необходимо учитывать несколько факторов:
- C едва осознает тип. Например, если вы использовали предложение @Jens Gustedt о инструментировании функций, у вас все еще есть проблема определения того, что именно вы смотрите на стек, и как правильно распечатать значения. IIRC, инструментальные функции не дадут вам прототип предстоящей функции, а также не предоставят удобную структуру с указателями на эти значения. Это будьте более сродни шаблону C++. Вам нужно будет написать функцию ввода/вывода, которая знала бы о прототипах всех функций и имела бы внутреннее знание точного соглашения о вызове и упаковочных механизмов для ваших переменных.
- вам нужно скомпилировать с отладочными символами, чтобы определить, какая память была связана с переменной, определенной в вашем коде, и когда эти переменные изменяются.
- более сложные типы не имеют стандартного способа быть печатается (например, структуры), даже на C++. Вам нужно будет иметь функции печати, которые соответствуют какому-то интерфейсу.
- указатели и массивы неопределенной длины будет очень трудно справиться. Вам нужно будет проверить нулевые указатели и заранее знать размеры массива, чтобы правильно напечатать их значения.
Я попытался что-то похожее на ваш "printf при вводе функции" в существующий проект я работаю над. Это полезно для важные функции, но я получил гораздо больше из значения утверждения находятся в ожидаемых диапазонах при вводе функции и выход. Вот журналзаголовок и источник, которые демонстрируют как значительно упростить печатание линий, функций и другой полезной информации вокруг вашего ручного трассировки. Вы заметите, что многие из моих типов имеют соответствующие *_valid()
функция и утверждения, окружающие их использование. Как только моя программа запускается с треков (что, я уверяю вас, довольно часто во время отладки), утверждения будут срабатывать, и я могу проанализировать ситуацию через трассировку стека.
Вы можете найти ответ полезно как в отношении сложности выполнения этого материала только с C, так и в отношении того, как обойти его с помощью макросов.
ваш лучший выбор, если вам нужно это сделать неявно, через GDB, предположительно, вы можете писать скрипты для анализа изменений после каждой инструкции и разумно определить и распечатать типы, используя отладочную информацию, предоставленную -g
.
отчет можно отслеживать все магазины автоматически, хотя стандартные инструменты не легко обеспечить явную трассировку. Вам нужно будет написать свой собственный инструмент valgrind, например, изменив cachegrind, чтобы проследить все инструкции Ist_Store.
увы, valgrind не работает на Windows.
отладчики gdb и Visual Studio поддерживают посмотреть-ских переменных. Это похоже на точку останова, но вместо места в коде ваша программа ломается при изменении значения переменной. Вы можете обнаружить, что они не ведут себя точно так же, если значение изменяется на точно такое же значение, которое оно уже имеет (не уверен, есть ли какая-либо разница, но может быть).
другое место, где вы можете найти различия в поведении двух отладчиков если вы смотрите локальные переменные. Один отладчик может забыть любые переменные наблюдения в функциях, которые возвращаются, в то время как другой может перематывать их каждый раз, в то время как другой может просто наблюдать за фактическим местоположением памяти, не заботясь о том, что эта память была повторно предназначена. Я не уверен, как эти отладчики работают в этом отношении, так как я не привык смотреть локальные переменные (я не уверен, что когда-либо смотрел локальные переменные).
Что вы, вероятно, захотите сделать, это установить точка останова, в которой инициализируются эти переменные, проходит инициализацию, а затем устанавливает наблюдение за переменными и запускает программу. Вы можете записать изменения (вручную или {по крайней мере, с помощью GDB}) и посмотреть, когда две программы расходятся в поведении.
вы, вероятно, обнаружите, что либо размер какого-либо типа отличается от другого, вы используете неинициализированную память (и неинициализированная память устанавливается по-разному на двух системы), или что некоторое заполнение структуры данных может что-то изменить. Если это сложнее, то это не что-то вроде ошибки функции API, и вы не проверяете ее, или какое-то предупреждение, которое один или оба компилятора дают вам, что вы игнорируете, тогда, вероятно, будет очень трудно найти.
вы должны убедиться, что вы отключите все оптимизации, если проблема не возникает только с включенными оптимизациями.
то, что будет вероятно, помочь вам много было бы попробовать модульное тестирование частей программы на каждой машине. Гораздо легче обнаружить и понять различия в поведении для небольших проблем, чем для больших проблем.
очевидный вопрос заключается в том, является ли ваша программа (и все библиотеки, которые она вызывает) действительно независимой от архитектуры или даже детерминированной (у вас может быть нестабильная сортировка, например, или даже потоки где-то), или если у вас просто есть неинициализированная переменная где-то.
предполагая, что вы считаете, что ваша программа полностью детерминирована, и вам действительно нужна трассировка данных по каждому назначению, способ выполнить это с помощью программы трансформации система. Такой инструмент принимает шаблоны "если вы видите это, замените его на то", используя синтаксис поверхности вашего целевого языка (в данном случае 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 ->replacebythis если somecondition. Кавычки действительно являются метаквотами и позволяют встроить c-синтаксис в правило langauge. \ - Это побег из синтаксиса C вернемся к правилу langauge.
эти правила работает с абстрактными деревьями синтаксиса, сгенерированными DMS в результате синтаксического анализа (предварительно обработанного) исходного кода C. Это конкретное правило точно соответствует исходному коду для точного синтаксиса lhs = * e * для всех юридических форм lhs и e. Когда он находит такое совпадение, он заменяет назначение вызовом функции это делает назначение, а также распечатывает значение трассировки.
функция \tostring принимает дерево lhs и генерирует исходный текст, соответствующий оригиналу выражение; это легко выполняется с помощью API DMS для prettyprinting ASTs. The int_type функция опрашивает таблицу символов, сгенерированную DMS, чтобы определить, является ли тип lhs int.
вам понадобится одно правило для каждого типа данных, который вы хотите распечатать. Вам также нужно правило для каждого вида синтаксиса присваивания (e.g, "=", "+=", "%="...) ваша программа использует. Итак, основные типы данных и несколько типов синтаксиса присваивания предполагают, что вам нужно 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*; но вы можете распечатать то, что вы знаете о переменной void*, например, IS IS NULL или нет, и это все равно будет полезно для данных трассировки.
все это может стоить хлопот, если вы это сделали вроде отладки много. ИМХО, вам, вероятно, лучше просто укусить пулю и отладить свой путь к решению, поскольку я ожидаю, что вы будете удивлены некоторой зависимостью от архитектуры.
Если вы хотите кросс-платформенный, printf ваш друг. Небольшая работа с grep найдет, где переменные назначаются. Кроме того, я думаю, что ваши усилия лучше всего потратить на то, чтобы выяснить, как сократить цикл редактирования/компиляции*2/run_tests*2/diff и подумать, где делать двоичные расщепления.
Я в то время, у меня была более или менее та же проблема для решения, но с дополнительной сложностью наличия неполного переводчика языка в середине. Бытие возможность запуска как версии, так и diff вывода быстро сделала его очень разумной проблемой.
вы можете сказать gdb, чтобы сломаться, когда какая-либо инструкция изменяет вашу переменную. IIRC команда "смотреть". Например, "watch A" или " watch *(int*)0x123456'
.
и вы даже можете сказать, что он сломается, когда кто-то читает это, с 'rwatch'.
Вы можете сказать gcc к вызовам функции аппаратуры:-finstrument-functions
. Это не дает вам гранулярности назначения, но близко, если вы упакуете некоторые элементарные функции в inline
функции.
Я думаю, что программа ctrace может быть то, что вам нужно; он был доступен в AT&T Unix (еще в те дни, когда AT&T владел Unix), но URL-адрес на странице руководства Sun. Вероятно, вы можете найти его, поэтому, на других проприетарных версиях Unix (AIX, HP-UX, SCO); не ясно, что есть версия для Linux.
библиотека CTrace в SourceForge-это совсем не одно и то же.
если вы знаете строки, где переменные изменяются, которые вас интересуют, то вы можете использовать команду точки останова, чтобы сделать простую трассировку:
пример:
#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;
= 0
0
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 1
1
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 2
2
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 3
3
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 4
4
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 5
5
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 6
6
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 7
7
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 8
8
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 9
9
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 10
10
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 11
11
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 12
12
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 13
13
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 14
14
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 15
15
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 16
16
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 17
17
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 18
18
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 19
19
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 20
20
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 21
21
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 22
22
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 23
23
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 24
24
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 25
25
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 26
26
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 27
27
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 28
28
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 29
29
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 30
30
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 31
31
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 32
32
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 33
33
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 34
34
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 35
35
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 36
36
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 37
37
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 38
38
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 39
39
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 40
40
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 41
41
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 42
42
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 43
43
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 44
44
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 45
45
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 46
46
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 47
47
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 48
48
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 49
49
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 50
50
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 51
51
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 52
52
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 53
53
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 54
54
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 55
55
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 56
56
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 57
57
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 58
58
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 59
59
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 60
60
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 61
61
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 62
62
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 63
63
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 64
64
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 65
65
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 66
66
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 67
67
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 68
68
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 69
69
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 70
70
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 71
71
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 72
72
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 73
73
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 74
74
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 75
75
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 76
76
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 77
77
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 78
78
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 79
79
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 80
80
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 81
81
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 82
82
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 83
83
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 84
84
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 85
85
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 86
86
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 87
87
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 88
88
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 89
89
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 90
90
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 91
91
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 92
92
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 93
93
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 94
94
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 95
95
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 96
96
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 97
97
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
= 98
98
Breakpoint 1, main () at t1.cpp:7
7 std::cout << i << std::endl;
0 = 99
99
Program exited normally.
(gdb) q
во-первых, я предполагаю, что вы фактически используете один и тот же код в обоих случаях. В противном случае начните искать, где код отличается.
Если ваша программа дает разные результаты на разных архитектурах, то есть вероятность, что вы не справляетесь с некоторыми вещами правильно. Первое, что я бы сделал, это включить все возможные предупреждения компилятора и обратить на них внимание.
Если это ничего не дает, я бы попробовал инструмент статического анализа кода. Я использовал coverity (коммерческий продукт), но есть и другие. Эти инструменты иногда помогут вам найти ошибки в ваших программах, которые компиляторы не обнаруживают.
когда эти автоматические параметры были исчерпаны, я мог бы попробовать сравнить все переменные во всей программе. В зависимости от размера вашей программы это может занять много времени.