Способы отладки приложений

Дмитрий Долгов
Родился в 1973 году. Окончил Санкт-Петербургский Государственный Технический Университет (бывший Политехнический Институт) по специальности "Прикладная математика". Более 8 лет проработал в CREAT Studio, где до недавнего времени занимал пост главного программиста и начальника отдела программирования. Участвовал в выпуске игр для PC, Playstation 2, Xbox и Dreamcast. В настоящее время работает в отделе игровых и мультимедийных продуктов фирмы "1С".

Вступление

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

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

Обзор способов отладки

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

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

  • Интерактивные средства отладки
  • Runtime-диагностика
  • Визуальные (графические) средства отладки
  • Unit Testing
  • Functional Testing
  • Протоколирование
  • Отладка по крэш-дампам
  • Визуальный просмотр кода

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

Интерактивные средства отладки

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

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

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

Какие ошибки при сборке в конфигурации Release могут быть пропущены при интерактивной отладке в Debug:

  • Другая карта распределения памяти - порча памяти, которая в отладочном режиме была незаметна, может случайным образом начать модифицировать чужие данные
  • Использование в коде неинициализированных переменных - в отладочном режиме они имеют одно значение (часто - фиксированное, например 0xcdcdcdcd), в Release версии - другое, причем случайное
  • Более высокое быстродействие программы - в многопоточном приложении могут вылезать ошибки синхронизации
  • Собственные ошибки компилятора - некоторые сложные расчеты могут быть неверно соптимизированы компилятором, и функция будет выдавать неверный результат. Проверяется по дизассемблеру и юнит-тестам, лечению практически не поддается (надо выключать оптимизацию или переписывать код). По статистике, в крупных проектах, разрабатываемых в Microsoft Visual Studio (имеются в виду стабильные версии компилятора, а не бета-релизы) такие ошибки встречаются в среднем 1-2 раза в год.
  • Отсутствие в Release-версии отладочных проверок типа assert() и кода, окаймленного условной компиляцией

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

Run-time диагностика

Run-time диагностика - это различные проверки, выполняемые в процессе выполнения программы. Такие проверки могут создаваться компилятором в отладочном или релизном режиме (проверка стека, buffer overrun, отладочный менеджер памяти), дополнительными отладочными утилитами (например, BoundsChecker'ом), а также выполняться собственным кодом программы (различные отладочные проверки, выполнение pre- и post- condition при помощи макроопределения assert(), собственные менеджеры памяти, структурная обработка исключений и т.п.).

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

Визуальные (графические) средства отладки

Часто используются для отображения различной информации на экране. Это могут быть: траектории движения AI персонажей, отладочная визуализация геометрии, bounding volumes у объектов, подсветка малополигональной collision геометрии и т.п. К визуальным средствам отладки также можно отнести графическую визуализацию GPU Usage, Memory Usage, PolyPerSec info и так далее (по аналогии с Диспетчером Задач такую информацию обычно выводит графиком или вертикальными прямоугольниками с края экрана).

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

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

Unit-testing

В явном виде unit testing не относится к способам отладки приложения, однако про него тоже хочется сказать несколько слов.

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

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

Однако полнота "доказательства работоспособности" при помощи unit testing - под большим вопросом. Чем крупнее наша система и чем больше у нее входных данных, тем более сложным должен быть тест, покрывающий все возможные варианты функционирования. На подготовку таких тестов требуется довольно много времени. Из-за сложности формирования тестов этот метод практически неприменим для проверки сложного межмодульного взаимодействия и вопросов синхронизации данных. Без специальных ухищрений со стороны тестирующей оболочки невозможно протестировать функции на некорректных данных, которые в большинстве случаев являются причиной появления unhandled exceptions. Unit testing не позволяет также проверить корректность обработки сбоев во внутренних вызовах (например, если malloc() вернул NULL).

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

Functional Testing

Функциональный тест - это проверка работоспособности программы в целом. В отличие от unit testing, который ориентирован на проверку работоспособности отдельных функций, функциональный тест предназначен для автоматизированной проверки корректности всей программы. Очень часто такая проверка выполняется при помощи дополнительных отладочных скриптов.

Пример функционального теста, который рекомендуется запускать после каждой сборки всех уровней - это автоматическая загрузка и выгрузка всех доступных уровней. Такой тест позволяет выявить потенциальные ошибки сборки (не добавили в pack-файл текстуру или ini-файл для уровня №19, забыли скопировать в дистрибутив начальный видеоролик для уровня №37 и так далее). Чем больше возможностей заложено в скриптовую систему игры, тем более полным может быть автоматизированная проверка функционирования.

Недостатки Functional Testing - такие же, как и у Unit Testing. Из-за большого количества вариаций написать полностью корректный Functional Test, проверяющий все игровые состояния, невозможно. С его помощью можно поймать только некоторые стопроцентно возникающие ошибки, приводящие к фатальным сбоям в программе.

Протоколирование

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

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

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

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

Визуальный просмотр кода

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

До возникновения интерактивных сред, совмещающих в себе редактор, компилятор и отладчик (IDE), просмотр кода и протоколирование являлись основными способами отладки приложения. Бурное развитие интегрированных сред, различных средств визуального проектирования и вспомогательных инструментов (таких как Visual Assist и т.п.) существенно снизило ценность этого способа отладки.

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

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

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

Отладка по крэш-дампам

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

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

Вместо заключения

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

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

Итак, продолжение следует.

Автор выражает благодарность за помощь в подготовке статьи:
Антону Юдинцеву (Gaijin Entertainment) - за полезную информацию для раздела "Обзор способов отладки"
Николаю Додонову (CREAT Studio) - за помощь в работе над разделом "Протоколирование"

[01.09.2005]

Copyright © 2019 ООО "ДТФ.РУ". Все права защищены.

Воспроизведение материалов или их частей в любом виде и форме без письменного согласия запрещено.

Замечания и предложения отправляйте через форму обратной связи.

Пользовательское соглашение