MinDBG: A Simple Win32 Debugger


From a distance, a Win32 debugger is a simple program, with only a couple requirements. The first requirement is that the debugger must pass a special flag in the dwCreationFlags parameter to CreateProcess: DEBUG_ONLY_THIS_PROCESS. This flag tells the operating system that the calling thread will enter a debug loop to control the process it's starting. If the debugger can handle multiple processes spawned by the initial debuggee, it will pass DEBUG_PROCESS as the creation flag.

As you can see, since the debugger uses CreateProcess, the debugger and debuggee are in separate processes, making the Win32 operating systems much more robust when debugging. Even if the debuggee has wild memory writes, the debuggee won't crash the debugger. (Debuggers in the 16-bit Windows and pre–OS X Macintosh operating systems are susceptible to debuggee mischief because the debugger and the debuggee run in the same process context.)

The second requirement is that after the debuggee starts, the debugger must enter into a loop calling the WaitForDebugEvent API function to receive debugging notifications. When it has finished processing a particular debugging event, it calls ContinueDebugEvent. Be aware that only the thread that called CreateProcess with the special debug creation flags can call the Debugging API functions. The following pseudocode shows just how little code is required to create a Win32 debugger:

void main ( void ) {     CreateProcess ( ..., DEBUG_ONLY_THIS_PROCESS ,... ) ;          while ( 1 == WaitForDebugEvent ( ... ) )     {         if ( EXIT_PROCESS )         {             break ;         }         ContinueDebugEvent ( ... ) ;     } }

As you can see, a minimal Win32 debugger doesn't require multithreading, a user interface, or much of anything else. Nevertheless, as with most applications in Windows, the difference between minimal and reasonable is considerable. In reality, the Win32 Debugging API almost dictates that the actual debug loop needs to sit in a separate thread. As the name implies, WaitForDebugEvent blocks on an internal operating system event until the debuggee performs some operation that makes the operating system stop the debuggee so that it can tell the debugger about the event. If your debugger had a single thread, your user interface would totally hang until the debuggee triggered a debug event.

During the time a debugger sits in the debug loop, it receives various notifications that certain events took place in the debuggee. The following DEBUG_EVENT structure, which is filled in by the WaitForDebugEvent function, contains all the interesting information about a debug event. Table 4-1 describes each of the individual events.

typedef struct _DEBUG_EVENT {     DWORD dwDebugEventCode;     DWORD dwProcessId;     DWORD dwThreadId;     union {         EXCEPTION_DEBUG_INFO Exception;         CREATE_THREAD_DEBUG_INFO CreateThread;         CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;         EXIT_THREAD_DEBUG_INFO ExitThread;         EXIT_PROCESS_DEBUG_INFO ExitProcess;         LOAD_DLL_DEBUG_INFO LoadDll;         UNLOAD_DLL_DEBUG_INFO UnloadDll;         OUTPUT_DEBUG_STRING_INFO DebugString;         RIP_INFO RipInfo;     } u; } DEBUG_EVENT 
Table 4-1: Debugging Events

Debugging Event

Description

CREATE_PROCESS_DEBUG_EVENT

This debugging event is generated whenever a new process is created in a process being debugged or whenever the debugger begins debugging an already active process. The kernel generates this debugging event before the process begins to execute in user mode and before the kernel generates any other debugging events for the new process.

The DEBUG_EVENT structure contains a CREATE_PROCESS_DEBUG_INFO structure. This structure includes a handle to the new process, a handle to the process's image file, a handle to the process's initial thread, and other information that describes the new process.

The handle to the process has PROCESS_VM_READ and PROCESS_VM_WRITE access. If a debugger has these types of access to a process handle, it can read and write to the process's memory by using the ReadProcessMemory and WriteProcessMemory functions.

The handle to the process's image file has GENERIC_READ access and is opened for read-sharing.

The handle to the process's initial thread has THREAD_GET_CONTEXT, THREAD_SET_CONTEXT, and THREAD_SUSPEND_RESUME access to the thread. If a debugger has these types of access to a thread, it can read from and write to the thread's registers by using the GetThreadContext and SetThreadContext functions and can suspend and resume the thread by using the SuspendThread and ResumeThread functions.

