Отладка приложений

Программа SUPERASSERT


Рассмотрев проблемы, возникающие с системными утверждениями, покажем, как можно усовершенствовать оператор утверждения, чтобы существенно расширить выводимую им информацию о причинах возникновения проблемы. На рис. 3.1 показан пример панели сообщений программы SUPERASSERT. Поля Program, File и Line самоочевидны. Интерес представляют те, что следуют за полем Last Error.

В SUPERASSERT значения последних ошибок переводятся в их текстовые представления. При сбоях функций API просматривать сообщения об ошибках в текстовой форме чрезвычайно полезно, т. к. можно сразу же увидеть, почему соответствующие функции завершились неудачно, и быстрее начинать отладку. Например, если функция GetModuieFileName завершается потому, что размер входного буфера недостаточен, SUPERASSERT установит значение последней ошибки равным 122, что соответствует строчному значению ERROR_INSUFFICIENT_BUFFER (ошибка недостаточного размера буфера) из WINERROR.H. Увидев текст "The data area passed to a system call is too small" (область данных, переданная системному вызову, слишком мала), вы будете точно знать, что это за проблема и как ее следует решать.

Рис. 3.1. Пример панели сообщений программы SUPERASSERT

Кроме того, если вы посмотрите на строку Last Error на рис. 3.1, то увидите, что это не стандартное Windows-сообщение об ошибке. Если вы устанавливаете собственные значения последней ошибки (что я и рекомендую делать), то для трансляции таких сообщений можно добавить в программу SUPERASSERT собственный модуль ресурсов сообщений. Чтобы получить дополнительную информацию об использовании собственных ресурсов сообщений, просмотрите в MSDN тему "Message Compiler" (Компилятор сообщений). Существует и дополнительный стимул для применения таких ресурсов: с их помощью гораздо легче осуществлять интернационализацию приложения.

Чрезвычайно полезна часть панели, расположенная ниже строки Last Error. Это — трасса стека. Она показывает путь к оператору утверждения. SUPERASSERT старается показывать как можно больше информации в сообщениях утверждений, чтобы не нужно было собирать ту же информацию с помощью отладчика.




Вот еще одно интересное свойство SUPERASSERT: можно отказаться от открытия панели его сообщений. Поначалу это может показаться контрпродуктивным, но я ручаюсь, что это не так! Если вы следовали рекомендациям главы 2 и начинали тестирование отладочных конструкций с помощью инструмента регрессивного тестирования (regression-testing tool), то знаете, что управление такими панелями (со случайными сообщениями утверждений) почти невозможно. Из-за подобных проблем инженерам, тестирующим ПО, не очень нравится возиться с отладочными конструкциями. Работая же с программой SUPERASSERT, можно указать, чтобы вывод направлялся в функцию OutputDebugsString, дескриптор файла или и туда, и туда. Такая гибкость позволяет управлять кодом, получать всю обширную информацию утверждений, и иметь возможность автоматизировать отладочные построения. Кроме того, такое утверждение будет работать и в тех случаях, когда приложение не содержит интерфейса пользователя.

Пользоваться программой SUPERASSERT довольно легко. При работе в среде С и C++ для этого нужно только включить файл заголовка BUGSLAYERUTIL.H и установить связь с библиотекой BUGSLAYERUTIL.LIB. В листинге 3-3 показан файл DIAGASSERT.H, который содержит все макросы и функции и автоматически включается в заголовочный файл BUGSLAYERUTIL.H.

Листинг 3-3. DIAGASSERT.H (включенный в BUGSLAYERUTIL.H)

/ - - - - - - - - - - - - - - - - - - - - - -

"Debugging Applications" (Microsoft Press)

Copyright (с) 1999-2000 John Robbins — All Rights Reserved.

/- - - - - - - - - - - - - - - - - - - -

#fndef _DIAGASSERT_H

#define _DIAGASSERT_H

#ifdef _cplusplus

