15.3.2. Установка контрольных точек, пошаговое выполнение и отслеживаемые точки
Часто при ошибках программ создается дамп ядра. Первым шагом является использование GDB с файлом core для определения процедуры, в которой произошло завершение программы. Если оригинальный двоичный файл не был откомпилирован для отладки (т.е. без -g), все, что может сообщить GDB, это имя функции, но больше никаких деталей.
Следующим шагом является перекомпилирование программы с возможностью отладки и без оптимизации, а также проверка того, что она все еще содержит ошибку. Предположив, что это так, можно запустить программу под контролем отладчика и установить контрольную точку в процедуре, вызывающей ошибку.
Контрольная точка (breakpoint) является точкой, в которой исполнение должно прерваться, остановиться. Контрольные точки можно установить по имени функции, номеру строки исходного файла, файлу исходного файла совместно с номером строки, а также другими способами.
После установки контрольной точки программа запускается с использованием команды run, за которой могут следовать аргументы командной строки, которые должны быть переданы отлаживаемой программе. (GDB удобным образом запоминает за вас аргументы; если нужно снова запустить программу с начала, все что нужно — это напечатать лишь саму команду run, и GDB запустит новую копию с теми же аргументами, как и ранее). Вот короткий сеанс с использованием gawk:
$ gdb gawk /* Запуск GDB для gawk */
GNU gdb 5.3
...
(gdb) break do_print /* Прерывание в do_print */
Breakpoint 1 at 0x805a36a: file builtin.c, line 1504.
(gdb) run 'BEGIN { print "hello, world" }' /* Запуск программы */
Starting program: /home/arnold/Gnu/gawk/gawk-3.1.3/gawk 'BEGIN { print "hello, world" }'
Breakpoint 1, do_print (tree=0x8095290) at builtin.c:1504
1504 struct redirect *rp = NULL; /* Исполнение достигает контрольной точки */
(gdb) list /* Показать исходный код */
1499
1500 void
1501 do_print(register NODE *tree)
1502 {
1503 register NODE **t;
1504 struct redirect *rp = NULL;
1505 register FILE *fp;
1506 int numnodes, i;
1507 NODE *save;
1508 NODE *tval;
По достижении контрольной точки вы проходите программу в пошаговом режиме. Это означает, что GDB разрешает программе исполнять лишь по одному оператору исходного кода за раз. GDB выводит строку, которую собирается выполнить, и выводит приглашение. Чтобы выполнить оператор, используется команда next:
(gdb) next /* Выполнить текущий оператор (строка 1504 выше) */
1510 fp = redirect_to_fp(tree->rnode, &rp); /* GDB выводит следующий оператор */
(gdb) /* Нажмите ENTER для его выполнения и перехода к следующему */
1511 if (fp == NULL)
(gdb) /* снова ENTER */
1519 save = tree = tree->lnode; (gdb) /* И снова */
1520 for (numnodes = 0; tree != NULL; tree = tree->rnode)
Команда step является альтернативной командой для пошагового исполнения. Между next и step есть важное различие, next выполняет следующий оператор. Если этот оператор содержит вызов функции, эта функция вызывается и возвращается до того, как GDB вернет себе управление от работающей программы.
С другой стороны, когда вы используете с содержащим вызов функции оператором step, GDB входит в вызываемую функцию, позволяя вам продолжить пошаговое исполнение (или трассировку) программы. Если оператор не содержит вызов функции, step аналогична next.
ЗАМЕЧАНИЕ. Легко забыть, какая команда была использована, и продолжать нажимать ENTER для выполнения последующих операторов. Если вы используете step, вы случайно можете войти в библиотечную функцию, такую как strlen() или printf(), с которой на самом деле не хотите возиться. В таком случае можно использовать команду finish, которая вызывает исполнение программы до возврата из текущей функции
Вывести содержимое памяти можно с использованием команды print. GDB распознает синтаксис выражений С, что упрощает и делает естественным проверку структур, на которые ссылаются указатели:
(gdb) print *save /* Вывести структуру, на которую указывает save */
$1 = {sub = {nodep = {l = {lptr = 0x8095250, param_name = 0x8095250 "pR\t\b",
l1 = 134828624}, r = {rptr = 0x0, pptr = 0, preg = 0x0,
hd = 0x0, av = 0x0, r_ent =0}, x = {extra = 0x0, x1 = 0,
param_list = 0x0},
name = 0x0, number = 1, reflags = 0}, val = {
fltnum = 6.6614191194446594e-316, sp = 0x0, slen = 0, sref = 1,
idx = 0}, hash = {next = 0x8095250, name = 0x0, length = 0, value = 0x0,
ref = 1}}, type = Node_expression_list, flags = 1}
В заключение, команда cont (continue — продолжить) дает возможность продолжить выполнение программы. Она будет выполняться до следующей контрольной точки или до нормального завершения, если других контрольных точек нет. Этот пример продолжается с того места, на котором остановился предыдущий:
1520 for (numnodes = 0; tree != NULL; tree = tree->rnode)
(gdb) cont /* Продолжить *!
Continuing.
hello, world
Program exited normally. /* Сообщение от GDB */
(gdb) quit /* Выйти из отладчика */
Отслеживаемая точка (watchpoint) подобна контрольной точке, но используется для данных, а не для кода. Отслеживаемые точки устанавливаются для переменной (или поля структуры или объединения или элемента массива), при их изменении GDB посылает уведомления. GDB проверяет значение отслеживаемой точки по мере пошагового исполнения программы и останавливается при изменении значения. Например, переменная do_lint_old в gawk равна true, когда была использована опция --lint_old. Эта переменная устанавливается в true функцией getopt_long(). (Мы рассмотрели getopt_long() в разделе 2.1.2 «Длинные опции GNU»). В файле main.c программы gawk:
int do_lint_old = FALSE;
/* предупредить о материале, не имевшейся в V7 awk */
...
static const struct option optab[] = {
...
{ "lint-old", no_argument, &do_lint_old, 1 },
...
};
Вот пример сеанса, показывающего отслеживаемую точку в действии:
$ gdb gawk /* Запустить GDB с gawk */