How's that again?

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:175
  • b start_work - установить брейкпойнт в начало функции start_work
  • b start_work if b == 0 - установить брейкпойнт с условием
  • p a - вывод переменной а
  • p *a - вывод значения по адресу а
  • bt - текущий бэктрэйс

Watchpoints

  • watch expr - установить брейкпойнт на запись выражения expr
  • rwatch expr - установить брейкпойнт на чтение выражения expr
  • awatch 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