GDB
Хороший туториал - https://beej.us/guide/bggdb/
peda
peda - удобное окружение для GDB
https://github.com/longld/peda
Установка:
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
Сборка
Чтобы было удобно пользоваться gdb, при сборке нужно использовать флаги -g и -O0
История команд
set history save on
set history filename ~/.gdb_history
Основные команды
b main.c:175
- установить брейкпойнт в main.c:175b start_work
- установить брейкпойнт в начало функции start_workb start_work if b == 0
- установить брейкпойнт с условиемp a
- вывод переменной аp *a
- вывод значения по адресу аbt
- текущий бэктрэйс
Watchpoints
watch expr
- установить брейкпойнт на запись выражения exprrwatch expr
- установить брейкпойнт на чтение выражения exprawatch expr
- установить брейкпойнт на чтение и запись выражения expr
Список можно получить по info watchpoints
. Любой вотчпойнт из списка можно удалить по delete N
.
Установка вотчпойнта на поле класса:
(gdb) p &bar
$1 = (int *) 0x10793ad0
(gdb) watch *0x10793ad0
Написание скриптов
Вот такой скрипт установит брейкпойнты в функции gst_object_ref
и gst_object_unref
, которые будут срабатывать, когда первый аргумент ($rdi
) указывает на тот же адрес, что и переменная audioconvert
. При каждом срабатывании брейкпойнта будет печетаться стектрейс и выполнение продолжится дальше.
set pagination off
break main.cpp:69
run
set $conv=audioconvert
break gst_object_ref if ($rdi == $conv)
commands
bt
cont
end
break gst_object_unref if ($rdi == $conv)
commands
bt
cont
end
cont
Ключевое слово commands
позволяет задать несколько действий, которые будут выполнены, когда сработает этот брейкпойнт. Список действий заключен между commands
и end
.
set pagination off
- указывает, что при выводе стектрейса не нужно останавливать выполнение и выводить "нажмите пробел для следующей страницы".
Выполнение скрипта
Сохраняем скрипт в файл script.txt затем делаем:
gdb -x script.txt
Проход по бэктрэйсу
gdb$ p $rbp
$15 = (void *) 0x7fffffffe5c0 # в регистре rbp хранится адрес начала фрейма предыдущей функции, то есть предыдущее значение rbp. В $15 теперь хранится как раз оно.
gdb$ p *(long*)$15
$16 = 0x7fffffffe5d0 # получаем значение адреса, лежащего в регистре $rbp. то есть адрес пред-предыдущего фрейма.
gdb$ p *(long*)$16
$17 = 0x7fffffffe5e0 # и так далее...
gdb$ p *(long*)$17
$18 = 0x7fffffffe600
gdb$ p *(long*)$18
$19 = 0x400c70 # а здесь адрес уже подозрительно маленький, наверно лежит в сегменте .code, а значит мы добрались до верха стектрейса
gdb$ p *(long*)$19
$20 = 0x41d7894956415741
Чтение значения регистра
gdb$ p $rbp # прочесть значение регистра rbp
gdb$ p ($rbp) # прочесть значение по адресу, лежащему в регистре rbp
gdb$ p ($rbp + 8) # прочесть значение по адресу, лежащему на 8 байт выше, чем адрес в регистре rbp
Отладка SIGSEGV
Если на SIGSEGV установлен хэндлер, то по умолчанию GDB в него не будет заходить, потому что ловит сигнал раньше программы и завершает ее выполнение. Чтобы зайти в хэндлер, надо сделать так:
handle SIGSEGV pass
handle SIGSEGV nostop