Книга: Linux программирование в примерах

15.5.1. Библиотека dbug — усовершенствованный printf()

15.5.1. Библиотека dbug — усовершенствованный printf()

Первым пакетом, который мы исследуем, является библиотека dbug. Она основана на идее условно компилируемого отладочного кода, которую мы представили ранее в данной главе, но идет намного дальше, предоставляя сравнительно сложную трассировку времени исполнения и условный вывод отладки. Она реализует многие из описанных нами советов, избавляя вас от хлопот по собственной их реализации.

Библиотека dbug, написанная Фредом Фишем (Fred Fish) в начале 1980-х, была с тех пор несколько усовершенствована. Теперь она явным образом является общим достоянием, поэтому ее можно использовать без всяких проблем как в свободном, так и частном программном обеспечении. Она доступна через архив FTP Фреда Фиша[175] как в виде сжатого файла tar, так и в виде архива ZIP. Документация хорошо резюмирует dbug:

dbug является примером внутреннего отладчика. Поскольку она требует внутренней поддержки программы и ее использование не зависит от каких бы то ни было особых возможностей среды исполнения, она всегда доступна и будет выполняться в любом окружении, в котором будет выполняться сама программа. Вдобавок, поскольку это законченный пакет с особым интерфейсом пользователя, все программы, которые ее используют, будут иметь сходные возможности отладки. Это резко контрастирует с другими формами внутренней поддержки, где у каждого разработчика своя собственная, обычно менее квалифицированная, форма внутреннего отладчика...

Пакет dbug лишь незначительно снижает скорость выполнения программ, обычно значительно менее 10%, и немного увеличивает их размеры, обычно от 10 до 20%. Определив особый идентификатор препроцессора С, можно снизить оба этих показателя до нуля без необходимости изменений в исходном коде.

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

• Трассировка исполнения, отображающая уровень потока управления полуграфическим способом с использованием отступов, обозначающих глубину вложения

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

• Ограничение действий определенным набором указанных функций.

• Ограничение трассировки функций указанной глубиной вложения.

• Пометку каждой выводимой строки названием исходного файла и номером строки.

• Пометку каждой выводимой строки названием текущего процесса.

• Сохранение в стеке или восстановление состояния отладки для обеспечения исполнения со встроенными значениями по умолчанию для отладки.

• Перенаправление потока вывода отладки в стандартный вывод (stdout) или указанный файл. По умолчанию поток вывода направляется в стандартную ошибку (stderr). Механизм перенаправления полностью независим от обычного перенаправления командной строки, чтобы избежать конфликтов вывода.

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

1  #include <stdio.h>
2  #include "dbug.h"
3
4  int
5  main(argc, argv)
6  int argc;
7  char *argv[];
8  {
9   register int result, ix;
10  extern int factorial(), atoi();
11
12  DBUG_ENTER("main");
13  DBUG_PROCESS(argv[0]);
14  DBUG_PUSH_ENV("DBUG");
15  for (ix = 1; ix < argc && argv[ix][0] == '-'; ix++) {
16   switch (argv[ix][1]) {
17   case '#':
18    DBUG_PUSH(&(argv[ix][2]));
19    break;
20   }
21  }
22  for (; ix < argc; ix++) {
23   DBUG_PRINT("args", ("argv[%d] = %s", ix, argv[ix]));
24   result = factorial(atoi(argv(ixj));
25   printf("%dn", result);
26   fflush(stdout);
27  }
28  DBUG_RETURN(0);
29 }

Эта программа иллюстрирует большинство важных моментов. Макрос DBUG_ENTER() (строка 12) должен быть вызван после объявлений переменных и перед любым другим кодом. (Это потому, что он сам объявляет несколько частных переменных.[176])

Макрос DBUG_PROCESS() (строка 13) устанавливает имя программы, главным образом, для использования в выводимых библиотекой сообщениях. Этот макрос должен вызываться лишь однажды, из main().

Макрос DBUG_PUSH_ENV() (строка 14) заставляет библиотеку проверить указанную переменную окружения (в данном случае DBUG) на предмет управляющей строки (Управляющие строки dbug вскоре будут рассмотрены.) Библиотека может, сохранив свое текущее состояние и использовав новое, создавать стек сохраненных состояний. Таким образом, этот макрос помещает в стек сохраненных состояний полученное от данной переменной окружения состояние. В данном примере использован случай, когда макрос создает первоначальное состояние. Если такой переменной окружения нет, ничего не происходит. (В качестве отступления, DBUG является довольно общей переменной, возможно, GAWK_DBUG было бы лучше [для gawk].)

Макрос DBUG_PUSH (строка 18) передает значение управляющей строки, полученной из опции командной строки -#. (Новый код должен использовать getopt() или getopt_long() вместо ручного анализа аргументов.) Таким образом обычно включается режим отладки, но использование переменной окружения предоставляет также дополнительную гибкость.

Макрос DBUG_PRINT() (строка 23) осуществляет вывод. Второй аргумент использует методику, которую мы описали ранее (см. раздел 15.4.1.1 «Используйте отладочные макросы»), по включению в скобки всего списка аргументов printf(), делая его простым аргументом, насколько это касается препроцессора С. Обратите внимание, что завершающий символ конца строки в форматирующей строке не указывается; библиотека dbug вставляет его за вас.

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

Наконец, макрос DBUG_RETURN() (строка 28) используется вместо обычного оператора return для возврата значения. Для использования с функциями void имеется соответствующий макрос DBUG_VOID_RETURN.

Оставшаяся часть программы заполнена функцией factorial():

1  #include <stdio.h>
2  #include "dbug.h"
3
4  int factorial (value)
5  register int value;
6  {
7   DBUG_ENTER("factorial");
8   DBUG_PRINT("find", ("find %d factorial", value));
9   if (value > 1) {
10   value *= factorial(value — 1);
11  }
12  DBUG_PRINT("result", ("result is %d", value));
13  DBUG_RETURN(value);
14 }

Когда программа откомпилирована и скомпонована вместе с библиотекой dbug, ее можно запустить обычным способом. По умолчанию, программа не создает вывод отладки. Но со включенной отладкой доступны различные виды вывода:

$ factorial 1 2 3 /* Обычный запуск, без отладки */
1
2
6
$ factorial -#t 1 2 3/* Вывести трассировку вызовов функций, обратите внимание на вложенность */
| >factorial
| <factorial
1 /* Обычный вывод в stdout */
| >factorial
| | >factorial
| | <factorial /* Вывод отладки в stderr */
| <factorial
2
| >factorial
| | >factorial
| | | >factorial
| | | <factorial
| | <factorial
| <factorial
6
<?func?
$ factorial -#d 1 2/* Показать отладочные сообщения DBUG_PRINT() */
?func?: args: argv[2] = 1
factorial: find: find 1 factorial
factorial: result: result is 1
1
?func?: args: argv[3] = 2
factorial: find: find 2 factorial
factorial: find: find 1 factorial
factorial: result: result is 1
factorial: result: result is 2
2

Опция -# управляет библиотекой dbug. Она «особая» в том смысле, что DBUG_PUSH() будет принимать всю строку, игнорируя ведущие символы '-#', хотя вы могли бы использовать при желании другую опцию, передав DBUG_PUSH() лишь строку аргументов опций (если вы используете getopt(), это optarg).

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

$ myprog -#d,mem,ipc:f,check_salary,check_start_date -f infile -o outfile

Опция d включает вывод DBUG_PRINT(), но лишь если первая строка аргумента является "mem" или "ipc". (Если аргументов нет, выводятся все сообщения DBUG_PRINT().) Сходным образом опция f ограничивает трассировку вызовов функций лишь указанными функциями, check_salary() и check_start_date().

Следующий список опций и аргументов воспроизведен из руководства библиотеки dbug. Квадратные скобки заключают необязательные аргументы. Мы включаем здесь лишь те, которые находим полезными; полный список см. в документации.

d [,ключевые слова]

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

F

Помечает каждую строку вывода отладки именем исходного файла, содержащего макрос, осуществляющий вывод.

i

Идентифицирует процесс, выводящий каждую отладочную или трассировочную строку номером ID для этого процесса.

L

Помечает каждую строку вывода отладчика номером строки исходного файла, в котором находится осуществляющий вывод макрос.

о[,файл]

Перенаправляет поток вывода отладчика в указанный файл. Потоком вывода по умолчанию является stderr. Пустой список аргументов перенаправляет вывод в stdout.

t[,N]

Включает трассировку потока управления функций. Максимальная глубина вложения определяется N, по умолчанию используется 200.

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

DBUG_EXECUTE(строка, код)

Этот макрос похож на DBUG_PRINT(): первый аргумент является строкой, выбранной с помощью опции d, а второй — код для исполнения:

DBUG_EXECUTE("abort", abort());
DBUG_FILE

Это значение типа FILE* для использования с процедурами <stdio.h>. Оно позволяет осуществлять собственный вывод в поток файла отладки.

DBUG_LONGJMP(jmp_buf env, int val)

Этот макрос заключает в оболочку вызов longjmp(), принимая те же самые аргументы, так что библиотека dbug будет знать, когда вы сделали нелокальный переход.

DBUG_POP()

Этот макрос выталкивает из стека один уровень сохраненного состояния отладки, созданный макросом DBUG_PUSH(). Он довольно эзотерический; вы скорее всего не будете его использовать.

DBUG_SETJMP(jmp_buf env)

Этот макрос заключает в оболочку вызов setjmp(), принимая те же самые аргументы. Он позволяет библиотеке dbug обрабатывать нелокальные переходы.

В другом воплощении, в первой начинающей компании, для которой мы работали[177], мы использовали в своем продукте библиотеку dbug. Она была неоценимой при разработке, а опустив -DDBUG в конечной сборке, мы смогли построить готовую версию без других изменений исходного кода.

Чтобы извлечь максимальную выгоду от библиотеки dbug, нужно использовать ее последовательно, по всей программе. Это проще, если вы используете ее с начала проекта, но в качестве эксперимента мы обнаружили, что с помощью простого сценария awk мы смогли включить библиотеку в программу с 30 000 строк кода за несколько часов работы. Если вы можете позволить себе накладные расходы, лучше всего оставить ее в конечной сборке вашей программы, чтобы можно было ее отлаживать без необходимости предварительной перекомпиляции.

Мы нашли, что библиотека dbug является удачным дополнением к внешним отладчикам, таким, как GDB; она обеспечивает организованный и последовательный способ применения поддержки к коду С. Она также довольно элегантно сочетает многие из методик, которые мы ранее в данной главе очертили отдельно. Особенно полезна особенность динамической трассировки вызовов функций, и она доказывает свою бесценность в качестве помощи в изучении поведения программы, если вы незнакомы с ней.

Оглавление книги


Генерация: 5.357. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз