Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

5.6. Средства отладки

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

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

Программа st rings полезна и для нахождения текста в других двоич­ных файлах. Файлы с изображениями часто содержат ASCII-строки, со­общающие, какая программа создала этот файл, а сжатые файлы и архи­вы (например, zip-файлы) могут содержать имена файлов: strings обнаружит и их.

Unix-системы обычно уже содержат реализацию программы strings, хоть она и отличается от той, которую запрограммируем мы. Unix-вер­сия в случае, если обрабатываемый файл — программа, просматривает только сегменты кода и данных, игнорируя таблицу символов. Ключ -а заставляет ее читать весь файл.

В сущности, strings извлекает ASCII-строку из двоичного файла, чтобы ее можно было прочитать или обработать с помощью другой про­граммы. Если в тексте сообщения об ошибке не говорится, какая именно программа выдала данное сообщение, то узнать это, не говоря уж о том, почему именно она его выдала, будет довольно сложно. В этом случае установить источник можно поиском в подозрительных каталогах; этот поиск выполняется с помощью такой команды:

% strings *.exe *.dll | grep 'mystery message'

Функция strings читает файл и печатает каждую последовательность из как минимум MINLEN = 6 печатных символов.

/* strings: извлечь из потока читабельные строки */

void strings(char *name, FILE *fin)

{

int c, i;

char buf[BUFSIZ];

do { /* один раз для каждой строки */

for (i = 0; (с = getc(fin)) != EOF; ) {

if (! isprint(c))

break;

buf[i++] = c;

if (i >= BUFSIZ)

break;

}

if (i >= MINLEN) /* если строка слишком длинная */

printf ("%'s:%. *s\n", name, i, buf);

} while (c != EOF);

}

форматная строка %, * s в функции printf берет длину строки изследующего аргумента (i), потому что buf не завершается нулем.

Цикл do-while находит и печатает каждую строку, заканчивая работу при обнаружении EOF. Проверка конца файла после тела цикла позволяет функции getc и циклу по строке иметь одинаковое условие завершения, а также с помощью единственного обращения к printf обрабаты­вать конец строки, конец файла и слишком длинные строки.

Стандартный внешний цикл с проверкой при входе или единственный цикл с getc и более сложным телом заставил бы использовать рrintf дважды. Эта функция сначала так и работала, но потом мы нашли ошибку в операторе printf. Исправив в одном месте, мы забыли исправить ее в двух других. ("А не делал ли я ту же самую ошибку где-нибудь еще?") Здесь нам стало ясно, что программу нужно переписать, чтобы дублиру­ющегося кода было меньше; так появился цикл do-while.

Основная процедура программы strings вызывает функцию strings для каждого файла-аргумента:

/* strings main: искать в файлах читабельные строки */

int main(int argc, char *argv[])

{

int i;

FILE *fin;

setprogname("strings");

if (argc ==1)

eprintf("использование: strings имена^файлов");

else {

for (i = 1; i < argc; i++) {

if ((fin = fopen(argv[i], "rb"))• == NULL)

weprintf("нe могу открыть %s:", argv[i]);

else {

strings(argv[i], fin);

fclose(fin);

}

}

}

return 0;

}

Вы, наверное, удивлены, что strings не читает стандартный ввод, если не было дано ни одного имени файла. Сначала именно так и было. Для того чтобы объяснить, почему теперь это изменилось, требуется расска­зать историю об отладке.

Очевидный тест программы strings — пропустить ее через саму себя. Это сработало отлично под Unix, но под Windows 95 команда

С:\> strings <strings.exe

выдала ровно пять строк:

!This program cannot be run in DOS mode.

'. rdata

@.data

.idata

.reloc

Первая строка "!Эта программа не может исполняться под DOS" выгля­дела как сообщение об ошибке, и мы потеряли некоторое время, пока не поняли, что это на самом деле строка из файла с программой, так что результат был правилен, по крайней мере до какого-то момента. Не сек­рет, что некоторые отладочные сессии терпели крушение из-за неверно­го понимания источника сообщения.

Но в любом случае должны быть еще строки! Где они? Однажды поздно ночью наконец забрезжил свет. ("Я где-то уже видел это!") Это — проблема с переносимостью, описанная подробнее в восьмой главе. Из­начально мы написали программу так, чтобы она читала только из стан­дартного ввода, используя функцию getchar. Под Windows, однако, getchar возвращает EOF, когда она встречает определенный байт (Ох1А или Control-Z) в текстовом режиме ввода, и именно это и приводило к преждевременному завершению.

Это абсолютно законное поведение, но совсем не то, что ожидали мы, с нашим опытом работы с Unix. Было решено открывать файл в двоич­ном режиме, используя "rb". Но stdin уже открыт, а стандартного спо­соба изменить режим его работы не существует. (Можно использовать функции fdopen или setmode, но они не являются частью стандарта.) Таким образом, мы столкнулись с набором неприятных альтернатив: за­ставить пользователя всегда задавать имя файла, чтобы программа рабо­тала под Windows за счет неудобства для пользователей Unix; без пред­упреждения выдавать неправильный ответ, если пользователь Windows пытается задействовать стандартный ввод; использовать условную ком­пиляцию, чтобы адаптировать поведение к различным системам ценой пониженной переносимости. Мы выбрали первый вариант, чтобы програамма везде работала одинаково.

Упражнение 5-2

Программа strings печатает строки длиной MINLEN или более символов, и иногда при этом обнаруживается гораздо больше строк, чем надо. Реализуйте необязательный аргумент, устанавливающий минимальную длину строки.

Упражнение 5-3

Напишите программу vis, которая копирует стандартный ввод на стандартный вывод, отображая непечатаемые символы типа "забоя", контрольных символов и не-ASCII-символов в виде \Xhh, где hh — шестнадцатеричное представление непечатаемого байта. В отличие от strings программа vis полезна при обработке файлов, содержащих лишь несколько непечатаемых символов.

Упражнение 5-4

Что выдает vis, если во входном потоке попадается строка \Х0А? Можете ли вы устранить двусмысленность результатов работы этой программы?

Упражнение 5-5

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]