Date Редакция Категория comp Теги gdb / C

Задача, которую предстоит решить, состоит в следующем: есть программа, нужно поставить точку останова на заданной функции и при каждой остановке печатать трассировку (backtrace), а затем продолжать выполнение программы.

В качестве примера рассмотрим программу вычисления факториала. Интересующей нас функцией является fact().

#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

Запустим программу в gdb и передадим ей в качестве аргумента число 4, факториал которого требуется вычислить.

dima@dima:~/work/tttest$ gdb -q fact
Reading symbols from /home/dima/work/tttest/fact...done.
(gdb) start 4
Temporary breakpoint 1 at 0x804847d: file fact.c, line 16.
Starting program: /home/dima/work/tttest/fact 4

Temporary breakpoint 1, main (argc=2, argv=0xbffff284) at fact.c:16
16      int a = atoi(argv[1]);

Поставим точку останова на функции fact()

(gdb) b fact
Breakpoint 2 at 0x8048453: file fact.c, line 6.

Каждый раз при попадании на точку останова должен выполнятся список команд command...end. В нашем случае это команды backtrace (bt) и continue

(gdb) commands
Type commands for breakpoint(s) 2, one per line.
End with a line saying just "end".
>bt
>continue
>end

Набор каждой команды списка завершается нажатием Enter.

Продолжим выполнение программы

(gdb) c
Continuing.

и получим следующие цепочки вызовов

Breakpoint 2, fact (num=4) at fact.c:6
6       if(num <= 1)
#0  fact (num=4) at fact.c:6
#1  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17

Breakpoint 2, fact (num=3) at fact.c:6
6       if(num <= 1)
#0  fact (num=3) at fact.c:6
#1  0x0804846e in fact (num=4) at fact.c:11
#2  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17

Breakpoint 2, fact (num=2) at fact.c:6
6       if(num <= 1)
#0  fact (num=2) at fact.c:6
#1  0x0804846e in fact (num=3) at fact.c:11
#2  0x0804846e in fact (num=4) at fact.c:11
#3  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17

Breakpoint 2, fact (num=1) at fact.c:6
6       if(num <= 1)
#0  fact (num=1) at fact.c:6

Команды в .gdbinit

Чтобы не набирать команды во время сеанса работы с отладчиком, их можно поместить в файл конфигурации .gdbinit

${HOME}/.gdbinit

выполняемый при старте gdb.

В нашем случае .gdbinit будет выглядеть так:

set breakpoint pending on
break fact
commands
bt
continue
end

Команда

set breakpoint pending on

позволяет отладчику создавать отложенные точки останова (pending breakpoints), то есть такие точки, которым на момент их создания не соответствует никакого реального адреса в коде.

Действительно, во время выполнения .gdbinit программа еще не запущена и никаких аргументов ей не передано. Когда же программа запустится, gdb прочитает ее таблицу символов и создаст реальную точку останова fact.

Снова запустим gdb и программу с нужным аргументом

dima@dima:~/work/tttest$ gdb -q fact
Breakpoint 1 (fact) pending.
Reading symbols from /home/dima/work/tttest/fact...done.
(gdb) start 4
Temporary breakpoint 2 at 0x804847d: file fact.c, line 16.
Starting program: /home/dima/work/tttest/fact 4

Temporary breakpoint 2, main (argc=2, argv=0xbffff284) at fact.c:16
16      int a = atoi(argv[1]);

gdb сигнализирует о создании точки останова fact. Продолжив выполнение программы

(gdb) c

получим уже знакомую цепочку вызовов функции fact().

Пакетное выполнение

Кроме того, можно полностью автоматизировать процесс. Для этого поместим наши команды не в .gdbinit, а в файл t.gdb (имя не имеет значения), который будем подключать при запуске программы в отладчике

gdb -q --command=t.gdb --args fact 4

Команды, помещенные в подключаемый с помощью опции --command файл, в отличие от тех, что находятся в .gdbinit, выполняются уже после чтения таблицы символов, так что строку set breakpoint pending on можно теперь убрать. С помощью опции --args программе передаются аргументы.

Теперь, чтобы получить цепочку вызовов в отладчике, достаточно выполнить команду run. Добавим ее в конец файла t.gdb:

break fact
commands
bt
continue
end
run

и воспользуемся опцией --batch для запуска gdb в пакетном режиме:

gdb --batch -q --command=t.gdb --args fact 4

Цепочка вызовов функции fact() будет выведена на стандартный вывод. При необходимости, результаты можно перенаправить в файл.

Макросы

Если нужно выводить трассировку для разных функций, можно создать следующий макрос

define ftr
  continue
  backtrace
end

Формат макросов в gdb имеет вид:

define <commandname>
  <commands>
end
document <commandname>
<help text>
end

Таким образом, перед нами макрос ftr, выполняющий программу до точки останова и выдающий результат трассировки. Сохраним его в файле test.gdb в текущем каталоге.

Запустим отладку программы в gdb, подгрузив созданным нами макрос

gdb --command=test.gdb -q fact

Теперь, передав команде параметр (4) и установив точку останова на нужной функции (fact)

(gdb) start 4
(gdb) b fact

выполним макрос

(gdb) ftr

В результате получим цепочку вызовов при первом попадании на точку останова:

Breakpoint 2, fact (num=4) at fact.c:6
6       if(num <= 1)
#0  fact (num=4) at fact.c:6
#1  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17

Повторным нажатием Enter продолжим выполнение ftr и получим следующие цепочки

(gdb) 

Breakpoint 2, fact (num=3) at fact.c:6
6       if(num <= 1)
#0  fact (num=3) at fact.c:6
#1  0x0804846e in fact (num=4) at fact.c:11
#2  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17
(gdb) 

Breakpoint 2, fact (num=2) at fact.c:6
6       if(num <= 1)
#0  fact (num=2) at fact.c:6
#1  0x0804846e in fact (num=3) at fact.c:11
#2  0x0804846e in fact (num=4) at fact.c:11
#3  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17
(gdb) 

Breakpoint 2, fact (num=1) at fact.c:6
6       if(num <= 1)
#0  fact (num=1) at fact.c:6
#1  0x0804846e in fact (num=2) at fact.c:11
#2  0x0804846e in fact (num=3) at fact.c:11
#3  0x0804846e in fact (num=4) at fact.c:11
#4  0x0804849d in main (argc=2, argv=0xbffff284) at fact.c:17
(gdb) 
4! = 24
[Inferior 1 (process 15138) exited normally]
No stack.

В секции document...end помещается справка по макросу, например

document ftr
My function trace macro.
end

Теперь, набрав

(gdb) help ftr

получим пояснение, что ftr -- это

My function trace macro.


Комментарии

comments powered by Disqus