Что-то пошло не так: основы пошаговой отладки

Что-то пошло не так: основы пошаговой отладки

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

Ошибки в программах делятся на три типа

  • синтаксические
  • логические
  • ошибки времени выполнения

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

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

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

Ловим логические ошибки: прогоняем алгоритм по шагам

Для отладки DLL, в отличие от программы, требуется дополнительно настроить её проект, а именно указать, какое приложение использует эту DLL и задать параметры его запуска. В нашум случаем модуль ПЕ используется процессом simulator.exe (код ПЕ работает в контексте этого процесса).

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

В поле «Программа» указывается путь к файлу simulator.exe, в поле командной строки указываются ключи, с которыми запускается simulator.exe, а именно

  • —route — путь к каталогу с маршрутом. В вашем случае может быть указан другой маршрут, в зависимости от того, какой установлен у вас;
  • —train-config — имя файла конфига поезда. Нам называется simple-loco.

Лаунчер запускает процесс simulator.exe именно так: определяет путь к выбранному в нем маршруту, определяет имя конфига поезда и подставляет эти значения в ключи процесса simulator.exe.

Теперь предположим, мы хотим пройти по алгоритму, загружающему из конфига пользовательские параметры, то есть по методу loadConfig(). Переходим обратно в редактор кода и ставим точку останова (F9, либо клик по полю с номерами строк) на нужной строке

Красный кружок на 97-й строке означает, что в этом месте программа будет остановлена и управление будет передано в отладчик.

Теперь жмем кнопку «Запуск в отладке» (слева внизу зеленый треугольничек с изображением жучка), дожидаемся запуска процесса (стартует довольно не быстро), и бац — мы остановились в нужном нам месте

Подробнее разберем те возможности, которые предоставляет нам режим отладки в QtCreator

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

Снизу, под окном редактора располагается окно так называемого стека вызовов, в котором, снизу вверх, показана последовательность с которой в процессе выполнения, до момента остановки, происходил вызов функций и методов программы и отлаживаемой DLL. Здесь, самой первой строкой (на вершине стека) мы видим метод SimpleLoco::loadConfig() на котором произошел останов. Однако ниже можно увидеть методы, вызов которых предшествовал данному вызову. Абракадабра вида

vehicle!_ZN7Vehicle17loadConfigurationE7QString

несет в себе массу полезной информации. Я специально выделил ее полужирным шрифтом

  • vehicle — имя библиотеки, из которой произошел вызов, в данном случае это vehicle.dll, входящая в состав симулятора;
  • Vehicle — имя класса, метод которого был вызван: в данном случае это класс Vehicle, базовый класс для SimpleLoco;
  • loadConfiguration — имя самого метода класса Vehicle
  • QString — тип параметра, переданного в вызванный метод.

Видите, сколько информации мы получили о симуляторе, даже не глядя в его исходный текст? В частности мы знаем теперь, что метод loadConfig() вызывается из метода loadConfiguration() класса Vehicle. А глядя еще ниже по стеку вызовов, мы видим что этот метод, в свою очередь вызывается из метода Train::loadTrain(), из класса Train, расположенного в библиотеке train.dll.

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

В окне просмотра локальных переменных мы видим значение переменной cfg_path, содержащую путь к конфигу нашего локомотива. И так и есть, этот путь C:\RRS\cfg\vehicles\simple-loco\simple-loco.xml.

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

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

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

Ловим ошибки времени выполнения

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

Давайте внесем критическую ошибку в код нашего локомотива, прямо в конструкторе его класса напишем такой код

 SimpleLoco::SimpleLoco(QObject *parent) : Vehicle (parent)
   , ref_traction_level(0.0)
   , F_max(450.0)
   , F_nom(350.0)
   , V_nom(80.0)
 {
     double *a;
     a[0] = 10.0;
 }

Тот кто хорошо знаком с C++ поймет подвох — мы объявили указатель на массив вещественных чисел, и не выделив память для этого массива пытаемся записать туда число. Соберем DLL в режиме «Выпуск» и запустим симулятор. С вероятность 99% у нас ничего не будет работать, программа вылетит с критической ошибкой, или просто не будет работать. И весь трагизм ситуации заключается в том, что возможно и будет нормально работать — например на разных машинах, в одном случае сим падал у меня сразу же, а в другом нормально работал. Во втором случае этот массив видимо указывает на какую-то доступную программе область памяти, и исключение не происходит. Если в вашем случае все работает, немного измените этот код

 double *a = 0; 

Ага! Вот мы и приплыли!

Зелена кнопка «Старт» в окне лаунчера означает что упал процесс simulator.exe. Вьювер что-то показывает, но стоим мы явно не на станции Ростов Главный, а в начале маршрута, так как симулятор не передал во вьювер данные о положении поезда. Что же случилось, как выяснить это в случае, когда мы не знаем об ошибке (в данном случае-то понятно)

Запускаем режим отладки DLL, как мы делали в предыдущем параграфе, но не ставим никаких точек останова. Бац!

Segmetation fault — ошибка означающая попытку доступа к недоступной области памяти. Что же, жмем переходим в окно отладчика

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

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

Запуск вьювера при отладке

При всех вышеперечисленных возможностях, существует одна проблема — при запуске отладки у нас запускается только процесс simulator.exe. Но в штатном режиме работы существует еще процесс viewer.exe, который, например, обрабатывает ввод команд с клавиатуры. Как быть? Запустить вьювер вручную!

Для начала запускаем DLL в режиме отладки без задания точек останова. При этом у нас стартует процесс simulator.exe.

Переведем вьювер в оконный режим, для чего в файле cfg/stttings.xml выставляем параметр FullScreen в 0

Жмем Win + R, и в появившемся окне вводим

Появляется окно командной строки Windows

Переходим в каталог bin симулятора, введя команду

cd C:\RRS\bin

Теперь запускаем viewer, передав ему в качестве параметров путь к маршруту и имя конфигурации поезда

viewer.exe --route ..\routes\rostov-goryachiy_kluch --train simple-loco

Вьювер должен успешно запуститься и получить положение поезда на участке

Теперь весь симулятор работает в отладочном режиме. Например мы без проблем можем привести локомотив в движение, а затем, перейдя в QtCreator, постaвить точку останова внутри метода step()

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

Допустим нас интересует как происходит обработка нажатия клавиш. Ставим точку останова, например, на действия, выполняемые по нажатию клавиши A

Переходим в окно вьювера, жмем A — вуаля! Симуляция замерла на обработке этого нажатия

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

Назад Вперед
Содержание