Отладка в The GNU Project Debugger

Отладчик (англ., debugger) – это программа, позволяющая контролировать выполнение другой (отлаживаемой) программы. Отладчик позволяет выполнять программу по шагам (пошаговая отладка), просматривать значения ячеек памяти, устанавливать точки останова и т. д

Debugging with GDB (англ.)

Можно выделить три основных способа использования отладчика:

  • запуск исследуемой программы из-под отладчика;
  • подключение отладчика к работающей программе;
  • «посмертная» отладка

В первом случае сначала запускается отладчик, настраиваются параметры запускаемой программы, затем отлаживаемая программа запускается под контролем отладчика.

В первом случае сначала запускается отладчик, настраиваются параметры запускаемой программы, затем отлаживаемая программа запускается под контролем отладчика.

При "посмертной" отладке исследуется т.н. файл дампа памяти (англ., core dump) программы. Файл дампа памяти содержит значения ячеек памяти адресного пространства процесса, он создается ядром операционной системы (ОС), когда программа получает сигнал какой-либо «фатальной» ошибки. При посмертной отладке процесса отлаживаемой программы уже не существует, поэтому возможности отладчика ограничены, тем не менее такая форма отладки может быть полезна.

С помощью отладчика можно отлаживать любой исполняемый файл в терминах адресов ОЗУ, регистров ЦП и инструкций процессора, но удобнее вести отладку в терминах языка высокого уровня (ЯВУ), то есть в терминах операторов ЯВУ, переменных и т. п. Для этого в скомпилированной программе должна присутствовать отладочная информация.

У компилятора GCC за подключение отладочной информации отвечает опция -g. Например,


$ gcc -g prog.c -o prog

программа в файле marvin.c будет скомпилирована в исполняемый файл marvin с отладочной информацией.

Рассмотрим подробнее три сценария использования отладчика

Запуск исследуемой программы из­под отладчика

При запуске отладчика указывается только исполняемый файл с запускаемой программой prog


$ gdb prog 

Исполняемый файл prog будет загружен, но не будет запущен. Отладчик GDB выдаст приглашение ко вводу команды. Перед запуском программы на выполнение можно задавать точки останова, отслеживаемые переменные. Для запуска программы надо выполнить команду отладчика run. Примерный сценарий работы приведен ниже. Команды, вводимые пользователем, выделены полужирным шрифтом.

компилируем с отладочной информацией


$ gcc -g prog.c -oprog

запускаем отладчик


$ gdb prog 

GNU gdb (GDB) Fedora (7.2-51.fc14) 
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/cher/gdb/prog...done.
(gdb) ...

устанавливаем точку останова на функцию main


(gdb) b main 
Breakpoint 1 at 0x80483bd: file prog.c, line 5.

запускаем программу на выполнение


(gdb) run 
Starting program: /home/cher/gdb/prog
Breakpoint 1, main () at prog.c:55 printf("Hello, I'm Eddie!\n");
(gdb) ...

-выполнение будет приостановлено в начале функцию main

Подключение отладчика к работающей программе 

Иногда предположение о наличии ошибки в программе возникает когда программа уже запущена и работает существенное время, а перезапуск программы с начала нецелесообразен. Например, вычислительная программа работает дольше ожидаемого времени и возникло подозрение, что она зациклилась, или серверная программа работает не так, как ожидается. В этих случаях можно подключиться к работающей программе отладчиком. Отладчик GDB в этом случае запускается с двумя аргументами: первый - это имя исполняемого файла, второй аргумент - число - идентификатор процесса (PID). Примерный сценарий работы с отладчиком приведен ниже.

программа называется ej-users и работает как процесс 7096


$ gdb ./ej-users 7096 
GNU gdb (GDB) Fedora (7.2-51.fc14)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. 
Type "show copying" and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu". 
Reading symbols from /home/cher/ejudge/ej-users...done.
Attaching to program: /home/cher/ejudge/ej-users, process 7096
0xb78c1424 in __kernel_vsyscall ()
(gdb) ...

Выполнение программы приостановлено в этой точке. Печатаем стек вызовов функций командой bt - backtrace:


(gdb) bt 
#0 0xb78c1424 in __kernel_vsyscall ()
#1 0x00a8788d in ___newselect_nocancel () at ../sysdeps/unix/syscall-template.S:82
#2 0x0806e502 in do_work () at userlist-server.c:10244
#3 0x0806fee5 in main (argc=5, argv=0xbfb06754) at userlist-server.c:10816 

