Обратная отладка
Довгалюк П. М.
Отладка
Отладка
 Известно, что отладка в два раза сложнее написания
программы. Поэтому если вы были предельно
хитроумны при написании программы, то что же вы
будете делать при ее отладке?
 Брайан Керниган, Элементы стиля программирования
 35-75% времени программиста уходит на отладку
Почему отладка сложна?
 Недетерминировнные баги
 Отладчик влияет на работу программы
 Отладчик работает только в одном направлении
 Не всегда удается передать сценарий
воспроизведения ошибки от тестировщика к
разработчику
Гейзенбаги
 Для отладки программа запускается многократно
 Ошибки в одних и тех же сценариях проявляются не
всегда
 Многопоточность
 Случайные числа
 Изменчивое окружение
 Неинициализированные переменные
Недетерминированные ошибки
void do_nothing(int &a)
{
if (rand() == 42)
++a; // happy debugging
}
Еще более редко проявляющиеся ошибки
DWORD WINAPI printThread(LPVOID lpParam)
{
while (TRUE)
if (var % 10000 == 0)
do_some_work(var);
return 0;
}
DWORD WINAPI incThread(LPVOID lpParam)
{
while (TRUE)
++var;
return 0;
}
Попробуем отладить с помощью printf
DWORD WINAPI printThread(LPVOID lpParam)
{
while (TRUE)
if (var % 10000 == 0) {
printf("%dn", var);
do_some_work(var);
}
return 0;
}
DWORD WINAPI incThread(LPVOID lpParam)
{
while (TRUE)
++var;
return 0;
}
Попробуем отладить с помощью printf
DWORD WINAPI printThread(LPVOID lpParam)
{
while (TRUE)
if (var % 10000 == 0) {
do_some_work(var);
printf("%dn", var);
}
return 0;
}
DWORD WINAPI incThread(LPVOID lpParam)
{
while (TRUE)
++var;
return 0;
}
Испорченные переменные
#include <stdio.h>
void f(int *a) {
int i;
for (i = -1 ; i < 6 ; ++i)
a[i] = i;
}
int z = 1016;
int main() {
int a[5];
int *p = &z;
printf("%dn", *p);
f(a);
printf("%dn", *p);
}
Отладка сетевых протоколов
 Приложение чувствительно ко времени
 Удаленный сервер не знает, что идет отладка
 Остановка программы приведет к таймауту
соединения
Обратная отладка
 Попытка «вернуться в прошлое» и
узнать что произошло
Отладка с помощью трассировки
 Нужно вставить много вызовов printf
 Не всегда удобно обращаться к переменной (адрес
неизвестен)
 Замедляет работу
 Может изменить результат работы
 Нужно знать заранее что хочешь посмотреть
 Требуется перекомпиляция
 Требуется повторный запуск
Обратная отладка с помощью трассы
инструкций
 Для каждой инструкции сохранять изменившиеся
ячейки
 Можно перейти на любое расстояние вперед и назад
перезаписывая и восстанавливая сохраненные
данные
 Работает медленно
 Сохраняет очень много данных
 Несколько килобайт на 1000 инструкций
Можно ли сохранять меньше данных?
int a, s = 0;
scanf(“%d”, &a);
for (int i = 0 ; i < a ; ++i)
s += i;
printf(“%d”, s);
Запись и воспроизведение
Обратная отладка с помощью воспроизведения
 Ход выполнения программы в основном
детерминирован
 Можно сохранять только входные данные,
информацию от планировщика потоков, прерывания
 Сохраняется мало данных
 Несколько байт на 1000 инструкций
 Повторные выполнения всегда одинаковы
 Быстро вернуться на произвольное расстояние назад
нельзя
Отладка с воспроизведением
 Сначала записываем работу программы
 Для отладки работа воспроизводится
 Можно «перемещаться во времени»
Отладчик
Отладчик
Результаты
анализа
Фаза записи Фаза воспроизведения
Реальный
мир
Пошаговое выполнение
Пошаговое выполнение
Пошаговое выполнение
Пошаговое выполнение
Обратная отладка
23
int *p = malloc(sizeof(int));
………….
p = NULL;
………….
int a = *p;
Обратная отладка
24
int *p = malloc(sizeof(int));
………….
p = NULL;
………….
int a = *p;
gdb> watch p
gdb> reverse-continue
Обратная отладка
25
int *p = malloc(sizeof(int));
………….
p = NULL;
………….
int a = *p;
gdb> watch p
gdb> reverse-continue
1234
Команды обратной отладки
 step
 next
 finish
 continue
 reverse step
 reverse next
 reverse finish
 reverse continue
