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]
Ссылки по теме
- GDB Tutorial
- How to Debug Using GDB
- 3.9 Options for Debugging Your Program
- 5.2 Continuing and Stepping
- Linux: C – адресное пространство процесса