GNU Debugger

GNU Debugger (GDB) — переносимый отладчик, работающий на многих Unix-подобных системах и поддерживающий множество языков программирования, включая Ada, ассемблер, C, C++, D, Fortran, Haskell, Go, Objective-C, OpenCL C, Modula-2, Pascal, Rust[2], а также частично и другие языки[3]. Позволяет обнаруживать ошибки в программах во время их выполнения и даёт пользователю широкие возможности по изучению содержимого памяти и регистров.

Общие сведения

История

GDB был впервые написан Ричардом Столлманом в 1986 году как часть операционной системы GNU после того, как GNU Emacs стал «достаточно устойчивым»[4]. GDB является свободным программным обеспечением и распространяется под лицензией GPL. Программа была создана по образцу отладчика DBX, входившего в дистрибутивы Berkeley Unix[4].

С 1990 по 1993 годы сопровождением GDB занимался Джон Гилмор[5]. В настоящее время за развитие отвечает руководящий комитет GDB, назначаемый Фондом свободного программного обеспечения[6].

Технические особенности

GDB предоставляет широкие возможности трассировки, изучения и изменения выполнения программ. Пользователь может просматривать и менять значения внутренних переменных, а также вызывать функции вне обычного контроля исполняемой программы.

Поддерживаемые платформы

В число поддерживаемых процессорных архитектур по состоянию на 2003 год входят: Alpha, ARM, AVR, H8/300, Altera Nios/Nios II, System/370, System 390, x86, x86-64, IA-64, Motorola 68000, MIPS, PA-RISC, PowerPC, RISC-V, SuperH, SPARC, VAX. Поддержка менее распространённых архитектур включает A29K, ARC, ETRAX CRIS, D10V, D30V, FR-30, FR-V, Intel i960, 68HC11, Motorola 88000, MCORE, MN10200, MN10300, NS32K, Stormy16 и Z8000 (в новых версиях поддержка части архитектур могла быть снята). Для некоторых малораспространённых процессоров, таких как M32R или V850, встроены эмуляторы инструкций[7].

Пошаговое выполнение

Команды next n и step n позволяют последовательно выполнять по n операторов (по умолчанию 1). Отличие этих команд в том, что step входит внутрь функции, а next выполняет её полностью и переходит к следующему оператору текущей процедуры[8].

Команда jump location применяется для пропуска проблемных участков кода либо возврата к предыдущей инструкции в рамках одной функции[9].

Точки останова и точки контроля (breakpoints/watchpoints)

Точки останова и точки контроля позволяют остановить выполнение программы перед подозрительным местом. Каждой точке присваивается идентификационный номер (1, 2, 3…), с помощью которого можно её enable, disable или delete. Команда info break или watch показывает список точек и их статус.

Точки останова могут устанавливаться на строку кода или на вход в функцию, например: break sourcefile:42 — останов на 42-й строке файла. Если имя файла опущено — используется текущий.

Условная точка останова срабатывает, только если выполнено выражение, например:

break sourcefile:275 if productNum=1275

Команда condition breakNum expression позволяет добавить условие к уже существующей точке[10].

Точка контроля (watchpoint) срабатывает, если значение переменной или выражения изменилось (вне зависимости от места в программе). Обычно GDB отслеживает адрес в памяти, что полезно, если к одному адресу обращаются разные указатели. Программная точка контроля (software watchpoint) работает медленнее и отслеживает только переменные.

Команды rwatch и awatch останавливают выполнение при чтении соответствующей области памяти или переменной[11].

Команда ignore breakNum count позволяет игнорировать остановку точки определённое число раз[12].

Поддержка сценариев

Начиная с версии 7.0, GDB поддерживает сценирование на Python[13], а начиная с версии 7.8 — на GNU Guile[14].

Обратимая отладка

С версии 7.0 реализована так называемая «обратимая отладка» — возможность двигаться назад по ходу выполнения программы (например, посмотреть, из-за чего произошёл сбой). Этот режим требует много памяти и снижает скорость задания; по умолчанию лимит составляет 20 000 инструкций. Рекомендованная практика: поставить точки останова до и после подозрительного участка, включить запись командой record, после второй точки — использовать команды reverse-step, reverse-next или reverse-continue. При этом обратимая отладка не откатывает вывод в консоль и не повторяет внешние события, такие как прерывания или сетевые пакеты[10][15].

Удалённая отладка

GDB поддерживает режим «удалённой» отладки, часто используемый в разработке встроенных систем. В этом режиме GDB работает на одной машине, а программа — на другой (отладчик соединяется с «stub» через последовательный порт или по TCP/IP)[16]. «Stub» может быть реализован с помощью специальных файлов, поставляемых с GDB, либо можно использовать утилиту gdbserver, не внося никаких изменений в код программы[17].