Запись и воспроизведение
 Всё нужное для воспроизведения ошибки
записывается
 Обычно нельзя изменить ход выполнения программы
во время воспроизведения
 Записывать сценарий нужно начать заранее
 Пошаговое перемещение назад работает медленно
Обратные отладчики native приложений
 Отдельные приложения
 GDB
 Mozilla RR
 TotalView
 UndoDB
 Виртуальные машины
 QEMU
 Wind River Simics
gdb
 Версия 7+
 Встроенная поддержка i386-linux, amd64-linux
 Буфер на 200000 инструкций
 set record insn-number-max
 Удаленная отладка (Simics, UndoDB и другие)
gdb
 break
 run
 target record-full
 continue
 reverse-continue
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;
trace_sched_process_fork(current, p);
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);
/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}
put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}
gdb
 Повторные запуски программы могут размещаться по
новым адресам
 checkpoint – сохраняет снимок программы
 restore id – восстанавливает указанный снимок
 Каждый снимок представлен отдельным процессом в
памяти
 Позволяет исследовать разные ветки выполнения
gdb
 Не отображает вывод на экран/консоль
 Поддерживает только x86 под Linux
 Длина буфера инструкций ограничена
 Медленно работает
 gzip замедляется в 50000 раз
Mozilla RR
 Используется для отладки Firefox
 Записывает меньше данных, чем GDB
 Сохраняются результаты выполнения системных вызовов,
переключение потоков и т.п.
 Работает относительно быстро
 20-40% overhead при записи обычных приложений
 В худшем случае ~300%
Mozilla RR
 Записывает недетерминированные входы
 clock_gettime(...&now);
 read(fd, buf, 4096);
 __asm__("rdtsc")
 ioctl(...)
 UNIX signals...
 Во время воспроизведения эмулирует эти вызовы
 Не записывает процессы и потоки параллельно
 Сериализует многопоточные приложения, чтобы всё
записалось корректно
 Каждому потоку по очереди выделяется интервал
времени для работы
Mozilla RR
 Автоматически запускает GDB при воспроизведении
 Поддерживает команды обратной отладки
 reverse-step
 reverse-next
 reverse-finish
 reverse-continue
 Поддерживает GDB checkpoint
Mozilla RR
 По умолчанию сохраняет сценарий в файл
 Поддерживает запись многопоточных приложений
 Работает не на всех процессорах из-за использования
аппаратных счетчиков
 Поддержки ARM в ближайшее время не будет
 Поддерживаются не все системные вызовы
 Не воспроизводит графический интерфейс
QEMU
 Эмулятор с открытым исходным кодом
 Эмулирует x86,ARM, MIPS, PowerPC и др.
Отладка драйверов и операционных систем
38
 Падение системы
 Замедление работы
 Изменение поведения из-за отладочных методов
Отладка ОС через gdbserver в симуляторе
39
 Не нужно сохранять дампы, чтобы их анализировать
 Можно подключиться в любой момент
 Даже при критическом сбое
 Даже до загрузки ОС
 Отладка без реального оборудования
 Отладка прошивки
 Отладка моделей оборудования
 Работает медленнее из-за виртуализации
 Ход работы может измениться из-за остановок
Детерминированное воспроизведение и
обратная отладка
40
 Должна работать на всех платформах QEMU
 Протестирована для x86, x64,ARM, MIPS
 Отладка и анализ всей системы
 Ядро и BIOS
 Виртуальные устройства
 Во время воспроизведения можно переключиться на
обычную работу
 Работает медленнее из-за виртуализации
QEMU
41
 Опубликованы все патчи для обратной отладки
 около 6000 LOC
 В QEMU 2.5 включено ядро воспроизведения
 Без блочных устройств, gdb, сети, USB
 Патчи для дисковых устройств в работе
Почему же люди не используют обратную
отладку? © Stackexchange, 2013
 В GDB появилась в 2009 году
 Historical debugging в MSVS
 Нужен специальный отладчик
 Работает медленно (в GDB)
 Люди думают, что она не работает
Обратная отладка
 Использование обратной отладки на 26% снижает
время, затрачиваемое на поиск ошибок
 Cambridge’s Judge Business School
 Бесплатный отладчик Mozilla RR
 Бесплатный отладчик GDB

Павел Довгалюк, Обратная отладка

  • 1.
  • 2.
  • 3.
    Отладка  Известно, чтоотладка в два раза сложнее написания программы. Поэтому если вы были предельно хитроумны при написании программы, то что же вы будете делать при ее отладке?  Брайан Керниган, Элементы стиля программирования  35-75% времени программиста уходит на отладку
  • 4.
    Почему отладка сложна? Недетерминировнные баги  Отладчик влияет на работу программы  Отладчик работает только в одном направлении  Не всегда удается передать сценарий воспроизведения ошибки от тестировщика к разработчику
  • 5.
    Гейзенбаги  Для отладкипрограмма запускается многократно  Ошибки в одних и тех же сценариях проявляются не всегда  Многопоточность  Случайные числа  Изменчивое окружение  Неинициализированные переменные
  • 6.
    Недетерминированные ошибки void do_nothing(int&a) { if (rand() == 42) ++a; // happy debugging }
  • 7.
    Еще более редкопроявляющиеся ошибки DWORD WINAPI printThread(LPVOID lpParam) { while (TRUE) if (var % 10000 == 0) do_some_work(var); return 0; } DWORD WINAPI incThread(LPVOID lpParam) { while (TRUE) ++var; return 0; }
  • 8.
    Попробуем отладить спомощью printf DWORD WINAPI printThread(LPVOID lpParam) { while (TRUE) if (var % 10000 == 0) { printf("%dn", var); do_some_work(var); } return 0; } DWORD WINAPI incThread(LPVOID lpParam) { while (TRUE) ++var; return 0; }
  • 9.
    Попробуем отладить спомощью printf DWORD WINAPI printThread(LPVOID lpParam) { while (TRUE) if (var % 10000 == 0) { do_some_work(var); printf("%dn", var); } return 0; } DWORD WINAPI incThread(LPVOID lpParam) { while (TRUE) ++var; return 0; }
  • 10.
    Испорченные переменные #include <stdio.h> voidf(int *a) { int i; for (i = -1 ; i < 6 ; ++i) a[i] = i; } int z = 1016; int main() { int a[5]; int *p = &z; printf("%dn", *p); f(a); printf("%dn", *p); }
  • 11.
    Отладка сетевых протоколов Приложение чувствительно ко времени  Удаленный сервер не знает, что идет отладка  Остановка программы приведет к таймауту соединения
  • 12.
    Обратная отладка  Попытка«вернуться в прошлое» и узнать что произошло
  • 13.
    Отладка с помощьютрассировки  Нужно вставить много вызовов printf  Не всегда удобно обращаться к переменной (адрес неизвестен)  Замедляет работу  Может изменить результат работы  Нужно знать заранее что хочешь посмотреть  Требуется перекомпиляция  Требуется повторный запуск
  • 14.
    Обратная отладка спомощью трассы инструкций  Для каждой инструкции сохранять изменившиеся ячейки  Можно перейти на любое расстояние вперед и назад перезаписывая и восстанавливая сохраненные данные  Работает медленно  Сохраняет очень много данных  Несколько килобайт на 1000 инструкций
  • 15.
    Можно ли сохранятьменьше данных? int a, s = 0; scanf(“%d”, &a); for (int i = 0 ; i < a ; ++i) s += i; printf(“%d”, s);
  • 16.
  • 17.
    Обратная отладка спомощью воспроизведения  Ход выполнения программы в основном детерминирован  Можно сохранять только входные данные, информацию от планировщика потоков, прерывания  Сохраняется мало данных  Несколько байт на 1000 инструкций  Повторные выполнения всегда одинаковы  Быстро вернуться на произвольное расстояние назад нельзя
  • 18.
    Отладка с воспроизведением Сначала записываем работу программы  Для отладки работа воспроизводится  Можно «перемещаться во времени» Отладчик Отладчик Результаты анализа Фаза записи Фаза воспроизведения Реальный мир
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
    Обратная отладка 23 int *p= malloc(sizeof(int)); …………. p = NULL; …………. int a = *p;
  • 24.
    Обратная отладка 24 int *p= malloc(sizeof(int)); …………. p = NULL; …………. int a = *p; gdb> watch p gdb> reverse-continue
  • 25.
    Обратная отладка 25 int *p= malloc(sizeof(int)); …………. p = NULL; …………. int a = *p; gdb> watch p gdb> reverse-continue 1234
  • 26.
    Команды обратной отладки step  next  finish  continue  reverse step  reverse next  reverse finish  reverse continue
  • 27.
    Запись и воспроизведение Всё нужное для воспроизведения ошибки записывается  Обычно нельзя изменить ход выполнения программы во время воспроизведения  Записывать сценарий нужно начать заранее  Пошаговое перемещение назад работает медленно
  • 28.
    Обратные отладчики nativeприложений  Отдельные приложения  GDB  Mozilla RR  TotalView  UndoDB  Виртуальные машины  QEMU  Wind River Simics
  • 29.
    gdb  Версия 7+ Встроенная поддержка i386-linux, amd64-linux  Буфер на 200000 инструкций  set record insn-number-max  Удаленная отладка (Simics, UndoDB и другие)
  • 30.
    gdb  break  run target record-full  continue  reverse-continue long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; /* * Determine whether and which event to report to ptracer. When * called from kernel_thread or CLONE_UNTRACED is explicitly * requested, no event is reported; otherwise, report if the event * for the type of forking is enabled. */ if (!(clone_flags & CLONE_UNTRACED)) { if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if ((clone_flags & CSIGNAL) != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace = 0; } p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace); /* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */ if (!IS_ERR(p)) { struct completion vfork; struct pid *pid; trace_sched_process_fork(current, p); pid = get_task_pid(p, PIDTYPE_PID); nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */ if (unlikely(trace)) ptrace_event_pid(trace, pid); if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid); } put_pid(pid); } else { nr = PTR_ERR(p); } return nr; }
  • 31.
    gdb  Повторные запускипрограммы могут размещаться по новым адресам  checkpoint – сохраняет снимок программы  restore id – восстанавливает указанный снимок  Каждый снимок представлен отдельным процессом в памяти  Позволяет исследовать разные ветки выполнения
  • 32.
    gdb  Не отображаетвывод на экран/консоль  Поддерживает только x86 под Linux  Длина буфера инструкций ограничена  Медленно работает  gzip замедляется в 50000 раз
  • 33.
    Mozilla RR  Используетсядля отладки Firefox  Записывает меньше данных, чем GDB  Сохраняются результаты выполнения системных вызовов, переключение потоков и т.п.  Работает относительно быстро  20-40% overhead при записи обычных приложений  В худшем случае ~300%
  • 34.
    Mozilla RR  Записываетнедетерминированные входы  clock_gettime(...&now);  read(fd, buf, 4096);  __asm__("rdtsc")  ioctl(...)  UNIX signals...  Во время воспроизведения эмулирует эти вызовы  Не записывает процессы и потоки параллельно  Сериализует многопоточные приложения, чтобы всё записалось корректно  Каждому потоку по очереди выделяется интервал времени для работы
  • 35.
    Mozilla RR  Автоматическизапускает GDB при воспроизведении  Поддерживает команды обратной отладки  reverse-step  reverse-next  reverse-finish  reverse-continue  Поддерживает GDB checkpoint
  • 36.
    Mozilla RR  Поумолчанию сохраняет сценарий в файл  Поддерживает запись многопоточных приложений  Работает не на всех процессорах из-за использования аппаратных счетчиков  Поддержки ARM в ближайшее время не будет  Поддерживаются не все системные вызовы  Не воспроизводит графический интерфейс
  • 37.
    QEMU  Эмулятор соткрытым исходным кодом  Эмулирует x86,ARM, MIPS, PowerPC и др.
  • 38.
    Отладка драйверов иоперационных систем 38  Падение системы  Замедление работы  Изменение поведения из-за отладочных методов
  • 39.
    Отладка ОС черезgdbserver в симуляторе 39  Не нужно сохранять дампы, чтобы их анализировать  Можно подключиться в любой момент  Даже при критическом сбое  Даже до загрузки ОС  Отладка без реального оборудования  Отладка прошивки  Отладка моделей оборудования  Работает медленнее из-за виртуализации  Ход работы может измениться из-за остановок
  • 40.
    Детерминированное воспроизведение и обратнаяотладка 40  Должна работать на всех платформах QEMU  Протестирована для x86, x64,ARM, MIPS  Отладка и анализ всей системы  Ядро и BIOS  Виртуальные устройства  Во время воспроизведения можно переключиться на обычную работу  Работает медленнее из-за виртуализации
  • 41.
    QEMU 41  Опубликованы всепатчи для обратной отладки  около 6000 LOC  В QEMU 2.5 включено ядро воспроизведения  Без блочных устройств, gdb, сети, USB  Патчи для дисковых устройств в работе
  • 42.
    Почему же людине используют обратную отладку? © Stackexchange, 2013  В GDB появилась в 2009 году  Historical debugging в MSVS  Нужен специальный отладчик  Работает медленно (в GDB)  Люди думают, что она не работает
  • 43.
    Обратная отладка  Использованиеобратной отладки на 26% снижает время, затрачиваемое на поиск ошибок  Cambridge’s Judge Business School  Бесплатный отладчик Mozilla RR  Бесплатный отладчик GDB