The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Каталог документации / Раздел "Руководства по FreeBSD на русском" / Оглавление документа

2.6 Отладка

2.6.1 Отладчик

Отладчик, поставляемый с FreeBSD, называется gdb (GNU debugger). Он запускается по команде

% gdb progname

хотя большинство предпочитает запускать его из Emacs. Вы можете сделать это так:

M-x gdb RET progname RET

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

В gdb имеется достаточно хорошая встроенная система помощи, а также набор info-страниц, так что в этом разделе упор будет делаться на несколько основных команд.

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

Этот раздел предназначен быть введением в использование gdb и не покрывает специальные вопросы, такие, как отладка ядра.

2.6.2 Запуск программы в отладчике

Чтобы получить максимальный результат от использования gdb, вам нужно откомпилировать программу с параметром -g. Отладчик будет работать и без этой опции, но вы сможете увидеть только название текущей функции, но не ее исходный код. Если вы увидите такое сообщение:

... (no debugging symbols found) ...

при запуске gdb, то определите, что программа не была откомпилирована с опцией -g.

В приглашении gdb наберите команду break main. Это укажет отладчику пропустить предварительный подготовительный код программы и начать сразу с вашего кода. Теперь выдайте команду run для запуска программы--она начнет выполняться с подготовительного кода и затем будет остановлена отладчиком при вызове main(). (Если вы когда-либо удивлялись, откуда вызывается main(), то теперь вы должны знать!).

Теперь вы можете выполнить программу построчно по шагам, нажимая n. Если вы попали на вызов функции, то можете перейти в нее, нажав s. Оказавшись в вызове функции, вы можете вернуться из пошагового выполнения функции нажатием f. Вы можете также использовать команды up и down для просмотра вызывающей подпрограммы.

Вот простой пример того, как выявить ошибку в программе при помощи gdb. Это наша программа (с намеренно допущенной ошибкой):

#include <stdio.h>

int bazz(int anint);

main() {
    int i;

    printf("This is my program\n");
    bazz(i);
    return 0;
}

int bazz(int anint) {
    printf("You gave me %d\n", anint);
    return anint;
}

Эта программа устанавливает значение переменной i равным 5 и передает ее в функцию bazz(), которая выводит переданное нами число.

При компиляции и запуске программы мы получили

% cc -g -o temp temp.c
% ./temp
This is my program
anint = 4231

Это не то, что мы ожидали! Самое время посмотреть, что же происходит!

% gdb temp
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) break main               Пропуск начального кода
Breakpoint 1 at 0x160f: file temp.c, line 9.    gdb устанавливает точку останова в main()
(gdb) run                   Запуск до вызова main()
Starting program: /home/james/tmp/temp      Программа начинает работать

Breakpoint 1, main () at temp.c:9       gdb останавливается в main()
(gdb) n                       Переход к следующей строке
This is my program              Программа выводит
(gdb) s                       Переход в bazz()
bazz (anint=4231) at temp.c:17          gdb выводит стек вызовов
(gdb)

Минуточку! Как параметр anint оказался равным 4231? Разве мы не присвоили ему значение 5 в функции main()? Давайте перейдем к функции main() и взглянем туда.

(gdb) up                   Переход вверх по стеку вызовов
#1  0x1625 in main () at temp.c:11      gdb выводит стек вызовов
(gdb) p i                   Вывод значения переменной i
$1 = 4231                   gdb выводит 4231

О боже! Судя по коду, мы забыли инициализировать переменную i. Вы хотели сделать вот что

...
main() {
    int i;

    i = 5;
    printf("This is my program\n");
...

но забыли про строку i=5;. Так как мы не присвоили начальное значение для i, то переменная принимает случайное значение, оказывающее в соответствующей области памяти при работе программы, и в нашем случае это оказалось число 4231.

Note: gdb выводит стек вызовов всякий раз, когда мы вызываем или возвращаемся из функции, даже если мы используем команды up и down для продвижения по стеку. При этом выводится имя функции и значения ее аргументов, что помогает нам отслеживать, где мы находимся и что происходит. (Стек является областью, где программа сохраняет информацию о передаваемых функциям аргументах и о том, куда нужно перейти после возврата из функции).

2.6.3 Исследование файла дампа

Файл дампа, вообще говоря, является файлом, содержащим полный образ процесса в момент его сбоя. В ``добрые старые времена'' программисты выводили шестнадцатеричные распечатки файлов дампа и корпели над справочниками по машинным кодам, но сейчас жизнь несколько облегчилась. В частности, во FreeBSD и других системах на основе 4.4BSD файлы дампа называются progname.core, а не просто core, для того, чтобы было понятнее, к какой программе относится соответствующий файл дампа.

Для исследования файла дампа запустите gdb обычным образом. Вместо того, чтобы выдавать команду break или run, наберите

(gdb) core progname.core

Если вы не в том же каталоге, что и файл дампа, то вам нужно сначала выполнить команду dir /path/to/core/file.

Вы должны увидеть нечто вроде следующего:

% gdb a.out
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) core a.out.core
Core was generated by `a.out'.
Program terminated with signal 11, Segmentation fault.
Cannot access memory at address 0x7020796d.
#0  0x164a in bazz (anint=0x5) at temp.c:17
(gdb)

В этом случае программа называлась a.out, так что файл дампа называется a.out.core. Мы можем видеть, что программа завершилась аварийно из-за попытки доступа к области памяти, ей недоступной, в функции bazz.

Иногда бывает полезно иметь возможность просмотреть, как функция была вызвана, потому что в сложной программе проблема могла появиться в любом месте большого стека вызовов. Команда bt заставляет gdb выдать обратную трассировку стека вызовов:

(gdb) bt
#0  0x164a in bazz (anint=0x5) at temp.c:17
#1  0xefbfd888 in end ()
#2  0x162c in main () at temp.c:11
(gdb)

При сбое программы была вызвана функция end(); в этом случае функция bazz() была вызвана из main().

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

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

В этой ситуации нужно запустить еще один отладчик gdb, воспользоваться командой ps для поиска идентификатора порожденного процесса и выполнить команду

(gdb) attach pid

в gdb, после чего отлаживать программу обычным образом.

``Это все хорошо'', думаете, наверное, вы, ``но к моменту, когда я все это сделаю, порожденный процесс уже завершит свою работу''. Может быть, и нет, дорогой читатель, и вот как это делается (согласно info-страницам программы gdb):

...
if ((pid = fork()) < 0)     /* _Always_ check this */
    error();
else if (pid == 0) {        /* child */
    int PauseMode = 1;

    while (PauseMode)
        sleep(10);  /* Wait until someone attaches to us */
    ...
} else {            /* parent */
    ...

Теперь всё, что вам нужно сделать, это подключиться к порождённому процессу, установить значение переменной PauseMode в 0 и дождаться возврата из вызова функции sleep()!

Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

По вопросам связанными с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам связанным с русским переводом документации, пишите <frdp@FreeBSD.org.ua>.




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру