Главная » Программирование для UNIX » Об ошибках и отладке UNIX

0

Каждому, кто хоть раз пытался написать программу, знакомо понятие

«баг»   (ошибка). Единственное,  что  можно  сделать, чтобы  написать программу без ошибок, – это выбрать простую, без излишеств конструкцию, аккуратно ее реализовать и сохранять ее простой по ходу любых изменений.

Некоторые средства UNIX  могут  помочь в  обнаружении ошибок, но надо сказать, что ни одно из них нельзя назвать первоклассным. Однако чтобы  показать это на примере, нужна ошибка, а все программы в этой книге совершенны. Так что создадим типичную ошибку. Рассмотрим  функцию pick, представленную выше. Вот еще одна ее версия, на этот раз с ошибкой (чур, не подсматривать в оригинал!).

pick(s)  /*  возможность  выбора  s  */ char  *s;

{

fprintf(stderr,  "%s?  ",  s); if  (ttyin()  ==  ‘y’)

printf("%s\n", s);

}

Что произойдет, если скомпилировать и запустить программу?

$ cc  pick.c -o  pick

$ pick  *.c                                                  Пробуем

Memory  fault  – core  dumped                     Катастрофа!

$

«Memory  fault» означает, что  программа  пытается сослаться на  область памяти, доступ в которую ей не разрешен. Обычно  так бывает, когда указатель ссылается «в никуда». Еще  одно диагностическое сообщение похожего содержания – это «bus  error», оно часто  возникает при сканировании незаконченной строки.

«Core dumped» означает, что ядро сохранило состояние исполняющейся программы в файле core в текущем каталоге. Можно заставить программу записать дамп  оперативной памяти, нажав ctl-\  (если  это интерактивная  программа) или  введя команду kill  –3 (если  программа выполняется в фоновом режиме).

«Вскрытие» производят две программы – adb и sdb. Как  и большинство отладчиков, они окутаны тайной, сложны и незаменимы. Программа adb входит в состав седьмой версии; а sdb доступна в более поздних версиях системы. Одна из них наверняка есть в каждой системе.1

В этой книге есть возможность представить только минимальное описание   каждого из  отладчиков:  вывести трассировку  стека,  то  есть функцию,  которая  выполнялась  в  момент  аварийного  завершения программы, функцию, которая вызвала ее, и т. д. Функция, чье  имя  стоит  первым в трассировке, – это та функция, на которой программа прервалась.

Чтобы получить трассировку стека с помощью adb, используем команду $C:

$ adb pick  core                 Вызов adb

$C                                                   Запрос трассировки стека

~_strout(0175722,011,0,011200) adjust:         0

fillch:         060542

   doprnt(0177345,0176176,011200)

~fprintf(011200,0177345)

iop:

011200

fmt:

0177345

args:

0

~pick(0177345)

s:             0177345

1        Пользователи свободно  распространяемых  ОС, таких как FreeBSD или  Linux, могут  обратиться к отладчику gdb. – Примеч. науч. ред.

~main(035,0177234)

argc:

035

argv:

0177234

i:

01

buf:

0

ctld

Выход

$

Это означает, что  main вызвала pick, которая  вызвала fprintf,  она,  в свою   очередь,  вызвала   _doprnt,  которая  вызвала  _strout.  Так   как

_doprnt не упоминается нигде  в pick.c, проблема должна быть  где-то  в

fprintf или выше. (Строки после каждой подпрограммы представляют значения локальных переменных. Команда $c подавляет вывод  такой информации, а в некоторых версиях adb это происходит и при использовании самой  команды $C.)

Прежде чем  окончательно выявить ошибку, попробуем сделать то же самое с помощью sdb:

$ sdb  pick  core

Warning: `a.out’ not  compiled  with  –g

lseek:  address 0xa64         Функция, на которой программа оборвалась

*t                                            Запрос трассировки стека

lseek() fprintf(6154,2147479154) pick(2147479154) main(30,2147478988,2147479112)

*q                                             Выход

$

Информация имеет  разный формат, но суть одна и та же: fprintf. (Трас сировка выглядит по-другому, потому что программа была запущена на другой машине – VAX-11/750, на которой иначе реализована стан дартная библиотека ввода-вывода.) И действительно, если посмотреть на вызов fprintf в испорченной версии pick, то видно, что он неверен:

fprintf("%s? ",  s);

Отсутствует stderr,  поэтому форматирующая строка "%s? " используется как указатель FILE, что, естественно, вызывает хаос.

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

Можно выявить ошибки вызова функции с неправильными аргументами, используя  верификатор Си  lint(1). Программа lint  обследует программы, написанные на Си, на предмет возможных ошибок, проблем  с переносимостью и сомнительных конструкций. Если  запустить lint для всего файла pick.c, ошибка будет идентифицирована:

$ lint pick.c

fprintf,  arg. 1 used  inconsistently "llib–lc"(69) ::  "pick.c"(28)

$

В  переводе это  означает, что  описание  первого  аргумента fprintf  в стандартной библиотеке отличается от способа его использования в 28-й  строке программы. Это полезное указание на нечто неправильное.

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

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии