Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
321
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 3

Отладка при кодировании

121

 

 

 

 

 

 

 

 

 

szFileName

,

 

szDir

,

 

dwLastError

,

 

dwLastError

,

 

szFmtMsg

,

 

*piFailCount

);

 

ASSERT ( SUCCEEDED ( hr ) ) ;

}

}

else

{

if ( NULL == szFunction )

{

szFunction = _T ( "Unknown function" ) ;

}

 

 

 

hr = StringCchPrintf

( szBuffer

 

,

BUFF_CHAR_SIZE

 

,

_T

( "Type

: %s\r\n"

) \

_T

( "Expression

: %s\r\n"

) \

_T

( "Function

: %s\r\n"

) \

_T

( "Module

: %s\r\n"

) \

_T

( "LastError

: 0x%08X (%d)\r\n"

)

_T

( "

%s"

) ,

szType

 

,

szExpression

 

,

szFunction

 

,

szModuleName

 

,

dwLastError

 

,

dwLastError

 

,

szFmtMsg

 

) ;

ASSERT ( SUCCEEDED (

hr ) ) ;

 

 

}

if ( DA_SHOWODS == ( DA_SHOWODS & GetDiagAssertOptions ( ) ) )

{

OutputDebugString ( szBuffer ) ;

OutputDebugString ( _T ( "\n" ) ) ;

}

if ( DA_SHOWEVENTLOG ==

( DA_SHOWEVENTLOG & GetDiagAssertOptions ( ) ) )

{

//Делаем запись в журнал событий,

//только если все действительно кошерно. static BOOL bEventSuccessful = TRUE ;

if ( TRUE == bEventSuccessful )

{

bEventSuccessful = OutputToEventLog ( szBuffer ) ;

}

}

см. след. стр.

122 ЧАСТЬ I Сущность отладки

if ( INVALID_HANDLE_VALUE != GetDiagAssertFile ( ) )

{

static BOOL bWriteSuccessful = TRUE ;

if ( TRUE == bWriteSuccessful )

{

DWORD dwWritten ;

 

 

int

iLen = lstrlen ( szBuffer ) ;

 

char * pToWrite = NULL ;

 

 

#ifdef UNICODE

 

 

 

pToWrite = (char*)_alloca ( iLen + 1 ) ;

 

BSUWide2Ansi ( szBuffer , pToWrite , iLen + 1 ) ;

 

#else

 

 

 

pToWrite = szBuffer ;

 

 

#endif

 

 

 

bWriteSuccessful = WriteFile (

GetDiagAssertFile ( )

,

 

 

pToWrite

,

 

 

iLen

,

 

 

&dwWritten

,

 

 

NULL

) ;

if ( FALSE == bWriteSuccessful

)

 

{

 

 

 

OutputDebugString (

_T ( "\n\nWriting assertion to file failed.\n\n" ) ) ;

}

}

}

//По умолчанию воспринимаем возвращаемое значение как IGNORE.

//Это особенно уместно, если пользователю не нужно окно MessageBox. INT_PTR iRet = IDIGNORE ;

//Отображаем диалог, только если он нужен пользователю

//и если процесс выполняется интерактивно.

if ( ( DA_SHOWMSGBOX == ( DA_SHOWMSGBOX & GetDiagAssertOptions()))&&

( TRUE == BSUIsInteractiveUser ( )

) )

{

 

iRet = PopTheFancyAssertion ( szBuffer

,

szEmail

,

dwStack

,

dwStackFrame

,

dwIP

,

piIgnoreCount

) ;

}

 

// Я закончил критическую секцию! LeaveCriticalSection ( &g_cCS.m_CritSec ) ;

ГЛАВА 3 Отладка при кодировании

123

 

 

SetLastError ( dwLastError ) ;

//Хочет ли пользователь перейти в отладчик? if ( IDRETRY == iRet )

{

return ( TRUE ) ;

}

//Хочет ли пользователь прервать программу? if ( IDABORT == iRet )

{

ExitProcess ( (UINT) 1 ) ; return ( TRUE ) ;

}

//Единственный оставшийся вариант — игнорировать утверждение. return ( FALSE ) ;

}

// Занимается отображением диалогового окна

утверждения.

 

