Как работает обратная отладка?

82

GDB выпустила новую версию, которая поддерживает обратную отладку (см. Http://www.gnu.org/software/gdb/news/reversible.html ). Мне стало интересно, как это работает.

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

Натан Феллман
источник
4
Я полагаю, вы могли бы обойтись хранением дельт состояний, а не всего состояния, но все же кажется, что это может быть дорого.
Спендер
1
связанный вопрос stackoverflow.com/questions/522619/…
Брайан Расмуссен,
Сохранение дельт действительно может работать очень хорошо и действительно необходимо для эффективного полнофункционального обратимого решения.
jakobengblom2,

Ответы:

131

Я сопровождаю GDB и являюсь одним из авторов новой обратной отладки. Буду рад рассказать, как это работает. Как предполагали некоторые люди, вам нужно сохранить достаточно состояния машины, чтобы вы могли восстановить его позже. Существует ряд схем, одна из которых заключается в простом сохранении регистров или ячеек памяти, которые изменяются каждой машинной командой. Затем, чтобы «отменить» эту инструкцию, вы просто возвращаете данные в эти регистры или ячейки памяти.

Да, это дорого, но современные процессоры настолько быстры, что, когда вы все равно интерактивны (выполняете пошаговые инструкции или точки останова), вы на самом деле этого не замечаете.

Майкл Снайдер
источник
4
Но разрешает ли обратная отладка только откат nextи stepкоманды, которые вы ввели, или позволяет отменить любое количество инструкций? Например, если я установлю точку останова для инструкции и позволю ей работать до тех пор, могу ли я затем вернуться к предыдущей инструкции, даже если я пропустил ее?
Натан Феллман
10
> Но разве обратная отладка позволяет вам откатить только набранные вами команды next и step или позволяет отменить любое количество инструкций. Вы можете отменить любое количество инструкций. Вы не ограничены, например, остановкой только в тех точках, где вы остановились, когда двигались вперед. Вы можете установить новую точку останова и вернуться к ней> Например, если я установил точку останова для инструкции и позволю ей работать до тех пор, могу ли я затем вернуться к предыдущей инструкции, даже если я пропустил ее Да Пока вы включил режим записи перед тем, как подойти к
Майкл Снайдер
3
Извините за неформатированный текст, не знаю, что с этим делать.
Майкл Снайдер,
10
Меня беспокоит, что обратная отладка может отменить время и вернуть нас в 60-е или 70-е годы. Я не хочу носить брюки-клеш и снова отращивать длинные волосы.
Железный Человек
3
А системные вызовы, изменяющие состояние в ОС? Это просто не работает должным образом? Что насчет того, когда он изменяет непрозрачную ручку?
Адриан
12

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

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

Другой способ - использовать метод контрольной точки + повторное выполнение, который используется в VmWare Workstation 6.5 и Virtutech Simics 3.0 (и более поздних версиях) и который, похоже, будет поставляться с Visual Studio 2010. Здесь вы используете виртуальную машину или симулятор чтобы получить уровень косвенного доступа к выполнению системы. Вы регулярно выгружаете все состояние на диск или в память, а затем полагаетесь на способность симулятора детерминированно повторно выполнить тот же самый путь к программе.

Проще говоря, это работает так: скажем, что вы находитесь в момент времени T в исполнении системы. Чтобы перейти к моменту T-1, вы выбираете некоторую контрольную точку из точки t <T, а затем выполняете (Tt-1) циклы, чтобы закончить на один цикл раньше, чем вы были. Это можно заставить работать очень хорошо и применяться даже к рабочим нагрузкам, которые выполняют дисковый ввод-вывод, состоят из кода уровня ядра и выполняют работу с драйверами устройства. Ключ в том, чтобы иметь симулятор, который содержит всю целевую систему со всеми ее процессорами, устройствами, памятью и вводом-выводом. См. Список рассылки gdb и обсуждение после этого в списке рассылки gdb для получения более подробной информации. Я сам довольно регулярно использую этот подход для отладки сложного кода, особенно в драйверах устройств и при ранней загрузке ОС.

Еще один источник информации - это технический документ Virtutech по контрольным точкам (который я написал с полным раскрытием).

jakobengblom2
источник
Также см. Jakob.engbloms.se/archives/1547 и два следующих сообщения в блоге для более подробного ознакомления с методами обратной отладки.
jakobengblom2
Как насчет возможности «установки точек сохранения» вместо реализации обратного шага. Итак, вы отлаживаете и в какой-то момент можете выбрать текущий шаг как «точку сохранения», а позже у вас есть возможность вернуться к этой точке сохранения и снова сделать шаг вперед, при необходимости отредактировав переменные. Что-то вроде «снимков» для ВМ или «точек восстановления» для ОС.
Rolf
9

Во время сеанса EclipseCon мы также спросили, как они это делают с помощью Chronon Debugger для Java. Это не позволяет вам фактически отступить, но может воспроизвести выполнение записанной программы таким образом, что это будет похоже на обратную отладку. (Основное отличие состоит в том, что вы не можете изменить запущенную программу в отладчике Chronon, в то время как вы можете сделать это в большинстве других отладчиков Java.)

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

Затем во время воспроизведения они могут воссоздать каждое состояние работающей программы из записанных изменений состояния.

Что интересно, изменения состояния намного меньше, чем можно было бы ожидать на первый взгляд. Итак, если у вас есть условный оператор if, вы можете подумать, что вам нужен хотя бы один бит для записи того, использовала ли программа оператор then или else. Во многих случаях вы можете избежать даже этого, например, в случае, когда эти разные ветки содержат возвращаемое значение. Тогда достаточно записать только возвращаемое значение (которое все равно понадобится) и пересчитать решение о выполненной ветке из самого возвращаемого значения.

Bananeweizen
источник
8

Хотя этот вопрос старый, большинство ответов тоже, и как остается интересной темой, я отправляю ответ 2015 года. В главах 1 и 2 моей магистерской диссертации « Объединение обратной отладки и программирования в реальном времени в направлении визуального мышления в компьютерном программировании» рассматриваются некоторые исторические подходы к обратной отладке (особенно сфокусированные на подходе с использованием снимков (или контрольных точек) и воспроизведения) и объясняет разницу между этим и всеведущей отладкой:

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

Программная всезнающая отладка началась с системы EXDAMS 1969 года, где она называлась «воспроизведение истории во время отладки». Отладчик GNU, GDB, поддерживает всестороннюю отладку с 2009 года с функцией «записи и воспроизведения процесса». TotalView, UndoDB и Chronon кажутся лучшими всеведущими отладчиками, доступными в настоящее время, но являются коммерческими системами. TOD для Java, по-видимому, является лучшей альтернативой с открытым исходным кодом, которая использует частичное детерминированное воспроизведение, а также частичный захват трассировки и распределенную базу данных, позволяющую записывать большие объемы информации.

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

Первой такой системой был прототип COPE 1981 года ...

Авраам
источник
4

mozilla rr- более надежная альтернатива обратной отладке GDB

https://github.com/mozilla/rr

Встроенная запись и воспроизведение GDB имеет серьезные ограничения, например, отсутствие поддержки инструкций AVX: обратная отладка gdb завершается неудачно с "Запись процесса не поддерживает инструкцию 0xf0d по адресу"

Плюсы р-р:

  • намного надежнее в настоящее время. Я тестировал его на относительно длительных прогонах нескольких сложных программ.
  • также предлагает интерфейс GDB с протоколом gdbserver, что делает его отличной заменой
  • небольшое падение производительности для большинства программ, сам не заметил без замеров
  • сгенерированные трассировки имеют небольшой размер на диске, потому что записывается очень мало недетерминированных событий, мне никогда не приходилось беспокоиться об их размере до сих пор

rr достигает этого, сначала запуская программу таким образом, чтобы записывать, что произошло при каждом недетерминированном событии, таком как переключение потока.

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

rr был первоначально разработан Mozilla, чтобы помочь им воспроизвести ошибки синхронизации, которые обнаружились во время их ночного тестирования на следующий день. Но аспект обратной отладки также важен, когда у вас есть ошибка, которая возникает только в течение нескольких часов после выполнения, поскольку вам часто нужно отступить, чтобы проверить, какое предыдущее состояние привело к последующему сбою.

Следующий пример демонстрирует некоторые из его особенностей, в частности reverse-next, reverse-stepи reverse-continueкоманду.

Установите на Ubuntu 18.04:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
# Overcome "rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is 3."
echo 'kernel.perf_event_paranoid=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Программа испытаний:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int f() {
    int i;
    i = 0;
    i = 1;
    i = 2;
    return i;
}