CREATE_THREAD_DEBUG_EVENT

This debugging event is generated whenever a new thread is created in a process being debugged or whenever the debugger begins debugging an already active process. This debugging event is generated before the new thread begins to execute in user mode.

The DEBUG_EVENT structure contains a CREATE_THREAD_DEBUG_INFO structure. This structure includes a handle to the new thread and the thread's starting address. The handle has THREAD_GET_CONTEXT, THREAD_SET_CONTEXT, and THREAD_SUSPEND_RESUME access to the thread. If a debugger has these types of access to a thread, it can read from and write to the thread's registers by using the GetThreadContext and SetThreadContext functions and can suspend and resume the thread by using the SuspendThread and ResumeThread functions.

EXCEPTION_DEBUG_EVENT

This debugging event is generated whenever an exception occurs in the process being debugged. Possible exceptions include attempting to access inaccessible memory, executing breakpoint instructions, attempting to divide by 0, or any other exception noted in the MSDN documentation, "Structured Exception Handling."

The DEBUG_EVENT structure contains an EXCEPTION_DEBUG_INFO structure. This structure describes the exception that caused the debugging event.

Besides the standard exception conditions, an additional exception code can occur during console process debugging. The kernel generates a DBG_CONTROL_C exception code when Ctrl+C is input to a console process that handles Ctrl+C signals and is being debugged. This exception code isn't meant to be handled by applications. An application should never use an exception handler to deal with it. It is raised only for the benefit of the debugger and is used only when a debugger is attached to the console process.

When a process isn't being debugged or when the debugger passes on the DBG_CONTROL_C exception unhandled, the application's list of handler functions is searched. (For more information about console process handler functions, see the MSDN documentation for the SetConsoleCtrlHandler function.)

EXIT_PROCESS_DEBUG_EVENT

This debugging event is generated whenever the last thread in a process being debugged exits or calls ExitProcess. It occurs immediately after the kernel unloads the process's DLLs and updates the process's exit code.

The DEBUG_EVENT structure contains an EXIT_PROCESS_DEBUG_INFO structure that specifies the exit code.

The debugger deallocates any internal structures associated with the process on receipt of this debugging event. The kernel closes the debugger's handle to the exiting process and all the process's threads. The debugger should not close these handles.

EXIT_THREAD_DEBUG_EVENT

This debugging event is generated whenever a thread that is part of a process being debugged exits. The kernel generates this debugging event immediately after it updates the thread's exit code.

The DEBUG_EVENT structure contains an EXIT_THREAD_DEBUG_INFO structure that specifies the exit code.

The debugger deallocates any internal structures associated with the thread on receipt of this debugging event. The system closes the debugger's handle to the exiting thread. The debugger should not close these handles.

This debugging event doesn't occur if the exiting thread is the last thread of a process. In this case, the EXIT_PROCESS_DEBUG_EVENT debugging event occurs instead.

LOAD_DLL_DEBUG_EVENT

This debugging event is generated whenever a process being debugged loads a DLL. This debugging event occurs when the system loader resolves links to a DLL or when the debugged process uses the LoadLibrary function. This debugging event is called each time the DLL loads into the address space. If the DLL's reference count falls to 0, the DLL is unloaded. The next time the DLL is loaded, this event will be generated again.

The DEBUG_EVENT structure contains a LOAD_DLL_DEBUG_INFO structure. This structure includes a file handle to the newly loaded DLL, the base address of the DLL, and other information that describes the DLL.

Typically, a debugger loads a symbol table associated with the DLL on receipt of this debugging event.

OUTPUT_DEBUG_STRING_E VENT

This debugging event is generated when a process being debugged uses the OutputDebugString function.

The DEBUG_EVENT structure contains an OUTPUT_DEBUG_STRING_INFO structure. This structure specifies the address, length, and format of the debugging string.

UNLOAD_DLL_DEBUG_EVENT

This debugging event is generated whenever a process being debugged unloads a DLL by using the FreeLibrary function. This debugging event occurs only the last time a DLL is unloaded from a process's address space (that is, when the DLL's usage count is 0).

The DEBUG_EVENT structure contains an UNLOAD_DLL_DEBUG_INFO structure. This structure specifies the base address of the DLL in the address space of the process that unloads the DLL.

Typically, a debugger unloads a symbol table associated with the DLL upon receiving this debugging event.

When a process exits, the kernel automatically unloads the process's DLLs but doesn't generate an UNLOAD_DLL_DEBUG_EVENT debugging event.

When the debugger is processing the debug events returned by WaitForDebugEvent, it has full control over the debuggee because the operating system stops all the threads in the debuggee and won't reschedule them until ContinueDebugEvent is called. If the debugger needs to read from or write to the debuggee's address space, it can use ReadProcessMemory and WriteProcessMemory. If the memory is marked as read-only, you can use the VirtualProtect function to reset the protection levels if you need to write to that memory. If the debugger patches the debuggee's code via a call to WriteProcessMemory, it must call FlushInstructionCache to clear out the instruction cache for the memory. If you forget to call FlushInstructionCache, your changes might work when the memory is not in the CPU cache. If the memory is already in the CPU cache, the changes won't be applied until the memory is re-read into the CPU cache. Calling FlushInstructionCache is especially important on multiprocessor machines. If the debugger needs to get or set the debuggee's current context or CPU registers, it can call GetThreadContext or SetThreadContext.

The only Win32 debug event that needs special handling is the loader breakpoint, which is also referred to as the initial breakpoint. After the operating system sends initial CREATE_PROCESS_DEBUG_EVENT and LOAD_DLL_DEBUG_EVENT notifications for the implicitly loaded modules, the debugger receives an EXCEPTION_DEBUG_EVENT. This debug event is the loader breakpoint. The debuggee executes this breakpoint because the CREATE_PROCESS_DEBUG_EVENT indicates only that the process was loaded, not that it was executed. The loader breakpoint, which the operating system forces each debuggee to execute, is the first time the debugger knows when the debuggee is truly running. In real-world debuggers, the main data structure initialization, such as for symbol tables, is handled during process creation, and the debugger starts showing code disassembly or doing necessary debuggee patching in the loader breakpoint.

When the loader breakpoint occurs, the debugger should record that it saw the breakpoint so that the debugger can handle subsequent breakpoints accordingly. The only other processing needed for the first breakpoint (and for all breakpoints in general) depends on the CPU. For the Intel Pentium family, the debugger has to continue processing by calling ContinueDebugEvent and passing it the DBG_CONTINUE flag so that the debuggee resumes execution. Other CPUs might need to increment the instruction pointer past the breakpoint.

Listing 4-1 shows MinDBG, a minimal debugger that is available with this book's sample files. MinDBG processes all the debug events and properly runs a debuggee process. Additionally, it shows how to attach to an existing process as well as how to detach from a process being debugged. To start a process under MinDBG, pass the process to run on the command line along with any debuggee parameters. To attach and debug an existing process, pass the decimal process ID of the process on the command line prefixed with a hyphen (-). For example, if the process ID is 3245, you'd pass -3245 on the command line to affect the debugger attach. If you're running Windows XP or Windows Server 2003 and later, you can detach from debugging the process by pressing Ctrl+Break. If you run MinDBG, notice that the debug event handlers don't really do much other than show some basic information. Taking a minimal debugger and turning it into a real debugger involves quite a bit of work.

Listing 4-1: MINDBG.CPP