static INT_PTR PopTheFancyAssertion ( TCHAR

*

szBuffer

,

LPCSTR

 

szEmail

,

DWORD64

dwStack

,

DWORD64

dwStackFrame

,

DWORD64

dwIP

,

int *

 

piIgnoreCount

)

{

 

 

 

//В этой подпрограмме я не выделяю память, потому что это может вызвать

//фатальные проблемы. Я собираюсь сильно повысить приоритет этих потоков,

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

//Если на этом этапе я попытаюсь выделить память, то могу попасть

//в ситуацию, когда потоки с малым приоритетом будут владеть CRT

//или синхронизирующим объектом кучи и он понадобится этому потоку.

//Следовательно, мы получим большое веселое зависание.

//(Да, я так уже делал, вот почему я это знаю!)

THREADINFO aThreadInfo [ k_MAXTHREADS ] ;

DWORD aThreadIds [ k_MAXTHREADS ] ;

//Первый поток в массиве информации о потоках это ВСЕГДА текущий

//поток. Это массив с нулевой базой, так что код диалога может

//рассматривать все потоки как равные. Однако в этой функции массив

//рассматривается как массив с единичной базой, поэтому текущий поток

//не приостанавливается вместе с остальными.

UINT uiThreadHandleCount = 1 ;

aThreadInfo[ 0 ].dwTID = GetCurrentThreadId ( ) ; aThreadInfo[ 0 ].hThread = GetCurrentThread ( ) ; aThreadInfo[ 0 ].szStackWalk = NULL ;

см. след. стр.

124ЧАСТЬ I Сущность отладки

//Сначала надо сразу повысить приоритет текущего потока. Я не хочу,

//чтобы создавались новые потоки, пока я готовлюсь их приостановить. int iOldPriority = GetThreadPriority ( GetCurrentThread ( ) ) ;

VERIFY ( SetThreadPriority ( GetCurrentThread ( )

,

THREAD_PRIORITY_TIME_CRITICAL ) ) ;

DWORD dwPID = GetCurrentProcessId ( ) ;

 

DWORD dwIDCount = 0 ;

 

if ( TRUE == GetProcessThreadIds ( dwPID

,

k_MAXTHREADS

,

(LPDWORD)&aThreadIds ,

&dwIDCount

) )

{

 

//Должен быть хоть один поток!! ASSERT ( 0 != dwIDCount ) ;

ASSERT ( dwIDCount < k_MAXTHREADS ) ;

//Вычисляем количество описателей. uiThreadHandleCount = dwIDCount ;

//Если количество описателей равно 1, это однопоточное

//приложение, и мне ничего не нужно делать!

if ( ( uiThreadHandleCount > 1

)

&&

 

( uiThreadHandleCount < k_MAXTHREADS )

 

)

{

 

 

 

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

//и сохраняем описатель, чтобы запустить его позже. int iCurrHandle = 1 ;

for ( DWORD i = 0 ; i < dwIDCount ; i++ )

{

//Конечно, не останавливать этот поток!!

if ( GetCurrentThreadId ( ) != aThreadIds[ i ] )

{

HANDLE

hThread =

 

 

 

OpenThread ( THREAD_ALL_ACCESS ,

 

 

FALSE

,

 

 

aThreadIds [ i ]

) ;

 

if ( (

NULL != hThread

) &&

 

(

INVALID_HANDLE_VALUE != hThread )

)

{

 

 

 

//Если SuspendThread возвращает 1,

//хранить значение этого потока незачем.

if ( (DWORD) 1 != SuspendThread ( hThread ) )

{

aThreadInfo[iCurrHandle].hThread = hThread ; aThreadInfo[iCurrHandle].dwTID =

aThreadIds[ i ] ; aThreadInfo[iCurrHandle].szStackWalk = NULL; iCurrHandle++ ;

}

ГЛАВА 3 Отладка при кодировании

125

 

 

else

{

VERIFY ( CloseHandle ( hThread ) ) ;

uiThreadHandleCount— ;

}

}

else

{

//Или для этого потока установлена какая то защита,

//или он закрылся сразу после того, как я собрал

//информацию о потоках. Значит, надо уменьшить

//общее число описателей потоков, или их будет

//на один больше.

TRACE( "Can't open thread: %08X\n" , aThreadIds [ i ] ) ;

uiThreadHandleCount— ;

}

}

}

}

}

//Возвращаем прежнее значение приоритета потока! SetThreadPriority ( GetCurrentThread ( ) , iOldPriority ) ;

//Убеждаемся, что ресурсы приложения установлены. JfxGetApp() >m_hInstResources = GetBSUInstanceHandle ( ) ;

//Сам диалог утверждения.

JAssertionDlg cAssertDlg (

szBuffer

 

 

,

 

szEmail

 

 

,

 

dwStack

 

 

,

 

dwStackFrame

 

 

,

 

dwIP

 

 

,

 

piIgnoreCount

 

 

,

 

(LPTHREADINFO)&aThreadInfo

,

 

uiThreadHandleCount

 

) ;

INT_PTR iRet = cAssertDlg.DoModal ( ) ;

 

 

 

if ( ( 1 != uiThreadHandleCount

) &&

 

 

( uiThreadHandleCount

< k_MAXTHREADS )

)

 

{

 

 

 

 

// Снова повышаем приоритет потока!

 

 

 

int iOldPriority = GetThreadPriority ( GetCurrentThread ( ) ) ;

VERIFY ( SetThreadPriority ( GetCurrentThread ( )

,

THREAD_PRIORITY_TIME_CRITICAL ) );

//Если в ходе работы я приостановил другие потоки, надо

//запустить их, закрыть описатели и удалить массив.

см. след. стр.

126 ЧАСТЬ I Сущность отладки

for ( UINT i = 1 ; i < uiThreadHandleCount ; i++ )

{

VERIFY ( (DWORD) 1 !=

ResumeThread ( aThreadInfo[ i ].hThread ) ) ;

VERIFY ( CloseHandle ( aThreadInfo[ i ].hThread ) ) ;

}

 

 

 

 

// Возвращаем прежнее значение приоритета потока.

 

 

VERIFY ( SetThreadPriority ( GetCurrentThread ( ) ,

 

 

 

iOldPriority

) ) ;

}

 

 

 

 

return ( iRet ) ;

 

 

 

 

}

 

 

 

 

