Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdfГЛАВА 4 Поддержка отладки ОС и как работают отладчики Win32 |
161 |
|
|
//Проверка наличия аргументов в командной строке. if ( 1 == argc )
{
ShowHelp ( ) ; return ;
}
//Необходим достаточно большой буфер для команды
//или параметров командной строки. TCHAR szCmdLine[ MAX_PATH + MAX_PATH ] ;
//Идентификатор процесса, если производится присоединение к нему. DWORD dwPID = 0 ;
szCmdLine[ 0 ] = _T ( '\0' ) ;
//Проверка, начинается ли командная строка со знака " ", так как это
//означает идентификатор процесса, к которому мы присоединяемся.
if ( _T ( ' ' ) == argv[1][0] )
{
//Попытка вычленить идентификатор процесса из командной строки.
//Передвинуться за символ ' ' в строке.
TCHAR * pPID = argv[1] + 1 ; dwPID = _tstol ( pPID ) ;
if ( 0 == dwPID )
{
_tprintf ( _T ( "Invalid PID value : %s\n" ) , pPID ) ; return ;
}
}
else
{
dwPID = 0 ;
// Я собираюсь запустить процесс. for ( int i = 1 ; i < argc ; i++ )
{
_tcscat ( szCmdLine , argv[ i ] ) ; if ( i < argc )
{
_tcscat ( szCmdLine , _T ( " " ) ) ;
}
}
}
//Место для возвращаемого значения. BOOL bRet = FALSE ;
//Установить обработчик CTRL+BREAK.
bRet = SetConsoleCtrlHandler ( CtrlBreakHandler , TRUE ) ;
см. след. стр.
162 ЧАСТЬ II Производительная отладка
if ( FALSE == bRet )
{
_tprintf ( _T ( "Unable to set CTRL+BREAK handler!\n" ) ) ; return ;
}
// Если идентификатор процесса равен 0, я запускаю процесс. if ( 0 == dwPID )
{
//Попытаемся запустить отлаживаемый процесс. Этот вызов функции
//выглядит, как обычный вызов CreateProcess, кроме специального
//необязательного флага DEBUG_ONLY_THIS_PROCESS.
STARTUPINFO |
stStartInfo |
; |
|
|
PROCESS_INFORMATION |
stProcessInfo |
; |
|
|
memset ( &stStartInfo |
, NULL , sizeof ( STARTUPINFO |
)); |
memset ( &stProcessInfo , NULL , sizeof ( PROCESS_INFORMATION));
stStartInfo.cb = sizeof ( STARTUPINFO ) ;
bRet = CreateProcess ( NULL |
, |
szCmdLine |
, |
NULL |
, |
NULL |
, |
FALSE |
, |
CREATE_NEW_CONSOLE | |
|
DEBUG_ONLY_THIS_PROCESS |
, |
NULL |
, |
NULL |
, |
&stStartInfo |
, |
&stProcessInfo |
) ; |
//Не забудьте закрыть описатели процесса и потока,
//возвращаемые CreateProcess.
VERIFY ( CloseHandle ( stProcessInfo.hProcess ) ) ;
VERIFY ( CloseHandle ( stProcessInfo.hThread ) ) ;
//Посмотрим, запустился ли процесс отлаживаемой программы. if ( FALSE == bRet )
{
_tprintf ( _T ( "Unable to start %s\n" ) , szCmdLine ) ; return ;
}
//Сохранить идентификатор процесса на случай
//необходимости отсоединения.
dwPID = stProcessInfo.dwProcessId ;
}
else
{
ГЛАВА 4 Поддержка отладки ОС и как работают отладчики Win32 |
163 |
|
|
bRet = DebugActiveProcess ( dwPID ) ; if ( FALSE == bRet )
{
_tprintf ( _T ( "Unable to attach to %u\n" ) , dwPID ) ; return ;
}
}
// Отлаживаемая программ запущена, поэтому запускаем цикл отладчика.
DEBUG_EVENT stDE |
|
|
; |
|
BOOL |
bSeenInitialBP |
= FALSE |
; |
|
BOOL |
bContinue |
= |
TRUE |
; |
HANDLE |
hProcess |
= |
INVALID_HANDLE_VALUE ; |
|
DWORD |
dwContinueStatus |
|
|
; |
// Цикл до тех пор, пока не потребуется остановиться. while ( TRUE == bContinue )
{
// Пауза до возникновения события отладки.
BOOL bProcessDbgEvent = WaitForDebugEvent ( &stDE , 100 ) ;
if ( TRUE == bProcessDbgEvent )
{
//Обработка конкретных событий отладки.
//Так как MinDBG — это только минимальный отладчик,
//он обрабатывает только несколько событий.
switch ( |
stDE.dwDebugEventCode ) |
|
{ |
|
|
case |
CREATE_PROCESS_DEBUG_EVENT |
: |
{ |
|
|
DisplayCreateProcessEvent(stDE.u.CreateProcessInfo);
//Сохраним описатель, который понадобится позже.
//Заметьте: вы не можете закрыть этот описатель.
//Если вы это сделаете, CloseHandle завершится с ошибкой. hProcess = stDE.u.CreateProcessInfo.hProcess ;
//Описатель файла можно закрыть безболезненно.
//Если вы закроете поток, CloseHandle провалится
//глубоко в ContinueDebugEvent, когда вы будете
//завершать приложение. VERIFY(CloseHandle(stDE.u.CreateProcessInfo.hFile));
dwContinueStatus = DBG_CONTINUE ;
} |
|
break ; |
|
case EXIT_PROCESS_DEBUG_EVENT |
: |
{ |
|
DisplayExitProcessEvent ( stDE.u.ExitProcess ) ; bContinue = FALSE ;
dwContinueStatus = DBG_CONTINUE ;
см. след. стр.
164 ЧАСТЬ II Производительная отладка
}
break ;
case LOAD_DLL_DEBUG_EVENT |
: |
{ |
|
DisplayDllLoadEvent ( hProcess , stDE.u.LoadDll ) ;
// Не забудьте закрыть описатель соответствующего файла. VERIFY ( CloseHandle( stDE.u.LoadDll.hFile ) ) ;
dwContinueStatus = DBG_CONTINUE ;
}
break ;
case UNLOAD_DLL_DEBUG_EVENT :
{
DisplayDllUnLoadEvent ( stDE.u.UnloadDll ) ; dwContinueStatus = DBG_CONTINUE ;
}
break ;
case CREATE_THREAD_DEBUG_EVENT : |
|
{ |
|
DisplayCreateThreadEvent ( stDE.dwThreadId |
, |
stDE.u.CreateThread |
) ; |
//Заметьте, что вы не можете закрыть описатель потока.
//Если вы это сделаете, CloseHandle провалится глубоко
//в ContinueDebugEvent.
dwContinueStatus = DBG_CONTINUE ; |
|
|
} |
|
|
break ; |
|
|
case EXIT_THREAD_DEBUG_EVENT |
: |
|
{ |
|
|
DisplayExitThreadEvent ( stDE.dwThreadId |
, |
|
stDE.u.ExitThread |
) ; |
|
dwContinueStatus = DBG_CONTINUE ; |
|
|
} |
|
|
break ; |
|
|
case OUTPUT_DEBUG_STRING_EVENT |
: |
|
{ |
|
|
DisplayODSEvent ( hProcess , stDE.u.DebugString ) ; dwContinueStatus = DBG_CONTINUE ;
}
break ;
case EXCEPTION_DEBUG_EVENT |
: |
{ |
|
DisplayExceptionEvent ( stDE.u.Exception ) ;
ГЛАВА 4 Поддержка отладки ОС и как работают отладчики Win32 |
165 |
|
|
//Единственное исключение, требующее специальной
//обработки, — это точка прерывания загрузчика. switch(stDE.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_BREAKPOINT :
{
//Если возникает исключение по точке прерывания
//и оно первое, я продолжаю свое веселье, иначе
//я передаю исключение отлаживаемой программе.
if ( FALSE == bSeenInitialBP )
{
bSeenInitialBP = TRUE ; dwContinueStatus = DBG_CONTINUE ;
}
else
{
// Хьюстон, у нас проблема! dwContinueStatus =
DBG_EXCEPTION_NOT_HANDLED ;
}
}
break ;
//Все остальные исключения передаем
//отлаживаемой программе.
default :
{
dwContinueStatus =
DBG_EXCEPTION_NOT_HANDLED ;
}
break ;
}
}
break ;
// Для всех остальных событий – просто продолжаем. default :
{
dwContinueStatus = DBG_CONTINUE ;
}
break ;
}
// Передаем управление ОС. #ifdef _DEBUG
BOOL bCntDbg =
#endif
ContinueDebugEvent ( stDE.dwProcessId , stDE.dwThreadId , dwContinueStatus ) ;
см. след. стр.
166 ЧАСТЬ II Производительная отладка
ASSERT ( TRUE == bCntDbg ) ;
}
// Необходимо ли отсоединение? if ( TRUE == g_bDoTheDetach )
{
//Отсоединение работает только в XP или более поздней версии,
//поэтому я должен выполнить GetProcAddress, чтобы найти
//DebugActiveProcessStop.
bContinue = FALSE ;
HINSTANCE hKernel32 =
GetModuleHandle ( _T ( "KERNEL32.DLL" ) ) ; if ( 0 != hKernel32 )
{
PFNDEBUGACTIVEPROCESSSTOP pfnDAPS = (PFNDEBUGACTIVEPROCESSSTOP)
GetProcAddress ( hKernel32 ,
"DebugActiveProcessStop" ) ;
if ( NULL != pfnDAPS )
{
#ifdef _DEBUG
BOOL bTemp =
#endif
pfnDAPS ( dwPID ) ;
ASSERT ( TRUE == bTemp ) ;
}
}
}
}
}
/*////////////////////////////////////////////////////////////////////// // Мониторы обработки Ctrl+Break
//////////////////////////////////////////////////////////////////////*/
BOOL WINAPI CtrlBreakHandler ( DWORD dwCtrlType )
{
//Я буду обрабатывать только Ctrl+Break.
//Все другое убивает отлаживаемую программу. if ( CTRL_BREAK_EVENT == dwCtrlType )
{
g_bDoTheDetach = TRUE ; return ( TRUE ) ;
}
return ( FALSE ) ;
}
/*////////////////////////////////////////////////////////////////////// // Отображает справку к программе.
//////////////////////////////////////////////////////////////////////*/
ГЛАВА 4 |
Поддержка отладки ОС и как работают отладчики Win32 |
167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
void ShowHelp ( void ) |
|
|
|
{ |
|
|
|
_tprintf ( _T ( |
"Start a program to debug:\n" ) |
|
|
_T ( |
" MinDBG <program to debug> " ) |
|
|
_T ( |
"<program's command line options>\n" ) |
|
|
_T ( |
"Attach to an existing program:\n" ) |
|
|
_T ( |
" |
MinDBG PID\n" ) |
|
_T ( |
" |
PID is the decimal process ID\n" ) ) ; |
|
} |
|
|
|
/*////////////////////////////////////////////////////////////////////// // Отображение события создания процесса.
//////////////////////////////////////////////////////////////////////*/
void DisplayCreateProcessEvent ( CREATE_PROCESS_DEBUG_INFO & stCPDI )
{
_tprintf ( _T ( "Create Process Event |
:\n" ) ) ; |
|
_tprintf ( _T ( " |
hFile |
: 0x%08X\n" ) , |
stCPDI.hFile |
) ; |
|
_tprintf ( _T ( " |
hProcess |
: 0x%08X\n" ) , |
stCPDI.hProcess |
) ; |
|
_tprintf ( _T ( " |
hThread |
: 0x%08X\n" ) , |
stCPDI.hThread |
) ; |
|
_tprintf ( _T ( " |
lpBaseOfImage |
: 0x%08X\n" ) , |
stCPDI.lpBaseOfImage |
) ; |
|
_tprintf ( _T ( " |
dwDebugInfoFileOffset |
: 0x%08X\n" ) , |
stCPDI.dwDebugInfoFileOffset |
) ; |
|
_tprintf ( _T ( " |
nDebugInfoSize |
: 0x%08X\n" ) , |
stCPDI.nDebugInfoSize |
) ; |
|
_tprintf ( _T ( " |
lpThreadLocalBase |
: 0x%08X\n" ) , |
stCPDI.lpThreadLocalBase |
) ; |
|
_tprintf ( _T ( " |
lpStartAddress |
: 0x%08X\n" ) , |
stCPDI.lpStartAddress |
) ; |
|
_tprintf ( _T ( " |
lpImageName |
: 0x%08X\n" ) , |
stCPDI.lpImageName |
) ; |
|
_tprintf ( _T ( " |
fUnicode |
: 0x%08X\n" ) , |
stCPDI.fUnicode |
) ; |
}
/*////////////////////////////////////////////////////////////////////// // Отображение событий создания потока.
//////////////////////////////////////////////////////////////////////*/
void DisplayCreateThreadEvent ( DWORD dwTID , CREATE_THREAD_DEBUG_INFO & stCTDI )
{
_tprintf ( _T ( "Create Thread Event |
:\n" ) ) ; |
|||||
_tprintf |
( |
_T |
( |
" |
TID |
: 0x%08X\n" ) , |
|
|
dwTID |
|
) ; |
||
_tprintf |
( |
_T |
( |
" |
hThread |
: 0x%08X\n" ) , |
|
|
stCTDI.hThread |
) ; |
см. след. стр.
168 ЧАСТЬ II Производительная отладка
_tprintf |
( |
_T |
( |
" |
lpThreadLocalBase |
: 0x%08X\n" |
) |
, |
|
stCTDI.lpThreadLocalBase |
|
|
) ; |
||||
_tprintf |
( |
_T |
( |
" |
lpStartAddress |
: 0x%08X\n" |
) |
, |
|
|
stCTDI.lpStartAddress |
|
|
) ; |
}
/*////////////////////////////////////////////////////////////////////// // Отображение событий завершения потока.
//////////////////////////////////////////////////////////////////////*/
void DisplayExitThreadEvent ( DWORD dwTID , EXIT_THREAD_DEBUG_INFO & stETDI )
{
_tprintf ( _T ( "Exit Thread Event |
:\n" ) ) ; |
|||||
_tprintf |
( |
_T |
( |
" |
TID |
: 0x%08X\n" ) , |
|
|
dwTID |
|
) ; |
||
_tprintf |
( |
_T |
( |
" |
dwExitCode |
: 0x%08X\n" ) , |
|
|
stETDI.dwExitCode |
) ; |
}
/*////////////////////////////////////////////////////////////////////// // Отображение событий завершения процесса.
//////////////////////////////////////////////////////////////////////*/
void DisplayExitProcessEvent ( EXIT_PROCESS_DEBUG_INFO & stEPDI )
{
_tprintf |
( |
_T |
( |
"Exit Process Event |
:\n" ) ) ; |
|
_tprintf |
( |
_T |
( |
" |
dwExitCode |
: 0x%08X\n" ) , |
|
|
stEPDI.dwExitCode |
) ; |
}
/*//////////////////////////////////////////////////////////////////////
// Отображение событий загрузки DLL.
//////////////////////////////////////////////////////////////////////*/
void DisplayDllLoadEvent ( HANDLE |
hProcess , |
|
|
|
LOAD_DLL_DEBUG_INFO |
& stLDDI |
) |
{ |
|
|
|
_tprintf ( _T ( "DLL Load Event |
:\n" ) ) ; |
|
|
_tprintf ( _T ( " |
hFile |
: 0x%08X\n" ) , |
|
stLDDI.hFile |
|
) ; |
|
_tprintf ( _T ( " |
lpBaseOfDll |
: 0x%08X\n" ) , |
|
stLDDI.lpBaseOfDll |
|
) ; |
|
_tprintf ( _T ( " |
dwDebugInfoFileOffset |
: 0x%08X\n" ) , |
|
stLDDI.dwDebugInfoFileOffset |
|
) ; |
|
_tprintf ( _T ( " |
nDebugInfoSize |
: 0x%08X\n" ) , |
|
stLDDI.nDebugInfoSize |
|
) ; |
|
_tprintf ( _T ( " |
lpImageName |
: 0x%08X\n" ) , |
|
stLDDI.lpImageName |
|
) ; |
|
_tprintf ( _T ( " |
fUnicode |
: 0x%08X\n" ) , |
|
stLDDI.fUnicode |
|
) ; |
static bool bSeenNTDLL = false ;
ГЛАВА 4 Поддержка отладки ОС и как работают отладчики Win32 |
169 |
|
|
TCHAR szDLLName[ MAX_PATH ] ;
//NTDLL.DLL – это специальный случай. В W2K lpImageName равен NULL,
//а в XP он указывает просто на 'ntdll.dll', поэтому я сфабрикую
//загрузочную информацию.
if ( false == bSeenNTDLL )
{
bSeenNTDLL = true ;
UINT uiLen = GetWindowsDirectory ( szDLLName , MAX_PATH ) ;
ASSERT ( uiLen > 0 ) ;
if ( uiLen > 0 )
{
_tcscpy ( szDLLName + uiLen , _T ( "\\NTDLL.DLL" ) ) ;
}
else
{
_tcscpy ( szDLLName , _T ( "GetWindowsDirectory FAILED!" ));
}
}
else
{
szDLLName[ 0 ] = _T ( '\0' ) ;
//Значение в lpImageName является указателем на полный путь
//загружаемой DLL. Этот адрес находится в адресном пространстве
//отлаживаемой программы.
LPCVOID lpPtr = 0 ;
DWORD dwBytesRead = 0 ;
BOOL bRet = FALSE ;
bRet = ReadProcessMemory ( hProcess |
, |
stLDDI.lpImageName |
, |
&lpPtr |
, |
sizeof ( LPCVOID ) , |
|
&dwBytesRead |
) ; |
if ( TRUE == bRet ) |
|
{ |
|
//Если имя в отлаживаемой программе задано в UNICODE,
//я могу копировать его прямо в szDLLName,
//так как здесь все в UNICODE.
if ( TRUE == stLDDI.fUnicode )
{
//Иногда невозможно сразу считать весь буфер,
//содержащий имя, поэтому необходимо делать это
//частями, пока оно не будет считано целиком. DWORD dwSize = MAX_PATH * sizeof ( TCHAR ) ; do
{
bRet = ReadProcessMemory ( hProcess |
, |
см. след. стр.
170 ЧАСТЬ II Производительная отладка
lpPtr |
, |
szDLLName |
, |
dwSize |
, |
&dwBytesRead |
) ; |
dwSize = dwSize 20 ;
}
while ( ( FALSE == bRet ) && ( dwSize > 20 ) ) ;
}
else
{
// Считывание строки ANSI и преобразование ее в UNICODE. char szAnsiName[ MAX_PATH ] ;
DWORD dwAnsiSize = MAX_PATH ;
do |
|
{ |
|
bRet = ReadProcessMemory ( hProcess |
, |
lpPtr |
, |
szAnsiName |
, |
dwAnsiSize |
, |
&dwBytesRead |
) ; |
dwAnsiSize = dwAnsiSize 20 ; |
|
|
} while ( ( FALSE == bRet |
) && ( dwAnsiSize > 20 ) ) ; |
|
if ( TRUE == bRet ) |
|
|
{ |
|
|
MultiByteToWideChar ( |
CP_THREAD_ACP |
, |
|
0 |
, |
|
szAnsiName |
, |
|
1 |
, |
|
szDLLName |
, |
|
MAX_PATH |
) ; |
} |
|
|
}
}
}
if ( _T ( '\0' ) == szDLLName[ 0 ] )
{
//С этой DLL связано несколько проблем. Попробуйте считать ее
//с помощью GetModuleHandleEx. Хотя вы и можете думать, что это
//будет работать, это только кажется, если не может быть получена
//информация о модуле этим способом. Если невозможно получить имя DLL
//вышеприведенной функцией, значит, вы в действительности имеете дело
//с перемещенной DLL.
DWORD dwRet = GetModuleFileNameEx ( hProcess |
, |
(HMODULE)stLDDI. |
|
lpBaseOfDll |
, |
szDLLName |
, |
MAX_PATH |
); |
ASSERT ( dwRet > 0 ) ; |
|
if ( 0 == dwRet ) |
|