Усильте свои навыки отладки
Отладчик проекта 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 фиксирует. У них есть свои преимущества и недостатки. При отладке программы вы можете комбинировать эти методы для эффективного решения проблемы. Преимущество в том, что у вас есть полный доступ к работающей программе, это очень полезно, когда ошибку трудно воспроизвести.