int main(void) {
    int i;

    i = 0;
    i = 1;
    i = 2;

    /* Local call. */
    f();

    printf("i = %d\n", i);

    /* Is randomness completely removed?
     * Recently fixed: https://github.com/mozilla/rr/issues/2088 */
    i = time(NULL);
    printf("time(NULL) = %d\n", i);

    return EXIT_SUCCESS;
}

скомпилировать и запустить:

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay

Теперь вы остались внутри сеанса GDB, и вы можете правильно отменить отладку:

(rr) break main
Breakpoint 1 at 0x55da250e96b0: file a.c, line 16.
(rr) continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;
(rr) next
17          i = 1;
(rr) print i
$1 = 0
(rr) next
18          i = 2;
(rr) print i
$2 = 1
(rr) reverse-next
17          i = 1;
(rr) print i
$3 = 0
(rr) next
18          i = 2;
(rr) print i
$4 = 1
(rr) next
21          f();
(rr) step
f () at a.c:7
7           i = 0;
(rr) reverse-step
main () at a.c:21
21          f();
(rr) next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) reverse-next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$5 = 1509245372
(rr) reverse-next
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$6 = 1509245372
(rr) reverse-continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;

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

reverse-finish

до этого кадра просто делать обычные upвещи недостаточно.