BOOL BUGSUTIL_DLLINTERFACE

 

 

 

 

SuperAssertionA ( LPCSTR

szType

,

 

 

LPCSTR

szExpression

,

 

 

LPCSTR

szFunction

,

 

 

LPCSTR

szFile

,

 

 

int

iLine

,

 

 

LPCSTR

szEmail

,

 

 

DWORD64 dwStack

,

 

 

DWORD64

dwStackFrame

,

 

 

int *

piFailCount

,

 

 

int *

piIgnoreCount

)

 

 

{

 

 

 

 

int iLenType = lstrlenA ( szType ) ;

 

 

 

int iLenExp = lstrlenA ( szExpression ) ;

 

 

int iLenFile = lstrlenA ( szFile ) ;

 

 

 

int iLenFunc = lstrlenA ( szFunction ) ;

 

 

wchar_t * pWideType = (wchar_t*)

 

 

 

HeapAlloc ( GetProcessHeap ( )

 

,

 

HEAP_GENERATE_EXCEPTIONS ,

 

( iLenType + 1 ) *

 

 

 

sizeof ( wchar_t )

 

) ;

wchar_t * pWideExp = (wchar_t*)

 

 

 

HeapAlloc ( GetProcessHeap ( )

 

,

 

HEAP_GENERATE_EXCEPTIONS ,

 

( iLenExp + 1 ) *

 

 

 

sizeof ( wchar_t )

 

) ;

wchar_t * pWideFile = (wchar_t*)

 

 

 

HeapAlloc ( GetProcessHeap ( )

 

,

 

HEAP_GENERATE_EXCEPTIONS ,

 

( iLenFile + 1 ) *

 

 

 

 

sizeof ( wchar_t )

);

wchar_t * pWideFunc = (wchar_t*)

 

 

 

HeapAlloc ( GetProcessHeap ( )

 

,

 

HEAP_GENERATE_EXCEPTIONS ,

 

( iLenFunc + 1 ) *

 

 

 

 

sizeof ( wchar_t )

) ;

 

ГЛАВА 3 Отладка при кодировании

127

 

 

 

 

 

 

BSUAnsi2Wide ( szType , pWideType , iLenType + 1 ) ;

 

 

BSUAnsi2Wide ( szExpression , pWideExp , iLenExp + 1 ) ;

 

 

BSUAnsi2Wide ( szFile , pWideFile , iLenFile + 1 ) ;

 

 

BSUAnsi2Wide ( szFunction , pWideFunc , iLenFunc + 1 ) ;

 

 

BOOL bRet ;

 

 

 

 

bRet = RealSuperAssertion ( pWideType

 

,

 

 

pWideExp

 

,

 

 

pWideFunc

 

,

 

 

pWideFile

 

,

 

 

iLine

 

,

 

 

szEmail

 

,

 

 

dwStack

 

,

 

 

dwStackFrame

 

,

 

 

(DWORD64)_ReturnAddress ( )

,

 

 

piFailCount

 

,

 

 

piIgnoreCount

) ;

 

VERIFY ( HeapFree ( GetProcessHeap ( ) , 0 , pWideType ) ) ;

 

VERIFY ( HeapFree ( GetProcessHeap ( ) , 0 , pWideExp ) ) ;

 

VERIFY ( HeapFree ( GetProcessHeap ( ) , 0 , pWideFile ) ) ;

 

return ( bRet ) ;

 

 

 

 

}

 

 

 

 

