Собираем его с опцией -g, что бы включить отладочную информацию:
gcc debug.c -g -o debug
Запускаем, и получаем ошибку:
./debug
Enter a number: 1
Segmentation fault
В результате получаем Segmentation fault, которая сигнализирует об ошибочном обращении к памяти.
Теперь используем gdb, что бы найти причину проблемы.
Содержание
Запуск gdb
Запускаем gdb, и первым аргументом передаём исполняемый файл для запуска:
gdb debug
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
...
Reading symbols from debug...done.
(gdb)
run
Теперь можно начать выполнение программы, используя команду run (или r):
(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.
backtrace
backtrace указывает gdb на необходимость вывести список всех вызываемых функций из стека программы:
(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
Тут видно, что debug.c в строке 33 вызывает printSum(), printSum () в строке 22 вызывает sprintf(), которая далее начинает выполнение более низкоуровневых функций, и в результате падает.
Всё, что выполняется после sprintf() нам неподконтрольно, и проблема возникает в данных, которые мы передаём в sprintf(), так что давайте изучим их внимательнее:
Далее — используем breakpoint-ы, что бы изучить значения переменных.
Break Point
Для того, что бы приостановить выполнение программы на каком-то этапе — мы можем задать один или цепочку брейкпоинтов, используя break (b).
Например, что бы сделать паузу перед вызовом sprintf() на строке 26 — указываем break 26, и запускаем программу заново — run (r):
(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)));
print
С помощью print (p) можно получить текущие значения переменных.
Тут в буфер sprintf() мы передаём переменую line — проверим её значение:
(gdb) print line
1 = "1\000\000IUUUU\000"
Видим нашу единицу, за ней null terminator — ‘\0‘, далее — мусор. С этим всё хорошо.
Проверим содержимое buf:
(gdb) print buf
2 = 0x0
Теперь проблема становится очевидной: мы пытаемся скопировать данные в буфер, на который указывает buf, но под него не выделена память, что и приводит к Segmentation fault.
К счастью — тут buf является глобальной переменной и была проинициализирована со значением 0 (null pointer). Если бы это было не так, и она находилась бы внутри функции — мы получили бы какое-то произвольное значение вида:
Зададим условие, но иначе — прямо при указании брейкпоинта, без использования condition, и используем if для самого break:
(gdb) break 27 if $_regex(buf, "^sum=1")
Breakpoint 1 at 0x920: file debug.c, line 27.
Запускаем, и указываем 1:
(gdb) r
Starting program: /home/admin/Scripts/debug
Enter a number: 1
Breakpoint 1, printSum () at debug.c:27
27 printf("%s\n",buf);
Если в sum= будет другое значение — условие не сработает:
(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]
Возвращаясь к проблеме с buf — проверим её значение сейчас, после того, как мы добавили malloc():
(gdb) p buf
2 = 0x555555756830 "sum=1"
next, step, until — шаги выполнения
next (n): выполняет текущую инструкцию, и переходит к началу следующей
step (s): аналогична next, но с отличиями: когда вы находитесь в начале функции, и используете next — функция будет выполнена полностью, и вернёт значение. Если использовать step — выполнение перейдёт к первой строке внутри функции
until (u): аналогична next, но в случае, если вы находитесь в начале цикла — выполнение будет продолжаться до конца выполнения этого цикла
Пример.
Запускаем программу, задаём точку на строке 8 — перед началом цикла: