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

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

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

8 ответов

Решение

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

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

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

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

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

Упрощенно, это работает так: скажем, что вы находитесь в момент времени T при выполнении системы. Чтобы перейти ко времени T-1, вы берете некоторую контрольную точку из точки t GDB и последующее обсуждение в списке рассылки GDB для получения более подробной информации. Я использую этот подход довольно регулярно для отладки сложного кода, особенно в драйверах устройств и ранних загрузках ОС.

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

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

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

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

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

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

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

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

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

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

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

https://github.com/mozilla/rr

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

Перевернутая сторона рр:

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

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

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

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance

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

Тестовая программа:

#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;

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

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

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

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

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

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

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

Вот как работает другой обратный отладчик ODB. Выдержка:

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

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

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

Другие вопросы по тегам