
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdfГЛАВА 17 Стандартная отладочная библиотека C и управление памятью |
643 |
|
|
Пакет Application Compatibility Toolkit
Я мог бы привести вам огромное описание подключения PageHeap при помощи странного средства командной строки, встроенного в GFLAGS, но есть способ получше. Пакет Application Compatibility Toolkit (ACT) не только вносит функци ональность PageHeap прямо в Visual Studio .NET, но и предоставляет некоторые прекрасные средства обнаружения ошибок, про которые вам следует знать. ACT предназначен в первую очередь для обеспечения выполнения программ под Micro soft Windows XP/Server 2003, но он также включает программу Application Verifier (AppVerifier). Она то нам и нужна.
Пакет ACT вы можете установить с CD, прилагаемого к книге, или загрузить его последнюю версию с сайта http://www.microsoft.com/windowsxp/appexperience/ default.asp. В документации говорится, что AppVerifier из версии ACT 2.6, доступ ной на момент написания этой книги, может работать под управлением Microsoft Windows 2000 SP3 и более поздних версий ОС, но я смог запустить эту програм му только на Windows XP/Server 2003. Я так и не добился правильной работы AppVerifier с Windows 2000. Кроме того, некоторые из тестов и ошибок не выво дят информации, хотя в документации утверждается обратное. В оставшейся ча сти обсуждения я буду полагать, что вы выполняете AppVerifier под Windows XP/ Server 2003 с правами администратора (это необходимое условие).
AppVerifier включает отдельный исполняемый файл (APPVERIF.EXE) и надстройку (VSAPPVERIF.DLL). Надстройка AppVerifier, включенная в ACT версии 2.6, интегри руется в панель инструментов Debug среды Visual Studio .NET 2002, но не Visual Studio .NET 2003. К счастью, благодаря опыту, полученному при работе с надстрой ками в главе 9, я смог обнаружить, как заставить AppVerifier работать и во втором случае. Если вы собираетесь использовать более позднюю версию AppVerifier, вероятно, она интегрируется в Visual Studio .NET 2003 сама, так что вы можете пропустить описание следующих действий.
Установив ACT, откройте командную строку и перейдите в подкаталог <ката лог установки ACT>\Applications. Зарегистрируйте DLL надстройки AppVerifier командой REGSVR32 VSAPPVERIF.DLL, чтобы внести в реестр информацию о компо нентах COM. Далее вы должны сообщить об этой надстройке среде Visual Studio
.NET 2003. В каталоге AppVerifierAddIn на CD находится .REG файл AppVerifier AddInReg.reg. Чтобы выполнить его, дважды щелкните его значок в Windows Explorer или введите в командной строке выражение REGEDIT AppVerifierAddInReg.REG.
Если вы беспокоитесь о том, может ли перенос надстройки, написанной для предыдущей версии Visual Studio .NET, в более новую версию этой среды привес ти к неприятностям, я отвечу, что это возможно. Если бы надстройка была напи сана при помощи .NET, использование предыдущих версий CLR могло бы вызвать проблемы. Однако AppVerifier написана только на C++, поэтому вы ничем не рис куете. Я знаю это совершенно точно, так как я проверил VSAPPVERIF.DLL при по мощи REGASM, и REGASM сообщил, что эта надстройка не является сборкой .NET. Конечно, я все же протестировал VSAPPVERIF.DLL и проверил все ее параметры, чтобы гарантировать полную безопасность. Запустив Visual Studio .NET с AppVerifier, не имея прав администратора, вы увидите странное окно сообщения об ошибке — «Installer Error» (ошибка программы установки) — с текстом «Error: insufficient




ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью |
647 |
|
|
Некоторые из кнопок — например, двойная инициализация критической секции — ошибок не вызывают: ACT 2.6 документирует эту ошибку, но на самом деле не генерирует ее. Уделите особое внимание разделу PageHeap Errors (ошибки Page Heap), который покажет вам, как прекрасно PageHeap справляется с обнаружени ем выхода за пределы блока и других неприятных проблем с памятью.
Выполнив свое приложение пару раз, вы можете изучить журналы, сохранен ные AppVerifier. Хотя большинство ошибок, обнаруженных надстройкой AppVerifier, приводит к немедленному нарушению доступа, некоторые появляются только в журнале, который открывается щелчком кнопки Log Files (файлы журналов) на панели инструментов Debug. Изучая журналы, убедитесь, что установлен параметр Show All (показывать все), иначе вы увидите не всю информацию.
Потрясающие ключи компилятора
Как я упоминал в главе 2, компилятор Microsoft Visual C++ .NET поддерживает новые ключи, которые необходимо устанавливать всегда. В этом разделе я опишу клю чи /RTCx и /GS и объясню, почему они так важны.
Ключи проверки ошибок в период выполнения
Даже если бы единственной новой функцией, добавленной в компилятор Visual C++ .NET, были ключи /RTCx (Run Time Error Checks, проверка ошибок во время выполнения), я бы всем говорил, что переход на эту версию просто обязателен. Как можно догадаться по названию, четыре ключа RTC следят за вашим кодом и вызывают отладчик, если при выполнении программы возникают определенные ошибки. На рис. 17 6 показана ошибка, возникшая в результате того, что какой то код выполнил запись после окончания локальной переменной. Как вы можете увидеть по информационному окну, показана и искаженная локальная перемен ная. К тому же рисунок ничего не говорит о том, что информационное окно по является в конце функции, в которой произошла ошибка, благодаря чему поиск и исправление проблемы становятся тривиальной задачей! Между прочим, клю чи RTC допускается применять только в отладочных компоновках.
Рис. 17 6. Сообщение об ошибке при установленном ключе /RTCs
Первый ключ, /RTCs, чье сообщение об ошибке показано на рис. 17 6, выпол няет в период выполнения программы разнообразные виды проверки стека. Во первых, он инициализирует все локальные переменные ненулевыми значениями. Это помогает находить те неприятные проблемы заключительных компоновок, которые не проявляются в отладочных компоновках, например, наличие в стеке

