/////////////////////////////////////////////////////////////////////////////
// Name: msw/crashrpt.cpp
-// Purpose: helpers for structured exception handling (SEH)
+// Purpose: code to generate crash dumps (minidumps)
// Author: Vadim Zeitlin
// Modified by:
// Created: 13.07.03
// 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
// ============================================================================
#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 <imagehlp.h>
-#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
// ----------------------------------------------------------------------------
public:
wxCrashReportImpl(const wxChar *filename);
- bool Generate(int flags);
+ bool Generate(int flags, EXCEPTION_POINTERS *ep);
~wxCrashReportImpl()
{
// 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
+//
+// 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,
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(_T("Context for crash report generation not available."));
+ return false;
}
- // failed to map to logical address...
- return false;
-}
+ // show to the user that we're doing something...
+ BusyCursor busyCursor;
-/* static */ BasicType
-wxCrashReportImpl::GetBasicType(DWORD64 modBase, DWORD typeIndex)
-{
- const HANDLE hProcess = GetCurrentProcess();
+ // user-specified crash report flags override those specified by the
+ // programmer
+ TCHAR envFlags[64];
+ DWORD dwLen = ::GetEnvironmentVariable
+ (
+ _T("WX_CRASH_FLAGS"),
+ envFlags,
+ WXSIZEOF(envFlags)
+ );
- // try the index we have
- BasicType bt;
- if ( SymGetTypeInfo(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &bt) )
+ int flagsEnv;
+ if ( dwLen && dwLen < WXSIZEOF(envFlags) &&
+ wxSscanf(envFlags, _T("%d"), &flagsEnv) == 1 )
{
- return bt;
+ flags = flagsEnv;
}
- // 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)) )
+ if ( wxDbgHelpDLL::Init() )
{
- return bt;
- }
+ MINIDUMP_EXCEPTION_INFORMATION minidumpExcInfo;
- return BASICTYPE_NOTYPE;
-}
+ minidumpExcInfo.ThreadId = ::GetCurrentThreadId();
+ minidumpExcInfo.ExceptionPointers = ep;
+ minidumpExcInfo.ClientPointers = FALSE; // in our own address space
-/* 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 )
+ // do generate the dump
+ MINIDUMP_TYPE dumpFlags;
+ if ( flags & wxCRASH_REPORT_LOCALS )
{
- // 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);
+ dumpFlags = MiniDumpNormal;
}
- }
-
- 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;
-}
+ 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(_T("MiniDumpWriteDump() failed."));
-// this is called for the struct members/base classes
-wxString
-wxCrashReportImpl::FormatField(DWORD64 modBase,
- DWORD dwTypeIndex,
- void *pVariable,
- unsigned level)
-{
- wxString s;
+ return false;
+ }
- // avoid infinite recursion
- if ( level > 10 )
- {
- return s;
+ return true;
}
-
- const HANDLE hProcess = GetCurrentProcess();
-
- DWORD dwTag = 0;
- SymGetTypeInfo(hProcess, modBase, dwTypeIndex, TI_GET_SYMTAG, &dwTag);
-
- switch ( dwTag )
+ else // dbghelp.dll couldn't be loaded
{
- 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(wxDbgHelpDLL::GetErrorMessage());
}
+#else // !wxUSE_DBGHELP
+ wxUnusedVar(flags);
+ wxUnusedVar(ep);
- if ( s.empty() )
- {
- // don't show if no value -- what for?
- return s;
- }
+ Output(_T("Support for crash report generation was not included ")
+ _T("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 wxChar *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;
+ wxStrncpy(gs_reportFilename, filename, WXSIZEOF(gs_reportFilename) - 1);
+ gs_reportFilename[WXSIZEOF(gs_reportFilename) - 1] = _T('\0');
}
-// display contents and type of the given variable
-/* static */ wxString
-wxCrashReportImpl::FormatSymbol(PSYMBOL_INFO pSym, STACKFRAME *sf)
+/* static */
+const wxChar *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);
+ RaiseException(0x1976, 0, 0, NULL);
}
- __except ( EXCEPTION_EXECUTE_HANDLER )
+ __except( rc = Generate(flags, (EXCEPTION_POINTERS *)GetExceptionInformation()),
+ EXCEPTION_CONTINUE_EXECUTION )
{
- self->Output(_T("Can't process symbol %hs\r\n"), pSymInfo->Name);
+ // never executed because of EXCEPTION_CONTINUE_EXECUTION above
}
- // 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);
+ 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, _T("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
- switch ( dwCode )
+ switch ( code )
{
CASE_EXCEPTION(ACCESS_VIOLATION);
CASE_EXCEPTION(DATATYPE_MISALIGNMENT);
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_HMODULE,
::GetModuleHandle(_T("NTDLL.DLL")),
- dwCode,
+ code,
0,
wxStringBuffer(s, 1024),
1024,
0
) )
{
- s = _T("UNKNOWN_EXCEPTION");
+ s.Printf(_T("UNKNOWN_EXCEPTION(%d)"), code);
}
}
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
+#endif // wxUSE_CRASHREPORT/!wxUSE_CRASHREPORT