На мой взгляд, наиболее серьезные ограничения rr:

  • https://github.com/mozilla/rr/issues/2089, вам нужно сделать второй повтор с нуля, что может быть дорогостоящим, если сбой, который вы пытаетесь отладить, произойдет, скажем, через несколько часов после выполнения
  • https://github.com/mozilla/rr/issues/1373 только x86

UndoDB - коммерческая альтернатива rr: https://undo.io. Оба они основаны на трассировке / воспроизведении, но я не уверен, как они сравниваются с точки зрения функций и производительности.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
Вы знаете, как я могу это сделать с помощью ddd? Спасибо
spraff
@spraff Я не уверен, но скорее всего. Сначала попробуйте подключить ddd к gdbserver. Если это сработает, оно также должно работать с rr.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
@spraff, однако, не используйте ddd, используйте панель управления gdb ;-) stackoverflow.com/questions/10115540/gdb-split-view-with-code/… Это определенно сработает, поскольку это обычный GDB.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
3

Натан Феллман писал:

Но позволяет ли обратная отладка только откатить набранные вами команды next и step или отменить любое количество инструкций?

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

Например, если я установлю точку останова для инструкции и позволю ей работать до тех пор, могу ли я затем вернуться к предыдущей инструкции, даже если я пропустил ее?

Да. Пока вы включили режим записи до того, как дойдете до точки останова.

Майкл Снайдер
источник
2
Важнейшей частью любого обратного решения является то, что вы включаете его в какой-то момент, а до этого момента можно только повернуть вспять. Нет никакой магии, которая могла бы запустить машину в обратном направлении и выяснить, что произошло раньше, без какой-либо записи того, что произошло.
jakobengblom2
2

Вот как работает другой обратный отладчик под названием ODB. Извлечь:

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

ODB ... вставляет код в классы программы по мере их загрузки, и когда программа запускается, события записываются.

Я предполагаю, что GDB работает таким же образом.

демонкодобезьяна
источник
Итак, потребуются ли для этого директивы в коде, чтобы сообщить компилятору и отладчику, где находятся эти интересные моменты?
Натан Феллман,
Нет. На сайте www.LambdaCS.com/debugger/debugger.html есть демонстрация Java Web Start, в которой показано, как это работает. Похоже на нормальную прогу. В любом случае это ODB, не знаю о gdb. Хотя это очень круто :)
demoncodemonkey
Обратите внимание, что решение gdb НИКОГДА НЕ меняет целевую программу. Если вам нужно настроить программу для ее отладки, у вас есть хорошие шансы на то, что проблема исчезнет из-за разницы во времени и других помех. Все коммерческие инструменты Revexec основаны на какой-либо форме внешней записи, которая не меняет код самой программы.
jakobengblom2
@ jakobengblom2: Я думаю, вы слишком много внимания уделяете разнице между изменением цели путем записи в ее память, имитацией выполнения или простым добавлением аппаратных точек останова. Все они меняют время. Фактически, целевая аппаратура, вероятно, меньше всего меняет время.
Бен Фойгт
2

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

Вам не нужно сохранять полное состояние машины для каждого шага, только изменения. Вероятно, это все еще довольно дорого.

звездно-голубой
источник
Понятно, но вам все равно нужно прерывать выполнение при каждом изменении, чтобы сохранить изменения.
Натан Феллман,
Да, это правильно, но машины сейчас довольно быстрые, и с человеческой точки зрения я не считаю, что замедление является невыносимым. Это сравнимо с valgrind, возможно, не таким медленным, как valgrind.
Майкл Снайдер