]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/crashrpt.cpp
support for Mac Help Button added (wxID_HELP) is automatically translated
[wxWidgets.git] / src / msw / crashrpt.cpp
index 9ddcf4902935b92737092a9ee989174e98f26a94..6bbdaa2b25215ae2f5cabf9239ba6a4bb4fb3c32 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 /*
-    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/dynlib.h"
 
 #include "wx/msw/crashrpt.h"
 
@@ -55,7 +77,7 @@
 //
 // 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,
@@ -86,11 +116,14 @@ 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
 
 // ----------------------------------------------------------------------------
-// constant
+// constants
 // ----------------------------------------------------------------------------
 
+#if !wxUSE_MINIDUMP
+
 // Stolen from CVCONST.H in the DIA 2.0 SDK
 enum BasicType
 {
@@ -131,12 +164,37 @@ enum SymbolTag
     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
 {
@@ -154,6 +212,7 @@ public:
     }
 
 private:
+
     // formatted output to m_hFile
     void Output(const wxChar *format, ...);
 
@@ -161,6 +220,8 @@ private:
     void OutputEndl() { Output(_T("\r\n")); }
 
 #if wxUSE_DBGHELP
+
+#if !wxUSE_MINIDUMP
     // translate exception code to its symbolic name
     static wxString GetExceptionString(DWORD dwCode);
 
@@ -197,9 +258,6 @@ private:
     // 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,
@@ -228,11 +286,18 @@ private:
 
     // 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);
@@ -243,6 +308,9 @@ private:
     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
@@ -271,6 +339,9 @@ static wxChar gs_reportFilename[MAX_PATH];
 
 #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);
@@ -281,6 +352,7 @@ 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
 
@@ -292,7 +364,7 @@ DEFINE_SYM_FUNCTION(SymGetTypeInfo);
 
 wxCrashReportImpl::wxCrashReportImpl(const wxChar *filename)
 {
-#if wxUSE_DBGHELP
+#if wxUSE_DBGHELP && !wxUSE_MINIDUMP
     m_sfCurrent = NULL;
 #endif // wxUSE_DBGHELP
 
@@ -323,6 +395,8 @@ void wxCrashReportImpl::Output(const wxChar *format, ...)
 
 #if wxUSE_DBGHELP
 
+#if !wxUSE_MINIDUMP
+
 bool
 wxCrashReportImpl::GetLogicalAddress(PVOID addr,
                                      PTSTR szModule,
@@ -432,10 +506,10 @@ wxCrashReportImpl::FormatSimpleValue(BasicType bt,
         {
             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;
             }
@@ -494,6 +568,12 @@ wxCrashReportImpl::FormatField(DWORD64 modBase,
 {
     wxString s;
 
+    // avoid infinite recursion
+    if ( level > 10 )
+    {
+        return s;
+    }
+
     const HANDLE hProcess = GetCurrentProcess();
 
     DWORD dwTag = 0;
@@ -674,7 +754,9 @@ wxCrashReportImpl::FormatSymbol(PSYMBOL_INFO pSym, STACKFRAME *sf)
         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;
 }
@@ -913,17 +995,22 @@ void wxCrashReportImpl::OutputGlobals(HANDLE hModule)
 #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);
@@ -934,12 +1021,15 @@ bool wxCrashReportImpl::ResolveSymFunctions(const wxDynamicLibrary& dllDbgHelp)
     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)
 {
@@ -995,16 +1085,17 @@ wxString wxCrashReportImpl::GetExceptionString(DWORD dwCode)
     return s;
 }
 
+#endif // !wxUSE_MINIDUMP
+
 #endif // wxUSE_DBGHELP
 
-// Remove warning
+bool wxCrashReportImpl::Generate(
 #if wxUSE_DBGHELP
-#define _WXUNUSED(x) x
+    int flags
 #else
-#define _WXUNUSED WXUNUSED
+    int WXUNUSED(flags)
 #endif
-
-bool wxCrashReportImpl::Generate(int _WXUNUSED(flags))
+)
 {
     if ( m_hFile == INVALID_HANDLE_VALUE )
         return false;
@@ -1013,6 +1104,7 @@ bool wxCrashReportImpl::Generate(int _WXUNUSED(flags))
     if ( !wxGlobalSEInformation )
         return false;
 
+#if !wxUSE_MINIDUMP
     PEXCEPTION_RECORD pExceptionRecord = wxGlobalSEInformation->ExceptionRecord;
     PCONTEXT pCtx = wxGlobalSEInformation->ContextRecord;
 
@@ -1020,13 +1112,79 @@ bool wxCrashReportImpl::Generate(int _WXUNUSED(flags))
         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) )
         {
+#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
@@ -1041,28 +1199,31 @@ bool wxCrashReportImpl::Generate(int _WXUNUSED(flags))
 
                 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;
 }
 
 // ----------------------------------------------------------------------------
@@ -1117,7 +1278,11 @@ bool wxHandleFatalExceptions(bool doit)
         // 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(),