extern "C" {

#endif //_cplusplus

#include <tchar.h>

/////////////////////////////////////////////

            Директивы препроцессора #define

//////////////////////////////////////////////

// Основной материал должен быть доступен как для выпускных, так и для

 // отладочных построений. // Использовать глобальные флажки утверждений



#define DA_USEDEFAULTS OxOOOO

// Включает показ утверждений в панели сообщений (по умолчанию).

#define DA_SHOWMSGBOX OxOOOl

// Включает показ утверждений как через OutputDebugString (по умолчанию).

// the default.

#define DA_SHOWODS 0x0002

// Показывает трассу стека в утверждении. Выключен по умолчанию в

// макросе ASSERT и включен в макросе SUPERASSERT.

ifdefine DA_SHOWSTACKTRACE 0x0004

/*- - - - - - - - - - - - - - - - - - - - - 

ФУНКЦИЯ : SetDiagAssertOptions 

ОПИСАНИЕ :

Устанавливает глобальные режимы для нормального макроса ASSERT.

 ПАРАМЕТРЫ :

dwOpts — флажок новых режимов 

ВОЗВРАЩАЕТ :

Предыдущие режимы

- - - - - - - - - - - - - - - - - - - - - */

DWORD BUGSUTIL_DLLINTERFACE _stdcall

SetDiagAssertOptions ( DWORD dwOpts);

 /*- - - - - - - - - - - - - - - - - - - - - 

ФУНКЦИЯ : SetDiagAssertFile 

ОПИСАНИЕ :

Устанавливает дескриптор файла, в который будут записываться данные    любого утверждения. Чтобы отключить регистрацию, вызывайте эту функцию     с параметром INVALID_HANDLE_VALUE. Набор режимов из SetDiagAssertOptions еще применим; эта функция позволяет регистрировать assertion-информацию в файле.

В дескрипторе файла не делается никаких проверок ошибок

или записей в него.

 ПАРАМЕТРЫ :

hFile — дескриптор файла

 ВОЗВРАЩАЕТ :

Дескриптор предыдущего файла

 - - - - - - - - - - - - - - - - - - - - -* /

HANDLE BUGSUTILJ3LLINTERFACE _stdcall

 SetDiagAssertFile ( HANDLE hFile);

 /* - - - - - - - - - - - - - - - - - - - - - 

ФУНКЦИЯ : AddDiagAssertModule 

DISCUSSION :

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

 выбираться строки ошибок

 ОПИСАНИЕ :

hMod — добавляемый модуль

ВОЗВРАЩАЕТ :

TRUE - модуль был добавлен.

FALSE - внутренняя таблица заполнена.

  - - - - - - - - - - - - - - - - - - - - -*/

BOOL BUGSUTIL_DLLINTERFACE _stdcall

AddDiagAssertModule ( HMODULE hMod);

/*- - - - - - - - - - - - - - - - - - - - 



ФУНКЦИЯ : DiagAssert

 ОПИСАНИЕ :

Функция утверждения для программ на С и C++ 

ПАРАМЕТРЫ :

dwOverrideOpts — DA_* режимы для переопределения глобальных умолчаний для этого вызова в DiagAssert

szMsg — сообщение для показа в панели сообщений

 szFile — файл, который показывается в утверждении

 dwLine — номер строки, в которой имеется утверждение 

ВОЗВРАЩАЕТ :

FALSE — игнорировать утверждение.

TRUE — запустить DebugBreak.

  - - - - - - - - - - - - - - - - - - - - - -* /

BOOL BUGSUTIL_DLLINTERFACE _stdcall

DiagAssertA ( DWORD dwOverrideOpts , 

LPCSTR szMsg , 

LPCSTR szFile DWORD dwLine ); 

BOOL BUGSUTILJDLLINTERFACE _stdcall

DiagAssertW ( DWORD dwOverrideOpts ,

 LPCWSTR szMsg , 

LPCSTR szFile

DWORD dwLine ) ;

#ifdef UNICODE

#define DiagAssert DiagAssertW ttelse

#define DiagAssert DiagAssertA

#endif

/*- - - - - - - - - - - - - - - - - - 

ФУНКЦИЯ : DiagAssertVB 

ОПИСАНИЕ :

Функция утверждения для Visual Basic-программ.

 ПАРАМЕТРЫ

dwOverrideOpts — DA_* режимы для переопределения глобальных умолчаний

для этого вызова в DiagAssert

bAllowHalts — Если TRUE, то не показывает кнопки Retry и Ignore

 szMsg — Выводимое сообщение. За форматирование строки

ответственна сторона Visual Basic

 ВОЗВРАЩАЕТ :

FALSE — игнорировать утверждение.

TRUE - запустит DebugBreak.

 - - - - - - - - - - - - - - - - - - - */

BOOL BUGSUTILJDLLINTERFACE _stdcall

DiagAssertVB ( DWORD dwOverrideOpts,

 BOOL bAllowHalts, 

LPCSTR szMsg);

/*- - - - - - - - - - - - - - - - - - - -

ФУНКЦИЯ : SetDiagOutputFile

 ОПИСАНИЕ :

Устанавливает дескриптор файла, куда будут (по желанию) записаны любые trace-операторы. Чтобы выключить регистрацию, вызовите эту функцию с параметром INVALID_HANDLE_VALUE.

Не делается никаких проверок ошибок для дескриптора файла или каких-либо записей в него. 

ПАРАМЕТРЫ :

hFile — дескриптор файла 

ВОЗВРАЩАЕТ :

Дескриптор предыдущего файла



 - - - - - - - - - - - - - - - - - */

HANDLE BUGSUTIL_DLLINTERFACE _stdcall

SetDiagOutputFile ( HANDLE hFile);

 /*- - - - - - - - - - - - - - - - -

ФУНКЦИЯ : DiagOutput 

ОПИСАНИЕ :

Обеспечивает подпрограмму трассировки для посылки строк через

OutputDebugString

 ПАРАМЕТРЫ :

szFmt — форматная строка

... — параметры, которые будут расширены в szFmt 

ВОЗВРАЩАЕТ :

Нет.

 - - - - - - - - - - - - - - - - - -  */

void BUGSUTIL_DLLINTERFACE

DiagOutputA ( LPCSTR szFtat, ...); 

void BUGSUTIL_DLLINTERFACE

DiagOutputW ( LPCWSTR szFmt, ...);

#ifdef UNICODE

#define DiagOutput DiagOutputW

#else

idefine DiagOutput DiagOutputA

#endif

/*- - - - - - - - - - - - - - - - - - 

ФУНКЦИЯ : DiagOutputVB

 ОПИСАНИЕ :

Обеспечивает подпрограмму трассировки для посылки строк через

OutputDebugString для Visual Basic-программ 

ПАРАМЕТРЫ :

szMsg — строка сообщения 

ВОЗВРАЩАЕТ :

нет.

- - - - - - - - - - - - - - - - - - - - - -*/

void BUGSUTIL_DLLINTERFACE _stdcall

DiagOutputVB ( LPCSTR szMsg); 

/*/////////////////////////////////////

Директивы #undef

////////////////////////////////////////*/ 

#ifdef ASSERT

#undef ASSERT

#endif

 #ifdef assert

#undef assert

#endif 

#ifdef VERIFY

#undef VERIFY 

#endif

#ifdef TRACE 

3undef TRACE

 #endif

#ifdef TRACED 

#undef TRACED

 #endif

 #ifdef TRACE1

#undef TRACE1

#endif

 #ifdef TRACE2

#undef TRACE2

 #endif

#ifdef TRACE3

 #undef TRACE3 

#endif

/*////////////////////////////////////

_DEBUG определен

///////////////////////////////////////*/

#ifdef _DEBUG

/*//////////////////////////////////////////////

Директивы #define

/////////////////////////////////////////////*/

// Различные глобальные режимы, которые могут быть установлены

// в SetDiagAssertOptions. Если любой из этих режимов пересылается

//в DiagAssert в первом параметре, то это значение будет переопределять



// глобальные установки.

// Макрос assert используется ASSERT и SUPERASSERT.

// Выключить "conditional expression is constant" ("условное выражение

// является константой") из-за того, что while(0).

// Нужно сделать это выключение глобально, потому что при расширении

// макроса происходит ошибка компиляции.

#pragma warning ( disable : 4127)

#ifdef PORTABLE_BUGSLAYERUTIL

#define ASSERTMACRO(a,x)                         \

do                                            \

{                                               \

if ( !(x)                                       &&\

DiagAssert ( a, _T ( #x), _FILE_, _LINE_) )     \

{                                            \

DebugBreak () ;                                \

}                                             \



} while (0)

#else //!PORTABLE_BUGSLAYERUTIL

#define ASSERTMACRO(a,x)                       \

do                                             \

{                                             \

if ( !(x)                                       &&\

DiagAssert ( a, _T ( Ix), _FILE_, _LINE_) . )   \

{                                       \ 

_asm int 3                                    \

}                                            \

} while (0)

#endif // PORTABLE_BUGSLAYERUTIL

 // Нормальное утверждение. Оно использует умолчания модуля.

#define ASSERT(x) ASSERTMACRO(DA_OSEDEFAULTS, x)

 // Выполнить assert.

#define assert ASSERT // Доверяй, но проверяй!



#define VERIFY(x) ASSERT(x)

// Полный assert со всеми украшениями

#define SUPERASSERT(x) ASSERTMACRO ( DA_SHOWSTACKTRACE | \

DA_SHOWMSGBOX | \

 DA_SHOWODS , \

 x ,)

// Макрос режимов

#define SETDIAGASSERTOPTIONS(x) SetDiagAssertOptions(x) 

// Добавить макрос модуля

#define ADDDIAGASSERTMODULE(x) AddDiagAssertModule(x)

 // Макрос трассировки TRACE

#ifdef _cplusplus

#define TRACE ::DiagOutput

#endif

#define TRACED(sz)              DiagOutput(_T("Is"), _T(sz))

#define TRACEl(sz, pi)          DiagOutput(_T(sz), pi)

 #define TRACE2(sz, pi, p2)     DiagOutput(_T(sz), pi, p2)

 #define TRACE3(sz, pi, p2, p3) DiagOutput(_T(sz), pi, p2, p3)

#else // !_DEBUG 

/*/////////////////////////////////////////

_DEBUG !!HE!! определен

//////////////////////////////////////////*/

 #define ASSERTMACRO(a,x)

#define ASSERT(x)

#define VERIFY(x) ((void)(x))

#define SUPERASSERT(x)

#define SETDIAGASSERTOPTIONS(x)

#define ADDDIAGASSERTMODULE(x)

#ifdef _cplusplus

//inline void TraceOutput(LPCTSTR, ...) { }

#define TRACE (void)0

#endif

#define TRACED(fmt)

#define TRACE1(fmt,argl)

 #define TRACE2(fmt,argl,arg2)

 #define TRACE3(fmt,argl,arg2,arg3)

 #endif // _DEBUG

#ifdef _cplusplus

}

#endif //_cplusplus

#endif // _DIAGASSERT_H

С помощью программы SUPERASSERT можно автоматически переадресовывать все вызовы ASSERT и assert к своим функциям. Макросы _ASSERT и __ASSERTE не переадресовываются, чтобы не мешать другим работать с отладочной библиотекой времени выполнения. Не затрагиваются также макросы Visual Basic ASSERT_KINDOF и ASSERT_VALID. Для программ на Visual Basic нужно только включать в проект файл VBASSERTANDTRACE.BAS.

Используя макрос или функцию SUPERASSERT, вы автоматически получите трассу стека. Для макроса ASSERT трассировка стека по умолчанию выключена, т.


к. нецелесообразно вносить издержки, связанные с трассировкой стека, в общие утверждения. Однако, при желании использовать трассировку стека, можно легко включить ее, установив соответствующие опции с помощью макроса SETDIAGASSERTOPTIONS или функции setoiagAssertcptions и передав в ASSERT битовый флажок DA_SHOWSTACKTRACE. SUPERASSERT лучше применять там, где не ожидается серьезных проблем. Вряд ли он будет хорошо работать, скажем, в блоке исключения. В нормальных же ситуациях прекрасно работает и макрос ASSERT.

Общий вопрос отладки

Почему константы всегда помещаются в левой части условных операций?

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

if ( INVALID_HANDLE_VALUE == hFile)

вместо

if ( hFile == INVALID_ HANDLE_VALUE)

Этот стиль используется для того, чтобы избежать ошибок. В первой версии можно легко пропустить один из знаков равенства, что приведет к ошибке во время компиляции. Вторая же версия может и не выдавать предупреждения (что зависит от его уровня), но будет изменять значение переменной. Как в C/C++, так и в Visual Basic, при попытке назначать значение константе будет выдаваться ошибка компилятора. Если когда-либо вам приходилось прослеживать ошибку, включающую случайное назначение, то вы знаете, насколько труден поиск ошибок такого типа.

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

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



Содержание раздела