Отладчик GDB позволяет нам увидеть, что происходит внутри выполняющейся программы, и, в частности, что программа "делает" в момент возникновения ошибки.
В качестве примера рассмотрим отладку программы вычисления факториала
#include <stdio.h>
#include <stdlib.h>
int fact(int num)
{
if(num <= 1)
{
return 1;
}
return num * fact(num - 1);
}
int main(int argc, char **argv)
{
int a = atoi(argv[1]);
printf("%d! = %d\n", a, fact(a));
return 0;
}
Скомпилируем ее с включением отладочной информации:
g++ -g fact.c -o fact
Опция -g
сообщает компилятору о том, что в программу нужно включить отладочную информацию, которой и воспользуется отладчик GDB. Опция -o сообщает компилятору g++ имя исполняемого файла (fact
). Если ее не указать, то, по умолчанию, компилятор создаст исполняемый файл и назовет его a.out.
Запустим нашу программу в режиме отладки:
gdb -q fact
Опция -q
используется для того, чтобы не выводилась информация о версии отладчика, лицензионное соглашение и т. п.
Итак, отладчик запущен. Теперь можно выполнять его команды.
Для просмотра исходного кода программы используется команда list, в которую можно передать номер строки для того, чтобы вывести код, окружающий переданную строку.
(gdb) list 4
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int fact(int num)
5 {
6 if(num <= 1)
7 {
8 return 1;
9 }
10
Также можно передать название функции, после чего будет выведен код, окружающий эту функцию. Так, list fact
даст тот же результат, что и предыдущая команда.
Запустим теперь выполнение нашей программы отладчиком. Это можно сделать одной из двух команд: run
или start
. Отличие между ними в том, что после вызова start
отладчик останавливается на входе в функцию main()
, а команда run
выполняется до тех пор, пока не встретит точку останова (о них мы поговорим позднее).
Обе эти команды принимают в качестве параметров аргументы программы, так же, как если бы программа запускалась из командной строки.
Запустим программу на выполнение с помощью start
:
(gdb) start 4
Temporary breakpoint 1 at 0x80484fc: file fact.c, line 16.
Starting program: /home/dima/work/fact 4
Temporary breakpoint 1, main (argc=2, argv=0xbffff1e4)
at /home/dima/work/fact.c:16
16 int a = atoi(argv[1]);
Как видно, отладчик остановился на входе в функцию main()
, и вывел содержимое и номер строки, перед которой остановился.
Используя команду print
, можно вывести на экран значения переменных и выражений. Причем писать полное название команды необязательно, достаточно использовать ее краткий вариант — p
: Посмотрим какие входные аргументы приняла наша программа:
(gdb) print argv[0]
$1 = 0xbffff38e "/home/dima/work/fact"
(gdb) p argv[1]
$2 = 0xbffff3ba "4"
В первом примере мы использовали полное название команды, во втором — сокращенное. Многие команды отладчика имеют подобные сокращенные варианты.
Как видно, в нулевом элементе массива argv
содержится путь к исполняемому файлу программы, а в первом элементе — переданный программе аргумент (4
).
Для того, чтобы перейти на следующую строку, необходимо выполнить команду step
или next
. С помощью этих команд можно передвигаться по программе. Отличие между ними состоит в том, что step
заходит внутрь функций (step in), а next
просто переходит на следующую строку (step over).
Перейдем к следующей строке:
(gdb) next
17 printf("%d! = %d\n", a, fact(a));
Мы сделали один шаг и остановились перед 17-ой строкой. Здесь мы скомандовали next
, а не step
, поскольку не хотели, чтобы отладчик заходил внутрь библиотечной функции atoi()
. Краткие названия step
и next
— s
и n
соответственно. Мы будем использовать в примерах полные названия, а затем, в конце, приведем сводку команд и их сокращений.
Пойдем дальше:
(gdb) step
fact (num=4) at fact.c:6
6 if(num <= 1)
Мы вошли в функцию fact
с входным параметром 4
и оказались перед 6-ой строкой.
Посмотрим теперь как будет изменяться значение переменной num
при каждом рекурсивном входе в функцию fact()
. Добавим в процесс отладки слежение за переменной num
. Для этого существует команда display
, которой необходимо передать название переменной (num
). Выполним команду display, и перейдем на один шаг вперед:
(gdb) display num
1: num = 4
(gdb) step
11 return num * fact(num - 1);
1: num = 4
Теперь при каждой остановке программы GDB будет сообщать нам значение переменной num
.
Сейчас мы находимся перед рекурсивным входом в функцию fact()
. Зайдем в нее и сделаем еще один шаг вперед:
(gdb) step
fact (num=3) at fact.c:6
6 if(num <= 1)
1: num = 3
(gdb) step
11 return num * fact(num - 1);
1: num = 3
Отладчик сообщает нам значение переменной num
, которое теперь равно 3
.
Вместо того, чтобы проделывать эти шаги вручную, нам достаточно было бы отслеживать какие значения принимает переменная num в 11-ой строке, т. к. именно в этой строке происходит рекурсивный вызов функции и возвращается результат ее выполнения.
Для того, чтобы этого добиться, используются точки останова. В этих точках отладчик останавливает выполнение программы и ждет дальнейших действий пользователя.
Точка останова указывается с помощью команды break
, аргументами которой являются номер строки или имя функции. Поставив точку останова, мы даем отладчику указание выполнять программу вплоть до этой строки без дальнейшего вмешательства с нашей стороны. Для этого используется команда continue
, которая продолжает выполнение программы до тех пор, пока не встретится точка останова. Если таких точек нет, то программа выполнится до конца.
Давайте поставим точку останова в строке 11, и выполним команду continue
:
(gdb) break 11
Breakpoint 1 at 0x80484df: file fact.c, line 11.
(gdb) continue
Continuing.
Breakpoint 1, fact (num=2) at fact.c:11
11 return num * fact(num - 1);
1: num = 2
Мы прошли все шаги, которые до этого проделывали командой step
. Теперь, каждый раз выполняя команду continue
мы будем оказываться на 11-ой строке и, благодаря выполненной ранее команде display
, сможем наблюдать как изменяется значение переменной num
на каждом шаге.
Если точка останова больше не нужна, ее можно удалить командой clear
. В качестве аргументов используются номер строки, в торой находится точка останова (11) или номер точки останова (1). clear
без аргументов удаляет все установленные в программе точки останова.
Удалим точку останова:
(gdb) clear 11
Deleted breakpoint 1
и продолжим выполнение программы:
(gdb) continue
Continuing.
4! = 24
Program exited normally.
Отладчик показал вывод нашей программы, а именно подсчитанный факториал от 4. Последняя строка в выводе отладчика сообщает, что программа завершилась нормально. Если бы это было не так, то отладчик вывел бы код возврата программы в восьмеричной форме.
Очень полезной может оказаться команда set variable
, которая позволяет изменять значение переменной по ходу выполнения программы. Запустим нашу программу сначала:
(gdb) start 4
Temporary breakpoint 1 at 0x8048556: file fact.c, line 16.
Starting program: /home/dima/work/fact 4
Temporary breakpoint 1, main (argc=2, argv=0xbfffef84) at fact.c:16
16 int a = atoi(argv[1]);
(gdb) step
17 printf("%d! = %d\n", a, fact(a));
(gdb) set variable a=10
(gdb) c
Continuing.
10! = 3628800
В этом примере мы запустили программу с входным параметром 4
, но по ходу выполнения программы заменили значение параметра на 10
. В результате программа подсчитала факториал 10.
Команду set variable
удобно применять, когда вы обнаружили, что программа ведет себя неверно, но путем изменения значения переменной можно исправить ситуацию и посмотреть как программа будет вести себя дальше.
Есть и другой способ следить за изменением переменной в ходе работы программы — с помощью команды watch
. В отличие от display
, команда watch
будет останавливать выполнение программы при каждом изменении переменной, переданной в качестве параметра. При этом будут выводиться старое и новое значение переменной.
Обе команды display
и watch
могут в качестве параметров принимать не только переменные, но и выражения.
Рассмотрим еще один пример
```C linenum
include
include
void bar(int p) { p = 128; }
void foo(int *p) { bar(p); }
int main() { int p = (int ) calloc(sizeof(int *), 1);
printf("main start: p is %d\n", *p);
foo(p);
printf("main end: p is %d\n", *p);
return 0;
}
В нем резервируется память, адрес которой хранится в указателе `*p`, и нам необходимо отследить, в каком месте программы изменяется содержимое этой области памяти.
Точки останова для этого использовать неудобно — их придется ставить во всех "подозрительных" местах, где встречается `*p`, а таких в реальной программе может оказаться много. Поэтому с помощью команды `wacth` мы установим точку наблюдения за `*p`.
Запустим программу в отладчике, сделаем несколько шагов, и установим точку наблюдения за `*p`:
```bash
(gdb) start
Temporary breakpoint 1 at 0x80484f6: file /home/dima/work/sample.c, line 16.
Starting program: /home/dima/work/sample
Temporary breakpoint 1, main () at /home/dima/work/sample
/main.c:16
16 int *p = (int *) calloc(sizeof(int *), 1);
(gdb) n
18 printf("main start: p is %d\n", *p);
(gdb) n
main start: p is 0
19 foo(p);
(gdb) watch *p
Hardware watchpoint 6: *p
(gdb) c
Continuing.
Hardware watchpoint 6: *p
Old value = 0
New value = 128
bar (p=0x804b008) at /home/dima/work/gdb_test/main.c:7
7 }
Выполнение программы было остановлено перед 7-ой строкой, внутри функции bar()
, где значение *p
стало равным 128.
Если нам интересно более подробно узнать, в каком именно месте остановилась выполнение программы, это можно сделать с помощью команды backtrace
:
(gdb) backtrace
#0 bar (p=0x804b008) at /home/dima/work/gdb_test/main.c:7
#1 0x080484eb in foo (p=0x804b008) at /home/dima/work/gdb_test/main.c:11
#2 0x08048530 in main () at /home/dima/work/gdb_test/main.c:19
Информация, которую выдаем нам отладчик означает, что мы находимся внутри выполняющейся функции bar()
(перед строкой 7), вызванной из функции foo()
(в строке 11), которая, в свою очередь, вызвана из функции main()
(в строке 19). Таким образом, команда backtrace
показывает весь стек вызываемых функций от начала программы до текущего места.
Сводка команд
Запуск и прекращение работы
Команда | Описание |
---|---|
r (run ) |
запуск программы |
kill |
убиваем текущий процесс |
Ctrl+c |
прерывание работы |
q (quit ) |
выход из GDB |
Точки останова и наблюдения
Команда | Описание |
---|---|
b (break ) |
установить точку останова |
b <функция> |
...на заголовке заданной функции |
b <строка> |
...в заданной строке |
b <файл>:<строка> |
... в заданной строке файла |
b <файл>:<функция> |
... на заголовке заданной функции из файла |
b <адрес функции> |
возможность установки точки останова на адрес функции очень полезна, когда надо попасть в одну из множества копий одной функции (например, статическая inline-функция, объявленная в заголовочном файле) |
b <выражение> if <условие> |
поставить условную точку останова, аналогично установке cond для нее |
tb (tbreak ) |
поставить временную точку останова которая сработает один раз, а затем будет удалена |
cond <N> <условие> |
установить условие срабатывания точки останова номер N |
[ watch | rwatch | awatch ] <выражение> |
установить точку наблюдения, которая сработает, если значение по адресу выражение [ изменяется | читается | читается/изменяется ] . Например: (gdb) watch ((int)0x0B0B0B) означает следить за значением по адресу 0x0B0B0B |
info brakepoints |
показать информацию обо всех точках останова и наблюдения |
disable <N> |
выключить точку останова/наблюдения под номером N, но не удалять ее |
d (delete ) [ <список номеров> ] |
удалить точки останова/наблюдения с номерами из списка |
clear [ <функция> | <файл:строка> ] |
удалить точки останова привязанные к функции/файлу |
Управление выполнением
Команда | Описание |
---|---|
n | next |
следующий шаг выполнения (следующая строчка кода) |
s | step |
углубляемся в стек выполнения (заходим внутрь вызываемых функций) |
c | continue |
продолжаем работу программы |
u | until <место> |
продолжаем работу программы до заданного места. Указания места такие же, как для break |
Трассировка
Команда | Описание |
---|---|
bt | backtrace |
показать стек вызовов текущей точки |
up |
подняться на одну функцию вверх в текущем стеке вызовов |
down |
вглубь стека вызовов на одну функцию |
Вывод: строки, переменные, выражения, функции
Команда | Описание |
---|---|
l (list ) |
показать исходник вокруг текущей строчки (по умолчанию - 10 строк, повторный вызов l показывает следующие 10 строк) |
l 247 |
показать исходник вокруг строки 247 |
l <функция> |
показать исходник функции <функция> |
p [ <переменная> | <выражение> ] |
показать значение переменной, памяти, выражения |
set <переменная> = <значение> |
установить <значение> для <переменной> |
info [ locals | args ] > |
показать значения [ локальных переменных | аргументов функции ] |
call <функция> |
вызвать функцию <функция> |
миниFAQ
Как в gdb запустить программу с аргументами arg1, arg2,...?
- Указать аргументы программы (
myprogram
) в командной строке при запуске gdb:
gdb --args myprogram arg1 arg2
- В запущенном gdb указать аргументы при старте программы:
gdb myprogram
(gdb) r arg1 arg2
Как начать отладку с первой строки программы?
(gdb) b main
(gdb) r
Как перейти к просмотру нужного метода класса?
(gdb) b filename:ClassName::methodName
(gdb) r # или c, если программа уже запущена
Как остановить выполнение программы в заданной строке при выполнении заданного условия?
Допустим, в следующей программе
#include <stdio.h>
int main()
{
for (char i = 0; ++i; )
printf("%i,", i);
return 0;
}
нужно остановить выполнение в строке 6, когда i
станет равно 127
.
Устанавливаем условную точку останова
(gdb) b 6 if i == 127
и выполняем программу до этой точки.
Комментарии
comments powered by Disqus