BOOL BUGSUTIL_DLLINTERFACE

 

 

 

 

SuperAssertionW ( LPCWSTR szType

,

 

 

LPCWSTR szExpression

,

 

 

LPCWSTR szFunction

,

 

 

LPCWSTR

szFile

,

 

 

int

iLine

,

 

 

LPCSTR

szEmail

,

 

 

DWORD64 dwStack

,

 

 

DWORD64

dwStackFrame

,

 

 

int *

piFailCount

,

 

 

int *

piIgnoreCount

)

 

 

{

 

 

 

 

return ( RealSuperAssertion ( szType

 

,

 

 

szExpression

,

 

 

szFunction

 

,

 

 

szFile

 

,

 

 

iLine

 

,

 

 

szEmail

 

,

 

 

dwStack

 

,

 

 

dwStackFrame

,

 

 

(DWORD64)_ReturnAddress ( ) ,

 

 

piFailCount

,

 

 

piIgnoreCount

) ) ;

 

}

 

 

 

 

 

 

 

 

 

см. след. стр.

128ЧАСТЬ I Сущность отладки

//Возвращает количество инициаций утверждения в приложении.

//В этом количестве учитываются все пропуски утверждения. int BUGSUTIL_DLLINTERFACE GetSuperAssertionCount ( void )

{

return ( g_iTotalAssertions ) ;

}

static BOOL SafelyGetProcessHandleCount ( PDWORD pdwHandleCount )

{

static BOOL bAlreadyLooked = FALSE ; if ( FALSE == bAlreadyLooked )

{

HMODULE hKernel32 = ::LoadLibrary ( _T ( "kernel32.dll" ) ) ;

g_pfnGPH = (GETPROCESSHANDLECOUNT)

 

::GetProcAddress ( hKernel32

,

"GetProcessHandleCount"

) ;

FreeLibrary ( hKernel32 ) ;

 

bAlreadyLooked = TRUE ;

 

}

 

if ( NULL != g_pfnGPH )

 

{

 

return ( g_pfnGPH ( GetCurrentProcess ( ) , pdwHandleCount ) );

}

else

{

return ( FALSE ) ;

}

}

 

 

static SIZE_T GetModuleWithAssert ( DWORD64

dwIP

,

TCHAR *

szMod

,

DWORD

dwSize

)

{

 

 

//Пытаемся получить базовый адрес памяти для значения из стека.

//По базовому адресу я попытаюсь получить модуль.

MEMORY_BASIC_INFORMATION stMBI ;

 

ZeroMemory ( &stMBI , sizeof ( MEMORY_BASIC_INFORMATION ) ) ;

 

SIZE_T dwRet = VirtualQuery (

(LPCVOID)dwIP

,

 

&stMBI

,

 

sizeof ( MEMORY_BASIC_INFORMATION ) );

if ( 0 != dwRet )

 

 

{

 

 

dwRet = GetModuleFileName

( (HMODULE)stMBI.AllocationBase

,

 

szMod

,

 

dwSize

) ;

if ( 0 == dwRet )

 

 

{

// Сдаемся и просто возвращаем EXE.

dwRet = GetModuleFileName ( NULL , szMod , dwSize ) ;

}

ГЛАВА 3 Отладка при кодировании

129

 

 

}

return ( dwRet ) ;

}

