Памятка по отладке при помощи GDB
Ранее в заметке Основы использования отладчика WinDbg мы узнали, как можно отлаживать приложения под Windows. Теперь настало время познакомиться с отладчиком gdb, который позволяет делать все то же самое под Linux и другими *nix системами. Благодаря этой заметке вы узнаете, как при помощи gdb ставить брейкпоинты и смотреть значения локальных переменных, анализировать coredump’ы и вот это все.
Для комфортной отладки сначала нужно собрать программу с правильными флагами. Во-первых, необходимо указать флаг -g. Иначе программа будет собрана без отладочных символов и отлаживать ее придется в ассемблерном коде (что, впрочем, вполне выполнимо для программ, написанных на чистом Си). Во-вторых, рекомендуется отключить оптимизации при помощи флага -O0, иначе некоторые переменные окажутся, что называется, optimized out, и вы не сможете посмотреть их значения при отладке.
Другими словами, при прочих равных программу лучше собирать как-то так:
gcc -O0 -g test.c -o test
Запускаем программу в отладчике:
gdb ./test
При необходимости передать программе какие-то аргументы можно так:
gdb --args ./test --ololo --trololo
Запуск с так называемом Text User Interface, интерфейсом на основе curses (мне лично он не нравится):
gdb -tui ./test
Еще можно прицепиться к уже работающему процессу по его id:
gdb -p 12345
Для анализа корок используем команду вроде такой:
gdb /path/to/prog /tmp/core_prog.1234
Чтобы корки вообще писались, систему нужно правильно настроить:
ulimit -c unlimited
sudo sysctl -w kernel.core_pattern='/tmp/core_%e.%p'
Плюс в /etc/security/limits.conf пишем что-то вроде:
# где eax - имя вашего пользователя в системе
eax soft core 50000
eax hard core 500000
… а в /etc/sysctl.conf:
kernel.core_pattern = /tmp/core_%e.%p
Кстати, корки можно создавать «вручную» прямо из gdb при помощи команды:
generate-core-file
Для удаленной отладки на сервере выполняем команды:
sudo apt-get install gdbserver
gdbserver :3003 myprog
На клиенте говорим откуда брать отладочные символы:
gdb -q myprog
… и цепляемся:
target remote 10.110.0.10:3003
Заметьте, что в Ubuntu при попытке прицепиться к своим процессам без sudo вы можете получить ошибку вроде такой:
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or
try again as the root user.
For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
Для решения этой проблемы говорим:
sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
… а также правим /etc/sysctl.d/10-ptrace.conf:
kernel.yama.ptrace_scope = 0
Итак, тем или иным образом мы прицепились отладчиком куда надо. Теперь нам доступны следующие команды.
Хэлп:
h
Для выполнения команды в gdb можно вводить либо всю команду целиком, либо только первые несколько букв. То есть, h, he и help или r, ru и run – это все одни и те же команды. Здесь я преимущественно буду использовать короткие версии, так как это то, что вы скорее всего будете использовать на практике. К тому же, соответствующие длинные версии обычно очевидны.
Начать выполнение программы, если она еще не выполняется:
r
Продолжить выполнение программы:
c
Прервать выполнение в любой момент можно нажатием Ctr+C.
Отцепиться от программы, оставшись при этом в отладчике:
detach
Выйти из отладчика (если цеплялись через -p, процесс продолжит работу):
q
Просмотр исходного кода программы:
l
l номер_строки
l откуда,докуда
Если вы отлаживаете программу не на том же сервере, на котором программа была собрана, на него нужно залить исходиники по тому же пути, что использовался на билд сервере, иначе листинг не будет работать.
Когда исходников под рукой нет, можно посмотреть ассемблерный код:
disassemble
Step – шаг вперед или несколько шагов вперед:
s
s 3
Next – как step, только без захода внутрь других методов и процедур:
n
n 3
Until – выполнить программу до указанной строчки:
u 100
Продолжить выполнение до возвращения из текущей процедуры:
fin
Показать стэктрейс:
backtrace
bt
Перемещение между фреймами стака:
f 0
f 1
Информация о текущем фрейме:
info frame
Показать аргументы в текущем фрейме:
info args
Показать локальные переменные в текущем фрейме:
info locals
Ставим бряк:
b mysourcefile.c:123
b my_procedure_name
Список бряков:
info b
Удаление бряка по номеру:
d 1
Удаление всех бряков:
d
Временное включение и выключение бряков:
enable 1
disable 1
Проигнорировать столько-то итераций:
ignore 1 15
Условные брейкпоинты ставятся как-то так:
b место if variable == 1
Еще вы можете установить брейкпоинт прямо в исходном коде вашей программы:
/* ... */
do_something();
/* одинаковый синтаксис для GCC и CLang */
__asm__ __volatile__("int3");
do_something_else();
/* ... */
Список нитей:
info threads
Переключение на нитку:
thread 1
Вывести значение переменной:
p myvar
Также можно кастовать типы:
p (int)myvar
Обращаться к полям:
p mystruct.field
p *mystryct.otherfield
Вообще можно использовать gdb как калькулятор:
p (10.0+20+30)/sizeof(int)
Можно менять значения переменных в программе:
p someVar=123
Есть ограниченная поддержка переменных:
set $i = (int)0
p $i
Можно вызывать процедуры и методы:
set $pi = (int*)malloc(sizeof(int))
p $pi
p free($pi)
Дамп памяти (explore):
x/16xb some_var
x/32uh some_var
x/64dw some_var
В приведенных примерах выводится дамп (1) 16-и байт с выводом в hex, (2) 32-х полуслов, которые выводятся, как числа без знака и (3) 64-х слов, которые выводятся, как числа со знаком.
Еще gdb умеет выполнять «скрипты», что позволяет, к примеру, быстро получить текущий стектрейс процесса и тут же отсоединиться, почти не останавливая его работу:
gdb --batch --command=gdb.script -p 12345
Описанных выше возможностей gdb вам хватит в 95% случаев. Но не следует думать, что ничего другого gdb не умеет. Мы не рассмотрели вачдоги, форматы вывода в стиле p/x, DDD, и многое другое. GDB User Manual занимает более 700 страниц, поэтому пересказать его в рамках небольшого поста никак не представляется возможным. Из мануала вы узнаете, как отлаживать процессы, использующие системный вызов fork(), а также многие другие интересные вещи.
Дополнение: Еще вас могут заинтересовать статьи Памятка по использованию отладчика LLDB, Reverse debugging кода на C/C++ с помощью GDB и RR, а также Один простой, но эффективный отладочный прием. Больше об отладке ассемблерного кода с помощью GDB вы узнаете из поста Написание и отладка кода на ассемблере x86/x64 в Linux.