Решение заключается в добавлении специальных отладочных функций «ловушек» («hook»), которые ваша программа может вызвать при приближении к интересующему вас состоянию.
Например, предположим, что вы знаете, что функция
check_salary
вызывает сбой, но лишь когда она вызвана 1427 раз. (Мы не смеемся над вами; в свое время нам пришлось наблюдать довольно странные вещи.)
Чтобы перехватить
check_salary
до того, как она завершится неудачей, создайте специальную фиктивную функцию, которая ничего не делает и просто возвращается, затем сделайте так, чтобы
check_salary
вызывала ее как раз перед 1427-м своим вызовом:
/* debug_dummy --- отладочная функция-ловушка */
void debug_dummy(void) { return; }
struct salary *check_salary(void) {
/* ...здесь
описания настоящих переменных... */
static int count = 0; /* для отладки */
if (++count == 1426)
debug_dummy;
/* ...оставшаяся часть кода... */
}
Теперь из GDB установите контрольную точку в
debug_dummy
, а затем запустите программу обычным способом:
(gdb) break debug_dummy /* Установить контрольную точку для фиктивной функции */
Breakpoint 1 at 0x8055885: file whizprog.c, line 3137.
(gdb) run /* Запуск программы */
По достижении контрольной точки для
debug_dummy
вы можете установить вторую контрольную точку для
check_salary
и продолжить исполнение:
(gdb) run /* Запуск программы */
Starting program: /home/arnold/whizprog
Breakpoint 1, debug_dummy at whizprog.c, line 3137
/* Установить контрольную точку для интересующей функции */
Breakpoint 2 at 0x8057913: file whizprog.c, line 3140.
(gdb) cont
По достижении второй контрольной точки программа готова завершиться неудачей, и вы можете пошагово ее пройти, делая все необходимое для отслеживания проблемы.
Вместо использования фиксированной константы ('
++count == 1426
') можно использовать глобальную переменную, которая устанавливается отладчиком в любое нужное вам значение. Это дает возможность избежать перекомпилирования программы
Для
gawk
мы пошли на один шаг дальше и внесли возможность отладочной ловушки в язык, так что функция ловушки могла быть вызвана из программы
awk
. При компилировании для отладки доступна специальная ничего не делающая функция
stopme
. Эта функция, в свою очередь, вызывает функцию С с тем же названием. Это позволяет нам поместить вызовы
stopme
в завершающуюся неудачей программу
awk
непосредственно перед сбойным участком. Например, если
gawk
выдает ошибочные результаты для программы
awk
в 1200-й вводимой записи, мы можем добавить в программу
awk
строку, подобную этой:
NR == 1198 { stopme } # Остановиться для отладки, когда число записей == 1198
/* ...оставшаяся часть программы как ранее... */
Затем из GDB мы можем установить контрольную точку на функции С
stopme
и запустить программу
awk
. Когда контрольная точка срабатывает, мы можем затем установить контрольные точки на другие части
gawk
, где, как мы ожидаем, находится действительная проблема.
Методика функции-ловушки полезна сама по себе. Однако, возможность переместить ее на уровень приложения умножает ее полезность, и она сохранила нам бесчисленное число часов отладки при отслеживании непонятных проблем.
15.5. Отладочные инструменты
Помимо GDB и различных ловушек в исходном коде, которые вы используете для общей отладки, имеется ряд полезных пакетов, которые могут помочь обнаружить определенные разновидности проблем. Поскольку управление динамической памятью является в крупномасштабных программах такой трудной задачей, многие инструменты фокусируются на этой области, часто действуя в качестве замещающих
malloc
и
free
элементов
Имеются коммерческие инструменты, которые делают множество (или все) из тех вещей, что и описываемые нами программы, но не все они доступны для GNU/Linux, а многие довольно дороги. Все пакеты, обсуждающиеся в данном разделе, являются свободно доступными.
15.5.1. Библиотека
dbug
— усовершенствованный
printf
Первым пакетом, который мы исследуем, является библиотека
dbug
. Она основана на идее условно компилируемого отладочного кода, которую мы представили ранее в данной главе, но идет намного дальше, предоставляя сравнительно сложную трассировку времени исполнения и условный вывод отладки. Она реализует многие из описанных нами советов, избавляя вас от хлопот по собственной их реализации.
Библиотека
dbug
, написанная Фредом Фишем (Fred Fish) в начале 1980-х, была с тех пор несколько усовершенствована. Теперь она явным образом является общим достоянием, поэтому ее можно использовать без всяких проблем как в свободном, так и частном программном обеспечении. Она доступна через архив FTP Фреда Фиша [175] как в виде сжатого файла tar, так и в виде архива ZIP. Документация хорошо резюмирует
dbug
:
dbug
является примером внутреннего отладчика. Поскольку она требует внутренней поддержки программы и ее использование не зависит от каких бы то ни было особых возможностей среды исполнения, она всегда доступна и будет выполняться в любом окружении, в котором будет выполняться сама программа. Вдобавок, поскольку это законченный пакет с особым интерфейсом пользователя, все программы, которые ее используют, будут иметь сходные возможности отладки. Это резко контрастирует с другими формами внутренней поддержки, где у каждого разработчика своя собственная, обычно менее квалифицированная, форма внутреннего отладчика...
Пакет
dbug
лишь незначительно снижает скорость выполнения программ, обычно значительно менее 10%, и немного увеличивает их размеры, обычно от 10 до 20%. Определив особый идентификатор препроцессора С, можно снизить оба этих показателя до нуля без необходимости изменений в исходном коде.
Следующий список является кратким изложением возможностей пакета
dbug
. Каждую возможность можно отдельно включать или отключать во время запуска программы, указав соответствующие аргументы командной строки.
• Трассировка исполнения, отображающая уровень потока управления полуграфическим способом с использованием отступов, обозначающих глубину вложения
• Вывод значений всех или любого набора ключевых внутренних переменных.
• Ограничение действий определенным набором указанных функций.
• Ограничение трассировки функций указанной глубиной вложения.
• Пометку каждой выводимой строки названием исходного файла и номером строки.
• Пометку каждой выводимой строки названием текущего процесса.
• Сохранение в стеке или восстановление состояния отладки для обеспечения исполнения со встроенными значениями по умолчанию для отладки.
• Перенаправление потока вывода отладки в стандартный вывод (
stdout
) или указанный файл. По умолчанию поток вывода направляется в стандартную ошибку (
stderr
). Механизм перенаправления полностью независим от обычного перенаправления командной строки, чтобы избежать конфликтов вывода.
175
ftp://ftp.ninemoons.corn/pub/dbug/
— Примеч. автора.
Пакет
dbug
требует от вас использования определенного порядка при написании своего кода. В частности, нужно использовать его макросы при возвращении из функции или вызове
setjmp
и
longjmp
. Нужно добавлять один вызов макроса в качестве первого исполняемого оператора каждой функции и вызвать несколько дополнительных макросов из
main
. Наконец, нужно добавить отладочную опцию командной строки, по соглашению, это
– #
, которая редко используется в качестве действительной опции, если вообще используется. В обмен на дополнительную работу вы получаете все только что очерченные преимущества. Давайте взглянем на пример в руководстве: