/////////////////////////////////////////////////////////////////////////////
/*
- The code in this file is heavily based on Matt Pietrek's column from
- the March 2002 issue of MSDN Magazine.
+ 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.
*/
// ============================================================================
#ifndef WX_PRECOMP
#endif //WX_PRECOMP
-#include "wx/longlong.h"
+/*
+ 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"
//
// in any case, the user may override by defining wxUSE_DBGHELP himself
#ifndef wxUSE_DBGHELP
- #ifdef DBHLPAPI
+ #ifdef DBHLPAPI
#define wxUSE_DBGHELP 1
#else
#define wxUSE_DBGHELP 0
// 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,
PSYM_ENUMERATESYMBOLS_CALLBACK, PVOID);
typedef BOOL (WINAPI *SymGetTypeInfo_t)(HANDLE, DWORD64, ULONG,
IMAGEHLP_SYMBOL_TYPE_INFO, PVOID);
+#endif // wxUSE_MINIDUMP
// ----------------------------------------------------------------------------
-// constant
+// constants
// ----------------------------------------------------------------------------
+#if !wxUSE_MINIDUMP
+
// Stolen from CVCONST.H in the DIA 2.0 SDK
enum BasicType
{
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
{
}
private:
+
// formatted output to m_hFile
void Output(const wxChar *format, ...);
void OutputEndl() { Output(_T("\r\n")); }
#if wxUSE_DBGHELP
+
+#if !wxUSE_MINIDUMP
// translate exception code to its symbolic name
static wxString GetExceptionString(DWORD dwCode);
// outputs information about the given symbol
void OutputSymbol(PSYMBOL_INFO pSymInfo, STACKFRAME *sf);
- // load all the functions we need from dbghelp.dll, return true if all ok
- bool ResolveSymFunctions(const wxDynamicLibrary& dllDbgHelp);
-
// map address to module (and also section:offset), retunr true if ok
static bool GetLogicalAddress(PVOID addr,
PTSTR szModule,
// 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(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
#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(SymSetContext);
DEFINE_SYM_FUNCTION(SymEnumSymbols);
DEFINE_SYM_FUNCTION(SymGetTypeInfo);
+#endif // wxUSE_MINIDUMP/!wxUSE_MINIDUMP
#undef DEFINE_SYM_FUNCTION
wxCrashReportImpl::wxCrashReportImpl(const wxChar *filename)
{
-#if wxUSE_DBGHELP
+#if wxUSE_DBGHELP && !wxUSE_MINIDUMP
m_sfCurrent = NULL;
#endif // wxUSE_DBGHELP
#if wxUSE_DBGHELP
+#if !wxUSE_MINIDUMP
+
bool
wxCrashReportImpl::GetLogicalAddress(PVOID addr,
PTSTR szModule,
{
static const size_t NUM_CHARS = 32;
- const wxChar * const pc = *(PSTR *)pAddress;
- if ( !::IsBadStringPtr(pc, NUM_CHARS) )
+ const char * const pc = *(PSTR *)pAddress;
+ if ( !::IsBadStringPtrA(pc, NUM_CHARS) )
{
- s << _T('"') << wxString(pc, NUM_CHARS) << _T('"');
+ s << _T('"') << wxString(pc, wxConvLibc, NUM_CHARS) << _T('"');
handled = true;
}
{
wxString s;
+ // avoid infinite recursion
+ if ( level > 10 )
+ {
+ return s;
+ }
+
const HANDLE hProcess = GetCurrentProcess();
DWORD dwTag = 0;
pVariable = (DWORD_PTR)pSym->Address;
}
- s << pSym->Name << _T(" = ") << FormatAnyValue(pSym, (PVOID)pVariable);
+ s << wxString(pSym->Name, wxConvLibc)
+ << _T(" = ")
+ << FormatAnyValue(pSym, (PVOID)pVariable);
return s;
}
#endif // _M_IX86
}
-bool wxCrashReportImpl::ResolveSymFunctions(const wxDynamicLibrary& dllDbgHelp)
+#endif // wxUSE_MINIDUMP
+
+bool wxCrashReportImpl::BindDbgHelpFunctions(const wxDynamicLibrary& dllDbgHelp)
{
#define LOAD_SYM_FUNCTION(name) \
- name = (name ## _t) dllDbgHelp.GetSymbol(#name); \
+ name = (name ## _t) dllDbgHelp.GetSymbol(_T(#name)); \
if ( !name ) \
{ \
- Output(_T("\r\nFunction ") __XFILE__(#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(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)
{
return s;
}
+#endif // !wxUSE_MINIDUMP
+
#endif // wxUSE_DBGHELP
-bool wxCrashReportImpl::Generate(int flags)
+bool wxCrashReportImpl::Generate(
+#if wxUSE_DBGHELP
+ int flags
+#else
+ int WXUNUSED(flags)
+#endif
+)
{
if ( m_hFile == INVALID_HANDLE_VALUE )
return false;
if ( !wxGlobalSEInformation )
return false;
+#if !wxUSE_MINIDUMP
PEXCEPTION_RECORD pExceptionRecord = wxGlobalSEInformation->ExceptionRecord;
PCONTEXT pCtx = wxGlobalSEInformation->ContextRecord;
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 ( ResolveSymFunctions(dllDbgHelp) )
+ if ( BindDbgHelpFunctions(dllDbgHelp) )
{
- SymSetOptions(SYMOPT_DEFERRED_LOADS);
+#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 */) )
return true;
}
+#endif // !wxUSE_MINIDUMP
}
else
{
- Output(_T("Please update your dbghelp.dll version, "
- "at least version 5.1 is needed!\r\n"));
+ 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
+ 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 "
- "http://www.microsoft.com/whdc/ddk/debugging/\r\n"));
+ 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."));
+ _T("in this wxWidgets version."));
#endif // wxUSE_DBGHELP/!wxUSE_DBGHELP
- return true;
+ return false;
}
// ----------------------------------------------------------------------------
// 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(),
wxGlobalSEInformation = pExcPtrs;
// give the user a chance to do something special about this
- wxTheApp->OnFatalException();
+ __try
+ {
+ wxTheApp->OnFatalException();
+ }
+ __except ( EXCEPTION_EXECUTE_HANDLER )
+ {
+ // nothing to do here, just ignore the exception inside the
+ // exception handler
+ ;
+ }
wxGlobalSEInformation = NULL;