648 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
неинициализированного указателя. Все локальные переменные получают значе ния 0xCC, что соответствует коду операции INT 3 (точка прерывания).
Во вторых, ключ /RTCs выполняет проверку указателя стека при каждом вызо ве функции, обнаруживая несоответствия соглашений вызова. Так, если функция объявлена с соглашением __cdecl, но экспортируется как __stdcall, при возвращении из функции __stdcall стек будет поврежден. Если вы уже давно следите за ключа ми компиляторов, вы уже догадались, что первые два варианта проверки стека ключом /RTCs аналогичны функции ключа /GZ в Microsoft Visual C++ 6.
К нашей всеобщей радости, Microsoft расширила ключ /RTCs, и теперь он так же проверяет запись данных до начала и после границ многобайтовых локаль ных переменных, таких как массивы. Он делает это, добавляя 4 байта к началу и концу этих массивов и проверяя в конце функции, сохранили ли эти байты пер воначальное значение 0xCC. Локальная проверка работает со всеми многобайто выми локальными переменными, кроме тех, что требуют от компилятора добав ления дополнительных байтов. Обычно дополнительные байты добавляются только при использовании директивы __declspec(align), ключа /Zp, выравнивающего члены структуры, или директивы #pragma pack(n).
Второй ключ, /RTCu, проверяет использование ваших переменных и выводит предупреждение, если вы вызываете какую нибудь из них, не проинициализиро вав. Если вы много лет охотитесь на насекомых, важность этого ключа может быть вызывать у вас удивление. Так как все ответственные читатели этой книги (и вы в том числе) уже компилируют свой код на уровне диагностики 4 (/W4) и рассмат ривают все предупреждения как ошибки (/WX), вы можете быть уверены в том, что предупреждения компилятора C4700 и C4701 укажут вам на стопроцентное и ве роятное использование неинициализированных переменных соответственно. Ну, а благодаря ключу /RTCu об этих видах ошибок могут узнать и более легкомыс ленные программисты. В связи с ключом /RTCu интересно отметить, что код, про веряющий неинициализированные переменные, включается в программу, если компилятор обнаруживает условие C4700 или C4701. Третий ключ, /RTC1, просто объединяет ключи /RTCu и /RTCs.
Последний ключ, /RTCc, обнаруживает отбрасывание данных при операциях присваивания — например, если вы пытаетесь присвоить значение 0x101 пере менной типа char. Как и в случае ключа /RTCu, если вы компилируете программу с ключами /W4 и /WX, отбрасывание данных приведет к ошибке C4244 во время ком пиляции. При получении ошибки /RTCc вам нужно или применить к нужным вам битам маску, или выполнить приведение типа. Поле Basic Runtime Checks (базо вые виды проверки периода выполнения) диалогового окна Property Pages (рис. 17 7) позволяет установить только /RTCu, /RTCs или /RTC1. Чтобы включить ключ /RTCc, нужно выбрать значение в поле Smaller Type Check (проверка при преобра зовании к меньшему типу), расположенному над полем Basic Runtime Checks.
Сначала я не мог понять, почему /RTCc не устанавливается ключом /RTC1. Одна ко потом я понял, что /RTCc может сообщать об ошибках при абсолютно коррек тном коде C, например:
char LoByte(int a)
{
return ((char)a) ;
}

