Памятка по отладке при помощи GDB

19 января 2016

Ранее в заметке Основы использования отладчика 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.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.