GDB
Существует стандарт отладки - DWARF, который поддерживается gdb. Флаги компилятора. В документации я не нашёл внятного ответа, но:
-g(n) - создаёт информацию об отладке в формате ОС: stabs, COFF, XCOFF или DWARF(2-5).
-ggdb(n) - выдаёт больше отладочной информации, специфичной для отладки через gdb. n ∈ [1,3]. Чем больше n, тем выше уровень отладки. Отсутствие числа - значение по умолчанию (2). Например, 3 включает отладку макросов препроцессора.
Отладочная информация
breakpoint'ы
Бывают аппаратные, виртуальные и програмные.
- Аппаратные брейкпоинты: Эти брейкпоинты поддерживаются процессором. В регистры (например, D*) помещается условие, на основании которого происходит проверка. Если условие срабатывает, программа останавливается. Это отлично подходит для проверки доступа к памяти.
- Виртуальные брейкпоинты: Программа запускается в эмуляторе, и эмулятор анализирует поток инструкций, исходящих из процессора. Если адрес исходящей инструкции совпадает с точкой останова, происходит остановка.
- Программные брейкпоинты: Их может быть неограниченное количество, и они могут быть установлены практически после каждой инструкции. Дебагер заменяет программную инструкцию перехватчиком, например, деление на 0. Процессор вызывает исключение, дебагер его обрабатывает и показывает программу в остановленном состоянии. Для продолжения выполнения дебагер заменяет исключение на исходную инструкцию, и выполнение продолжается.
Аппаратные - поддерживаются процессором. В D* регистры помещается условие на которое происходит проверка. Если срабатывает - программа останавливается. Отлично подходит для проверки на доступ к памяти.
Виртуальные - программа запускается в эмуляторе и эмулятор анализирует поток инструкций исходящих из процессора. Если адрес исходящей инструкции совпадает с точкой останова, то происходит остановк.
Програмные точки останова. Их может быть неограниченное количество и они могут быть на каждой инструкции. Дебагер заменяет програмную инструкцию перехватчиком - тем, что вызовет исключение, например деление на 0. Процессор вызовет исключение, дебагер его обработает и покажет программу в остановленном состоянии. Для продолжения он заменит исключение на исходную инструкцию и выполнение продолжится. Также програмные точки брейкпоинты могут быть реализованы как временные аппаратные.
В GDB по умолчанию выбирается подходящий тип брейкпоинта.
Точка останова - привычный для всех момент приостановки.
b (имя функции, номер строки или адрес инструкции);
Можно добавлять условия и потоки b func if a == 10
tbjreak/hbreak/thbreak - временная, аппаратная и одновременно оба типа точек останова соответственно.
rbreak - точки на всех функциях, соответствующих регулярному выражению.
info break - просмотр всех точек останова.
watchpoint'ы
Эти функции позволяют отслеживать конкретные переменные. Их удобно использовать с аппаратным обеспечением, но они также могут работать на программном уровне.
- watch: Срабатывает при изменении переменной. Можно настроить для работы на программном уровне с помощью команды
set can-use-hw-watchpoints 0
. - rwatch: Срабатывает при чтении переменной. В настоящее время работает только аппаратно.
- awatch: Срабатывает при любом действии с переменной. В настоящее время работает только аппаратно.
Эти функции поддерживают следующие аргументы: thread
- поток, изменения в котором нужно отслеживать, mask
- маска в шестнадцатеричном формате, которая будет применена к адресу переменной. Флаг -location
заставляет отслеживать содержимое ячейки памяти, а не значение выражения.
Для просмотра информации о точках отслеживания используйте команду info watchpoints
.
Tracepoint'ы
The tracepoint facility is currently available only for remote targets. XD
Чтобы использовать трейспоинты нужно подключиться к своей же сессии через gdbserver. GNU)
Те же брейкпоинты, только при их срабатывании выполняется определенное действие (запись необходимой информации), а не остановка. Может поддерживаться на аппаратном уровне.
- trace: Обычная точка трассировки.
- strace: Статическая точка трассировки. Основное отличие в том, что в статических точках можно использовать команду
collect $_sdata
. - ftrace: Быстрая точка трассировки, основное значение только в скорости (то есть, нет особой функциональности). Синтаксис аналогичен команде
break
.
Команды enable/disable tracepoint
работают аналогично.
- passcount: Определяет, сколько раз должна выполниться точка трассировки.
- tvariable $name: Задает тип данных для хранения информации при трассировке.
- actions [n]: Определяет действия, выполняемые при срабатывании точки трассировки. Команда
collect
позволяет собирать данные из различных источников, таких как$regs
,$args
,$locals
,$ret
(адрес возврата),$probe_argc
,$probe_argn
,$sdata
, и другие. - teval: Позволяет выполнять операции в блоке
action
. Например,teval $counter = $counter + 1
. - while-stepping n: Выполняет n шагов на каждой итерации, выполняя тело цикла (завершается командой
end
). - set default-collect: Устанавливает дефолтные значения для сбора данных на каждой точке трассировки.
- t{start/stop/status}: Управление началом, окончанием и статусом трассировки. Можно изменять размер и тип буфера, настраивать события при подключении и отключении. Трассировка не работает в полноценном многопоточном режиме, нужно использовать команду
set scheduler-locking on
.
Ну и ряд случаев, когда gdb ведёт себя как говно - tfind arg: Поиск снимков. Без аргументов - следующий,
none
- прекратить,tracepoint n
- следующий связанный с номером n. - tdump: Выводит содержимое буфера трассировки.
- save tracepoints filename: Сохраняет состояние трассировки (все точки и т.д.).
- tsave: Сохраняет данные трассировки.
Target tfile
- выгружает.
Также есть ряд переменных для использования: $trace_frame
, $tracepoint
, $trace_line
, $trace_file
(символьный массив), $trace_func...
Выполнение в обратном порядке
reverse-{step(i), next(i), finish, continue). При включённой записи инструкций позволяет возвращаться на несколько инструкций назад. Также можно включать set exec-direction reverse
и все инструкции будут инвертирваны (continue -> reverse continue).
Чтобы это работало нужно включить запись.
record full / btrace + bts/pt - запись, stop, function-call-history instruction-history, goto - использование (помимо reverse-* команд).
GDB поддерживает только технологии intel: BTS и PT. BTB у амуде сосёт. BTS - записывает изменения состояния, pt - хранит трассировку выполнения всей программы в сжатом виде. Работает с крайне низкими накладными расходами и может запоминать больше инструкций, чем BTS. PT есть не у всех.
При full-record не работает function-call-history instruction-history.
Record save у меня ломает gdb.
Остановка по событию
catch/tcatch - эти команды позволяют отлавливать события, такие как syscall, fork, vfork, load и другие. Префикс t
делает catch
одноразовым.
throw - используется для отлова исключений (например, std::exception). В качестве аргумента передается регулярное выражение.
rethrow - используется для отлова критических ошибок (например, std::runtime_error).
Управление
- delete (d) (тип) (экземпляры): Удаляет все точки останова, соответствующие указанному типу и экземплярам.
- disable breakpoints (n): Отключает точки останова. По умолчанию отключаются все брейкпоинты.
- enable breakpoints (count/delete/once): Включает брейкпоинты на указанное количество раз/удаляет после одного срабатывания/отключает после одного срабатывания.
- ignore [N [count]]: Игнорирует точки останова N раз.
- condition: Устанавливает условие срабатывания точки останова. Например,
condition bnum a == 2
будет срабатывать только при равенстве переменной a значению 2. Чтобы удалить все условия, используйтеcondition bnum
. Если переменная появляется только при линковке, добавьте флаг-force
. - dprintf: Это обычная команда
printf
, которую можно вставить в любое место кода. Например:
dprintf factorial.c:fact:the_top "%d\n", i // вывод переменной i в начале функции fact
Стек:
frame. Фреймы - структура данных хранимая в стеке.
В нем хранится адрес возврата функции, локальные переменные и указатели на переменные, выходящие за область видимости.
backtrace - показывает содержащиеся в стеке фреймы.
enable frame-filter - возможность включить скрипт для Python, который будет возвращать
info stack - аналогичен backtrace.
x/FMT address, где F - количество, M - система счисления, F - длина слова. Лютое изменение кода в рантайме.
return [n] - возвращает значение из функции прямо сейчас. Отличается от finish тем, что выходит из функции на следующей же инструкции, не выполняя ее до конца.
Можно вызывать любую функцию через call name(args). Для перегруженных функций на C++ можно указывать тип через (type).
Изменение ходы выполнения программы.
- set name = value. Также можно делать по адресу с приведением типов. Регистры изменяются через set $rax = nж
- jump - аналог goto из лучших версий Паскаля.
- skip в оригинале должен пропускать функции, но у меня не завелось. jump в принципе заменяет.
- queue-signal/signal SIGTRAP - добавить в очередь/послать сигнал программе.
- call func(args) - вызывает функцию с переданными аргументами. то же самое, что и print func(args)
Парралельизм
- info threads: Показывает все запущенные программой процессы.
- thread n: Переключается на n-ный процесс.
- (taas) thread apply: Отправляет команду
all
или потокам по имени.
С дочерними процессами всё сложнее. Можно установить, кто будет отлаживаться после системного вызова форка с помощью set follow-fork-mode child/parent
. Также можно пометить, будет ли дочерний процесс работать независимо от GDB после форка с помощью set detach-on-fork on/off
. Также точки останова можно поставить на vfork через catch (v)fork
.
Закладки
Можно сохранить состояние процесса, по крайней мере на линуксе, создается форк. В случае необходимости можно вернуться к сохраненному состоянию процесса. Все, кроме ввода/вывода, будет возвращено в исходное состояние. Для этого можно использовать следующие команды:
- checkpoint: Создает точку сохранения состояния процесса.
- info checkpoints: Показывает информацию о доступных точках сохранения.
- restore id: Восстанавливает состояние процесса из указанной точки сохранения (где id - идентификатор точки сохранения).
- delete checkpoint: Удаляет указанную точку сохранения после восстановления.
Исходный код
- list .: Начинает показывать файл с начала.
- +/-: Показывает следующие/предыдущие n строк.
- set listsize: Устанавливает количество строк, которые будет показывать одна команда list.
- n: Показывает вокруг n строки.
- edit [n]: Включает редактор на n строке. Эквивалентно использованию команды
!nvim
.
Для поиска пути можно использовать шаблоны, например, foo.*
, lib/foo.*
. Команда dir n
позволяет добавить путь, где будет искаться указанное название.
Дизассемблер
Команда disassemble используется для дизассемблирования текущей функции. Она принимает аргументы (адрес), определяющие что и откуда до куда дизассемблировать. Например:
/s
- показывает соответствие операций (;) и их перевода в ассемблер (в порядке адресов)./m
- устаревший флаг./r
- показывает дизассемблирование включая hex-представление./b
(отсутствует в официальном help) делает то же, что и/r
.
Следует установить set disassembly-flavor intel
для читаемого синтаксиса инструкций. Также рекомендуется включить вывод следующей инструкции с помощью set disassemble-next-line on
.
GNU решили, что на amd64 это работать не будет. Фактически вся сложность заключается в передаче флагов и аргументов objdump, который умеет делать всё это. На ARC и S/390 поддерживается, на x86 - нет.
Оптимизации
Для дебага логики программы, как правило, лучше всего убрать оптимизации компилятора. Это позволит получить более понятный и предсказуемый код при дебаге.
Однако, если вам необходимо дебажить реализацию, пердолится (или просто читать ассемблер). Точки останова в inline-функциях могут работать не всегда корректно из-за особенностей оптимизации кода компилятором. В таких случаях слудет использовать команду stepi
, которая позволяет шагать по инструкциям, игнорируя оптимизации и inline-код.
Команда info frame
позволяет посмотреть, какие функции заинлайнены, а какие нет. Также это может быть полезно при работе с оптимизациями, такими как tail call optimization.
Дейайны
info macro NAME позволяет посмотреть на макросы, и определить свои через macro define. Дефайны можно посмотреть на его результат через macro expand NAME. Макросо можно вызывать через print NAME(a,r,g,s,)
Прочее
- maint - это команда, предназначенная для разработчиков GDB, и не трогай.
- alias - позволяет добавлять алиасы для команд GDB помимо стандартных. Например,
alias pipi = print
создаст алиасpipi
для командыprint
. - info proc - предоставляет много информации о текущем процессе.
- display - позволяет показывать выражение при каждом шаге выполнения программы. Например,
display /i $ps
покажет следующую ассемблерную инструкцию. - attach - позволяет подключиться к существующему процессу для дальнейшего отладки.
- pretty-printer - обеспечивает красивый вывод сложных структур данных в GDB. - https://habr.com/ru/articles/325472/
- shell - запускает эмулятор терминала внутри GDB, позволяя выполнить команды в командной строке.
- ! - выполняет команду во внешнем терминале, например,
!neofetch
. - pipe [command] | shell - перенаправляет вывод команды в эмулятор терминала.
- set logging - включает логирование действий в GDB, что может быть полезно для отладки и анализа процесса выполнения программы.
- whatis x - показывает что такое переменная x
- info functions/varibles/classes - показывает все функции/переменные/классы в программе
- Можно установить set write on, компилировать через compile и загружать файлы через file, не выходя из GDB.
Конфиг
# - комментарий, воспринимает обычный синтаксис gdb. Мой конфиг:
set disassembly-flavor intel
set disassemble-next-line on
set tui border-kind space
set tui active-border-mode bold
set tui mouse-events on
set history save on
set history filename ~/.gdb_history
https://www.sourceware.org/gdb/current/onlinedocs/gdb.html/index.html#SEC_Contents - документация, я читал её.
https://evilfingers.com/publications/research_RU/gdb-bin-II.pdf - просто читается, есть достаточно инфы