поднимаемся на два уровня вверх по стеку вызовов вверх


(gdb) up 2
#2 0x0806e502 in do_work () at userlist-server.c:10244 10244 
val = select(max_fd, &rset, &wset, NULL, &timeout); 

печатаем значение переменной max_fd командой p - print (значение переменной равно 7):


(gdb) p max_fd
$1 = 7

завершаем отладку


(gdb) quit 
A debugging session is active.
    Inferior 1 [process 7096] will be detached.
    Quit anyway? (y or n) y
Detaching from program: /home/cher/ejudge/ej-users, process 7096

подтверждаем завершение отладки, осле этого программа продолжит выполнение

Посмертная отладка 

После аварийного завершения программы, например, в результате попытки доступа к области памяти, не принадлежащей процессу, либо при делении на 0, либо при вызове функции abort(), еще остается возможность узнать состояние, в котором находилась программа в момент ошибки.

Для этого при аварийном завершении программы ядром ОС должен быть сгенерирован файл дампа памяти аварийно- завершившейся программы (так называемый core-файл). Создание core-файлов может быть по умолчанию отключено. Например,

запускаем программу


$ ./prog 
Hello, I'm Eddie!
Aborted 

Если выполнение программы аварийно завершилось, но core-файла нет, то чтобы разрешить создание core-файлов нужно снять ограничение на максимальный размер core-файлов.


$ ulimit -c unlimited 

Тогда работа программы может выглядеть примерно так:

Запускаем программу


$ ./prog 
Hello, I'm Eddie!
Aborted 

в текущем каталоге должен появиться core-файл core.7911


$ ls 
core.7911 prog prog.c 

В данном примере имя core-файла включает в себя PID процесса, при завершении которого был создан core-файл, но иногда такой файл называется просто core.

Для посмертной отладки GDB запускается с двумя аргументами: первый - это имя исполняемого файла, а второй - это имя core-файла. Примерный сценарий работы с отладчиком в этом случае приведен ниже.

запускаем посмертную отладку


$ gdb ./prog core.7911 
GNU gdb (GDB) Fedora (7.2-51.fc14)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later

