Обратная отладка с помощью детерминированного воспроизведения программ в виртуальной машине

ИНФОРМАТИКА И ПРИКЛАДНАЯ МАТЕМАТИКА

УДК 004.4'233

ОБРАТНАЯ ОТЛАДКА С ПОМОЩЬЮ ДЕТЕРМИНИРОВАННОГО ВОСПРОИЗВЕДЕНИЯ

ПРОГРАММ В ВИРТУАЛЬНОЙ МАШИНЕ

П.М.Довгалюк, В.А.Макаров

Институт электронных и информационных систем НовГУ, Pavel.Dovgaluk@ispras.ru

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

Ключевые слова: обратная отладка, виртуальная машина, детерминированное воспроизведение, динамический анализ программ

This paper contains description of the reverse debugging method developed by authors. The method is based on the use of determinated simulation of software in a virtual machine. Reverse debugging allows faster finding origins of defects in programs for x86 and ARM hardware platforms. This method was implemented in multi-platform simulator QEMU.

Keywords: reverse debugging, virtual machine, determinated simulation, dynamic analysis of software

1. Введение

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

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

2. Обзор существующих методов обратной отладки

Встроенные возможности по обратной отладке в отладчике gdb (для x86), а также в Trace32 (для ARM) реализованы с помощью записи состояний процессора и значений ячеек памяти [1,2]. Таким образом, можно просматривать ранее пройденные состояния и наблюдать значения переменных. Но такой подход имеет и ограничения. Записывается только определенное количество состояний процессора (откат возможен на ограниченное число шагов). Если не

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

Подобный подход также используется в реализации отладчиков для различных программных платформ, например Java, C# и т.п. При этом в отлаживаемую программу добавляется специальный код, записывающий интересующие пользователя данные, что может повлиять на поведение отлаживаемой программы [3,4].

Кроме методов, записывающих состояния процессора, существуют также методы обратной отладки, использующие детерминированное воспроизведение программ [5,6]. Детерминированное воспроизведение в самом простом случае предполагает сохранение начального состояния программы и результатов системных вызовов в процессе ее выполнения. Так появляется возможность перехода к заданному шагу программы с помощью загрузки ее начального состояния и выполнения с подменой системных вызовов. В случае большого количества системных вызовов, использования разделяемой памяти или многопоточных приложений, использования слишком многих системных библиотек применение данного подхода становится слишком трудоемким.

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

Внешние

устройства

Цикл ожидания

I

Цикл выполнения

Основной поток

QEMU

Транслятор кода Сгенерированный Прерывающий

код поток

Рис.1. Структура симулятора QEMU

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

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

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

3. Детерминированное воспроизведение программ

Задачи анализа и отладки, которые предполагается решать с помощью разработанного метода, требуют наличия возможности изучения системного кода, подсистемы BIOS и драйверов внешних устройств. Поэтому для реализации детерминированного воспроизведения сценариев выполнения программ был выбран симулятор QEMU [7]. Данный симулятор поддерживает множество архитектур симулируемых машин (i386, ARM, MIPS, PowerPC и т.д.), используется в составе SDK для платформы Android, а также имеет открытый код, что позволяет вносить в него изменения, необходимые для динамического анализа программ.

В данном разделе описывается ранее реализованный метод детерминированного воспроизведения программ [8].

3.1. Структура симулятора

Симулятор QEMU имеет структуру, показанную на рис.1.

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

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

3.2. Запись журнала событий

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

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

3.2.1. Выполнение очередной инструкции

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

3.2.2. События от часов реального времени

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

Ядро QEMU

Системные часы

Запрос показаний часов

I

Рис.2. Получение показаний часов в исходной версии QEMU

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

Ядро QEMU

Интерфейс часов

I

I

Запрос показаний часов |

Часы

Журнал событий

Запись показаний часов

Рис.3. Запись показаний часов в журнал

Ядро QEMU Интерфейс часов Журнал событий

I

Запрос показаний часов |

Чтение записанных показаний часов

I

Рис.4. Чтение показаний часов из журнала

При воспроизведении журнала событий вместо считывания показаний аппаратных часов выполняется чтение их из журнала (рис.4). Так как воспроизведение является детерминированным, операция чтения происходит в тот же момент (относительно позиции в журнале), что и при записи.

3.2.3. Прерывание выполнения симулируемого кода с помощью таймера

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

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

3.2.4. События от периферии

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

3.3. Детерминированное воспроизведение журнала событий

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

— считывание показаний часов из журнала, когда их запрашивает какой-либо код; если соответствующих показаний нет, возвращается кэшированное значение;

— передача сообщений от клавиатуры, мыши, звуковой и сетевой карт в тот момент, когда они считываются из журнала в соответствующие обработчики;

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

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

4. Детерминированная отладка с использованием журнала событий

Детерминированная отладка — это способ поиска ошибок в недетерминированных приложениях,

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

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

2. Разработчик может неоднократно проигрывать полученный сценарий в симуляторе, анализируя причины появления дефекта. Разработчику не нужно настраивать сложное окружение системы так, как это делал тестировщик, поскольку все особенности взаимодействия с этим окружением уже записаны в журнал.

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

5. Сохранение состояний виртуальной машины

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

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

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

6. Обратная отладка

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

Ход выполнения Ход отладки

ошибки

Рис.5. Ход выполнения обратной отладки при поиске ближайшей точки останова

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

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

Отладчик gdb, начиная с 7-й версии, поддерживает команды обратной отладки, такие как шаг назад и поиск первой сработавшей контрольной точки в обратном направлении. Для того, чтобы интерпретировать эти команды, был доработан модуль взаимодействия QEMU с gdb. Кроме того были внесены доработки в основной модуль детерминированного воспроизведения для реализации сценариев обратной отладки.

Чтобы выполнить действие «шаг назад», QEMU загружает ближайшее из сохраненных состоя-

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

Команда «выполнение в обратном направлении до первой точки останова» (reverse-continue) выполняется в несколько этапов. Сначала загружается состояние виртуальной машины, предшествующее текущему положению, и выполняется поиск точки останова, которая срабатывает последней перед достижением текущего шага. Если такой точки останова не найдено, данный этап повторяется для предыдущего состояния. После нахождения сработавшей точки останова, происходит переход к ней с помощью загрузки ближайшего состояния и воспроизведения журнала событий до этой точки (см. рис.5).

Таким образом, использование сохраненных в процессе записи сценария состояний виртуальной машины позволяет находить нужную точку останова за время, в лучшем случае равное удвоенному периоду времени между записанными состояниями. В худшем случае это время будет равняться удвоенному времени выполнения сценария целиком. На практике удается реально сохранять состояние виртуальной машины каждые 4-5 секунд, что означает, что команда reverse-continue чаще всего будет выполняться за 8-10 секунд.

7. Заключение

Таким образом, в результате проведенной работы получены следующие результаты.

1. Реализована поддержка команд обратной отладки gdb в симуляторе QEMU.

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

Работа выполнена при поддержке РФФИ (грант 11-07-00353).

1. GDB and Reverse Debugging — http://sourceware.org/gdb/ news/reversible.html, 13.03.2012

2. Microprocessor Development Tools — http://www.lauter-bach.com/frames.html?home.html, 14.03.2012

3. Omniscient Debugging — http://www.lambdacs.com/de-bugger/ODBDescription.html, 13.03.2012

4. How Does VS2010 Historical Debugging Work? — http://www.wintellect.com/CS/blogs/jrobbins/archive/2009/06/16 /how-does-vs2010-historical-debugging-work.aspx, 13.03.2012

5. King Samuel T., Dunlap George W., and Chen Peter M. Debugging Operating Systems with Time-Traveling Virtual Machines // ATEC '05 Proc. of the USENIX Annual Technical Conference. Berkley, CA, USA, 2005. P.1-15.

6. Koju Toshihiko, Takada Shingo, and Doi Norihisa. An efficient and generic reversible debugger using the virtual machine based approach // VEE '05 Proc. of the 1st

ACM/USENIX international conference on Virtual execution environments. N.Y., 2005. Р.79-88.

7. QEMU — open source processor emulator — http://wiki. qemu. org/ Main_Page, 15.03.2012

8. Довгалюк П. Детерминированное воспроизведение процесса выполнения программ в виртуальной машине // Тр. Ин-та системного программирования РАН. 2011. Т.21. С.123-132.

Bibliography

1. GDB and Reverse Debugging — http://sourceware.org/gdb/ news/reversible.html, 13.03.2012

2. Microprocessor Development Tools — http:// www.lauter-bach.com/frames.html?home.html, 14.03.2012

3. Omniscient Debugging — http://www.lambdacs.com/de-bugger/ODBDescription.html, 13.03.2012

4. How Does VS2010 Historical Debugging Work? — http://www.wintellect.com/CS/blogs/jrobbins/archive/2009/0 6/16/how-does-vs2010-historical-debugging-work.aspx, 13.03.2012

5. King Samuel T., Dunlap George W., and Chen Peter M. Debugging Operating Systems with Time-Traveling Virtual Machines // ATES '05 Proc. of the USENIX Annual Technical Conference. Berkley, CA, USA, 2005. P.1-15.

6. Koju Toshihiko, Takada Shingo, and Doi Norihisa. An efficient and generic reversible debugger using the virtual machine based approach // VEE '05 Proc. of the 1st ACM/USENIX international conference on Virtual execution environments. N.Y., 2005. R.79-88.

7. QEMU — open source processor emulator — http://wiki. qemu.org/Main_Page, 15.03.2012

8. Dovgaljuk P. Determinirovannoe vosproizvedenie processa vypolnenija programm v virtual'noj mashine // Tr. In-ta sis-temnogo programmirovanija RAN. 2011. T.21. S.123-132.