ГЛАВА 17 Стандартная отладочная библиотека C и управление памятью |
649 |
|
|
Рис. 17 7. Установка ключей /RTCx
Если бы ключ /RTCc входил в состав /RTC1, люди могли бы думать, что все клю чи проверки ошибок в периода выполнения могут поднимать ложную тревогу. Тем не менее я все равно всегда устанавливаю ключ /RTCc, так как хочу знать обо всех потенциальных проблемах при выполнении своей программы.
Теперь обратимся к уведомлениям, которые вы получаете в случае обнаруже ния ошибки. При выполнении ваших программ вне отладчика код проверки в период выполнения выводит обычное диагностическое информационное окно стандартной библиотеки C. Если вы пишете службы или модули, не имеющие пользовательского интерфейса, вы должны будете перенаправить сообщение информационного окна при помощи функции _CrtSetReportMode, передав ей в качестве типа сообщения значение _CRT_ASSERT. Возможно, вы думали, что ключи /RTCx имеют единственный стандартный механизм уведомления пользователя, но это не так. При выполнении программы под отладчиком имеет место совершен но иной механизм уведомлений.
Если вы посмотрите на диалоговое окно Exceptions (исключения) интегриро ванной среды разработки Visual Studio .NET, вы можете заметить несколько но вых классов исключений. В данный момент нам интересен класс Native Run Time Checks (проверка в период выполнения). Открыв его в окне Exceptions, вы увиди те четыре разных исключения, соответствующих ключам /RTCx. Наверное, вы уже догадались, что, работая под управлением отладчика и сталкиваясь с проверкой в период выполнения, ваша программа генерирует специальное исключение, что бы отладчик мог его обработать.
Управление выводом проверки в период выполнения
Во многих ситуациях вас устроит информация, выводимая по умолчанию, одна ко в некоторых случаях вам захочется обрабатывать вывод данных об ошибке самостоятельно. Пример собственного обработчика ошибок приведен в листин ге 17 6. Список параметров, передаваемых в обработчики проверки ошибок в период выполнения, отличается тем, что он может включать переменное число параметров. Очевидно, эта гибкость показывает, что в будущем Microsoft плани рует добавить много других видов проверки ошибок в период выполнения. Ваш

