///////////////////////////////////////////////////////////////////////////// // Name: msw/crashrpt.cpp // Purpose: helpers for structured exception handling (SEH) // Author: Vadim Zeitlin // Modified by: // Created: 13.07.03 // RCS-ID: $Id$ // Copyright: (c) 2003 Vadim Zeitlin // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// /* The code generating the crash reports in this file is heavily based on Matt Pietrek's column from the March 2002 issue of MSDN Magazine. Note that this code is not currently used by default, however. In any case, all bugs are my alone. */ // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_ON_FATAL_EXCEPTION #ifndef WX_PRECOMP #endif //WX_PRECOMP /* We have two possibilities here: one, a priori more interesting, is to generate the crash report ourselves and include the values of all the variables in the dump. Unfortunately my code to do it doesn't work in "real life" situations i.e. it works in small examples but invariably gets confused by something in big programs which makes quite useless. The other possibility is to let dbghelp.dll to do the work for us and analyze its results later using a debugger with knowledge about crash dumps, such as (free!) WinDbg. This also has another advantage of not needing to ship the .pdb file (containing debug info) to the user. So this is the default now, but I keep the old code just in case, and if you really want you can still use it. */ #define wxUSE_MINIDUMP 1 #if !wxUSE_MINIDUMP #include "wx/longlong.h" #endif // wxUSE_MINIDUMP #include "wx/datetime.h" #include "wx/dynload.h" #include "wx/msw/crashrpt.h" #include "wx/msw/wrapwin.h" #include #include "wx/msw/private.h" // we need to determine whether we have the declarations for the function in // debughlp.dll version 5.81 (at least) and we check for DBHLPAPI to test this // // reasons: // - VC6 version of imagehlp.h doesn't define it // - VC7 one does // - testing for compiler version doesn't work as you can install and use // the new SDK headers with VC6 // // in any case, the user may override by defining wxUSE_DBGHELP himself #ifndef wxUSE_DBGHELP #ifdef DBHLPAPI #define wxUSE_DBGHELP 1 #else #define wxUSE_DBGHELP 0 #endif #endif #if wxUSE_DBGHELP // ---------------------------------------------------------------------------- // types of imagehlp.h functions // ---------------------------------------------------------------------------- #if wxUSE_MINIDUMP typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_EXCEPTION_INFORMATION, CONST PMINIDUMP_USER_STREAM_INFORMATION, CONST PMINIDUMP_CALLBACK_INFORMATION); #else // !wxUSE_MINIDUMP typedef DWORD (WINAPI *SymSetOptions_t)(DWORD); typedef BOOL (WINAPI *SymInitialize_t)(HANDLE, LPSTR, BOOL); typedef BOOL (WINAPI *StackWalk_t)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE); typedef BOOL (WINAPI *SymFromAddr_t)(HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO); typedef LPVOID (WINAPI *SymFunctionTableAccess_t)(HANDLE, DWORD); typedef DWORD (WINAPI *SymGetModuleBase_t)(HANDLE, DWORD); typedef BOOL (WINAPI *SymGetLineFromAddr_t)(HANDLE, DWORD, PDWORD, PIMAGEHLP_LINE); typedef BOOL (WINAPI *SymSetContext_t)(HANDLE, PIMAGEHLP_STACK_FRAME, PIMAGEHLP_CONTEXT); typedef BOOL (WINAPI *SymEnumSymbols_t)(HANDLE, ULONG64, PCSTR, PSYM_ENUMERATESYMBOLS_CALLBACK, PVOID); typedef BOOL (WINAPI *SymGetTypeInfo_t)(HANDLE, DWORD64, ULONG, IMAGEHLP_SYMBOL_TYPE_INFO, PVOID); #endif // wxUSE_MINIDUMP // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- #if !wxUSE_MINIDUMP // Stolen from CVCONST.H in the DIA 2.0 SDK enum BasicType { BASICTYPE_NOTYPE = 0, BASICTYPE_VOID = 1, BASICTYPE_CHAR = 2, BASICTYPE_WCHAR = 3, BASICTYPE_INT = 6, BASICTYPE_UINT = 7, BASICTYPE_FLOAT = 8, BASICTYPE_BCD = 9, BASICTYPE_BOOL = 10, BASICTYPE_LONG = 13, BASICTYPE_ULONG = 14, BASICTYPE_CURRENCY = 25, BASICTYPE_DATE = 26, BASICTYPE_VARIANT = 27, BASICTYPE_COMPLEX = 28, BASICTYPE_BIT = 29, BASICTYPE_BSTR = 30, BASICTYPE_HRESULT = 31 }; // Same as above enum SymbolTag { SYMBOL_TAG_NULL, SYMBOL_TAG_FUNCTION = 5, SYMBOL_TAG_DATA = 7, SYMBOL_TAG_PUBLIC = 10, // appears in .DBGs SYMBOL_TAG_UDT, SYMBOL_TAG_ENUM, SYMBOL_TAG_FUNCTION_TYPE, SYMBOL_TAG_POINTER_TYPE, SYMBOL_TAG_ARRAY_TYPE, SYMBOL_TAG_BASE_TYPE, SYMBOL_TAG_TYPEDEF, SYMBOL_TAG_BASECLASS }; #endif // wxUSE_MINIDUMP #endif // wxUSE_DBGHELP // ---------------------------------------------------------------------------- // classes // ---------------------------------------------------------------------------- // low level wxBusyCursor replacement: we use Win32 API directly here instead // of going through wxWidgets calls as this could be dangerous class BusyCursor { public: BusyCursor() { HCURSOR hcursorBusy = ::LoadCursor(NULL, IDC_WAIT); m_hcursorOld = ::SetCursor(hcursorBusy); } ~BusyCursor() { if ( m_hcursorOld ) { ::SetCursor(m_hcursorOld); } } private: HCURSOR m_hcursorOld; }; // the real crash report generator class wxCrashReportImpl { public: wxCrashReportImpl(const wxChar *filename); bool Generate(int flags); ~wxCrashReportImpl() { if ( m_hFile != INVALID_HANDLE_VALUE ) { ::CloseHandle(m_hFile); } } private: // formatted output to m_hFile void Output(const wxChar *format, ...); // output end of line void OutputEndl() { Output(_T("\r\n")); } #if wxUSE_DBGHELP #if !wxUSE_MINIDUMP // translate exception code to its symbolic name static wxString GetExceptionString(DWORD dwCode); // return the type from "type index" static BasicType GetBasicType(DWORD64 modBase, DWORD typeIndex); // return the name for the type index static wxString GetSymbolName(DWORD64 modBase, DWORD dwTypeIndex); // return the string representation of the variable value static wxString FormatSimpleValue(BasicType bt, DWORD64 length, PVOID pAddress); // return string representation of a struct field (which may itself be a // struct, of course) static wxString FormatField(DWORD64 modBase, DWORD dwTypeIndex, void *pVariable, unsigned level); // show the name and value of the given symbol static wxString FormatSymbol(PSYMBOL_INFO pSym, STACKFRAME *sf); // show value described by SYMBOL_INFO located at pVariable static wxString FormatAnyValue(PSYMBOL_INFO pSym, void *pVariable); // show value of possibly complex (user-defined) type static wxString FormatUDT(DWORD64 modBase, DWORD dwTypeIndex, void *pVariable, unsigned level = 0); // outputs information about the given symbol void OutputSymbol(PSYMBOL_INFO pSymInfo, STACKFRAME *sf); // map address to module (and also section:offset), retunr true if ok static bool GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset); // callback used with SymEnumSymbols() to process all variables static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext); // show the general information about exception which should be always // available // // returns the module of the handle where the crash occured HANDLE OutputBasicContext(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pCtx); // output the call stack and local variables values void OutputStack(const CONTEXT *pCtx, int flags); // output the global variables values void OutputGlobals(HANDLE hModuleCrash); // the current stack frame (may be NULL) STACKFRAME *m_sfCurrent; #endif // !wxUSE_MINIDUMP // load all the functions we need from dbghelp.dll, return true if all ok bool BindDbgHelpFunctions(const wxDynamicLibrary& dllDbgHelp); // dynamically loaded dbghelp.dll functions #define DECLARE_SYM_FUNCTION(func) static func ## _t func #if wxUSE_MINIDUMP DECLARE_SYM_FUNCTION(MiniDumpWriteDump); #else // !wxUSE_MINIDUMP DECLARE_SYM_FUNCTION(SymSetOptions); DECLARE_SYM_FUNCTION(SymInitialize); DECLARE_SYM_FUNCTION(StackWalk); DECLARE_SYM_FUNCTION(SymFromAddr); DECLARE_SYM_FUNCTION(SymFunctionTableAccess); DECLARE_SYM_FUNCTION(SymGetModuleBase); DECLARE_SYM_FUNCTION(SymGetLineFromAddr); DECLARE_SYM_FUNCTION(SymSetContext); DECLARE_SYM_FUNCTION(SymEnumSymbols); DECLARE_SYM_FUNCTION(SymGetTypeInfo); #endif // wxUSE_MINIDUMP/!wxUSE_MINIDUMP #undef DECLARE_SYM_FUNCTION #endif // wxUSE_DBGHELP // the handle of the report file HANDLE m_hFile; }; // ---------------------------------------------------------------------------- // globals // ---------------------------------------------------------------------------- // global pointer to exception information, only valid inside OnFatalException extern WXDLLIMPEXP_BASE EXCEPTION_POINTERS *wxGlobalSEInformation = NULL; // flag telling us whether the application wants to handle exceptions at all static bool gs_handleExceptions = false; // the file name where the report about exception is written static wxChar gs_reportFilename[MAX_PATH]; // ============================================================================ // implementation // ============================================================================ #if wxUSE_DBGHELP #define DEFINE_SYM_FUNCTION(func) func ## _t wxCrashReportImpl::func = 0 #if wxUSE_MINIDUMP DEFINE_SYM_FUNCTION(MiniDumpWriteDump); #else // !wxUSE_MINIDUMP DEFINE_SYM_FUNCTION(SymSetOptions); DEFINE_SYM_FUNCTION(SymInitialize); DEFINE_SYM_FUNCTION(StackWalk); DEFINE_SYM_FUNCTION(SymFromAddr); DEFINE_SYM_FUNCTION(SymFunctionTableAccess); DEFINE_SYM_FUNCTION(SymGetModuleBase); DEFINE_SYM_FUNCTION(SymGetLineFromAddr); DEFINE_SYM_FUNCTION(SymSetContext); DEFINE_SYM_FUNCTION(SymEnumSymbols); DEFINE_SYM_FUNCTION(SymGetTypeInfo); #endif // wxUSE_MINIDUMP/!wxUSE_MINIDUMP #undef DEFINE_SYM_FUNCTION #endif // wxUSE_DBGHELP // ---------------------------------------------------------------------------- // wxCrashReportImpl // ---------------------------------------------------------------------------- wxCrashReportImpl::wxCrashReportImpl(const wxChar *filename) { #if wxUSE_DBGHELP && !wxUSE_MINIDUMP m_sfCurrent = NULL; #endif // wxUSE_DBGHELP m_hFile = ::CreateFile ( filename, GENERIC_WRITE, 0, // no sharing NULL, // default security CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL // no template file ); } void wxCrashReportImpl::Output(const wxChar *format, ...) { va_list argptr; va_start(argptr, format); DWORD cbWritten; wxString s = wxString::FormatV(format, argptr); ::WriteFile(m_hFile, s, s.length() * sizeof(wxChar), &cbWritten, 0); va_end(argptr); } #if wxUSE_DBGHELP #if !wxUSE_MINIDUMP bool wxCrashReportImpl::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. const DWORD nSections = pNtHdr->FileHeader.NumberOfSections; for ( DWORD i = 0; i < nSections; 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; } } // failed to map to logical address... return false; } /* static */ BasicType wxCrashReportImpl::GetBasicType(DWORD64 modBase, DWORD typeIndex) { const HANDLE hProcess = GetCurrentProcess(); // try the index we have BasicType bt; if ( SymGetTypeInfo(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &bt) ) { return bt; } // if failed, try to get the "real" typeid first DWORD typeId; if ( SymGetTypeInfo(hProcess, modBase, typeIndex, TI_GET_TYPEID, &typeId) && (typeId != typeIndex && SymGetTypeInfo(hProcess, modBase, typeId, TI_GET_BASETYPE, &bt)) ) { return bt; } return BASICTYPE_NOTYPE; } /* static */ wxString wxCrashReportImpl::FormatSimpleValue(BasicType bt, DWORD64 length, PVOID pAddress) { wxString s; s.reserve(256); // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!) if ( length == 1 ) { s.Printf(_T("%#04x"), *(PBYTE)pAddress); } else if ( length == 2 ) { s.Printf(_T("%#06x"), *(PWORD)pAddress); } else if ( length == 4 ) { bool handled = false; if ( bt == BASICTYPE_FLOAT ) { s.Printf(_T("%f"), *(PFLOAT)pAddress); handled = true; } else if ( bt == BASICTYPE_CHAR ) { static const size_t NUM_CHARS = 32; const char * const pc = *(PSTR *)pAddress; if ( !::IsBadStringPtrA(pc, NUM_CHARS) ) { s << _T('"') << wxString(pc, wxConvLibc, NUM_CHARS) << _T('"'); handled = true; } } if ( !handled ) { // treat just as an opaque DWORD s.Printf(_T("%#x"), *(PDWORD)pAddress); } } else if ( length == 8 ) { if ( bt == BASICTYPE_FLOAT ) { s.Printf(_T("%lf"), *(double *)pAddress); } else // opaque 64 bit value { s.Printf(_T("%#" wxLongLongFmtSpec _T("x")), *(PDWORD *)pAddress); } } return s; } /* static */ wxString wxCrashReportImpl::GetSymbolName(DWORD64 modBase, DWORD dwTypeIndex) { wxString s; WCHAR *pwszTypeName; if ( SymGetTypeInfo ( GetCurrentProcess(), modBase, dwTypeIndex, TI_GET_SYMNAME, &pwszTypeName ) ) { s = wxConvCurrent->cWC2WX(pwszTypeName); ::LocalFree(pwszTypeName); } return s; } // this is called for the struct members/base classes wxString wxCrashReportImpl::FormatField(DWORD64 modBase, DWORD dwTypeIndex, void *pVariable, unsigned level) { wxString s; // avoid infinite recursion if ( level > 10 ) { return s; } const HANDLE hProcess = GetCurrentProcess(); DWORD dwTag = 0; SymGetTypeInfo(hProcess, modBase, dwTypeIndex, TI_GET_SYMTAG, &dwTag); switch ( dwTag ) { case SYMBOL_TAG_UDT: case SYMBOL_TAG_BASECLASS: s = FormatUDT(modBase, dwTypeIndex, pVariable, level); break; case SYMBOL_TAG_FUNCTION: // don't show break; default: // try to treat all the rest as data even though it's not clear if // it's really a good idea... // Get the offset of the child member, relative to its parent DWORD dwMemberOffset = 0; SymGetTypeInfo(hProcess, modBase, dwTypeIndex, TI_GET_OFFSET, &dwMemberOffset); // Get the real "TypeId" of the child. We need this for the // SymGetTypeInfo(TI_GET_LENGTH) call below. DWORD typeId; if ( !SymGetTypeInfo(hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &typeId) ) { typeId = dwTypeIndex; } // Get the size of the child member ULONG64 size; SymGetTypeInfo(hProcess, modBase, typeId, TI_GET_LENGTH, &size); // Calculate the address of the member DWORD_PTR dwFinalOffset = (DWORD_PTR)pVariable + dwMemberOffset; BasicType basicType = GetBasicType(modBase, dwTypeIndex); s = FormatSimpleValue(basicType, size, (PVOID)dwFinalOffset); break; } if ( s.empty() ) { // don't show if no value -- what for? return s; } return wxString(_T('\t'), level + 1) + GetSymbolName(modBase, dwTypeIndex) + _T(" = ") + s + _T("\r\n"); } // If it's a user defined type (UDT), recurse through its members until we're // at fundamental types. wxString wxCrashReportImpl::FormatUDT(DWORD64 modBase, DWORD dwTypeIndex, void *pVariable, unsigned level) { wxString s; s.reserve(512); s = GetSymbolName(modBase, dwTypeIndex) + _T(" {\r\n"); const HANDLE hProcess = GetCurrentProcess(); // Determine how many children this type has. DWORD dwChildrenCount = 0; SymGetTypeInfo(hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, &dwChildrenCount); // Prepare to get an array of "TypeIds", representing each of the children. TI_FINDCHILDREN_PARAMS *children = (TI_FINDCHILDREN_PARAMS *) malloc(sizeof(TI_FINDCHILDREN_PARAMS) + (dwChildrenCount - 1)*sizeof(ULONG)); if ( !children ) return s; children->Count = dwChildrenCount; children->Start = 0; // Get the array of TypeIds, one for each child type if ( !SymGetTypeInfo(hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN, children) ) { return s; } // Iterate through all children for ( unsigned i = 0; i < dwChildrenCount; i++ ) { s += FormatField(modBase, children->ChildId[i], pVariable, level + 1); } free(children); s << wxString(_T('\t'), level + 1) << _T('}'); return s; } // return the string containing the symbol of the given symbol /* static */ wxString wxCrashReportImpl::FormatAnyValue(PSYMBOL_INFO pSym, void *pVariable) { DWORD dwTag = 0; SymGetTypeInfo(GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex, TI_GET_SYMTAG, &dwTag); wxString s; switch ( dwTag ) { case SYMBOL_TAG_FUNCTION: break; case SYMBOL_TAG_UDT: case SYMBOL_TAG_BASECLASS: // show UDT recursively s = FormatUDT(pSym->ModBase, pSym->TypeIndex, pVariable); break; default: // variable of simple type (but could be array which we don't // handle correctly yet...), format it using its type and size BasicType bt = GetBasicType(pSym->ModBase, pSym->TypeIndex); s = FormatSimpleValue(bt, pSym->Size, pVariable); break; } return s; } // display contents and type of the given variable /* static */ wxString wxCrashReportImpl::FormatSymbol(PSYMBOL_INFO pSym, STACKFRAME *sf) { wxString s; if ( pSym->Tag == SYMBOL_TAG_FUNCTION ) { // If it's a function, don't do anything. return s; } if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER ) { // Don't try to report register variable return s; } s.reserve(512); // Indicate if the variable is a local or parameter if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER ) s += _T("\t[param] "); else if ( pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL ) s += _T("\t[local] "); // Will point to the variable's data in memory DWORD_PTR pVariable = 0; if ( (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE) && sf ) { pVariable = sf->AddrFrame.Offset; pVariable += (DWORD_PTR)pSym->Address; } else // It must be a global variable { pVariable = (DWORD_PTR)pSym->Address; } s << wxString(pSym->Name, wxConvLibc) << _T(" = ") << FormatAnyValue(pSym, (PVOID)pVariable); return s; } void wxCrashReportImpl::OutputSymbol(PSYMBOL_INFO pSymInfo, STACKFRAME *sf) { wxString s = FormatSymbol(pSymInfo, sf); if ( !s.empty() ) { Output(_T("%s\r\n"), s.c_str()); } //else: not an interesting symbol } // callback for SymEnumSymbols() /* static */ BOOL CALLBACK wxCrashReportImpl::EnumerateSymbolsCallback(PSYMBOL_INFO pSymInfo, ULONG WXUNUSED(SymbolSize), PVOID UserContext) { wxCrashReportImpl *self = (wxCrashReportImpl *)UserContext; __try { self->OutputSymbol(pSymInfo, self->m_sfCurrent); } __except ( EXCEPTION_EXECUTE_HANDLER ) { self->Output(_T("Can't process symbol %hs\r\n"), pSymInfo->Name); } // continue with enumeration return true; } HANDLE wxCrashReportImpl::OutputBasicContext(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pCtx) { // First print information about the type of fault const DWORD dwCode = pExceptionRecord->ExceptionCode; Output(_T("Exception code: %s (%#10x)\r\n"), GetExceptionString(dwCode).c_str(), dwCode); // Now print information about where the fault occured TCHAR szFaultingModule[MAX_PATH]; DWORD section, offset; void * const pExceptionAddress = pExceptionRecord->ExceptionAddress; if ( !GetLogicalAddress(pExceptionAddress, szFaultingModule, WXSIZEOF(szFaultingModule), section, offset) ) { section = offset = 0; wxStrcpy(szFaultingModule, _T("<< unknown >>")); } Output(_T("Fault address: %08x %02x:%08x %s\r\n"), pExceptionAddress, section, offset, szFaultingModule); #ifdef _M_IX86 // Show the registers Output( _T("\r\nRegisters:\r\n") ); Output(_T("EAX: %08x EBX: %08x ECX: %08x EDX: %08x ESI: %08x EDI: %08x\r\n"), pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi); Output(_T("CS:EIP: %04x:%08x SS:ESP: %04x:%08x EBP: %08x\r\n"), pCtx->SegCs, pCtx->Eip, pCtx->SegSs, pCtx->Esp, pCtx->Ebp ); Output(_T("DS: %04x ES: %04x FS: %04x GS: %04x\r\n"), pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs); Output(_T("Flags: %08x\r\n"), pCtx->EFlags ); #endif // _M_IX86 return ::GetModuleHandle(szFaultingModule); } void wxCrashReportImpl::OutputStack(const CONTEXT *pCtx, int flags) { enum { Output_Stack, Output_Locals, Output_Max #ifndef _M_IX86 // can't show locals under other architectures = Output_Locals #endif }; for ( int step = 0; step < Output_Max; step++ ) { // don't do things we're not asked for if ( (step == Output_Stack) && !(flags & wxCRASH_REPORT_STACK) || (step == Output_Locals) && !(flags & wxCRASH_REPORT_LOCALS) ) { continue; } // the context is going to be modified below so make a copy CONTEXT ctx = *pCtx; Output(_T("\r\n%s\r\n") _T(" # Address Frame Function SourceFile\r\n"), step == Output_Stack ? _T("Call stack") : _T("Local variables")); DWORD dwMachineType = 0; STACKFRAME sf; wxZeroMemory(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 = ctx.Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = ctx.Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = ctx.Ebp; sf.AddrFrame.Mode = AddrModeFlat; dwMachineType = IMAGE_FILE_MACHINE_I386; #endif // _M_IX86 const HANDLE hProcess = GetCurrentProcess(); const HANDLE hThread = GetCurrentThread(); // first show just the call stack int frame = 0; for ( ;; ) { // Get the next stack frame if ( !StackWalk(dwMachineType, hProcess, hThread, &sf, &ctx, 0, SymFunctionTableAccess, SymGetModuleBase, 0) ) { break; } // Basic sanity check to make sure the frame is OK. if ( !sf.AddrFrame.Offset ) break; Output(_T("%2d %08x %08x "), frame++, 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; // Displacement of the input address, relative to the start of the // symbol DWORD64 symDisplacement = 0; if ( SymFromAddr(hProcess, sf.AddrPC.Offset, &symDisplacement,pSymbol) ) { Output(_T("%hs()+%#") wxLongLongFmtSpec _T("x"), pSymbol->Name, symDisplacement); } else // No symbol found. Print out the logical address instead. { TCHAR szModule[MAX_PATH]; DWORD section, offset; if ( !GetLogicalAddress((PVOID)sf.AddrPC.Offset, szModule, sizeof(szModule), section, offset) ) { szModule[0] = _T('\0'); section = offset = 0; } Output(_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 ( SymGetLineFromAddr(hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo )) { Output(_T(" %s line %u"), lineInfo.FileName, lineInfo.LineNumber); } OutputEndl(); #ifdef _M_IX86 // on the second iteration also show the local variables and // parameters if ( step == Output_Locals ) { // Use SymSetContext to get just the locals/params for this // frame IMAGEHLP_STACK_FRAME imagehlpStackFrame; imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset; SymSetContext(hProcess, &imagehlpStackFrame, 0); // Enumerate the locals/parameters m_sfCurrent = &sf; SymEnumSymbols(hProcess, 0, 0, EnumerateSymbolsCallback, this); OutputEndl(); } #endif // _M_IX86 } } } void wxCrashReportImpl::OutputGlobals(HANDLE hModule) { #ifdef _M_IX86 Output(_T("\r\nGlobal variables:\r\n")); m_sfCurrent = NULL; SymEnumSymbols(::GetCurrentProcess(), (DWORD64)hModule, NULL, EnumerateSymbolsCallback, this); #endif // _M_IX86 } #endif // wxUSE_MINIDUMP bool wxCrashReportImpl::BindDbgHelpFunctions(const wxDynamicLibrary& dllDbgHelp) { #define LOAD_SYM_FUNCTION(name) \ name = (name ## _t) dllDbgHelp.GetSymbol(_T(#name)); \ if ( !name ) \ { \ Output(_T("\r\nFunction ") _T(#name) \ _T("() not found.\r\n")); \ return false; \ } #if wxUSE_MINIDUMP LOAD_SYM_FUNCTION(MiniDumpWriteDump); #else // !wxUSE_MINIDUMP LOAD_SYM_FUNCTION(SymSetOptions); LOAD_SYM_FUNCTION(SymInitialize); LOAD_SYM_FUNCTION(StackWalk); LOAD_SYM_FUNCTION(SymFromAddr); LOAD_SYM_FUNCTION(SymFunctionTableAccess); LOAD_SYM_FUNCTION(SymGetModuleBase); LOAD_SYM_FUNCTION(SymGetLineFromAddr); LOAD_SYM_FUNCTION(SymSetContext); LOAD_SYM_FUNCTION(SymEnumSymbols); LOAD_SYM_FUNCTION(SymGetTypeInfo); #endif // wxUSE_MINIDUMP/!wxUSE_MINIDUMP #undef LOAD_SYM_FUNCTION return true; } #if !wxUSE_MINIDUMP /* static */ wxString wxCrashReportImpl::GetExceptionString(DWORD dwCode) { wxString s; #define CASE_EXCEPTION( x ) case EXCEPTION_##x: s = _T(#x); break switch ( dwCode ) { CASE_EXCEPTION(ACCESS_VIOLATION); CASE_EXCEPTION(DATATYPE_MISALIGNMENT); CASE_EXCEPTION(BREAKPOINT); CASE_EXCEPTION(SINGLE_STEP); CASE_EXCEPTION(ARRAY_BOUNDS_EXCEEDED); CASE_EXCEPTION(FLT_DENORMAL_OPERAND); CASE_EXCEPTION(FLT_DIVIDE_BY_ZERO); CASE_EXCEPTION(FLT_INEXACT_RESULT); CASE_EXCEPTION(FLT_INVALID_OPERATION); CASE_EXCEPTION(FLT_OVERFLOW); CASE_EXCEPTION(FLT_STACK_CHECK); CASE_EXCEPTION(FLT_UNDERFLOW); CASE_EXCEPTION(INT_DIVIDE_BY_ZERO); CASE_EXCEPTION(INT_OVERFLOW); CASE_EXCEPTION(PRIV_INSTRUCTION); CASE_EXCEPTION(IN_PAGE_ERROR); CASE_EXCEPTION(ILLEGAL_INSTRUCTION); CASE_EXCEPTION(NONCONTINUABLE_EXCEPTION); CASE_EXCEPTION(STACK_OVERFLOW); CASE_EXCEPTION(INVALID_DISPOSITION); CASE_EXCEPTION(GUARD_PAGE); CASE_EXCEPTION(INVALID_HANDLE); default: // unknown exception, ask NTDLL for the name if ( !::FormatMessage ( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, ::GetModuleHandle(_T("NTDLL.DLL")), dwCode, 0, wxStringBuffer(s, 1024), 1024, 0 ) ) { s = _T("UNKNOWN_EXCEPTION"); } } #undef CASE_EXCEPTION return s; } #endif // !wxUSE_MINIDUMP #endif // wxUSE_DBGHELP bool wxCrashReportImpl::Generate( #if wxUSE_DBGHELP int flags #else int WXUNUSED(flags) #endif ) { if ( m_hFile == INVALID_HANDLE_VALUE ) return false; #if wxUSE_DBGHELP if ( !wxGlobalSEInformation ) return false; #if !wxUSE_MINIDUMP PEXCEPTION_RECORD pExceptionRecord = wxGlobalSEInformation->ExceptionRecord; PCONTEXT pCtx = wxGlobalSEInformation->ContextRecord; if ( !pExceptionRecord || !pCtx ) return false; HANDLE hModuleCrash = OutputBasicContext(pExceptionRecord, pCtx); #endif // !wxUSE_MINIDUMP // show to the user that we're doing something... BusyCursor busyCursor; // user-specified crash report flags override those specified by the // programmer TCHAR envFlags[64]; DWORD dwLen = ::GetEnvironmentVariable ( _T("WX_CRASH_FLAGS"), envFlags, WXSIZEOF(envFlags) ); int flagsEnv; if ( dwLen && dwLen < WXSIZEOF(envFlags) && wxSscanf(envFlags, _T("%d"), &flagsEnv) == 1 ) { flags = flagsEnv; } // for everything else we need dbghelp.dll wxDynamicLibrary dllDbgHelp(_T("dbghelp.dll"), wxDL_VERBATIM); if ( dllDbgHelp.IsLoaded() ) { if ( BindDbgHelpFunctions(dllDbgHelp) ) { #if wxUSE_MINIDUMP MINIDUMP_EXCEPTION_INFORMATION minidumpExcInfo; minidumpExcInfo.ThreadId = ::GetCurrentThreadId(); minidumpExcInfo.ExceptionPointers = wxGlobalSEInformation; minidumpExcInfo.ClientPointers = FALSE; // in our own address space // do generate the dump MINIDUMP_TYPE dumpFlags; if ( flags & wxCRASH_REPORT_LOCALS ) { // the only way to get local variables is to dump the entire // process memory space -- but this makes for huge (dozens or // even hundreds of Mb) files dumpFlags = MiniDumpWithFullMemory; } else if ( flags & wxCRASH_REPORT_GLOBALS ) { // MiniDumpWriteDump() has the option for dumping just the data // segment which contains all globals -- exactly what we need dumpFlags = MiniDumpWithDataSegs; } else // minimal dump { dumpFlags = MiniDumpNormal; } if ( !MiniDumpWriteDump ( ::GetCurrentProcess(), ::GetCurrentProcessId(), m_hFile, // file to write to dumpFlags, // kind of dump to craete &minidumpExcInfo, NULL, // no extra user-defined data NULL // no callbacks ) ) { Output(_T("MiniDumpWriteDump() failed.")); return false; } return true; #else // !wxUSE_MINIDUMP SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME); // Initialize DbgHelp if ( SymInitialize(GetCurrentProcess(), NULL, TRUE /* invade */) ) { OutputStack(pCtx, flags); if ( hModuleCrash && (flags & wxCRASH_REPORT_GLOBALS) ) { OutputGlobals(hModuleCrash); } return true; } #endif // !wxUSE_MINIDUMP } else { Output(_T("\r\nPlease update your dbghelp.dll version, ") _T("at least version 5.1 is needed!\r\n") _T("(if you already have a new version, please ") _T("put it in the same directory where the program is.)\r\n")); } } else // failed to load dbghelp.dll { Output(_T("Please install dbghelp.dll available free of charge ") _T("from Microsoft to get more detailed crash information!")); } Output(_T("\r\nLatest dbghelp.dll is available at ") _T("http://www.microsoft.com/whdc/ddk/debugging/\r\n")); #else // !wxUSE_DBGHELP Output(_T("Support for crash report generation was not included ") _T("in this wxWidgets version.")); #endif // wxUSE_DBGHELP/!wxUSE_DBGHELP return false; } // ---------------------------------------------------------------------------- // wxCrashReport // ---------------------------------------------------------------------------- /* static */ void wxCrashReport::SetFileName(const wxChar *filename) { wxStrncpy(gs_reportFilename, filename, WXSIZEOF(gs_reportFilename) - 1); gs_reportFilename[WXSIZEOF(gs_reportFilename) - 1] = _T('\0'); } /* static */ const wxChar *wxCrashReport::GetFileName() { return gs_reportFilename; } /* static */ bool wxCrashReport::Generate(int flags) { wxCrashReportImpl impl(gs_reportFilename); return impl.Generate(flags); } // ---------------------------------------------------------------------------- // wxApp::OnFatalException() support // ---------------------------------------------------------------------------- bool wxHandleFatalExceptions(bool doit) { // assume this can only be called from the main thread gs_handleExceptions = doit; if ( doit ) { // try to find a place where we can put out report file later if ( !::GetTempPath ( WXSIZEOF(gs_reportFilename), gs_reportFilename ) ) { wxLogLastError(_T("GetTempPath")); // when all else fails... wxStrcpy(gs_reportFilename, _T("c:\\")); } // use PID and date to make the report file name more unique wxString fname = wxString::Format ( #if wxUSE_MINIDUMP _T("%s_%s_%lu.dmp"), #else // !wxUSE_MINIDUMP _T("%s_%s_%lu.rpt"), #endif // wxUSE_MINIDUMP/!wxUSE_MINIDUMP wxTheApp ? wxTheApp->GetAppName().c_str() : _T("wxwindows"), wxDateTime::Now().Format(_T("%Y%m%d")).c_str(), ::GetCurrentProcessId() ); wxStrncat(gs_reportFilename, fname, WXSIZEOF(gs_reportFilename) - wxStrlen(gs_reportFilename) - 1); } return true; } extern unsigned long wxGlobalSEHandler(EXCEPTION_POINTERS *pExcPtrs) { if ( gs_handleExceptions && wxTheApp ) { // store the pointer to exception info wxGlobalSEInformation = pExcPtrs; // give the user a chance to do something special about this __try { wxTheApp->OnFatalException(); } __except ( EXCEPTION_EXECUTE_HANDLER ) { // nothing to do here, just ignore the exception inside the // exception handler ; } wxGlobalSEInformation = NULL; // this will execute our handler and terminate the process return EXCEPTION_EXECUTE_HANDLER; } return EXCEPTION_CONTINUE_SEARCH; } #else // !wxUSE_ON_FATAL_EXCEPTION bool wxHandleFatalExceptions(bool WXUNUSED(doit)) { wxFAIL_MSG(_T("set wxUSE_ON_FATAL_EXCEPTION to 1 to use this function")); return false; } #endif // wxUSE_ON_FATAL_EXCEPTION/!wxUSE_ON_FATAL_EXCEPTION