/* ========================================================================== File : ExceptionReport.h Vers.: 0.9 Plat.: Windows 98 or above Desc.: ExceptionHandle routine (report) Req. : DbgHelp v5.1 or above Adapted from MSDN March 2002 - Under the Hood ========================================================================== */ // #define _WIN32_WINDOWS 0x0410 // Windows 98 // #define _WIN32_WINNT 0x0500 // Windows 2000 #define USE_DBGHELPS 0 #include #include #include #include #include "ExceptionReport.h" #if USE_DBGHELPS == 1 #include "DBGHELPS.H" #endif CExceptionReport g_CExceptionReport; TCHAR CExceptionReport::m_szLogPrefixName[ MAX_PATH ]; TCHAR CExceptionReport::m_szModuleName[ MAX_PATH ]; LPTOP_LEVEL_EXCEPTION_FILTER CExceptionReport::m_OldFilter; HANDLE CExceptionReport::m_hReportFile; HANDLE CExceptionReport::m_hProcess; BOOL CExceptionReport::m_bHasSymbol; BOOL CExceptionReport::m_bWriteInfo; LPCERUserFunc CExceptionReport::m_lpUserFunc; CDBGFuncClass CExceptionReport::m_DBGHELP; // #pragma comment(linker, "/defaultlib:dbghelp.lib") // // Constructor // CExceptionReport::CExceptionReport() { m_bHasSymbol = FALSE; m_bWriteInfo = TRUE; SetProgramName(); SetUserFunc( NULL ); #if USE_DBGHELPS == 1 DBGHELP_DllMain( GetModuleHandle( NULL ), DLL_PROCESS_ATTACH, NULL ); #else if( m_DBGHELP.Init( "DBGHELP.DLL" ) == TRUE ) #endif // Install the unhandled exception filter function m_OldFilter = SetUnhandledExceptionFilter( UnhandledExceptionFilter ); m_hProcess = GetCurrentProcess(); } // // Destructor // CExceptionReport::~CExceptionReport() { SetUnhandledExceptionFilter( m_OldFilter ); #if USE_DBGHELPS == 1 DBGHELP_DllMain( GetModuleHandle( NULL ), DLL_PROCESS_DETACH, NULL ); #endif } // // Set application-specific function // void CExceptionReport::SetUserFunc( LPCERUserFunc lpUserFunc ) { m_lpUserFunc = lpUserFunc; } // // Set application-name for prefix of log filename // void CExceptionReport::SetProgramName( PTSTR pszProgramName ) { if( pszProgramName == NULL ) { TCHAR szDrive[MAX_PATH], szDir[MAX_PATH], szFilename[MAX_PATH], szExt[MAX_PATH]; // Figure out what the report file will be named, and store it away GetModuleFileName( 0, m_szLogPrefixName, MAX_PATH ); PTSTR pszDot = m_szLogPrefixName; // Look for the '.' before the "EXE" extension. Replace '.' to '\0' if( (pszDot = _tcsrchr( pszDot, _T('.') )) ) *pszDot = 0; _tsplitpath( m_szLogPrefixName, szDrive, szDir, szFilename, szExt ); _tcscpy( m_szLogPrefixName, szFilename ); } else { _tcscpy( m_szLogPrefixName, pszProgramName ); } } LONG WINAPI CExceptionReport::UnhandledExceptionFilterTwo( PEXCEPTION_POINTERS pExceptionInfo ) { if( m_hReportFile ) { Printf( "Exception occured AGAIN! EXITING NOW!\r\n" ); CloseHandle( m_hReportFile ); m_hReportFile = NULL; } if( m_OldFilter ) return m_OldFilter( pExceptionInfo ); else return EXCEPTION_CONTINUE_SEARCH; } // // This is called when unhandled exception occurs // LONG WINAPI CExceptionReport::UnhandledExceptionFilter( PEXCEPTION_POINTERS pExceptionInfo ) { TCHAR szLogFileName[ MAX_PATH ]; SetUnhandledExceptionFilter( UnhandledExceptionFilterTwo ); if(!m_bWriteInfo) return EXCEPTION_EXECUTE_HANDLER; _tcscpy( szLogFileName, m_szLogPrefixName ); SYSTEMTIME stSystemTime; GetLocalTime( &stSystemTime ); wsprintf( szLogFileName + strlen( szLogFileName ), " %04d-%02d-%02d %02d,%02d,%02d.TXT", stSystemTime.wYear, stSystemTime.wMonth, stSystemTime.wDay, stSystemTime.wHour, stSystemTime.wMinute, stSystemTime.wSecond ); if( (m_hReportFile = CreateFile( szLogFileName, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0 )) != INVALID_HANDLE_VALUE ) { SetFilePointer( m_hReportFile, 0, 0, FILE_END ); WriteExceptionReport( pExceptionInfo ); CloseHandle( m_hReportFile ); m_hReportFile = NULL; } #if USE_MINIDUMP == 1 TCHAR szDumpFileName[ MAX_PATH ]; _tcscpy( szDumpFileName, m_szLogPrefixName ); wsprintf( szDumpFileName + strlen( szDumpFileName ), " %04d-%02d-%02d %02d,%02d,%02d.DMP", stSystemTime.wYear, stSystemTime.wMonth, stSystemTime.wDay, stSystemTime.wHour, stSystemTime.wMinute, stSystemTime.wSecond ); HANDLE hDumpFile; if( m_DBGHELP.MiniDumpWriteDump &&(hDumpFile = CreateFile( szDumpFileName, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 )) != INVALID_HANDLE_VALUE ) { MINIDUMP_EXCEPTION_INFORMATION ExInfo; ExInfo.ThreadId = ::GetCurrentThreadId(); ExInfo.ExceptionPointers = pExceptionInfo; ExInfo.ClientPointers = NULL; m_DBGHELP.MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &ExInfo, NULL, NULL ); CloseHandle( hDumpFile ); } #endif if( m_lpUserFunc && (m_hReportFile = CreateFile( szLogFileName, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0 )) != INVALID_HANDLE_VALUE ) { SetFilePointer( m_hReportFile, 0, 0, FILE_END ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( " Application-specific log\r\n" ) ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( "\r\n" ) ); m_lpUserFunc( m_hReportFile ); Printf( _T( "\r\n" ) ); Printf( _T( "\r\n" ) ); Printf( _T( "==============================================================================\r\n" ) ); CloseHandle( m_hReportFile ); m_hReportFile = NULL; } // if( m_OldFilter ) // return m_OldFilter( pExceptionInfo ); // else // return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_EXECUTE_HANDLER; } // // Write informations to Report file. Called by UnhandledExceptionFilter // void CExceptionReport::WriteExceptionReport( PEXCEPTION_POINTERS pExceptionInfo ) { // Start out with a banner Printf( _T( "==============================================================================\r\n\r\n" ) ); WriteBasicInfo( pExceptionInfo->ExceptionRecord ); WriteRegistersInfo( pExceptionInfo->ContextRecord ); if( !m_DBGHELP.SymInitialize ) return; // if( m_DBGHELP.SymSetOptions ) // m_DBGHELP.SymSetOptions( SYMOPT_DEFERRED_LOADS ); // Initialize DbgHelp if( !m_DBGHELP.SymInitialize( GetCurrentProcess(), 0, TRUE ) ) return; CONTEXT trashableContext = *(pExceptionInfo->ContextRecord); WriteStackDetails( &trashableContext, FALSE ); #ifdef _M_IX86 // X86 Only! if( m_bHasSymbol ) { trashableContext = *(pExceptionInfo->ContextRecord); WriteStackDetails( &trashableContext, TRUE ); // Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); // Printf( _T( " Global Variables\r\n" ) ); // Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); // Printf( _T( "\r\n" ) ); // if( m_DBGHELP.SymEnumSymbols) // m_DBGHELP.SymEnumSymbols( GetCurrentProcess(), (DWORD64) GetModuleHandle( m_szModuleName ), 0, EnumerateSymbolsCallback, 0 ); } #endif // X86 Only! if( m_DBGHELP.SymCleanup ) m_DBGHELP.SymCleanup( GetCurrentProcess() ); Printf( _T("\r\n") ); Printf( _T("\r\n") ); WriteMemoryDump( pExceptionInfo->ContextRecord ); if( m_lpUserFunc ) { Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( " Application-specific log\r\n" ) ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( "\r\n" ) ); m_lpUserFunc( m_hReportFile ); Printf( _T( "\r\n" ) ); Printf( _T( "\r\n" ) ); } if( !m_lpUserFunc ) Printf( _T( "==============================================================================\r\n" ) ); } // // Write basic informations(user, computer name, error type) // void CExceptionReport::WriteBasicInfo( PEXCEPTION_RECORD pExceptionRecord ) { TCHAR szFileName[MAX_PATH] = _T( "" ), szUserName[MAX_PATH] = _T( "" ), szComputerName[MAX_PATH] = _T( "" ); DWORD dwUserLen = MAX_PATH, dwComputerLen = MAX_PATH; SYSTEMTIME stSystemTime; GetLocalTime( &stSystemTime ); GetModuleFileName( (HMODULE) NULL, szFileName, MAX_PATH ); GetUserName( szUserName, &dwUserLen ); GetComputerName( szComputerName, &dwComputerLen ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( " Basic Information\r\n" ) ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( "\r\n" ) ); Printf( _T( "Program Name : %s\r\n" ), m_szLogPrefixName ); Printf( _T( "EXE : %s\r\n" ), szFileName ); Printf( _T( "User : %s\r\n" ), szUserName ); Printf( _T( "Computer : %s\r\n" ), szComputerName ); Printf( _T( "\r\n" ) ); // Print information about the type of fault and where the fault occured DWORD section, offset; GetLogicalAddress( pExceptionRecord->ExceptionAddress, m_szModuleName, sizeof( m_szModuleName ), section, offset ); Printf( _T( "Program : %s\r\n" ), m_szModuleName ); Printf( _T( "Exception : %08X (%s)\r\n"), pExceptionRecord->ExceptionCode, GetExceptionString( pExceptionRecord->ExceptionCode ) ); Printf( _T( "Fault Address: %08X %02X:%08X\r\n"), pExceptionRecord->ExceptionAddress, section, offset ); Printf( _T( "\r\n" ) ); Printf( _T( "\r\n" ) ); // TODO: System Information! } // // Show the registers // void CExceptionReport::WriteRegistersInfo( PCONTEXT pContext ) { #ifdef _M_IX86 // X86 Only! Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( " x86 Registers\r\n" ) ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( "\r\n" ) ); Printf( _T("EAX=%08X EBX=%08X ECX=%08X EDX=%08X\r\n"), pContext->Eax, pContext->Ebx, pContext->Ecx, pContext->Edx ); Printf( _T("ESI=%08X EDI=%08X EBP=%08X\r\n"), pContext->Esi, pContext->Edi, pContext->Ebp ); Printf( _T("DS =%04X ES=%04X FS=%04X GS:%04X\r\n"), pContext->SegDs, pContext->SegEs, pContext->SegFs, pContext->SegGs ); Printf( _T("CS:EIP=%04X:%08X\r\n"), pContext->SegCs, pContext->Eip ); Printf( _T("SS:ESP=%04X:%08X\r\n"), pContext->SegSs, pContext->Esp ); Printf( _T("Flags=%08X\r\n"), pContext->EFlags ); Printf( _T("\r\n") ); Printf( _T("\r\n") ); #endif } // // Show the Memory // void CExceptionReport::WriteMemoryDump( PCONTEXT pContext ) { Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( " Memory Dump\r\n" ) ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( "\r\n" ) ); Printf( _T( "Code: %d bytes starting at (EIP = %08lX)\r\n" ), 16, pContext->Eip ); Dump( pContext->Eip, 16, FALSE ); Printf( _T("\r\n") ); Printf( _T( "Stack: %d bytes starting at (ESP = %08lX)\r\n" ), 1024, pContext->Esp ); Dump( pContext->Esp, 1024, TRUE ); Printf( _T("\r\n") ); Printf( _T("\r\n") ); } // // Given an exception code, returns a pointer to a static string with a description of the exception // #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x); LPTSTR CExceptionReport::GetExceptionString( DWORD dwCode ) { switch ( dwCode ) { EXCEPTION( ACCESS_VIOLATION ) EXCEPTION( DATATYPE_MISALIGNMENT ) EXCEPTION( BREAKPOINT ) EXCEPTION( SINGLE_STEP ) EXCEPTION( ARRAY_BOUNDS_EXCEEDED ) EXCEPTION( FLT_DENORMAL_OPERAND ) EXCEPTION( FLT_DIVIDE_BY_ZERO ) EXCEPTION( FLT_INEXACT_RESULT ) EXCEPTION( FLT_INVALID_OPERATION ) EXCEPTION( FLT_OVERFLOW ) EXCEPTION( FLT_STACK_CHECK ) EXCEPTION( FLT_UNDERFLOW ) EXCEPTION( INT_DIVIDE_BY_ZERO ) EXCEPTION( INT_OVERFLOW ) EXCEPTION( PRIV_INSTRUCTION ) EXCEPTION( IN_PAGE_ERROR ) EXCEPTION( ILLEGAL_INSTRUCTION ) EXCEPTION( NONCONTINUABLE_EXCEPTION ) EXCEPTION( STACK_OVERFLOW ) EXCEPTION( INVALID_DISPOSITION ) EXCEPTION( GUARD_PAGE ) EXCEPTION( INVALID_HANDLE ) } // If not one of the "known" exceptions, try to get the string // from NTDLL.DLL's message table. static TCHAR szBuffer[512] = { 0 }; FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle( _T("NTDLL.DLL") ), dwCode, 0, szBuffer, sizeof( szBuffer ), 0 ); return szBuffer; } #undef EXCEPTION // // Given a linear address, locates the module, section, and offset containing that address. // Note: the szModule paramater buffer is an output buffer of length specified by the len parameter (in characters!) // BOOL CExceptionReport::GetLogicalAddress( PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset ) { MEMORY_BASIC_INFORMATION mbi; if( !VirtualQuery( addr, &mbi, sizeof( mbi ) ) ) return FALSE; PVOID hMod = mbi.AllocationBase; if( !GetModuleFileName( (HMODULE) hMod, szModule, len ) ) { return FALSE; } // Point to the DOS header in memory PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER) hMod; // From the DOS header, find the NT (PE) header PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(((DWORD64) hMod) + pDosHdr->e_lfanew); PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr ); DWORD64 rva = (DWORD64)addr - (DWORD64)hMod; // RVA is offset from module load address // Iterate through the section table, looking for the one that encompasses // the linear address. for( unsigned i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++ ) { DWORD sectionStart = pSection->VirtualAddress; DWORD sectionEnd = sectionStart + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize); // Is the address in this section??? if ( (rva >= sectionStart) && (rva <= sectionEnd) ) { // Yes, address is in the section. Calculate section and offset, // and store in the "section" & "offset" params, which were // passed by reference. section = i+1; offset = (DWORD) (rva - sectionStart); return TRUE; } } return FALSE; // Should never get here! } // // Walks the stack, and writes the results to the report file // void CExceptionReport::WriteStackDetails( PCONTEXT pContext, BOOL bWriteVariables ) // TRUE if local/params should be output { Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( " Call Stack (%s)\r\n" ), bWriteVariables ? "Detail" : "Short" ); Printf( _T( "------------------------------------------------------------------------------\r\n" ) ); Printf( _T( "\r\n" ) ); Printf( _T("Address Frame Function SourceFile\r\n") ); DWORD dwMachineType = 0; // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag STACKFRAME sf; memset( &sf, 0, sizeof(sf) ); #ifdef _M_IX86 // Initialize the STACKFRAME structure for the first call. This is only // necessary for Intel CPUs, and isn't mentioned in the documentation. sf.AddrPC.Offset = pContext->Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = pContext->Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = pContext->Ebp; sf.AddrFrame.Mode = AddrModeFlat; dwMachineType = IMAGE_FILE_MACHINE_I386; #endif while( 1 ) { // Get the next stack frame if( !m_DBGHELP.StackWalk ) break; if( !m_DBGHELP.StackWalk( dwMachineType, m_hProcess, GetCurrentThread(), &sf, pContext, 0, m_DBGHELP.SymFunctionTableAccess, m_DBGHELP.SymGetModuleBase, 0 ) ) break; if( sf.AddrFrame.Offset == 0 ) // Basic sanity check to make sure break; // the frame is OK. Bail if not. Printf( _T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); // Get the name of the function for this stack frame entry BYTE symbolBuffer[ sizeof(SYMBOL_INFO) + 2048 ]; PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer; pSymbol->SizeOfStruct = sizeof(symbolBuffer); pSymbol->MaxNameLen = 2048; DWORD64 symDisplacement = 0; // Displacement of the input address, // relative to the start of the symbol if( m_DBGHELP.SymFromAddr && m_DBGHELP.SymFromAddr( m_hProcess, sf.AddrPC.Offset, &symDisplacement, pSymbol ) ) { TCHAR szModule[MAX_PATH] = _T(""); wsprintf( szModule, _T("%-s+%I64X"), pSymbol->Name, symDisplacement ); Printf( _T( "%-30s" ), szModule ); } else { // No symbol found. Print out the logical address instead. TCHAR szModule[MAX_PATH] = _T(""); DWORD section = 0, offset = 0; GetLogicalAddress( (PVOID) (DWORD64) sf.AddrPC.Offset, szModule, sizeof( szModule ), section, offset ); Printf( _T("%04X:%08X %s"), section, offset, szModule ); } // Get the source line for this stack frame entry IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) }; DWORD dwLineDisplacement; if ( m_DBGHELP.SymGetLineFromAddr && (m_DBGHELP.SymGetLineFromAddr( m_hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo )) ) { Printf(_T(" %s line %u"),lineInfo.FileName,lineInfo.LineNumber); if( m_bHasSymbol == FALSE ) m_bHasSymbol = TRUE; } Printf( _T("\r\n") ); // Write out the variables, if desired if( bWriteVariables ) { // Use SymSetContext to get just the locals/params for this frame IMAGEHLP_STACK_FRAME imagehlpStackFrame; imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset; if( m_DBGHELP.SymSetContext ) m_DBGHELP.SymSetContext( m_hProcess, &imagehlpStackFrame, 0 ); // Enumerate the locals/parameters if( m_DBGHELP.SymEnumSymbols) m_DBGHELP.SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &sf ); Printf( _T("\r\n") ); } } Printf( _T( "\r\n" ) ); Printf( _T( "\r\n" ) ); } // // The function invoked by SymEnumSymbols // BOOL CALLBACK CExceptionReport::EnumerateSymbolsCallback( PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext ) { char szBuffer[2048]; __try { if( FormatSymbolValue( pSymInfo, (STACKFRAME *) UserContext, szBuffer, sizeof( szBuffer ) ) ) Printf( _T("\t%s\r\n"), szBuffer ); } __except( 1 ) { Printf( _T("punting on symbol %s\r\n"), pSymInfo->Name ); } return TRUE; } // // Given a SYMBOL_INFO representing a particular variable, displays its contents. // If it's a user defined type, display the members and their values. // BOOL CExceptionReport::FormatSymbolValue( PSYMBOL_INFO pSym, STACKFRAME * sf, char * pszBuffer, unsigned cbBuffer ) { char * pszCurrBuffer = pszBuffer; // Indicate if the variable is a local or parameter if( pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER ) pszCurrBuffer += sprintf( pszCurrBuffer, "Parameter " ); else if( pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL ) pszCurrBuffer += sprintf( pszCurrBuffer, "Local " ); // If it's a function, don't do anything. if ( pSym->Tag == 5 ) // SymTagFunction from CVCONST.H from the DIA SDK return FALSE; // Emit the variable name pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", pSym->Name ); DWORD_PTR pVariable = 0; // Will point to the variable's data in memory if( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE ) { // if ( pSym->Register == 8 ) // EBP is the value 8 (in DBGHELP 5.1) { // This may change!!! pVariable = sf->AddrFrame.Offset; pVariable += (DWORD_PTR)pSym->Address; } // else // return FALSE; } else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER ) { return FALSE; // Don't try to report register variable } else { pVariable = (DWORD_PTR)pSym->Address; // It must be a global variable } // Determine if the variable is a user defined type (UDT). IF so, bHandled // will return TRUE. BOOL bHandled; pszCurrBuffer = DumpTypeIndex(pszCurrBuffer,pSym->ModBase, pSym->TypeIndex, 0, pVariable, bHandled ); if( !bHandled ) { // The symbol wasn't a UDT, so do basic, stupid formatting of the // variable. Based on the size, we're assuming it's a char, WORD, or // DWORD. BasicType basicType = GetBasicType( pSym->TypeIndex, pSym->ModBase ); pszCurrBuffer = FormatOutputValue(pszCurrBuffer, basicType, pSym->Size, (PVOID) pVariable ); } return TRUE; } ////////////////////////////////////////////////////////////////////////////// // If it's a user defined type (UDT), recurse through its members until we're // at fundamental types. When he hit fundamental types, return // bHandled = FALSE, so that FormatSymbolValue() will format them. ////////////////////////////////////////////////////////////////////////////// char *CExceptionReport::DumpTypeIndex( char *pszCurrBuffer, DWORD64 modBase, DWORD dwTypeIndex, unsigned nestingLevel, DWORD_PTR offset, BOOL & bHandled ) { bHandled = FALSE; if( !m_DBGHELP.SymGetTypeInfo ) return NULL; // Get the name of the symbol. This will either be a Type name (if a UDT), or the structure member name. WCHAR * pwszTypeName; if( m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_SYMNAME, &pwszTypeName ) ) { pszCurrBuffer += sprintf( pszCurrBuffer, " %ls", pwszTypeName ); LocalFree( pwszTypeName ); } // Determine how many children this type has. DWORD dwChildrenCount = 0; m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, &dwChildrenCount ); if( !dwChildrenCount ) // If no children, we're done return pszCurrBuffer; // Prepare to get an array of "TypeIds", representing each of the children. // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this. struct FINDCHILDREN : TI_FINDCHILDREN_PARAMS { ULONG MoreChildIds[1024]; FINDCHILDREN(){Count = sizeof(MoreChildIds) / sizeof(MoreChildIds[0]);} } children; children.Count = dwChildrenCount; children.Start= 0; // Get the array of TypeIds, one for each child type if( !m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN, &children ) ) { return pszCurrBuffer; } // Append a line feed pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); // Iterate through each of the children for( unsigned i = 0; i < dwChildrenCount; i++ ) { // Add appropriate indentation level (since this routine is recursive) for( unsigned j = 0; j <= nestingLevel+1; j++ ) pszCurrBuffer += sprintf( pszCurrBuffer, "\t" ); // Recurse for each of the child types BOOL bHandled2; pszCurrBuffer = DumpTypeIndex( pszCurrBuffer, modBase, children.ChildId[i], nestingLevel+1, offset, bHandled2 ); // If the child wasn't a UDT, format it appropriately if( !bHandled2 ) { // Get the offset of the child member, relative to its parent DWORD dwMemberOffset; m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], TI_GET_OFFSET, &dwMemberOffset ); // Get the real "TypeId" of the child. We need this for the SymGetTypeInfo( TI_GET_TYPEID ) call below. DWORD typeId; m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], TI_GET_TYPEID, &typeId ); // Get the size of the child member ULONG64 length; m_DBGHELP.SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_LENGTH,&length); // Calculate the address of the member DWORD_PTR dwFinalOffset = offset + dwMemberOffset; BasicType basicType = GetBasicType(children.ChildId[i], modBase ); pszCurrBuffer = FormatOutputValue( pszCurrBuffer, basicType, length, (PVOID)dwFinalOffset ); pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); } } bHandled = TRUE; return pszCurrBuffer; } char *CExceptionReport::FormatOutputValue( char *pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress ) { // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!) if( length == 1 ) pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PBYTE)pAddress ); else if ( length == 2 ) pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PWORD)pAddress ); else if ( length == 4 ) { if ( basicType == btFloat ) { pszCurrBuffer += sprintf(pszCurrBuffer," = %f", *(PFLOAT)pAddress); } else if ( basicType == btChar ) { if( !IsBadStringPtr( *(PSTR*)pAddress, 32) ) { pszCurrBuffer += sprintf( pszCurrBuffer, " = \"%.31s\"", *(PDWORD)pAddress ); } else pszCurrBuffer += sprintf( pszCurrBuffer, " = %X", *(PDWORD)pAddress ); } else pszCurrBuffer += sprintf(pszCurrBuffer," = %X", *(PDWORD)pAddress); } else if ( length == 8 ) { if( basicType == btFloat ) { pszCurrBuffer += sprintf( pszCurrBuffer, " = %lf", *(double *)pAddress ); } else pszCurrBuffer += sprintf( pszCurrBuffer, " = %I64X", *(DWORD64*)pAddress ); } return pszCurrBuffer; } BasicType CExceptionReport::GetBasicType( DWORD typeIndex, DWORD64 modBase ) { BasicType basicType; if( !m_DBGHELP.SymGetTypeInfo ) return btNoType; if( m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, typeIndex, TI_GET_BASETYPE, &basicType ) ) { return basicType; } // Get the real "TypeId" of the child. We need this for the // SymGetTypeInfo( TI_GET_TYPEID ) call below. DWORD typeId; if (m_DBGHELP.SymGetTypeInfo(m_hProcess,modBase, typeIndex, TI_GET_TYPEID, &typeId)) { if ( m_DBGHELP.SymGetTypeInfo( m_hProcess, modBase, typeId, TI_GET_BASETYPE, &basicType ) ) { return basicType; } } return btNoType; } #define BYTES_PER_LINE 16 #define SHIFT_NUM 4 #define IsPrint(c) ( isprint(c) || ((c) >= 0xA1) && ((c) <= 0xFE) ) // // Memory Dump function // void CExceptionReport::Dump( DWORD64 dw64Offset, DWORD dwSize, BOOL bAlign ) { DWORD dwLoc, dwILoc, dwX; LPBYTE pOut = (LPBYTE) dw64Offset; if( bAlign == TRUE ) pOut = (LPBYTE) ((dw64Offset >> SHIFT_NUM) << SHIFT_NUM); for( dwLoc = 0; dwLoc < dwSize; dwLoc += 16, pOut += BYTES_PER_LINE ) { LPBYTE pLine = pOut; Printf( _T( "%08lX: " ), (DWORD64) pOut ); for( dwX = 0, dwILoc = dwLoc; dwX < BYTES_PER_LINE; dwX++ ) { if( dwX == (BYTES_PER_LINE / 2) ) Printf( _T( " " ) ); if( dwILoc++ > dwSize ) { Printf( _T( "?? " ) ); } else { Printf( _T( "%02X " ), *(pLine++) ); } } pLine = pOut; Printf( " " ); for( dwX = 0, dwILoc = dwLoc; dwX < BYTES_PER_LINE; dwX++ ) { if( dwILoc++ > dwSize ) { Printf( _T( " " ) ); } else { Printf( _T( "%c" ), isprint( *pLine ) ? *pLine : '.'); pLine++; } } Printf( "\r\n" ); } } // // Helper function that writes to the report file, and allows the user to use printf style formating // int __cdecl CExceptionReport::Printf( const TCHAR * format, ... ) { TCHAR szBuff[2048]; int retValue; DWORD cbWritten; va_list argptr; va_start( argptr, format ); retValue = vsprintf( szBuff, format, argptr ); va_end( argptr ); WriteFile( m_hReportFile, szBuff, retValue * sizeof( TCHAR ), &cbWritten, 0 ); return retValue; }