//========================================== // Matt Pietrek // MSDN Magazine, 2002 // FILE: WheatyExceptionReport.CPP //========================================== #define WIN32_LEAN_AND_MEAN #pragma warning(disable:4996) #pragma warning(disable:4312) #pragma warning(disable:4311) #include #include #include #include #define _NO_CVCONST_H #include #include "WheatyExceptionReport.h" #define CrashFolder _T("Crashes") //#pragma comment(linker, "/defaultlib:dbghelp.lib") inline LPTSTR ErrorMessage(DWORD dw) { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); return (LPTSTR)lpMsgBuf; } //============================== Global Variables ============================= // // Declare the static variables of the WheatyExceptionReport class // TCHAR WheatyExceptionReport::m_szLogFileName[MAX_PATH]; LPTOP_LEVEL_EXCEPTION_FILTER WheatyExceptionReport::m_previousFilter; HANDLE WheatyExceptionReport::m_hReportFile; HANDLE WheatyExceptionReport::m_hProcess; // Declare global instance of class WheatyExceptionReport g_WheatyExceptionReport; //============================== Class Methods ============================= WheatyExceptionReport::WheatyExceptionReport( ) // Constructor { // Install the unhandled exception filter function m_previousFilter = SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter); m_hProcess = GetCurrentProcess(); } //============ // Destructor //============ WheatyExceptionReport::~WheatyExceptionReport( ) { if(m_previousFilter) SetUnhandledExceptionFilter( m_previousFilter ); } //=========================================================== // Entry point where control comes on an unhandled exception //=========================================================== LONG WINAPI WheatyExceptionReport::WheatyUnhandledExceptionFilter( PEXCEPTION_POINTERS pExceptionInfo ) { TCHAR module_folder_name[MAX_PATH]; GetModuleFileName( 0, module_folder_name, MAX_PATH ); TCHAR* pos = _tcsrchr(module_folder_name, '\\'); if(!pos) return 0; pos[0] = '\0'; ++pos; TCHAR crash_folder_path[MAX_PATH]; sprintf(crash_folder_path, "%s\\%s", module_folder_name, CrashFolder); if(!CreateDirectory(crash_folder_path, NULL)) { if(GetLastError() != ERROR_ALREADY_EXISTS) return 0; } SYSTEMTIME systime; GetLocalTime(&systime); sprintf(m_szLogFileName, "%s\\%s_[%u-%u_%u-%u-%u].txt", crash_folder_path, pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond ); m_hReportFile = CreateFile( m_szLogFileName, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0 ); if ( m_hReportFile ) { SetFilePointer( m_hReportFile, 0, 0, FILE_END ); GenerateExceptionReport( pExceptionInfo ); CloseHandle( m_hReportFile ); m_hReportFile = 0; } if ( m_previousFilter ) return m_previousFilter( pExceptionInfo ); else return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/; } BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount) { if(!sProcessorName) return FALSE; HKEY hKey; LONG lRet; lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), 0, KEY_QUERY_VALUE, &hKey); if (lRet != ERROR_SUCCESS) return FALSE; TCHAR szTmp[2048]; DWORD cntBytes = sizeof(szTmp); lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), NULL, NULL, (LPBYTE)szTmp, &cntBytes); if (lRet != ERROR_SUCCESS) return FALSE; ::RegCloseKey(hKey); sProcessorName[0] = '\0'; // Skip spaces TCHAR* psz = szTmp; while (iswspace(*psz)) ++psz; _tcsncpy(sProcessorName, psz, maxcount); return TRUE; } BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax) { // Try calling GetVersionEx using the OSVERSIONINFOEX structure. // If that fails, try using the OSVERSIONINFO structure. OSVERSIONINFOEX osvi = { 0 }; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); BOOL bOsVersionInfoEx; bOsVersionInfoEx = ::GetVersionEx((LPOSVERSIONINFO)(&osvi)); if (!bOsVersionInfoEx) { osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (!::GetVersionEx((OSVERSIONINFO*)&osvi)) return FALSE; } *szVersion = _T('\0'); TCHAR wszTmp[128]; switch (osvi.dwPlatformId) { // Windows NT product family. case VER_PLATFORM_WIN32_NT: // Test for the specific product family. if (osvi.dwMajorVersion == 6) _tcsncat(szVersion, _T("Windows Vista or Windows Server 2008 "), cntMax); if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax); if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax); if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax); if (osvi.dwMajorVersion <= 4 ) _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax); // Test for specific product on Windows NT 4.0 SP6 and later. if (bOsVersionInfoEx) { // Test for the workstation type. #if WINVER < 0x0500 if (osvi.wReserved[1] == VER_NT_WORKSTATION) #else if (osvi.wProductType == VER_NT_WORKSTATION) #endif // WINVER < 0x0500 { if (osvi.dwMajorVersion == 4) _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax); #if WINVER < 0x0500 else if (osvi.wReserved[0] & VER_SUITE_PERSONAL) #else else if (osvi.wSuiteMask & VER_SUITE_PERSONAL) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Home Edition "), cntMax); #if WINVER < 0x0500 else if (osvi.wReserved[0] & VER_SUITE_EMBEDDEDNT) #else else if (osvi.wSuiteMask & VER_SUITE_EMBEDDEDNT) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Embedded "), cntMax); else _tcsncat(szVersion, _T("Professional "), cntMax); } // Test for the server type. #if WINVER < 0x0500 else if (osvi.wReserved[1] == VER_NT_SERVER) #else else if (osvi.wProductType == VER_NT_SERVER) #endif // WINVER < 0x0500 { if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) { #if WINVER < 0x0500 if (osvi.wReserved[0] & VER_SUITE_DATACENTER) #else if (osvi.wSuiteMask & VER_SUITE_DATACENTER) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Datacenter Edition "), cntMax); #if WINVER < 0x0500 else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE) #else else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Enterprise Edition "), cntMax); #if WINVER < 0x0500 else if (osvi.wReserved[0] == VER_SUITE_BLADE) #else else if (osvi.wSuiteMask == VER_SUITE_BLADE) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Web Edition "), cntMax); else _tcsncat(szVersion, _T("Standard Edition "), cntMax); } else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) { #if WINVER < 0x0500 if (osvi.wReserved[0] & VER_SUITE_DATACENTER) #else if (osvi.wSuiteMask & VER_SUITE_DATACENTER) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Datacenter Server "), cntMax); #if WINVER < 0x0500 else if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE ) #else else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE ) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Advanced Server "), cntMax); else _tcsncat(szVersion, _T("Server "), cntMax); } else // Windows NT 4.0 { #if WINVER < 0x0500 if (osvi.wReserved[0] & VER_SUITE_ENTERPRISE) #else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE) #endif // WINVER < 0x0500 _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax); else _tcsncat(szVersion, _T("Server 4.0 "), cntMax); } } } // Display service pack (if any) and build number. if (osvi.dwMajorVersion == 4 && _tcsicmp(osvi.szCSDVersion, _T("Service Pack 6")) == 0) { HKEY hKey; LONG lRet; // Test for SP6 versus SP6a. lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey); if (lRet == ERROR_SUCCESS) { _stprintf(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"), osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); _tcsncat(szVersion, wszTmp, cntMax); } else // Windows NT 4.0 prior to SP6a { _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); _tcsncat(szVersion, wszTmp, cntMax); } ::RegCloseKey(hKey); } else // Windows NT 3.51 and earlier or Windows 2000 and later { if (!_tcslen(osvi.szCSDVersion)) _stprintf(wszTmp, _T("(Version %d.%d, Build %d)"), osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); else _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); _tcsncat(szVersion, wszTmp, cntMax); } break; default: _stprintf(wszTmp, _T("%s (Version %d.%d, Build %d)"), osvi.szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF); _tcsncat(szVersion, wszTmp, cntMax); break; } return TRUE; } void WheatyExceptionReport::PrintSystemInfo() { SYSTEM_INFO SystemInfo; ::GetSystemInfo(&SystemInfo); MEMORYSTATUS MemoryStatus; MemoryStatus.dwLength = sizeof (MEMORYSTATUS); ::GlobalMemoryStatus(&MemoryStatus); TCHAR sString[1024]; _tprintf(_T("//=====================================================\r\n")); if (_GetProcessorName(sString, countof(sString))) _tprintf(_T("*** Hardware ***\r\nProcessor: %s\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); else _tprintf(_T("*** Hardware ***\r\nProcessor: \r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"), SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400); if(_GetWindowsVersion(sString, countof(sString))) _tprintf(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString); else _tprintf(_T("\r\n*** Operation System:\r\n\r\n")); } //=========================================================================== void WheatyExceptionReport::printTracesForAllThreads() { HANDLE hThreadSnap = INVALID_HANDLE_VALUE; THREADENTRY32 te32; DWORD dwOwnerPID = GetCurrentProcessId(); m_hProcess = GetCurrentProcess(); // Take a snapshot of all running threads hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); if( hThreadSnap == INVALID_HANDLE_VALUE ) return; // Fill in the size of the structure before using it. te32.dwSize = sizeof(THREADENTRY32 ); // Retrieve information about the first thread, // and exit if unsuccessful if( !Thread32First( hThreadSnap, &te32 ) ) { CloseHandle( hThreadSnap ); // Must clean up the // snapshot object! return; } // Now walk the thread list of the system, // and display information about each thread // associated with the specified process do { if( te32.th32OwnerProcessID == dwOwnerPID ) { CONTEXT context; context.ContextFlags = 0xffffffff; HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION,false, te32.th32ThreadID); if(threadHandle && GetThreadContext(threadHandle, &context)) { WriteStackDetails( &context, false, threadHandle ); } CloseHandle(threadHandle); } } while( Thread32Next(hThreadSnap, &te32 ) ); // Don't forget to clean up the snapshot object. CloseHandle( hThreadSnap ); } //=========================================================================== // Open the report file, and write the desired information to it. Called by // WheatyUnhandledExceptionFilter //=========================================================================== void WheatyExceptionReport::GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo ) { SYSTEMTIME systime; GetLocalTime(&systime); // Start out with a banner _tprintf(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute); PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; PrintSystemInfo(); // First print information about the type of fault _tprintf(_T("\r\n//=====================================================\r\n")); _tprintf( _T("Exception code: %08X %s\r\n"), pExceptionRecord->ExceptionCode, GetExceptionString(pExceptionRecord->ExceptionCode) ); // Now print information about where the fault occured TCHAR szFaultingModule[MAX_PATH]; DWORD section; DWORD_PTR offset; GetLogicalAddress( pExceptionRecord->ExceptionAddress, szFaultingModule, sizeof( szFaultingModule ), section, offset ); #ifdef _M_IX86 _tprintf( _T("Fault address: %08X %02X:%08X %s\r\n"), pExceptionRecord->ExceptionAddress, section, offset, szFaultingModule ); #endif #ifdef _M_X64 _tprintf( _T("Fault address: %016I64X %02X:%016I64X %s\r\n"), pExceptionRecord->ExceptionAddress, section, offset, szFaultingModule ); #endif PCONTEXT pCtx = pExceptionInfo->ContextRecord; // Show the registers #ifdef _M_IX86 // X86 Only! _tprintf( _T("\r\nRegisters:\r\n") ); _tprintf(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n") ,pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi ); _tprintf( _T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip ); _tprintf( _T("SS:ESP:%04X:%08X EBP:%08X\r\n"), pCtx->SegSs, pCtx->Esp, pCtx->Ebp ); _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags ); #endif #ifdef _M_X64 _tprintf( _T("\r\nRegisters:\r\n") ); _tprintf(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n") _T("R8: %016I64X\r\nR9: %016I64X\r\nR10:%016I64X\r\nR11:%016I64X\r\nR12:%016I64X\r\nR13:%016I64X\r\nR14:%016I64X\r\nR15:%016I64X\r\n") ,pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, pCtx->Rsi, pCtx->Rdi ,pCtx->R9,pCtx->R10,pCtx->R11,pCtx->R12,pCtx->R13,pCtx->R14,pCtx->R15); _tprintf( _T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip ); _tprintf( _T("SS:RSP:%04X:%016X RBP:%08X\r\n"), pCtx->SegSs, pCtx->Rsp, pCtx->Rbp ); _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"), pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); _tprintf( _T("Flags:%08X\r\n"), pCtx->EFlags ); #endif SymSetOptions( SYMOPT_DEFERRED_LOADS ); // Initialize DbgHelp if ( !SymInitialize( GetCurrentProcess(), 0, TRUE ) ) { _tprintf(_T("\n\rCRITICAL ERROR.\n\r Couldn't initialize the symbol handler for process.\n\rError [%s].\n\r\n\r"), ErrorMessage(GetLastError())); } CONTEXT trashableContext = *pCtx; WriteStackDetails( &trashableContext, false, NULL ); printTracesForAllThreads(); // #ifdef _M_IX86 // X86 Only! _tprintf( _T("========================\r\n") ); _tprintf( _T("Local Variables And Parameters\r\n") ); trashableContext = *pCtx; WriteStackDetails( &trashableContext, true, NULL ); _tprintf( _T("========================\r\n") ); _tprintf( _T("Global Variables\r\n") ); SymEnumSymbols( GetCurrentProcess(), (DWORD64)GetModuleHandle(szFaultingModule), 0, EnumerateSymbolsCallback, 0 ); // #endif // X86 Only! SymCleanup( GetCurrentProcess() ); _tprintf( _T("\r\n") ); } //====================================================================== // Given an exception code, returns a pointer to a static string with a // description of the exception //====================================================================== LPTSTR WheatyExceptionReport::GetExceptionString( DWORD dwCode ) { #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x); 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; } //============================================================================= // 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 WheatyExceptionReport::GetLogicalAddress( PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset ) { MEMORY_BASIC_INFORMATION mbi; if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) ) return FALSE; DWORD_PTR hMod = (DWORD_PTR)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)(hMod + DWORD_PTR(pDosHdr->e_lfanew)); PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr ); DWORD_PTR rva = (DWORD_PTR)addr - 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_PTR sectionStart = pSection->VirtualAddress; DWORD_PTR sectionEnd = sectionStart + DWORD_PTR(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 = rva - sectionStart; return TRUE; } } return FALSE; // Should never get here! } // It contains SYMBOL_INFO structure plus additional // space for the name of the symbol struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE { CSymbolInfoPackage() { si.SizeOfStruct = sizeof(SYMBOL_INFO); si.MaxNameLen = sizeof(name); } }; //============================================================ // Walks the stack, and writes the results to the report file //============================================================ void WheatyExceptionReport::WriteStackDetails( PCONTEXT pContext, bool bWriteVariables, HANDLE pThreadHandle) // true if local/params should be output { _tprintf( _T("\r\nCall stack:\r\n") ); _tprintf( _T("Address Frame Function SourceFile\r\n") ); DWORD dwMachineType = 0; // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag STACKFRAME64 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 #ifdef _M_X64 sf.AddrPC.Offset = pContext->Rip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = pContext->Rsp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = pContext->Rbp; sf.AddrFrame.Mode = AddrModeFlat; dwMachineType = IMAGE_FILE_MACHINE_AMD64; #endif while ( 1 ) { // Get the next stack frame if ( ! StackWalk64( dwMachineType, m_hProcess, pThreadHandle != NULL ? pThreadHandle : GetCurrentThread(), &sf, pContext, 0, SymFunctionTableAccess64, SymGetModuleBase64, 0 ) ) break; if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure break; // the frame is OK. Bail if not. #ifdef _M_IX86 _tprintf( _T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); #endif #ifdef _M_X64 _tprintf( _T("%016I64X %016I64X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); #endif DWORD64 symDisplacement = 0; // Displacement of the input address, // relative to the start of the symbol // Get the name of the function for this stack frame entry CSymbolInfoPackage sip; if ( SymFromAddr( m_hProcess, // Process handle of the current process sf.AddrPC.Offset, // Symbol address &symDisplacement, // Address of the variable that will receive the displacement &sip.si // Address of the SYMBOL_INFO structure (inside "sip" object) )) { _tprintf( _T("%hs+%I64X"), sip.si.Name, symDisplacement ); } else // No symbol found. Print out the logical address instead. { TCHAR szModule[MAX_PATH] = _T(""); DWORD section = 0; DWORD_PTR offset = 0; GetLogicalAddress( (PVOID)sf.AddrPC.Offset, szModule, sizeof(szModule), section, offset ); #ifdef _M_IX86 _tprintf( _T("%04X:%08X %s"), section, offset, szModule ); #endif #ifdef _M_X64 _tprintf( _T("%04X:%016I64X %s"), section, offset, szModule ); #endif } // Get the source line for this stack frame entry IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE) }; DWORD dwLineDisplacement; if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) ) { _tprintf(_T(" %s line %u"),lineInfo.FileName,lineInfo.LineNumber); } _tprintf( _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; SymSetContext( m_hProcess, &imagehlpStackFrame, 0 ); // Enumerate the locals/parameters SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &sf ); _tprintf( _T("\r\n") ); } } } ////////////////////////////////////////////////////////////////////////////// // The function invoked by SymEnumSymbols ////////////////////////////////////////////////////////////////////////////// BOOL CALLBACK WheatyExceptionReport::EnumerateSymbolsCallback( PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext ) { char szBuffer[2048]; __try { if ( FormatSymbolValue( pSymInfo, (STACKFRAME*)UserContext, szBuffer, sizeof(szBuffer) ) ) _tprintf( _T("\t%s\r\n"), szBuffer ); } __except( 1 ) { _tprintf( _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 WheatyExceptionReport::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; 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, pSym->Name ); 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 += sprintf( pszCurrBuffer, rgBaseType[basicType]); // Emit the variable name pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", pSym->Name ); 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 * WheatyExceptionReport::DumpTypeIndex( char * pszCurrBuffer, DWORD64 modBase, DWORD dwTypeIndex, unsigned nestingLevel, DWORD_PTR offset, bool & bHandled, char* Name) { bHandled = false; // Get the name of the symbol. This will either be a Type name (if a UDT), // or the structure member name. WCHAR * pwszTypeName; if ( 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; 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 ( !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; BasicType basicType = GetBasicType(children.ChildId[i], modBase ); pszCurrBuffer += sprintf( pszCurrBuffer, rgBaseType[basicType]); pszCurrBuffer = DumpTypeIndex( pszCurrBuffer, modBase, children.ChildId[i], nestingLevel+1, offset, bHandled2, ""/*Name */); // 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; 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; SymGetTypeInfo( m_hProcess, modBase, children.ChildId[i], TI_GET_TYPEID, &typeId ); // Get the size of the child member ULONG64 length; 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 += sprintf( pszCurrBuffer, rgBaseType[basicType]); // // Emit the variable name // pszCurrBuffer += sprintf( pszCurrBuffer, "\'%s\'", Name ); pszCurrBuffer = FormatOutputValue( pszCurrBuffer, basicType, length, (PVOID)dwFinalOffset ); pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); } } bHandled = true; return pszCurrBuffer; } char * WheatyExceptionReport::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 WheatyExceptionReport::GetBasicType( DWORD typeIndex, DWORD64 modBase ) { BasicType basicType; if ( 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 (SymGetTypeInfo(m_hProcess,modBase, typeIndex, TI_GET_TYPEID, &typeId)) { if ( SymGetTypeInfo( m_hProcess, modBase, typeId, TI_GET_BASETYPE, &basicType ) ) { return basicType; } } return btNoType; } //============================================================================ // Helper function that writes to the report file, and allows the user to use // printf style formating //============================================================================ int __cdecl WheatyExceptionReport::_tprintf(const TCHAR * format, ...) { TCHAR szBuff[1024]; 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; }