#include "./exceptionmgr.h" #include "./LogMgr.h" #pragma comment(linker, "/defaultlib:dbghelp.lib") namespace Caldron { namespace Base { LPTOP_LEVEL_EXCEPTION_FILTER CExceptionMgr::m_lpPrevFilter = NULL; HANDLE CExceptionMgr::m_hProcess = NULL; CExceptionMgr::CExceptionMgr(void) { m_lpPrevFilter = SetUnhandledExceptionFilter(Caldron::Base::CExceptionMgr::ExceptionFilter); m_hProcess = GetCurrentProcess(); } CExceptionMgr::~CExceptionMgr(void) { SetUnhandledExceptionFilter(m_lpPrevFilter); } LONG WINAPI CExceptionMgr::ExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo ) { WriteExceptionReport(pExceptionInfo); if(m_lpPrevFilter) return m_lpPrevFilter(pExceptionInfo); else return EXCEPTION_CONTINUE_SEARCH; } void CExceptionMgr::WriteExceptionReport(PEXCEPTION_POINTERS pExceptionInfo ) { // Start out with a banner CLogMgr::_LogNoHeader("========= Exception Report ============\n"); PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; // First print information about the type of fault CLogMgr::_LogNoHeader("Exception Code : %08X %s\n",pExceptionRecord->ExceptionCode,GetExceptionString(pExceptionRecord->ExceptionCode) ); // Now print information about where the fault occured TCHAR szFaultingModule[MAX_PATH]; DWORD section, offset; GetLogicalAddress( pExceptionRecord->ExceptionAddress, szFaultingModule, sizeof( szFaultingModule ), section, offset ); CLogMgr::_LogNoHeader("Fault address: %08X %02X:%08X %s\n", pExceptionRecord->ExceptionAddress, section, offset, szFaultingModule ); PCONTEXT pCtx = pExceptionInfo->ContextRecord; // Show the registers #ifdef _M_IX86 // X86 Only! CLogMgr::_LogNoHeader("\nRegisters :\n"); CLogMgr::_LogNoHeader("EAX:%08X\nEBX:%08X\nECX:%08X\nEDX:%08X\nESI:%08X\nEDI:%08X\n", pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi ); CLogMgr::_LogNoHeader("CS:EIP:%04X:%08X\n", pCtx->SegCs, pCtx->Eip ); CLogMgr::_LogNoHeader("SS:ESP:%04X:%08X EBP:%08X\n", pCtx->SegSs, pCtx->Esp, pCtx->Ebp ); CLogMgr::_LogNoHeader("DS:%04X ES:%04X FS:%04X GS:%04X\n", pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); CLogMgr::_LogNoHeader("Flags:%08X\n",pCtx->EFlags ); #endif SymSetOptions( SYMOPT_DEFERRED_LOADS ); // Initialize DbgHelp if ( !SymInitialize( GetCurrentProcess(), 0, TRUE ) ) return; CONTEXT trashableContext = *pCtx; WriteCallStack( &trashableContext, false ); #ifdef _M_IX86 // X86 Only! CLogMgr::_LogNoHeader("======================================\n"); CLogMgr::_LogNoHeader("Local Variable & Parameters\n"); trashableContext = *pCtx; WriteCallStack( &trashableContext, true ); CLogMgr::_LogNoHeader("======================================\n"); CLogMgr::_LogNoHeader("Global Variable\n"); SymEnumSymbols( GetCurrentProcess(), (DWORD64)GetModuleHandle(szFaultingModule), 0, EnumerateSymbolsCallback, 0 ); #endif // X86 Only! SymCleanup( GetCurrentProcess() ); CLogMgr::_LogNoHeader("\n"); } LPSTR CExceptionMgr::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 CExceptionMgr::GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len,DWORD& section, DWORD& offset) { MEMORY_BASIC_INFORMATION mbi; if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) ) return FALSE; DWORD hMod = (DWORD)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 + pDosHdr->e_lfanew); PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr ); DWORD rva = (DWORD)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 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 = rva - sectionStart; return TRUE; } } return FALSE; // Should never get here! } void CExceptionMgr::WriteCallStack(PCONTEXT pContext,bool bWriteVariables ) { CLogMgr::_LogNoHeader("==========Call Stack==========\n"); CLogMgr::_LogNoHeader("Address Frame Function SourceFile\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 ( ! StackWalk( dwMachineType, m_hProcess, GetCurrentThread(), &sf, pContext, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) ) break; if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure break; // the frame is OK. Bail if not. CLogMgr::_LogNoHeader("%08X %08X ",sf.AddrPC.Offset, sf.AddrFrame.Offset ); // Get the name of the function for this stack frame entry BYTE symbolBuffer[ sizeof(SYMBOL_INFO) + 1024 ]; PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer; pSymbol->SizeOfStruct = sizeof(symbolBuffer); pSymbol->MaxNameLen = 1024; DWORD64 symDisplacement = 0; // Displacement of the input address, // relative to the start of the symbol if ( SymFromAddr(m_hProcess,sf.AddrPC.Offset,&symDisplacement,pSymbol)) { CLogMgr::_LogNoHeader("%hs+%I64X",pSymbol->Name, symDisplacement ); } else // No symbol found. Print out the logical address instead. { TCHAR szModule[MAX_PATH] = _T(""); DWORD section = 0, offset = 0; GetLogicalAddress( (PVOID)sf.AddrPC.Offset, szModule, sizeof(szModule), section, offset ); CLogMgr::_LogNoHeader("%04X:%08X %s",section, offset, szModule ); } // Get the source line for this stack frame entry IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) }; DWORD dwLineDisplacement; if ( SymGetLineFromAddr( m_hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) ) { CLogMgr::_LogNoHeader(" %s line %u",lineInfo.FileName,lineInfo.LineNumber); } CLogMgr::_LogNoHeader("\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 ); CLogMgr::_LogNoHeader("\n"); } } } BOOL CALLBACK CExceptionMgr::EnumerateSymbolsCallback( PSYMBOL_INFO pSymInfo, ULONG SymbolSize,PVOID UserContext ) { char szBuffer[2048]; __try { if ( FormatSymbolValue( pSymInfo, (STACKFRAME*)UserContext, szBuffer, sizeof(szBuffer) ) ) CLogMgr::_LogNoHeader("\t%s\n",szBuffer); } __except( 1 ) { CLogMgr::_LogNoHeader("punting on symbol %s\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 CExceptionMgr::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 * CExceptionMgr::DumpTypeIndex( char * pszCurrBuffer, DWORD64 modBase, DWORD dwTypeIndex, unsigned nestingLevel, DWORD_PTR offset, bool & bHandled ) { 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; 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; 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 = FormatOutputValue( pszCurrBuffer, basicType, length, (PVOID)dwFinalOffset ); pszCurrBuffer += sprintf( pszCurrBuffer, "\r\n" ); } } bHandled = true; return pszCurrBuffer; } char * CExceptionMgr::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 CExceptionMgr::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; } }}