Copyright © Microsoft Corporation. This document is an archived reproduction of a version originally published by Microsoft. It may have slight formatting modifications for consistency and to improve readability. |
Msjexhnd.h
#ifndef __MSJEXHND_H__ #define __MSJEXHND_H__ class MSJExceptionHandler { public: MSJExceptionHandler( ); ~MSJExceptionHandler( ); void SetLogFileName( PTSTR pszLogFileName ); private: // entry point where control comes on an unhandled exception static LONG WINAPI MSJUnhandledExceptionFilter( PEXCEPTION_POINTERS pExceptionInfo ); // where report info is extracted and generated static void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo ); // Helper functions static LPTSTR GetExceptionString( DWORD dwCode ); static BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset ); static void IntelStackWalk( PCONTEXT pContext ); #if 1 static void ImagehlpStackWalk( PCONTEXT pContext ); #endif static int __cdecl _tprintf(const TCHAR * format, ...); #if 1 static BOOL InitImagehlpFunctions( void ); #endif // Variables used by the class static TCHAR m_szLogFileName[MAX_PATH]; static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter; static HANDLE m_hReportFile; #if 1 // Make typedefs for some IMAGEHLP.DLL functions so that we can use them // with GetProcAddress typedef BOOL (__stdcall * SYMINITIALIZEPROC)( HANDLE, LPSTR, BOOL ); typedef BOOL (__stdcall *SYMCLEANUPPROC)( HANDLE ); typedef BOOL (__stdcall * STACKWALKPROC) ( DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE,PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE ); typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESSPROC)( HANDLE, DWORD ); typedef DWORD (__stdcall *SYMGETMODULEBASEPROC)( HANDLE, DWORD ); typedef BOOL (__stdcall *SYMGETSYMFROMADDRPROC) ( HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL ); static SYMINITIALIZEPROC _SymInitialize; static SYMCLEANUPPROC _SymCleanup; static STACKWALKPROC _StackWalk; static SYMFUNCTIONTABLEACCESSPROC _SymFunctionTableAccess; static SYMGETMODULEBASEPROC _SymGetModuleBase; static SYMGETSYMFROMADDRPROC _SymGetSymFromAddr; #endif }; extern MSJExceptionHandler g_MSJExceptionHandler; // global instance of class #endifMsjexhnd.cpp
//========================================== // Matt Pietrek // Microsoft Systems Journal, May 1997 // FILE: MSJEXHND.CPP //========================================== #include <windows.h> #include <tchar.h> #include <imagehlp.h> #include "msjexhnd.h" //============================== Global Variables ============================= // // Declare the static variables of the MSJExceptionHandler class // TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH]; LPTOP_LEVEL_EXCEPTION_FILTER MSJExceptionHandler::m_previousFilter; HANDLE MSJExceptionHandler::m_hReportFile; MSJExceptionHandler::SYMINITIALIZEPROC MSJExceptionHandler::_SymInitialize = 0; MSJExceptionHandler::SYMCLEANUPPROC MSJExceptionHandler::_SymCleanup = 0; MSJExceptionHandler::STACKWALKPROC MSJExceptionHandler::_StackWalk = 0; MSJExceptionHandler::SYMFUNCTIONTABLEACCESSPROC MSJExceptionHandler::_SymFunctionTableAccess = 0; MSJExceptionHandler::SYMGETMODULEBASEPROC MSJExceptionHandler::_SymGetModuleBase = 0; MSJExceptionHandler::SYMGETSYMFROMADDRPROC MSJExceptionHandler::_SymGetSymFromAddr = 0; MSJExceptionHandler g_MSJExceptionHandler; // Declare global instance of class //============================== Class Methods ============================= //============= // Constructor //============= MSJExceptionHandler::MSJExceptionHandler( ) { // Install the unhandled exception filter function m_previousFilter = SetUnhandledExceptionFilter(MSJUnhandledExceptionFilter); // Figure out what the report file will be named, and store it away GetModuleFileName( 0, m_szLogFileName, MAX_PATH ); // Look for the '.' before the "EXE" extension. Replace the extension // with "RPT" PTSTR pszDot = _tcsrchr( m_szLogFileName, _T('.') ); if ( pszDot ) { pszDot++; // Advance past the '.' if ( _tcslen(pszDot) >= 3 ) _tcscpy( pszDot, _T("RPT") ); // "RPT" -> "Report" } } //============ // Destructor //============ MSJExceptionHandler::~MSJExceptionHandler( ) { SetUnhandledExceptionFilter( m_previousFilter ); } //============================================================== // Lets user change the name of the report file to be generated //============================================================== void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName ) { _tcscpy( m_szLogFileName, pszLogFileName ); } //=========================================================== // Entry point where control comes on an unhandled exception //=========================================================== LONG WINAPI MSJExceptionHandler::MSJUnhandledExceptionFilter( PEXCEPTION_POINTERS pExceptionInfo ) { 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_CONTINUE_SEARCH; } //=========================================================================== // Open the report file, and write the desired information to it. Called by // MSJUnhandledExceptionFilter //=========================================================================== void MSJExceptionHandler::GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo ) { // Start out with a banner _tprintf( _T("//=====================================================\n") ); PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; // First print information about the type of fault _tprintf( _T("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 ); _tprintf( _T("Fault address: %08X %02X:%08X %s\n"), pExceptionRecord->ExceptionAddress, section, offset, szFaultingModule ); PCONTEXT pCtx = pExceptionInfo->ContextRecord; // Show the registers #ifdef _M_IX86 // Intel Only! _tprintf( _T("\nRegisters:\n") ); _tprintf(_T("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 ); _tprintf( _T("CS:EIP:%04X:%08X\n"), pCtx->SegCs, pCtx->Eip ); _tprintf( _T("SS:ESP:%04X:%08X EBP:%08X\n"), pCtx->SegSs, pCtx->Esp, pCtx->Ebp ); _tprintf( _T("DS:%04X ES:%04X FS:%04X GS:%04X\n"), pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs ); _tprintf( _T("Flags:%08X\n"), pCtx->EFlags ); #endif if ( !InitImagehlpFunctions() ) { OutputDebugString(_T("IMAGEHLP.DLL or its exported procs not found")); #ifdef _M_IX86 // Intel Only! // Walk the stack using x86 specific code IntelStackWalk( pCtx ); #endif return; } ImagehlpStackWalk( pCtx ); _SymCleanup( GetCurrentProcess() ); _tprintf( _T("\n") ); } //====================================================================== // Given an exception code, returns a pointer to a static string with a // description of the exception //====================================================================== LPTSTR MSJExceptionHandler::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 MSJExceptionHandler::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! } //============================================================ // Walks the stack, and writes the results to the report file //============================================================ void MSJExceptionHandler::IntelStackWalk( PCONTEXT pContext ) { _tprintf( _T("\nCall stack:\n") ); _tprintf( _T("Address Frame Logical addr Module\n") ); DWORD pc = pContext->Eip; PDWORD pFrame, pPrevFrame; pFrame = (PDWORD)pContext->Ebp; do { TCHAR szModule[MAX_PATH] = _T(""); DWORD section = 0, offset = 0; GetLogicalAddress((PVOID)pc, szModule,sizeof(szModule),section,offset ); _tprintf( _T("%08X %08X %04X:%08X %s\n"), pc, pFrame, section, offset, szModule ); pc = pFrame[1]; pPrevFrame = pFrame; pFrame = (PDWORD)pFrame[0]; // proceed to next higher frame on stack if ( (DWORD)pFrame & 3 ) // Frame pointer must be aligned on a break; // DWORD boundary. Bail if not so. if ( pFrame <= pPrevFrame ) break; // Can two DWORDs be read from the supposed frame address? if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) ) break; } while ( 1 ); } //============================================================ // Walks the stack, and writes the results to the report file //============================================================ void MSJExceptionHandler::ImagehlpStackWalk( PCONTEXT pContext ) { _tprintf( _T("\nCall stack:\n") ); _tprintf( _T("Address Frame\n") ); // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag STACKFRAME sf; memset( &sf, 0, sizeof(sf) ); // 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; while ( 1 ) { if ( ! _StackWalk( IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), 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. _tprintf( _T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset ); // IMAGEHLP is wacky, and requires you to pass in a pointer to an // IMAGEHLP_SYMBOL structure. The problem is that this structure is // variable length. That is, you determine how big the structure is // at runtime. This means that you can't use sizeof(struct). // So...make a buffer that's big enough, and make a pointer // to the buffer. We also need to initialize not one, but TWO // members of the structure before it can be used. BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + 512 ]; PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)symbolBuffer; pSymbol->SizeOfStruct = sizeof(symbolBuffer); pSymbol->MaxNameLength = 512; DWORD symDisplacement = 0; // Displacement of the input address, // relative to the start of the symbol if ( _SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &symDisplacement, pSymbol) ) { _tprintf( _T("%hs+%X\n"), 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 ); _tprintf( _T("%04X:%08X %s\n"), section, offset, szModule ); } } } //============================================================================ // Helper function that writes to the report file, and allows the user to use // printf style formating //============================================================================ int __cdecl MSJExceptionHandler::_tprintf(const TCHAR * format, ...) { TCHAR szBuff[1024]; int retValue; DWORD cbWritten; va_list argptr; va_start( argptr, format ); retValue = wvsprintf( szBuff, format, argptr ); va_end( argptr ); WriteFile( m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0 ); return retValue; } //========================================================================= // Load IMAGEHLP.DLL and get the address of functions in it that we'll use //========================================================================= BOOL MSJExceptionHandler::InitImagehlpFunctions( void ) { HMODULE hModImagehlp = LoadLibrary( _T("IMAGEHLP.DLL") ); if ( !hModImagehlp ) return FALSE; _SymInitialize = (SYMINITIALIZEPROC)GetProcAddress( hModImagehlp, "SymInitialize" ); if ( !_SymInitialize ) return FALSE; _SymCleanup = (SYMCLEANUPPROC)GetProcAddress( hModImagehlp, "SymCleanup" ); if ( !_SymCleanup ) return FALSE; _StackWalk = (STACKWALKPROC)GetProcAddress( hModImagehlp, "StackWalk" ); if ( !_StackWalk ) return FALSE; _SymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC) GetProcAddress( hModImagehlp, "SymFunctionTableAccess" ); if ( !_SymFunctionTableAccess ) return FALSE; _SymGetModuleBase=(SYMGETMODULEBASEPROC)GetProcAddress( hModImagehlp, "SymGetModuleBase"); if ( !_SymGetModuleBase ) return FALSE; _SymGetSymFromAddr=(SYMGETSYMFROMADDRPROC)GetProcAddress( hModImagehlp, "SymGetSymFromAddr" ); if ( !_SymGetSymFromAddr ) return FALSE; if ( !_SymInitialize( GetCurrentProcess(), 0, TRUE ) ) return FALSE; return TRUE; }