650 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
собственный обработчик получает те же параметры, что и версия по умолчанию, поэтому вы можете выводить сведения об ошибках, соответствующих им перемен ных и другую информацию. Как видите, вы должны сами выбирать способ инфор мирования пользователя. Код листинга 17 6 находится на CD в примере RTCHan dling, так что вы можете изменять обработку ошибок, как вам будет угодно.
Листинг 17-6. Создание собственных отчетов об ошибках /RTCx
/*/////////////////////////////////////////////////////////////////////
ФУНКЦИЯ: |
HandleRTCFailure |
|
||
ОПИСАНИЕ: |
|
|
|
|
Обработчик |
ошибок |
периода |
выполнения (Run Time Checking, RTC), работающий |
|
при выполнении |
НЕ под |
отладчиком. При выполнении под управлением отладчика |
||
эта функция игнорируется. Поэтому вы никак не можете ее отладить! |
||||
ПАРАМЕТРЫ: |
|
|
|
|
iError |
7 |
тип ошибки, сообщаемый функции _RTC_SetErrorType. |
||
|
|
Обратите внимание, что я не использую этот параметр. |
||
szFile |
7 |
имя исходного файла, в котором произошла ошибка. |
||
iLine |
7 |
номер строки ошибки. |
||
szModule – |
модуль |
ошибки. |
|
|
szFormat – |
строка |
формата |
в стиле printf для переменного списка |
|
|
|
параметров. Обратите внимание, что я использую этот |
||
|
|
параметр только для получения их значений. |
||
... |
– |
"переменный" список параметров. Здесь могут передаваться |
||
|
|
только |
два значения. Первое — идентификатор ошибки RTC: |
|
|
|
1 |
= /RTCc |
|
|
|
2 |
= /RTCs |
|
|
|
3 |
= /RTCu |
|
|
|
Второе |
— указатель на строку, выводимую отладчиком. Это |
|
|
|
значение важно |
только для ключей /RTCs и /RTCu, так как |
|
|
|
они показывают |
переменную, вызвавшую проблему. |
|
ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ: |
|
|
||
TRUE |
7 после возврата из |
этой функции выполняется вызов _DebugBreak. |
FALSE – выполнение продолжается.
/////////////////////////////////////////////////////////////////////*/
//Отключение проверок периода выполнения для этой функции,
//нужное для предотвращения ее рекурсивного вызова. #pragma runtime_checks("", off)
//Критическая секция, защищающая функцию HandleRTCFailure
//от реентерабельности.
CRITICAL_SECTION g_csRTCLock ;
int HandleRTCFailure ( int |
/*iError*/ |
, |
const char * szFile |
, |
|
int |
iLine |
, |
const char * szModule |
, |
|
const char * szFormat |
, |
|
... |
|
) |
{ |
|
|


652 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|
|
|
|
|
|
|
|
|
|
|
iLine |
, |
|
szFile |
, |
|
szModule |
) ; |
|
} |
|
|
int iRes = TRUE ; |
|
|
if ( IDYES == MessageBox ( GetForegroundWindow ( ) |
, |
|
szBuff |
, |
|
_T ( "Run Time Check Failure" ) |
, |
|
MB_YESNO | MB_ICONQUESTION |
) ) |
|
{ |
|
|
// Возврат 1 означает, что по окончании этой функции |
|
|
// будет вызвана функция DebugBreak. |
|
|
iRes = 1 ; |
|
|
} |
|
|
else |
|
|
{ |
|
|
iRes = 0 ; |
|
|
} |
|
|
// Выход из критической секции. |
|
|
LeaveCriticalSection ( &g_csRTCLock ) ; |
|
return ( iRes ) ;
}
#pragma runtime_checks("", restore)
Установка собственного обработчика ошибок тривиальна; для этого нужно просто передать указатель на него в _RTC_SetErrorFunc. Есть несколько других фун кций, помогающих обрабатывать ошибки в период выполнения. Первая, _RTC_Get6 ErrDesc, получает строку, описывающую конкретную ошибку. _RTC_NumErrors возвра щает общее число ошибок, поддерживаемых текущими версиями компилятора и стандартной библиотеки. Кроме того, есть еще и функция _RTC_SetErrorType, ко торуя я нахожу немного опасной. Она позволяет отключить обработку ошибок для отдельных или всех специфических проверок в период выполнения.
Так как проверки в период выполнения основаны на возможностях стандарт ной библиотеки C, вы можете подумать, что, если ваша программа не использует стандартную библиотеку C, вы утрачиваете все преимущества ключей RTC. Если вам интересно, зачем вам когда нибудь может понадобиться программировать без стандартной библиотеки C, вспомните про ATL и макрос _ATL_MIN_CRT. Если вы не используете стандартную библиотеку C, то при определенном макросе __MSVC_RUN6 TIME_CHECKS нужно вызвать функцию _RTC_Initialize. Вы также должны предоста вить функцию _CRT_RTC_INIT, возвращающую указатель на ваш собственный обра ботчик ошибок.
Когда я только начал писать собственные обработчики вывода, я столкнулся с небольшой проблемой. Я не мог отладить собственный обработчик! Немного подумав об этом, вы поймете причину. Как я уже сказал, код проверки в период выполнения может определить, выполняете ли вы программу под управлением отладчика, и вывести информацию или в отладчике, или при помощи обычного