Ãœðù - Xakep Online
Ãœðù - Xakep Online
Ãœðù - Xakep Online
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
coding<br />
обстоятельствах приводящая к потере нуля<br />
в конце ASCIIZ-строки. Но поскольку за ней<br />
следовало двойное слово, заброшенное на<br />
стек командой PUSH reg16, и я отлаживал<br />
программу на процессоре, очищающем<br />
старший разряд, то все работало более или<br />
менее нормально (2 байта мусора, появляющихся<br />
в конце строки, никому не мешали). Но<br />
вот при запуске на другом процессоре, где<br />
завершающего нуля не оказывалось, возникала<br />
критическая ситуация, завершающаяся<br />
исключением.<br />
Или вот: незначительные различия в реализации<br />
«плавающих» команд на различных<br />
процессорах могут привести к странному<br />
поведению программы, которое будет<br />
невозможно воспроизвести на любом другом<br />
процессоре!<br />
Про разгон, дефекты памяти и т.д. я вообще<br />
молчу! Никогда нельзя быть уверенным в<br />
том, что после выполнения «a=6; a=a+3;»<br />
в переменной а окажется именно 9, а не<br />
83737382. И виноват тут может быть не<br />
только процессор, но и «удар по памяти»,<br />
когда совершенно посторонняя функция,<br />
обратившись по неинициализированному<br />
указателю, запишет что-то в чужую область<br />
данных.<br />
Естественно, самотестирование занимает<br />
некоторое время, и для достижения максимальной<br />
производительности я использую<br />
его в том случае, если предыдущий запуск<br />
программы завершился в аварийном режиме.<br />
Кроме того, на всякий непредвиденный<br />
случай присутствует недокументированный<br />
флаг, форсирующий самотестирование, даже<br />
если предыдущий запуск был завершен<br />
нормально.<br />
Функции самотестирования не раз выручали<br />
меня и помогли мне сэкономить колоссальное<br />
количество времени, поскольку ряд ошибок<br />
был связан с особенностями конкретного<br />
оборудования, то есть причина крылась вне<br />
исходного кода программы.<br />
03<br />
секреты<br />
отладочной печати<br />
Отладочная печать — великолепное изобретение,<br />
появившееся еще в те времена, когда<br />
интерактивных отладчиков не существовало<br />
и в помине, а отлаживать было надо.<br />
Большинство программистов использует<br />
тривиальную запись в текстовый log-файл<br />
или API-функцию OutputDebugString. Первый<br />
метод, естественно, лучше, поскольку он<br />
не требует наличия отладчика или специальной<br />
утилиты для перехвата отладочной<br />
печати, которую конечному пользователю<br />
нужно устанавливать на свой компьютер. Мы<br />
же ведь не собираемся исключать отладочную<br />
печать из финальной версии, верно?<br />
Естественно, не собираемся! Достаточно добавить<br />
специальный ключ командой строки,<br />
секретную комбинацию клавиш или вполне<br />
честную опцию в настройках программы.<br />
Лог лучше всего вести в текстовой форме.<br />
Так пользователю будет проще пересылать<br />
его нам по почте и он сможет убедиться, что<br />
там нет ничего такого, чего бы он не хотел<br />
разглашать.<br />
Вот только… при возникновении критической<br />
ошибки система завершает работу<br />
приложения еще до того, как будут сброшены<br />
дисковые буферы. Даже использование функции<br />
fflush ничего не решает (а вот скорость<br />
программы замедляет весьма существенно).<br />
Как же быть?! Да очень просто: создать в<br />
shared-memory кольцевой буфер заданного<br />
размера и весь отладочный вывод направлять<br />
туда, читая его с помощью дочернего<br />
процесса. Тогда, при аварийном завершении<br />
материнского процесса, shared-memory не<br />
будет освобождена системой и дочерний<br />
процесс успеет принять последнее отладочное<br />
сообщение, отправленное упавшей<br />
программой. К тому же этот метод работает<br />
намного быстрее прямой записи на диск.<br />
А почему буфер должен быть именно кольцевым?!<br />
В общем, это не требование, а так,<br />
простое пожелание. Обычно нас интересует<br />
не весь отладочный вывод целиком, а<br />
события, непосредственно предшествующие<br />
падению. Но при интенсивном отладочном<br />
выводе полный размер лога может достигать<br />
десятков мегабайт, большая часть которых<br />
не несет никакой полезной нагрузки, так что<br />
лучше заранее исключить ее, замкнув буфер<br />
в кольцо.<br />
04<br />
автоматический трассировщик<br />
— это просто<br />
В самых ответственных случаях программу,<br />
поставляемую заказчику, имеет смысл<br />
снабдить простейшим автоматическим<br />
трассировщиком, на создание которого уйдет<br />
не больше одного вечера. Просто взводим<br />
флаг трассировки (TF) и отлавливаем<br />
отладочные исключения штатными средствами<br />
операционной системы (через SEH),<br />
записывая: а) адрес машинной команды;<br />
б) содержимое регистров; в) адрес ячейки<br />
памяти, к которой она обращается.<br />
Когда трассировка из прикладного уровня<br />
дойдет до ядра, процессор самостоятельно<br />
опустит флаг трассировки на время<br />
прохождения нулевого кольца и потом<br />
поднимет его при возвращении на прикладной<br />
уровень, так что предусматривать<br />
специальную обработку для исключения<br />
системных вызов из списка трассируемых<br />
функций не надо.<br />
Естественно, трассировка на несколько<br />
порядков (!) замедляет скорость работы<br />
программы и потому должна включаться<br />
специальной комбинацией клавиш, которую<br />
пользователь нажимает в тот момент,<br />
когда программа приближается к месту<br />
сбоя на максимально близкое расстояние.<br />
А для этого пользователю придется<br />
воспроизвести ситуацию, при которой<br />
возникает ошибка. Если же ему это сделать<br />
не удастся, что ж! Включаем трассировщик<br />
при старте программы специальным<br />
ключом командой строки и пишет результат<br />
трассировки в кольцевой буфер, который<br />
внимательно изучаем.<br />
Располагая информацией о ходе<br />
выполнения программы, содержимом<br />
регистров и ячеек памяти, мы сможем<br />
поймать любую ошибку, какой бы заковыристой<br />
она ни была, ведь фактически<br />
мы отлаживаем программу на<br />
клиентской стороне в неинтерактивном<br />
режиме. При желании (если жаба душит)<br />
трассировщик можно реализовать в<br />
виде отдельной динамической библиотеки,<br />
высылаемой клиенту только при<br />
возникновении серьезных проблем. То<br />
же самое, кстати, относится к функциям<br />
самодиагностики.<br />
Понятное дело, программа, защищенная<br />
протекторами, содержащими антиотладочные<br />
приемы, с автоматическим трассировщиком<br />
работать не будет. Так что придется<br />
отказаться или от протекторов, или от<br />
трассировки, либо же надо писать «умный»<br />
трассировщик, обходящий антиотладочные<br />
приемы, но это уже серьезная задача,<br />
решение которой может затянуться не на<br />
одну неделю. z<br />
xàêåð 05 /101/ 07<br />
/ 133