X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8d0089653228874c88553e9494d8077b27ea6cd3..43f4e852a1b2ac37c3db6a2b87315192ac549191:/src/msw/crashrpt.cpp diff --git a/src/msw/crashrpt.cpp b/src/msw/crashrpt.cpp index 8831a96f32..e618107487 100644 --- a/src/msw/crashrpt.cpp +++ b/src/msw/crashrpt.cpp @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: msw/crashrpt.cpp -// Purpose: helpers for structured exception handling (SEH) +// Name: src/msw/crashrpt.cpp +// Purpose: code to generate crash dumps (minidumps) // Author: Vadim Zeitlin // Modified by: // Created: 13.07.03 @@ -9,13 +9,6 @@ // 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 // ============================================================================ @@ -31,149 +24,20 @@ #pragma hdrstop #endif -#if wxUSE_ON_FATAL_EXCEPTION +#if wxUSE_CRASHREPORT #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/debughlp.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 wxWindows calls as this could be dangerous +// of going through wxWidgets calls as this could be dangerous class BusyCursor { public: @@ -201,7 +65,7 @@ class wxCrashReportImpl public: wxCrashReportImpl(const wxChar *filename); - bool Generate(int flags); + bool Generate(int flags, EXCEPTION_POINTERS *ep); ~wxCrashReportImpl() { @@ -217,101 +81,7 @@ private: 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 + void OutputEndl() { Output(wxT("\r\n")); } // the handle of the report file HANDLE m_hFile; @@ -321,53 +91,25 @@ private: // 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 +// +// we use fixed buffer to avoid (big) dynamic allocations when the program +// crashes static wxChar gs_reportFilename[MAX_PATH]; +// this is defined in msw/main.cpp +extern EXCEPTION_POINTERS *wxGlobalSEInformation; + // ============================================================================ // 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, @@ -388,656 +130,209 @@ void wxCrashReportImpl::Output(const wxChar *format, ...) DWORD cbWritten; wxString s = wxString::FormatV(format, argptr); - ::WriteFile(m_hFile, s, s.length() * sizeof(wxChar), &cbWritten, 0); + + wxCharBuffer buf(s.mb_str(wxConvUTF8)); + ::WriteFile(m_hFile, buf.data(), strlen(buf.data()), &cbWritten, 0); va_end(argptr); } -#if wxUSE_DBGHELP - -#if !wxUSE_MINIDUMP - -bool -wxCrashReportImpl::GetLogicalAddress(PVOID addr, - PTSTR szModule, - DWORD len, - DWORD& section, - DWORD& offset) +bool wxCrashReportImpl::Generate(int flags, EXCEPTION_POINTERS *ep) { - MEMORY_BASIC_INFORMATION mbi; - - if ( !::VirtualQuery(addr, &mbi, sizeof(mbi)) ) - return false; - - DWORD hMod = (DWORD)mbi.AllocationBase; - - if ( !::GetModuleFileName((HMODULE)hMod, szModule, len) ) + if ( m_hFile == INVALID_HANDLE_VALUE ) 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 +#if wxUSE_DBGHELP + if ( !ep ) + ep = wxGlobalSEInformation; - // 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++ ) + if ( !ep ) { - 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; - } + Output(wxT("Context for crash report generation not available.")); + return false; } - // failed to map to logical address... - return false; -} - -/* static */ BasicType -wxCrashReportImpl::GetBasicType(DWORD64 modBase, DWORD typeIndex) -{ - const HANDLE hProcess = GetCurrentProcess(); + // show to the user that we're doing something... + BusyCursor busyCursor; - // try the index we have - BasicType bt; - if ( SymGetTypeInfo(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &bt) ) - { - return bt; - } + // user-specified crash report flags override those specified by the + // programmer + TCHAR envFlags[64]; + DWORD dwLen = ::GetEnvironmentVariable + ( + wxT("WX_CRASH_FLAGS"), + envFlags, + WXSIZEOF(envFlags) + ); - // 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)) ) + int flagsEnv; + if ( dwLen && dwLen < WXSIZEOF(envFlags) && + wxSscanf(envFlags, wxT("%d"), &flagsEnv) == 1 ) { - return bt; + flags = flagsEnv; } - 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 ) + if ( wxDbgHelpDLL::Init() ) { - s.Printf(_T("%#04x"), *(PBYTE)pAddress); - } - else if ( length == 2 ) - { - s.Printf(_T("%#06x"), *(PWORD)pAddress); - } - else if ( length == 4 ) - { - bool handled = false; + MINIDUMP_EXCEPTION_INFORMATION minidumpExcInfo; - if ( bt == BASICTYPE_FLOAT ) - { - s.Printf(_T("%f"), *(PFLOAT)pAddress); + minidumpExcInfo.ThreadId = ::GetCurrentThreadId(); + minidumpExcInfo.ExceptionPointers = ep; + minidumpExcInfo.ClientPointers = FALSE; // in our own address space - handled = true; - } - else if ( bt == BASICTYPE_CHAR ) + // do generate the dump + MINIDUMP_TYPE dumpFlags; + if ( flags & wxCRASH_REPORT_LOCALS ) { - 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); + // 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 ( length == 8 ) - { - if ( bt == BASICTYPE_FLOAT ) + else if ( flags & wxCRASH_REPORT_GLOBALS ) { - s.Printf(_T("%lf"), *(double *)pAddress); + // MiniDumpWriteDump() has the option for dumping just the data + // segment which contains all globals -- exactly what we need + dumpFlags = MiniDumpWithDataSegs; } - else // opaque 64 bit value + else // minimal dump { - s.Printf(_T("%#" wxLongLongFmtSpec _T("x")), *(PDWORD *)pAddress); + // the file size is not much bigger than when using MiniDumpNormal + // if we use the flags below, but the minidump is much more useful + // as it contains the values of many (but not all) local variables + dumpFlags = (MINIDUMP_TYPE)(MiniDumpScanMemory +#if _MSC_VER > 1300 + |MiniDumpWithIndirectlyReferencedMemory +#endif + ); } - } - - return s; -} -/* static */ -wxString wxCrashReportImpl::GetSymbolName(DWORD64 modBase, DWORD dwTypeIndex) -{ - wxString s; + if ( !wxDbgHelpDLL::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(wxT("MiniDumpWriteDump() failed.")); - WCHAR *pwszTypeName; - if ( SymGetTypeInfo - ( - GetCurrentProcess(), - modBase, - dwTypeIndex, - TI_GET_SYMNAME, - &pwszTypeName - ) ) - { - s = wxConvCurrent->cWC2WX(pwszTypeName); + return false; + } - ::LocalFree(pwszTypeName); + return true; } - - 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 ) + else // dbghelp.dll couldn't be loaded { - 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; - + Output(wxT("%s"), wxDbgHelpDLL::GetErrorMessage().c_str()); } +#else // !wxUSE_DBGHELP + wxUnusedVar(flags); + wxUnusedVar(ep); - if ( s.empty() ) - { - // don't show if no value -- what for? - return s; - } + Output(wxT("Support for crash report generation was not included ") + wxT("in this wxWidgets version.")); +#endif // wxUSE_DBGHELP/!wxUSE_DBGHELP - return wxString(_T('\t'), level + 1) + - GetSymbolName(modBase, dwTypeIndex) + - _T(" = ") + s + _T("\r\n"); + return false; } -// 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; -} +// ---------------------------------------------------------------------------- +// wxCrashReport +// ---------------------------------------------------------------------------- -// return the string containing the symbol of the given symbol -/* static */ wxString -wxCrashReportImpl::FormatAnyValue(PSYMBOL_INFO pSym, void *pVariable) +/* static */ +void wxCrashReport::SetFileName(const wxString& filename) { - 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; + wxStrlcpy(gs_reportFilename, filename.t_str(), WXSIZEOF(gs_reportFilename)); } -// display contents and type of the given variable -/* static */ wxString -wxCrashReportImpl::FormatSymbol(PSYMBOL_INFO pSym, STACKFRAME *sf) +/* static */ +wxString wxCrashReport::GetFileName() { - 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; + return gs_reportFilename; } -void -wxCrashReportImpl::OutputSymbol(PSYMBOL_INFO pSymInfo, STACKFRAME *sf) +/* static */ +bool wxCrashReport::Generate(int flags, EXCEPTION_POINTERS *ep) { - wxString s = FormatSymbol(pSymInfo, sf); - if ( !s.empty() ) - { - Output(_T("%s\r\n"), s.c_str()); - } - //else: not an interesting symbol + wxCrashReportImpl impl(gs_reportFilename); + + return impl.Generate(flags, ep); } -// callback for SymEnumSymbols() /* static */ -BOOL CALLBACK -wxCrashReportImpl::EnumerateSymbolsCallback(PSYMBOL_INFO pSymInfo, - ULONG WXUNUSED(SymbolSize), - PVOID UserContext) +bool wxCrashReport::GenerateNow(int flags) { - wxCrashReportImpl *self = (wxCrashReportImpl *)UserContext; + bool rc = false; __try { - self->OutputSymbol(pSymInfo, self->m_sfCurrent); - } - __except ( EXCEPTION_EXECUTE_HANDLER ) - { - self->Output(_T("Can't process symbol %hs\r\n"), pSymInfo->Name); + RaiseException(0x1976, 0, 0, NULL); } - - // 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) ) + __except( rc = Generate(flags, (EXCEPTION_POINTERS *)GetExceptionInformation()), + EXCEPTION_CONTINUE_EXECUTION ) { - section = - offset = 0; - - wxStrcpy(szFaultingModule, _T("<< unknown >>")); + // never executed because of EXCEPTION_CONTINUE_EXECUTION above } - 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); + return rc; } -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 +// ---------------------------------------------------------------------------- +// wxCrashContext +// ---------------------------------------------------------------------------- -bool wxCrashReportImpl::BindDbgHelpFunctions(const wxDynamicLibrary& dllDbgHelp) +wxCrashContext::wxCrashContext(_EXCEPTION_POINTERS *ep) { - #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; + wxZeroMemory(*this); + + if ( !ep ) + { + wxCHECK_RET( wxGlobalSEInformation, wxT("no exception info available") ); + ep = wxGlobalSEInformation; + } + + // TODO: we could also get the operation (read/write) and address for which + // it failed for EXCEPTION_ACCESS_VIOLATION code + const EXCEPTION_RECORD& rec = *ep->ExceptionRecord; + code = rec.ExceptionCode; + addr = rec.ExceptionAddress; + +#ifdef __INTEL__ + const CONTEXT& ctx = *ep->ContextRecord; + regs.eax = ctx.Eax; + regs.ebx = ctx.Ebx; + regs.ecx = ctx.Ecx; + regs.edx = ctx.Edx; + regs.esi = ctx.Esi; + regs.edi = ctx.Edi; + + regs.ebp = ctx.Ebp; + regs.esp = ctx.Esp; + regs.eip = ctx.Eip; + + regs.cs = ctx.SegCs; + regs.ds = ctx.SegDs; + regs.es = ctx.SegEs; + regs.fs = ctx.SegFs; + regs.gs = ctx.SegGs; + regs.ss = ctx.SegSs; + + regs.flags = ctx.EFlags; +#endif // __INTEL__ } -#if !wxUSE_MINIDUMP - -/* static */ -wxString wxCrashReportImpl::GetExceptionString(DWORD dwCode) +wxString wxCrashContext::GetExceptionString() const { wxString s; - #define CASE_EXCEPTION( x ) case EXCEPTION_##x: s = _T(#x); break + #define CASE_EXCEPTION( x ) case EXCEPTION_##x: s = wxT(#x); break - switch ( dwCode ) + switch ( code ) { CASE_EXCEPTION(ACCESS_VIOLATION); CASE_EXCEPTION(DATATYPE_MISALIGNMENT); @@ -1068,15 +363,15 @@ wxString wxCrashReportImpl::GetExceptionString(DWORD dwCode) ( FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, - ::GetModuleHandle(_T("NTDLL.DLL")), - dwCode, + ::GetModuleHandle(wxT("NTDLL.DLL")), + code, 0, wxStringBuffer(s, 1024), 1024, 0 ) ) { - s = _T("UNKNOWN_EXCEPTION"); + s.Printf(wxT("UNKNOWN_EXCEPTION(%d)"), code); } } @@ -1085,247 +380,5 @@ wxString wxCrashReportImpl::GetExceptionString(DWORD dwCode) return s; } -#endif // !wxUSE_MINIDUMP - -#endif // wxUSE_DBGHELP - -bool wxCrashReportImpl::Generate(int flags) -{ - 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 wxWindows 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 +#endif // wxUSE_CRASHREPORT/!wxUSE_CRASHREPORT