Сам код диалога в ASSERTDLG.CPP довольно скромен, так что его не стоило приводить в книге. Когда мы со Скоттом Байласом обсуждали, на чем должно быть написано диалоговое окно, мы решили, что это должен быть простой язык, не требующий дополнительных двоичных файлов, кроме DLL, содержащей диалоговое окно, — все указывало на MFC. Когда я писал диалоговое окно, библиотека шаб лонов Windows Template Library (WTL) еще не вышла. Но скорее всего я и не стал бы ее использовать, так как отношусь к шаблонам с опаской. Лишь немногие раз работчики на самом деле понимают все переплетения в шаблонах, и большин ство ошибок, с которыми приходилось бороться моей компании, были прямым следствием применения шаблонов. Несколько лет назад мы с Джеффри Рихтером (Jeffrey Richter) участвовали в проекте, для которого требовался исключительно легковесный UI, и разработали простую библиотеку классов UI под именем JFX. Джеффри будет утверждать, что JFX означает «Jeffrey’s Framework», но на самом деле это «John’s Framework», что бы он ни говорил. Как бы то ни было, для созда ния UI я использовал JFX. Полный исходный код содержится среди файлов при меров к этой книге. В каталоге JFX есть пара тестовых программ, показывающих, как использовать JFX, и код диалога SUPERASSERT. Хорошая новость: JFX исключи тельно мал и компактен — финальная версия BugslayerUtil.DLL, включающая го раздо больше, чем просто SUPERASSERT, занимает менее 70 Кб.

Стандартный вопрос отладки

Почему в условных операторах ты всегда размещаешь константы слева?

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

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

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

130 ЧАСТЬ I Сущность отладки

Trace, Trace, Trace и еще раз Trace

Утверждения — возможно лучший прием профилактического программирования из всех, что вы узнали, а операторы Trace при правильном использовании вместе с утверждениями действительно позволят отлаживать приложения без отладчи ка. Для некоторых опытных программистов среди вас операторы Trace — суть отладка в стиле printf. Мощность отладки в стиле printf нельзя недооценивать, поскольку так отлаживалось большинство приложений до изобретения интерак тивных отладчиков. Трассировка в .NET интригует, так как, когда Microsoft впер вые публично упомянула про .NET, ключевые преимущества были ориентирова ны не на разработчиков, а на администраторов сетей и IT персонал, ответствен ных за развертывание написанных разработчиками приложений. Одним из важ нейших новых преимуществ Microsoft называла возможность для IT персонала легко включать трассировку, чтобы находить проблемы в приложениях! Читая это, я был ошеломлен, поскольку это говорило о том, что Microsoft откликнулась на страдания наших конечных пользователей, сталкивающихся с ошибками в про граммах.

Тонкость трассировки — в определении объема нужной информации для ре шения проблем на машинах, на которых не установлена среда разработки. Запи сать слишком много — получатся большие файлы, работа с которыми станет му кой, слишком мало — вы не сможете решить проблему. Действия по балансиров ке требуют наличия ровно такого объема записанной информации, чтобы избе жать экстренного перелета за 8 000 километров к пользователю, у которого толь ко что появилась та мерзкая ошибка, — перелета, в котором вам придется сидеть на среднем сиденье рядом с плачущим ребенком и больным пассажиром. В об щем, это значит, что вам понадобятся два уровня трассировки: один, отражающий основную работу в программе, чтобы видеть, что и когда вызывалось, и второй — для добавления в файл ключевых данных, чтобы вы могли отыскивать проблемы, связанные с потоками данных.

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

Как я отмечал в главе 2, следует иметь общекомандную систему ведения жур налов. Частью разработки этой системы должно стать определение формата трас сировки, особенно для облегчения работы с отладочной трассировкой. Без тако го формата эффективность трассировки быстро исчезает, так как никто не захо чет пробираться сквозь тонны текста без особых на то причин. Хорошая новость для приложений .NET в том, что Microsoft проделала большую работу, чтобы об легчить управление выводом. В машинных приложениях вам придется создавать собственные системы, но ниже я дам вам кое какие рекомендации в разделе «Трас сировка в приложениях C++».

Соседние файлы в предмете Программирование на C++