This is free software: you are free to change and
redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/cher/gdb/prog...done.
[New Thread 7911]
Core was generated by `./prog'.
Program terminated with signal 6, Aborted.
#0 0xb782a424 in __kernel_vsyscall ()
(gdb)

просматриваем стек вызовов


(gdb) bt 
#0 0xb782a424 in __kernel_vsyscall ()
#1 0x009e32f1 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2 0x009e4d5e in abort () at abort.c:92
#3 0x080483fe in main () at prog.c:6 

завершаем отладку


(gdb) quit 

Основные команды отладчика 

Ниже описываются основные команды отладчика GDB. Команды вводятся с консоли после отображения отладчиком (gdb) - строки-приглашения ко вводу очередной команды.

quit завершение работы отладчика. Если программа запускалась из-под отладчика (первый способ отладки), то вместе с завершением отладки завершится и программа. Если отладчик подключался к работающей программе, то программа продолжит выполнение в обычном режиме. Если отладчик запускался для исследования core-файла, то отладчик просто завершается, так как никакого отлаживаемого процесса не существует. Вместо набора команды quit можно просто нажать комбинацию Ctrl-D – стандартная комбинация, обозначающая признак конца файла (конец ввода) в Unux.

run запустить программу на выполнение. Команда доступна только, если программа еще не запущена. При подключении к работающему процессу или исследовании core-файлов данная команда не доступна. Допускается указывать аргументы командной строки и перенаправление потоков ввода-вывода, как если бы программа запускалась командным процессором. Пример:


(gdb) run < 001.in > 001.out

Здесь программа запускается на выполнение с файлом 001.in, перенаправленным на стандартный поток ввода, и файлом 001.out, в который перенаправляется вывод на стандартный поток вывода.

Выполнение программы может быть прервано в любой момент нажатием на комбинацию Ctrl-C. Отладчик приостановит выполнение программы и выдаст приглашение ко вводу очередной команды отладчика.

Выполнение программы также приостанавливается при получении программой любого сигнала, при завершении программы либо достижении точки останова.

bt (или backtrace) – распечатать стек вызовов. Стек вызовов печатается от самой вложенной функции к функции main. Для каждого стекового фрейма печатается адрес в коде точки вызова, название функции и параметры, переданные в функцию, а также позиция в исходном коде. Команда bt full дополнительно печатает значения локальных переменных.


(gdb) bt full
#0 0xb78c1424 in __kernel_vsyscall () No symbol table info available.
#1 0x00a8788d in ___newselect_nocancel () at ../sysdeps/unix/syscall-template.S:82No locals.
#2 0x0806e502 in do_work () at userlist-server.c:10244 addr = {sun_family = 1,
 sun_path = "\000\060\060\060\071b", '\000' <repeats 101 times>}
 val = 0
 max_fd = 7
 timeout = {tv_sec = 0, tv_usec = 702429}
 rset = {fds_bits = {80, 0 <repeats 31 times>}}
 wset = {fds_bits = {0 <repeats 32 times>}}
 p = 0x0
 q = 0x9286e68
 saved_fd = 6 

up - переход на указанное количество фреймов вверх по стеку вызовов функций. Если аргумент у команды не указан, он принимается равным 1 (переход на один фрейм вверх, то есть переход к функции, которая вызвала текущую функцию).

down - переход на указанное количество фреймов вниз по стеку вызовов функций. Если аргумент у команды не указан, он принимается равным 1 (переход на один фрейм вниз, то есть переход к функции, которая была вызвана в текущей точке текущей функции).

info frame - получить информацию о текущем стековом фрейме.

info locals - получить информацию о значениях локальных переменных текущего стекового фрейма.

p (или print) – напечатать значение выражения. Аргументом команды может быть почти произвольное выражение языка Си, даже включающее в себя вызовы функций программы, если, конечно, отлаживаемый процесс существует. Таким образом, вызовы функций недоступны при «посмертной» отладке. Если аргумент команды не указан, берется аргумент, который был указан в команде p в последний раз.

печатаем значение переменной user


(gdb) p user    # печатаем значение переменной user
$1 = (const unsigned char *) 0x0 
(gdb) p         # еще раз печатаем значение переменной user
$2 = (const unsigned char *) 0x0 

l (или list) – напечатать исходный код. Команду можно использовать во многих вариантах, часть из которых перечислена ниже.


(gdb) l     # напечатать очередные 10 строк исходного файла
(gdb) l -   # напечатать предыдущие 10 строк исходного файла
(gdb) l 200 # напечатать 10 строк в окрестности 200 строки текущего файла
(gdb) l prog.c:200  # напечатать 10 строк в окрестности 200 строки файла prog.c
(gdb) l main        # напечатать 10 строк в окрестности начала функции main
(gdb) l *0x0806e502 # напечатать 10 строк в окрестности кода по указанному адресу

b (или break) – установка точки останова. Параметром команды является точка в программе, помечаемая как точка останова. Команду можно использовать во многих вариантах, часть из которых перечислена ниже


(gdb) b main    # установить точку останова в начале функции main
(gdb) b 200     # установить точку останова на 200 строке текущего файла
(gdb) b prog.c:200  # установить точку останова на 200 строке файла prog.c
(gdb) b *0x0806e502 # установить точку останова по указанному адресу

delete – удаляет точку останова или контрольное выражение.

clear – удаляет все точки останова на текущем уровне стека (то есть в текущей функции).

c (или continue) – продолжает выполнение программы от текущей точки до конца.

finish - продолжить выполнение программы до достижения конца текущей функции.

display – добавляет выражение в список выражений, значения которых отображаются каждый раз при остановке программы;

info breakpoints – выводит список всех имеющихся точек останова.

info watchpoints – выводит список всех имеющихся контрольных выражений.

info frame - получить информацию о текущем стековом фрейме.

info locals - получить информацию о значениях локальных переменных текущего стекового фрейма.

watch – устанавливает контрольное выражение, программа остановится, как только значение контрольного выражения изменится;

set – устанавливает новое значение переменной.

n (или next) - сделать указанное количество шагов выполнения (по умолчанию 1). Это шаг без захода т.е. вызовы функций рассматриваются как одна инструкция, то есть вызванные функции выполняются в обычном, а не пошаговом режиме.

s (или step) - сделать указанное количество шагов выполнения программы (по умолчанию 1). Это шаг с заходом т.е. при вызовах функций выполнение входит в функции в пошаговом режиме.

По теме Хабр: Использование отладчика GDB по максимуму Меня попросили взломать программу на собеседовании