Усильте свои навыки отладки

Отладчик проекта GNU (GDB) — очень полезный отладчик в Linux.
Умные программисты обычно используют gdb для отладки ошибок, когда происходит сброс ядра программы или программа ведет себя непредвиденно. В этой статье я покажу вам, как эффективно использовать gdb.

Выберите соответствующие флаги компиляции

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

  • используйте -g, чтобы позволить компилятору встроить отладочную информацию в исполняемый файл.
  • использование -ggdb3 может сделать макросы отладки gdb.
  • используйте -fno-omit-frame-pointer, чтобы компилятор не оптимизировал небольшие функции, чтобы вы могли видеть полный стек вызовов.
  • используйте -0g, чтобы позволить компилятору включить оптимизацию, которая не влияет на отладку.

Запустить gdb

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

  • В первом сценарии у нас есть исполняемый файл, который принимает некоторые параметры. Мы можем запустить gdb с gdb executable, а затем запустить программу с run arg1 arg2.
  • Во втором сценарии файл дампа ядра создается при запуске исполняемого файла, возможно, из-за ошибки сегмента. Мы можем запустить gdb с gdb executable coredump_file, и gdb остановится там, где произошла ошибка.
  • В третьем сценарии у нас есть исполняемый файл, который работает. Мы можем использовать gdb attach pid для присоединения к процессу.

Точки останова и часы

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

  • break func сломается при выполнении функции.
  • break example.cc:10 прервется на 10-й строке файла example.cc, если есть только один файл, мы можем опустить имя файла, просто используйте break 10 . И мы можем ввести list, чтобы показать коды вокруг текущей строки.
  • info breaks может показать вам все установленные вами точки останова.
  • delete 2 удалит вторую точку останова.
  • Вы также можете установить условные точки останова, просто добавьте if [condition] к команде останова.
  • watch [variable] может установить наблюдение за переменной, часы - это еще один вид точки останова, которая приостанавливает программу, если наблюдаемая переменная изменилась.

Переменные и выражения

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

  • info args может показать нам аргументы функции.
  • info locals может показать нам локальные переменные текущей функции.
  • print можно использовать для отображения результата переменной или выражения. См. приведенные ниже иллюстрации. print a печатает переменную. print a=2 сначала оценит a=2, а затем распечатает результат. Обратите внимание, что если вы наберете a=2 напрямую, вы получите сообщение об ошибке. print sum(1, a) вызывает функцию sum .

(gdb) напечатать a
$1 = 1
(gdb) напечатать a=2
$2 = 2
(gdb) напечатать sum(1, a)

Вы также можете отформатировать результат print ,

  • print /x <exp> покажет результат в шестнадцатеричном формате.
  • print /t <exp> покажет результат в двоичном формате.
  • print /d <exp> покажет результат в формате unsigned int.
  • print /c <exp> покажет результат в формате подписанного целого числа.
  • Обратите внимание, что результаты print, такие как $1, $2, также являются переменными, которые можно использовать в дальнейшем.
  • dprintf locaion, format-string, expr1, expr2 . drpintf — это удобная команда, которая может динамически печатать в указанном месте так же, как вы вставили в это место выражение printf.
  • set $foo = 4 может установить переменную. Это удобно, если вы хотите сохранить некоторые промежуточные результаты.
  • command 2 — это еще одна команда gdb, с помощью которой вы можете установить команды, которые будут выполняться при попадании в определенные точки останова. На приведенном ниже рисунке info locals будет выполняться автоматически, когда сработают первые точки останова. Вы также можете использовать этот метод для достижения тех же эффектов, что и dprintf.

(gdb) command 1
Введите команды для точек останова 1, по одной на строку.
В конце строки укажите просто «end».
›info locals
›end

  • print *&arr[96]@5 может напечатать 96–100 элементов массива.

Обратите внимание, что в настоящее время мы не можем легко печатать std::vector или другие контейнеры stdlib c++. Мы обсудим эту тему позже.

Исполнительный контроль

Есть несколько команд, позволяющих нам контролировать выполнение программы в сеансе gdb.

  • run запустит программу и остановится на первой точке останова, если точек останова нет, программа остановится на выходе.
  • start запустит программу и остановит функцию main.
  • continue продолжит выполнение программы и остановится на следующей точке останова или выходе.
  • finish остановится, когда текущая функция завершится.
  • next остановится на следующей строке.
  • step войдет в функцию и остановится на этом.

Текстовый пользовательский интерфейс

Gdb содержит текстовый пользовательский интерфейс (TUI), как и большинство IDE, который может отображать строки кода во время отладки. tui enable для входа в режим tui и tui disable для выхода.

Обратная трассировка и потоки

Backtrace полезен для отладки, особенно при отладке дампа ядра.

  • bt покажет текущую обратную трассировку.
  • up переместит вас на верхнюю рамку.
  • down переместит вас на нижнюю рамку.
  • frame 2 перенесет вас прямо ко второму кадру.

При отладке многопоточной программы

  • info threads показывает все темы.
  • thread 5 перейдет к пятому потоку.
  • thread apply all [command] выполнит команду во всех потоках. Очень удобно использовать thread apply all bt full для вывода всей трассировки всех потоков.

Настройка gdb с помощью .gdbinit

Gdb имеет файл конфигурации, расположенный по адресу ~/.gitinit или в текущей папке, точно так же, как .vimrc для vim. Файл конфигурации по текущему пути имеет более высокий приоритет загрузки, чем ~/.gitinit . Давайте обсудим некоторые конфигурации.

  • set logging on выведет весь вывод на gdb.txt . Это полезно, когда команды выводят так много информации, как thread apply all bt full выше.
  • set history save on сохраняет историю команд.
  • set pagination off отключить интерактивное отображение длинного экрана.
  • set print pretty on может отображать класс С++ в более красивом формате.
  • set confirm off отключить подтверждение.

Поскольку gdb не может удобно печатать контейнеры C++, такие как std::vector, мы можем написать несколько скриптов, чтобы упростить эту задачу. Следует https://gist.github.com/skyscribe/3978082.

Путь к исходному файлу

Иногда мы сталкиваемся с такими ошибками, как xxx.c: No such file or directory . Это означает, что gdb не может найти исходный файл. Решения:

  • используйте directory [folder], чтобы добавить конкретный каталог в путь поиска gdb.
  • используйте set substitute-path [src] [dst] для замены пути.

Расширение макроса

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

  • используйте macro expand some_macro(macro_arg) для расширения макроса.
  • используйте info macro some_macro, чтобы увидеть его определение.

Обратите внимание, что gdb проанализирует контекст текущей функции, можно раскрыть только те макросы, которые видны в текущем контексте.

Запись статуса программы

Одной из мощных команд gdb является record, которая записывает состояние работающей программы и позволяет вам запускать программу в обратном порядке. Gdb также предоставляет группу команд, начинающихся с префикса reverse, например reverse-continue, reverse-finish, reverse-next, reverse-nexti, reverse-step, reverse-stepi.

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

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

Сохраните и загрузите сеанс отладки

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

Если вы используете make-файл для сборки своей программы, вы можете просто ввести make в сеансе gdb, он перестроит вашу программу и сбросит контрольные точки.

Вы также можете использовать save breakpoints bp.txt для сохранения точек останова в bp.txt и использовать source bp.txt для повторной загрузки этих точек останова позже или использовать gdb -x bp.txt --args [exe] для загрузки при запуске gdb.

Подробнее об отладке

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