
gdb (GNU Project Debugger) используется при отладке/дебаге кода.
Ниже приводятся примеры работы с кодом на С.
Используем такой код:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char * buf;
int sum_to_n(int num) {
int i,sum=0;
for(i=1;i<=num;i++)
sum+=i;
return sum;
}
void printSum() {
char line[10];
printf("Enter a number: ");
fgets(line, 10, stdin);
if(line != NULL)
strtok(line, "\n");
sprintf(buf,"sum=%d", sum_to_n(atoi(line)));
printf("%s\n",buf);
}
int main(void) {
printSum();
return 0;
}
Собираем его с опцией -g, что бы включить отладочную информацию:
[simterm]
$ gcc debug.c -g -o debug
[/simterm]
Запускаем, и получаем ошибку:
[simterm]
$ ./debug Enter a number: 1 Segmentation fault
[/simterm]
В результате получаем Segmentation fault, которая сигнализирует об ошибочном обращении к памяти.
Теперь используем gdb, что бы найти причину проблемы.
Содержание
Запуск gdb
Запускаем gdb, и первым аргументом передаём исполняемый файл для запуска:
[simterm]
$ gdb debug GNU gdb (Debian 7.12-6) 7.12.0.20161007-git ... Reading symbols from debug...done. (gdb)
[/simterm]
run
Теперь можно начать выполнение программы, используя команду run (или r):
[simterm]
(gdb) r Starting program: /home/admin/Scripts/debug Enter a number: 1 Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7aaebcd in __GI__IO_default_xsputn (f=0x7fffffffe340, data=<optimized out>, n=4) at genops.c:450 450 genops.c: No such file or directory.
[/simterm]
backtrace
backtrace указывает gdb на необходимость вывести список всех вызываемых функций из стека программы:
[simterm]
(gdb) backtrace #0 0x00007ffff7aaebcd in __GI__IO_default_xsputn (f=0x7fffffffe340, data=<optimized out>, n=4) at genops.c:450 #1 0x00007ffff7a80e36 in _IO_vfprintf_internal (s=s@entry=0x7fffffffe340, format=format@entry=0x5555555549b7 "sum=%d", ap=ap@entry=0x7fffffffe468) at vfprintf.c:1320 #2 0x00007ffff7aa3afb in __IO_vsprintf (string=0x555555554720 <_start> "1\355I\211\321^H\211\342H\203\344\360PTL\215\005Z\002", format=0x5555555549b7 "sum=%d", args=args@entry=0x7fffffffe468) at iovsprintf.c:42 #3 0x00007ffff7a89357 in __sprintf (s=<optimized out>, format=<optimized out>) at sprintf.c:32 #4 0x00005555555548ef in printSum () at debug.c:26 #5 0x000055555555490c in main () at debug.c:33
[/simterm]
Тут видно, что debug.c в строке 33 вызывает printSum(), printSum () в строке 22 вызывает sprintf(), которая далее начинает выполнение более низкоуровневых функций, и в результате падает.
Всё, что выполняется после sprintf() нам неподконтрольно, и проблема возникает в данных, которые мы передаём в sprintf(), так что давайте изучим их внимательнее:
... 26 sprintf(buf,"sum=%d", sum_to_n(atoi(line))); ...
Далее — используем breakpoint-ы, что бы изучить значения переменных.
Break Point
Для того, что бы приостановить выполнение программы на каком-то этапе — мы можем задать один или цепочку брейкпоинтов, используя break (b).
Например, что бы сделать паузу перед вызовом sprintf() на строке 26 — указываем break 26, и запускаем программу заново — run (r):
[simterm]
(gdb) b 26 Breakpoint 1 at 0x8c2: file debug.c, line 26. (gdb) r Starting program: /home/admin/Scripts/debug Enter a number: 1 Breakpoint 1, printSum () at debug.c:26 26 sprintf(buf,"sum=%d", sum_to_n(atoi(line)));
[/simterm]
print
С помощью print (p) можно получить текущие значения переменных.
Тут в буфер sprintf() мы передаём переменую line — проверим её значение:
[simterm]
(gdb) print line $1 = "1\000\000IUUUU\000"
[/simterm]
Видим нашу единицу, за ней null terminator — ‘\0‘, далее — мусор. С этим всё хорошо.
Проверим содержимое buf:
[simterm]
(gdb) print buf $2 = 0x0
[/simterm]
Теперь проблема становится очевидной: мы пытаемся скопировать данные в буфер, на который указывает buf, но под него не выделена память, что и приводит к Segmentation fault.
К счастью — тут buf является глобальной переменной и была проинициализирована со значением 0 (null pointer). Если бы это было не так, и она находилась бы внутри функции — мы получили бы какое-то произвольное значение вида:
[simterm]
(gdb) p buf $2 = 0x555555554720 <_start> "1\355I\211\321^H\211\342H\203\344\360PTL\215\005Z\002"
[/simterm]
Попробуем исправить наш код — добавим вызов malloc() для выделения памяти под buf:
...
void printSum() {
char line[10];
printf("Enter a number: ");
fgets(line, 10, stdin);
if(line != NULL)
strtok(line, "\n");
char * buf = malloc(1024);
sprintf(buf,"sum=%d", sum_to_n(atoi(line)));
printf("%s\n",buf);
}
...
list
После обновления исходного кода — номера строк изменились.
Что бы увидеть исходный код из самого gdb — используем list (l):
[simterm]
(gdb) l
18 printf("Enter a number: ");
19 fgets(line, 10, stdin);
20
21 if(line != NULL)
22 strtok(line, "\n");
23
24 char * buf = malloc(1024);
25 sprintf(buf,"sum=%d", sum_to_n(atoi(line)));
26
27 printf("%s\n",buf);
[/simterm]
condition — break point с условиями
gdb позволяет задать условные точки паузы, т.е. пауза будет выполнена если условие верно.
Например — сделать паузу, если значение переменной line будет равно «1» можно так:
- задаём точку паузы —
(gdb) b 25 - задаём условие использовать breakpoint под номером 1, используя функцию
$_streq—(gdb) condition 1 $_streq(line, "1")
Проверяем:
[simterm]
(gdb) b 25 Breakpoint 1 at 0x920: file debug.c, line 25. (gdb) condition 1 $_streq(line, "1") (gdb) r Starting program: /home/admin/Scripts/debug Enter a number: 1 Breakpoint 1, printSum () at debug.c:25 25 sprintf(buf,"sum=%d", sum_to_n(atoi(line)));
[/simterm]
Enter a number: 1 — тут в line мы передаём единицу в виде string, условие срабатывает — получаем остановку выполнения.
При другом значении — программа выполнится полностью:
[simterm]
(gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/admin/Scripts/debug Enter a number: 2 sum=3 [Inferior 1 (process 3404) exited normally]
[/simterm]
См. 10.12 Convenience Functions и 5.1.6 Break Conditions.
Аналогично используется $_regex.
Зададим условие, но иначе — прямо при указании брейкпоинта, без использования condition, и используем if для самого break:
[simterm]
(gdb) break 27 if $_regex(buf, "^sum=1") Breakpoint 1 at 0x920: file debug.c, line 27.
[/simterm]
Запускаем, и указываем 1:
[simterm]
(gdb) r
Starting program: /home/admin/Scripts/debug
Enter a number: 1
Breakpoint 1, printSum () at debug.c:27
27 printf("%s\n",buf);
[/simterm]
Если в sum= будет другое значение — условие не сработает:
[simterm]
(gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/admin/Scripts/debug Enter a number: 2 sum=3 [Inferior 1 (process 3663) exited normally]
[/simterm]
Возвращаясь к проблеме с buf — проверим её значение сейчас, после того, как мы добавили malloc():
[simterm]
(gdb) p buf $2 = 0x555555756830 "sum=1"
[/simterm]
next, step, until — шаги выполнения
next(n): выполняет текущую инструкцию, и переходит к началу следующейstep(s): аналогичнаnext, но с отличиями: когда вы находитесь в начале функции, и используетеnext— функция будет выполнена полностью, и вернёт значение. Если использоватьstep— выполнение перейдёт к первой строке внутри функцииuntil(u): аналогичнаnext, но в случае, если вы находитесь в начале цикла — выполнение будет продолжаться до конца выполнения этого цикла
Пример.
Запускаем программу, задаём точку на строке 8 — перед началом цикла:
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int sum_to_n(int num) {
6
7 int i,sum=0;
8 for(i=1;i<=num;i++)
9 sum+=i;
10
11 return sum;
12 }
...
[simterm]
(gdb) b 8 Breakpoint 1 at 0x8ae: file debug.c, line 8.
[/simterm]
Начинаем выполнение:
[simterm]
(gdb) r Starting program: /home/admin/Scripts/debug Enter a number: 1 Breakpoint 1, sum_to_n (num=1) at debug.c:8 8 for(i=1;i<=num;i++)
[/simterm]
Остановились в начале цикла.
Пробуем next:
[simterm]
(gdb) n 9 sum+=i;
[/simterm]
Ещё раз next — снова возращаемся к началу цикла:
[simterm]
(gdb) 8 for(i=1;i<=num;i++)
[/simterm]
Но если вызовем until — весь цикл будет пройден полностью:
[simterm]
(gdb) u 11 return sum; (gdb) p sum $1 = 1
[/simterm]
Другие команды
list(l): уже упоминалась выше, отобразит исходный кодdelete(d): удаляет брейкпоиинты. Если вызвать без агрументов — удалит все точки, если указать номер (d 1) — то удалит заданную по номеруclear function_name: удалить все брейкпоинты в указанной функцииx: отобразить содержимое адреса
Пример использования x.
Получаем адрес переменной sum (используя &):
[simterm]
(gdb) p &sum $2 = (int *) 0x7fffffffe528
[/simterm]
Получаем значение памяти по адресу 0x7fffffffe528:
[simterm]
(gdb) x 0x7fffffffe528 0x7fffffffe528: 0x00000001
[/simterm]
Вот и наша единичка — 0x00000001:
[simterm]
(gdb) p sum $3 = 1
[/simterm]