В этом же режиме работает KGDB, применяемая для отладки ядра Linux на исходном уровне. С её помощью разработчики могут ставить точки останова, выполнять пошаговую отладку и анализировать переменные ядра. Если доступны специальные аппаратные средства, можно устанавливать точки контроля на аппаратном уровне. KGDB требует второй машины, соединённой с отлаживаемым компьютером через последовательный кабель или Ethernet. В FreeBSD возможна отладка и через FireWire с использованием DMA[18].

Интерфейс пользователя

GDB не включает собственного графического интерфейса и по умолчанию работает через командную строку, хотя поддерживает и текстовый режим. Для него разработаны многочисленные графические оболочки, включая UltraGDB, Xxgdb, Data Display Debugger (DDD), Nemiver, KDbg, Xcode Debugger, GDBtk/Insight, Gede[19], Seer[20], HP Wildebeest Debugger GUI (WDB GUI) и другие. Многие интегрированные среды разработки также используют GDB как отладчик: Codelite, Code::Blocks, Dev-C++, Geany, GNAT Programming Studio (GPS), KDevelop, Qt Creator, Lazarus, MonoDevelop, различные проекты Eclipse, NetBeans, Visual Studio. В GNU Emacs реализован «GUD mode», для Vim существуют отдельные расширения (например, clewn).

Ряд сторонних инструментов для поиска утечек памяти и других ошибок также разработан для совместной работы с GDB.

Внутренняя архитектура

Для контроля выполнения программ GDB использует системный вызов ptrace, который позволяет отслеживать, управлять процессом, читать и менять содержимое его памяти и регистров[21].

Основные команды gdb Соответствующие вызовы ptrace
(gdb) start PTRACE_TRACEME — сделать родительский процесс отладчиком (вызывается отслеживаемым процессом)
(gdb) attach PID PTRACE_ATTACH — подключение к запущенному процессу
(gdb) step PTRACE_SINGLESTEP — переход к следующей инструкции
(gdb) stop kill(child_pid, SIGSTOP) или PTRACE_INTERRUPT
(gdb) continue PTRACE_CONT
(gdb) info registers PTRACE_GET(FP)REGS(ET) и PTRACE_SET(FP)REGS(ET)
(gdb) x PTRACE_PEEKTEXT, PTRACE_POKETEXT

Точка останова реализуется заменой инструкции в памяти на специальную; при её выполнении срабатывает сигнал SIGTRAP.

Примеры команд

$ gdb program Отладка программы «program» из командной строки
(gdb) run -v Запуск программы с параметрами
(gdb) bt Обратная трассировка (например, при сбое)
(gdb) info registers Вывод всех регистров
(gdb) disas $pc-32, $pc+32 Дизассемблирование

Пример сеанса отладки

Пример программы на C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

size_t foo_len(const char *s)
{
  return strlen(s);
}

int main(int argc, char *argv[])
{
  const char *a = NULL;

  printf("size of a = %lu\n", foo_len(a));

  exit(0);
}

Компиляция с помощью gcc на Linux с включением информации для отладки:

$ gcc example.c -Og -g -o example

Запуск скомпилированной программы:

$ ./example
Segmentation fault

Так как в примере возникает ошибка сегментирования, её можно проанализировать через GDB:

$ gdb ./example
GNU gdb (GDB) Fedora (7.3.50.20110722-13.fc16)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /path/example...done.
(gdb) run
Starting program: /path/example

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400527 in foo_len (s=0x0) at example.c:7
7	  return strlen (s);
(gdb) print s
$1 = 0x0

Ошибка происходит на строке 7, при вызове функции strlen с нулевым указателем s. В зависимости от реализации strlen (inline или нет) результат может быть разным, например:

GNU gdb (GDB) 7.3.1
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /tmp/gdb/example...done.
(gdb) run
Starting program: /tmp/gdb/example

Program received signal SIGSEGV, Segmentation fault.
0xb7ee94f3 in strlen () from /lib/i686/cmov/libc.so.6
(gdb) bt
#0  0xb7ee94f3 in strlen () from /lib/i686/cmov/libc.so.6
#1  0x08048435 in foo_len (s=0x0) at example.c:7
#2  0x0804845a in main (argc=<optimized out>, argv=<optimized out>) at example.c:14

Для корректной работы переменная a в функции main должна содержать строку:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

size_t foo_len(const char *s)
{
  return strlen(s);
}

int main(int argc, char *argv[])
{
  const char *a = "This is a test string";

  printf("size of a = %lu\n", foo_len(a));

  exit(0);
}

После перекомпиляции и повторного прогона в GDB результат будет корректен:

$ gdb ./example
GNU gdb (GDB) Fedora (7.3.50.20110722-13.fc16)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /path/example...done.
(gdb) run
Starting program: /path/example
size of a = 21
[Inferior 1 (process 14290) exited normally]

GDB выводит результат работы printf на экран и сообщает о нормальном завершении программы.

Примечания