start example
 /*---------------------------------------------------------------------- Debugging Applications for Microsoft .NET and Microsoft Windows Copyright (c) 1997-2003 John Robbins -- All rights reserved.     The world's simplest debugger for Win32 programs ----------------------------------------------------------------------*/     /*////////////////////////////////////////////////////////////////////// // The Usual Includes //////////////////////////////////////////////////////////////////////*/ #include "stdafx.h"     /*////////////////////////////////////////////////////////////////////// // Prototypes and Types. //////////////////////////////////////////////////////////////////////*/ // Shows the minimal help. void ShowHelp ( void ) ;     // The break handler. BOOL WINAPI CtrlBreakHandler ( DWORD dwCtrlType ) ;     // Display functions void DisplayCreateProcessEvent ( CREATE_PROCESS_DEBUG_INFO & stCPDI ) ; void DisplayCreateThreadEvent ( DWORD                      dwTID  ,                                 CREATE_THREAD_DEBUG_INFO & stCTDI  ) ; void DisplayExitThreadEvent ( DWORD                    dwTID  ,                               EXIT_THREAD_DEBUG_INFO & stETDI  ) ; void DisplayExitProcessEvent ( EXIT_PROCESS_DEBUG_INFO & stEPDI ) ; void DisplayDllLoadEvent ( HANDLE                hProcess ,                            LOAD_DLL_DEBUG_INFO & stLDDI    ) ; void DisplayDllUnLoadEvent ( UNLOAD_DLL_DEBUG_INFO & stULDDI ) ; void DisplayODSEvent ( HANDLE                     hProcess ,                        OUTPUT_DEBUG_STRING_INFO & stODSI    ) ; void DisplayExceptionEvent ( EXCEPTION_DEBUG_INFO & stEDI ) ;     // The typedef for DebugActiveProcessStop. typedef BOOL (WINAPI *PFNDEBUGACTIVEPROCESSSTOP)(DWORD) ;     /*////////////////////////////////////////////////////////////////////// // File Scope Globals //////////////////////////////////////////////////////////////////////*/ // The flag that indicates I'm supposed to detach. static BOOL g_bDoTheDetach = FALSE ;     /*////////////////////////////////////////////////////////////////////// // The Entry Point. //////////////////////////////////////////////////////////////////////*/ void _tmain ( int argc , TCHAR * argv[ ] ) {     // Check that there is a command-line argument.     if ( 1 == argc )     {         ShowHelp ( ) ;         return ;     }         // Have a buffer big enough for the command and command line     // parameters.     TCHAR szCmdLine[ MAX_PATH + MAX_PATH ] ;     // The possible PID if attaching.     DWORD dwPID = 0 ;         szCmdLine[ 0 ] = _T ( '\0' ) ;          // Check if the command line starts with "-" as that indicates a     // process ID I will attach to.     if ( _T ( '-' ) == argv[1][0] )     {         // Attempt to strip off the PID from the command line option.                  // Move past the '-' in the string.         TCHAR * pPID = argv[1] + 1 ;         dwPID = _tstol ( pPID ) ;         if ( 0 == dwPID )         {             _tprintf ( _T ( "Invalid PID value : %s\n" ) , pPID ) ;             return ;         }     }     else     {         dwPID = 0 ;                  // I'm going to start up the process.         for ( int i = 1 ; i < argc ; i++ )         {             _tcscat ( szCmdLine , argv[ i ] ) ;             if ( i < argc )             {                 _tcscat ( szCmdLine , _T ( " " ) ) ;             }         }     }         // The return value holder.     BOOL bRet = FALSE ;          // Set the CTRL+BREAK handler.     bRet = SetConsoleCtrlHandler ( CtrlBreakHandler , TRUE ) ;     if ( FALSE == bRet )     {         _tprintf ( _T ( "Unable to set CTRL+BREAK handler!\n" ) ) ;         return ;     }          // If the PID is zero, I'm starting the process.     if ( 0 == dwPID )     {         // Try to start the debuggee process. The function call looks         // like a normal CreateProcess call except for the special start         // option flag 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             ) ;                  // Don't forget to close process and thread handles returned         // from CreateOricess.         VERIFY ( CloseHandle ( stProcessInfo.hProcess ) ) ;         VERIFY ( CloseHandle ( stProcessInfo.hThread ) ) ;             // See whether the debuggee process started.         if ( FALSE == bRet )         {             _tprintf ( _T ( "Unable to start %s\n" ) , szCmdLine ) ;             return ;         }                  // Save the process ID in case there's a detach.         dwPID = stProcessInfo.dwProcessId ;     }     else     {         bRet = DebugActiveProcess ( dwPID ) ;         if ( FALSE == bRet )         {             _tprintf ( _T ( "Unable to attach to %u\n" ) , dwPID ) ;             return ;         }     }          // The debuggee started, so let's enter the debug loop.     DEBUG_EVENT stDE                      ;     BOOL        bSeenInitialBP   = FALSE  ;     BOOL        bContinue        = TRUE   ;     HANDLE      hProcess         = INVALID_HANDLE_VALUE ;     DWORD       dwContinueStatus          ;         // Loop until told to stop.     while ( TRUE == bContinue )     {         // Pause until a debug event notification happens.         BOOL bProcessDbgEvent = WaitForDebugEvent ( &stDE , 100 ) ;                  if ( TRUE == bProcessDbgEvent )         {             // Handle the particular debug events. Because MinDBG is             // only a minimal debugger, it handles only a few events.             switch ( stDE.dwDebugEventCode )             {                 case CREATE_PROCESS_DEBUG_EVENT   :                 {                     DisplayCreateProcessEvent(stDE.u.CreateProcessInfo);                     // Save the handle information needed for later.                     // Note that you can't close this handle. If you                     // do, CloseHandle fails.                     hProcess = stDE.u.CreateProcessInfo.hProcess ;                         // You can safely close the file handle. If you                     // close the thread, CloseHandle fails deep in                     // ContinueDebugEvent when you end the application.                     VERIFY(CloseHandle(stDE.u.CreateProcessInfo.hFile));                         dwContinueStatus = DBG_CONTINUE ;                 }                 break ;                 case EXIT_PROCESS_DEBUG_EVENT   :                 {                     DisplayExitProcessEvent ( stDE.u.ExitProcess ) ;                     bContinue = FALSE ;                     dwContinueStatus = DBG_CONTINUE ;                 }                 break ;                     case LOAD_DLL_DEBUG_EVENT    :                 {                     DisplayDllLoadEvent ( hProcess , stDE.u.LoadDll ) ;                                          // Don't forget to close the corresponding file                     // handle.                     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  ) ;                     // Note that you can't close the thread handle. If                     // you do, CloseHandle fails deep inside                     // 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 ) ;                         // The only exception that I have to treat specially                     // is the initial breakpoint the loader provides.                     switch(stDE.u.Exception.ExceptionRecord.ExceptionCode)                     {                         case EXCEPTION_BREAKPOINT :                         {                             // If a breakpoint exception occurs and it's                             // the first seen, I continue on my merry                             // way; otherwise, I pass the exception on                             // to the debuggee.                             if ( FALSE == bSeenInitialBP )                             {                                 bSeenInitialBP = TRUE ;                                 dwContinueStatus = DBG_CONTINUE ;                             }                             else                             {                                 // Houston, we have a problem!                                 dwContinueStatus =                                              DBG_EXCEPTION_NOT_HANDLED ;                             }                         }                         break ;                             // Just pass on any other exceptions to the                         // debuggee.                         default                         :                         {                             dwContinueStatus =                                              DBG_EXCEPTION_NOT_HANDLED ;                         }                         break ;                     }                 }                 break ;                     // For any other events, just continue on.                 default                         :                 {                     dwContinueStatus = DBG_CONTINUE ;                 }                 break ;             }                 // Pass on to the operating system.     #ifdef _DEBUG             BOOL bCntDbg =     #endif                 ContinueDebugEvent ( stDE.dwProcessId ,                                     stDE.dwThreadId  ,                                     dwContinueStatus  ) ;                                                   ASSERT ( TRUE == bCntDbg ) ;         }         // Check to see if the detach is supposed to happen.         if ( TRUE == g_bDoTheDetach )         {             // Detaching only works on XP and higher so I'll have to             // do the GetProcAddress to look up the             // 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 ) ;                 }             }         }     } }     /*////////////////////////////////////////////////////////////////////// // Monitors CTRL+BREAK processing. //////////////////////////////////////////////////////////////////////*/ BOOL WINAPI CtrlBreakHandler ( DWORD dwCtrlType ) {     // I'll only handle CTRL+BREAK. Anything else kills the debuggee.     if ( CTRL_BREAK_EVENT == dwCtrlType )     {         g_bDoTheDetach = TRUE ;         return ( TRUE ) ;     }     return ( FALSE ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's program help. //////////////////////////////////////////////////////////////////////*/ 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" ) ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's create process events. //////////////////////////////////////////////////////////////////////*/ 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                                ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's create thread events. //////////////////////////////////////////////////////////////////////*/ 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                                 ) ;     _tprintf ( _T ( "   lpThreadLocalBase      : 0x%08X\n" ) ,              stCTDI.lpThreadLocalBase                         ) ;     _tprintf ( _T ( "   lpStartAddress         : 0x%08X\n" ) ,                stCTDI.lpStartAddress                          ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's exit thread events. //////////////////////////////////////////////////////////////////////*/ 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                              ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's exit process events. //////////////////////////////////////////////////////////////////////*/ void DisplayExitProcessEvent ( EXIT_PROCESS_DEBUG_INFO & stEPDI ) {     _tprintf ( _T ( "Exit Process Event        :\n" ) ) ;     _tprintf ( _T ( "   dwExitCode             : 0x%08X\n" ) ,                stEPDI.dwExitCode                              ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's DLL load events. //////////////////////////////////////////////////////////////////////*/ 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 ;     TCHAR szDLLName[ MAX_PATH ] ;          // NTDLL.DLL is special. On W2K, the lpImageName is NULL and     // on XP, the name just points to 'ntdll.dll' so I will fake the     // load information.     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' ) ;                  // The value in lpImageName is a pointer to the full path         // and name of the DLL being loaded. The address is in the         // debuggee address space.         LPCVOID lpPtr = 0 ;         DWORD dwBytesRead = 0 ;         BOOL bRet = FALSE ;             bRet = ReadProcessMemory ( hProcess           ,                                    stLDDI.lpImageName ,                                    &lpPtr             ,                                    sizeof ( LPCVOID ) ,                                    &dwBytesRead        ) ;         if ( TRUE == bRet )         {             // If the name in the debuggee is UNICODE, I can read it             // directly into the szDLLName variable as all this code             // is UNICODE.             if ( TRUE == stLDDI.fUnicode )             {                 // Occasionally, you can't read the whole buffer that                 // contains the name so I need to step down until                 // I can be sure there's no name at all.                 DWORD dwSize = MAX_PATH  * sizeof ( TCHAR ) ;                 do                 {                         bRet = ReadProcessMemory ( hProcess         ,                                                lpPtr            ,                                                szDLLName        ,                                                dwSize           ,                                                &dwBytesRead      ) ;                     dwSize = dwSize - 20 ;                 }                 while ( ( FALSE == bRet ) && ( dwSize > 20 ) ) ;             }             else             {                 // Read the ANSI string and convert it to 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 ] )     {         // This DLL has some issues. Try to read it with         // GetModuleHandleEx. While you'd think this would work for all         // it only seems to work if the module can't be retrieved by         // the above means. If you can't retrieve the DLL name with the         // code above, you're actually looking at a rebased DLL.         DWORD dwRet = GetModuleFileNameEx ( hProcess                 ,                                             (HMODULE)stLDDI.                                                         lpBaseOfDll  ,                                             szDLLName                ,                                             MAX_PATH                  );         ASSERT ( dwRet > 0 ) ;         if ( 0 == dwRet )         {             szDLLName[ 0 ] = _T ( '\0' ) ;         }     }          if ( _T ( '\0' ) != szDLLName[ 0 ] )     {         _tcsupr ( szDLLName ) ;         _tprintf ( _T ( "   DLL name               : %s\n" ) ,                    szDLLName                                  ) ;     }     else     {         _tprintf ( _T ( "UNABLE TO READ DLL NAME!!\n" ) ) ;     } }     /*////////////////////////////////////////////////////////////////////// // Display's DLL unload events. //////////////////////////////////////////////////////////////////////*/ void DisplayDllUnLoadEvent ( UNLOAD_DLL_DEBUG_INFO & stULDDI ) {     _tprintf ( _T ( "DLL Unload Event          :\n" ) ) ;     _tprintf ( _T ( "   lpBaseOfDll            : 0x%08X\n" ) ,                stULDDI.lpBaseOfDll                            ) ; }     /*////////////////////////////////////////////////////////////////////// // Display's OutputDebugString events. //////////////////////////////////////////////////////////////////////*/ void DisplayODSEvent ( HANDLE                     hProcess ,                        OUTPUT_DEBUG_STRING_INFO & stODSI    ) {     _tprintf ( _T ( "OutputDebugString Event   :\n" ) ) ;     _tprintf ( _T ( "   lpDebugStringData      : 0x%08X\n" ) ,                stODSI.lpDebugStringData                       ) ;     _tprintf ( _T ( "   fUnicode               : 0x%08X\n" ) ,                stODSI.fUnicode                                ) ;     _tprintf ( _T ( "   nDebugStringLength     : %d\n"     ) ,                stODSI.nDebugStringLength                      ) ;     _tprintf ( _T ( "   String                 : " ) ) ;         TCHAR szFinalBuff[ 512 ] ;     if ( stODSI.nDebugStringLength > 512 )     {         _tprintf ( _T ( "String to large!!\n" ) ) ;         return ;     }          DWORD dwRead ;     BOOL bRet ;          // Interestingly enough, all OutputDebugString calls, no matter if     // the application is full UNICODE or not always come in as ANSI     // strings.     if ( false == stODSI.fUnicode )     {         // Read the ANSI string.         char szAnsiBuff[ 512 ] ;         bRet = ReadProcessMemory ( hProcess                  ,                                    stODSI.lpDebugStringData  ,                                    szAnsiBuff                ,                                    stODSI.nDebugStringLength ,                                    &dwRead                    ) ;         if ( TRUE == bRet )         {             MultiByteToWideChar ( CP_THREAD_ACP ,                                   0             ,                                   szAnsiBuff    ,                                   -1            ,                                   szFinalBuff   ,                                   512            ) ;         }         else         {             szFinalBuff[ 0 ] = _T ( '\0' ) ;         }     }     else     {         // Read the UNICODE string.         bRet = ReadProcessMemory ( hProcess                     ,                                    stODSI.lpDebugStringData     ,                                    szFinalBuff                  ,                                    stODSI.nDebugStringLength *                                             sizeof ( TCHAR )    ,                                    &dwRead                       ) ;         if ( FALSE == bRet )         {             szFinalBuff[ 0 ] = _T ( '\0' ) ;         }     }         if ( _T ( '\0' ) != szFinalBuff[ 0 ] )     {         _tprintf ( _T ( "%s\n" ) , szFinalBuff ) ;     }     else     {         _tprintf ( _T ( "UNABLE TO READ ODS STRING!!\n" ) ) ;     } }     /*////////////////////////////////////////////////////////////////////// // Display's exception events. //////////////////////////////////////////////////////////////////////*/ void DisplayExceptionEvent ( EXCEPTION_DEBUG_INFO & stEDI ) {     _tprintf ( _T ( "Exception Event           :\n" ) ) ;     _tprintf ( _T ( "   dwFirstChance          : 0x%08X\n" ) ,                stEDI.dwFirstChance                            ) ;     _tprintf ( _T ( "   ExceptionCode          : 0x%08X\n" ) ,                stEDI.ExceptionRecord.ExceptionCode            ) ;     _tprintf ( _T ( "   ExceptionFlags         : 0x%08X\n" ) ,                stEDI.ExceptionRecord.ExceptionFlags           ) ;     _tprintf ( _T ( "   ExceptionRecord        : 0x%08X\n" ) ,                stEDI.ExceptionRecord.ExceptionRecord          ) ;     _tprintf ( _T ( "   ExceptionAddress       : 0x%08X\n" ) ,                stEDI.ExceptionRecord.ExceptionAddress         ) ;     _tprintf ( _T ( "   NumberParameters       : 0x%08X\n" ) ,                stEDI.ExceptionRecord.NumberParameters         ) ; }
end example




Debugging Applications for Microsoft. NET and Microsoft Windows
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2003
Pages: 177
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net