]> git.saurik.com Git - wxWidgets.git/commitdiff
first version of crash reporting code
authorVadim Zeitlin <vadim@wxwidgets.org>
Sun, 13 Jul 2003 02:28:11 +0000 (02:28 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Sun, 13 Jul 2003 02:28:11 +0000 (02:28 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@21933 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/msw/seh.h [new file with mode: 0644]
src/msw/seh.cpp [new file with mode: 0644]

diff --git a/include/wx/msw/seh.h b/include/wx/msw/seh.h
new file mode 100644 (file)
index 0000000..ae7c2f9
--- /dev/null
@@ -0,0 +1,40 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        wx/msw/seh.h
+// Purpose:     helpers for the structured exception handling (SEH) under Win32
+// Author:      Vadim Zeitlin
+// Modified by:
+// Created:     13.07.2003
+// RCS-ID:      $Id$
+// Copyright:   (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WX_MSW_SEH_H_
+#define _WX_MSW_SEH_H_
+
+#include "wx/defs.h"
+
+#if wxUSE_ON_FATAL_EXCEPTION
+
+// ----------------------------------------------------------------------------
+// wxSEHReport: this class is used as a namespace for the SEH-related functions
+// ----------------------------------------------------------------------------
+
+struct WXDLLIMPEXP_BASE wxSEHReport
+{
+    // set the name of the file to which the report is written, it is
+    // constructed from the .exe name by default
+    static void SetFileName(const wxChar *filename);
+
+    // return the current file name
+    static const wxChar *GetFileName();
+
+    // write the exception report to the file, return true if it could be done
+    // or false otherwise
+    static bool Generate();
+};
+
+#endif // wxUSE_ON_FATAL_EXCEPTION
+
+#endif // _WX_MSW_SEH_H_
+
diff --git a/src/msw/seh.cpp b/src/msw/seh.cpp
new file mode 100644 (file)
index 0000000..1e5e336
--- /dev/null
@@ -0,0 +1,585 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name:        msw/seh.cpp
+// Purpose:     helpers for structured exception handling (SEH)
+// Author:      Vadim Zeitlin
+// Modified by:
+// Created:     13.07.03
+// RCS-ID:      $Id$
+// Copyright:   (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
+// Licence:     wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+/*
+    The code in this file is heavily based on Matt Pietrek's column from
+    the 2002 issue of MSDN Magazine.
+ */
+
+// ============================================================================
+// declarations
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+#if wxUSE_ON_FATAL_EXCEPTION
+
+#ifndef WX_PRECOMP
+#endif  //WX_PRECOMP
+
+#include "wx/datetime.h"
+#include "wx/dynload.h"
+
+#include "wx/msw/seh.h"
+
+#include <windows.h>
+#include <imagehlp.h>
+#include "wx/msw/private.h"
+
+// ----------------------------------------------------------------------------
+// types of imagehlp.h functions
+// ----------------------------------------------------------------------------
+
+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);
+
+// ----------------------------------------------------------------------------
+// classes
+// ----------------------------------------------------------------------------
+
+// the real crash report generator
+class wxSEHReportImpl
+{
+public:
+    wxSEHReportImpl(const wxChar *filename);
+
+    bool Generate();
+
+    ~wxSEHReportImpl()
+    {
+        if ( m_hFile != INVALID_HANDLE_VALUE )
+        {
+            ::CloseHandle(m_hFile);
+        }
+    }
+
+private:
+    // formatted output to m_hFile
+    void Output(const wxChar *format, ...);
+
+    // translate exception code to its symbolic name
+    static wxString GetExceptionString(DWORD dwCode);
+
+    // 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,
+                                  DWORD len,
+                                  DWORD& section,
+                                  DWORD& offset);
+
+
+    // show the general information about exception which should be always
+    // available
+    bool OutputBasicContext(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pCtx);
+
+    // output the call stack (pCtx may be modified, make copy before call!)
+    void OutputStack(CONTEXT *pCtx);
+
+
+    // the handle of the report file
+    HANDLE m_hFile;
+
+    // dynamically loaded dbghelp.dll functions
+    #define DECLARE_SYM_FUNCTION(func) func ## _t func
+
+    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);
+};
+
+// ----------------------------------------------------------------------------
+// globals
+// ----------------------------------------------------------------------------
+
+// global pointer to exception information, only valid inside OnFatalException
+extern WXDLLIMPEXP_BASE EXCEPTION_POINTERS *wxGlobalSEInformation = NULL;
+
+
+// flag telling us whether the application wants to handle exceptions at all
+static bool gs_handleExceptions = false;
+
+// the file name where the report about exception is written
+static wxChar gs_reportFilename[MAX_PATH];
+
+// ============================================================================
+// implementation
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// wxSEHReport
+// ----------------------------------------------------------------------------
+
+wxSEHReportImpl::wxSEHReportImpl(const wxChar *filename)
+{
+    m_hFile = ::CreateFile
+                (
+                    filename,
+                    GENERIC_WRITE,
+                    0,                          // no sharing
+                    NULL,                       // default security
+                    CREATE_ALWAYS,
+                    FILE_FLAG_WRITE_THROUGH,
+                    NULL                        // no template file
+                );
+}
+
+void wxSEHReportImpl::Output(const wxChar *format, ...)
+{
+    va_list argptr;
+    va_start(argptr, format);
+
+    DWORD cbWritten;
+
+    wxString s = wxString::FormatV(format, argptr);
+    ::WriteFile(m_hFile, s, s.length() * sizeof(wxChar), &cbWritten, 0);
+
+    va_end(argptr);
+}
+
+bool
+wxSEHReportImpl::GetLogicalAddress(PVOID addr,
+                                   PTSTR szModule,
+                                   DWORD len,
+                                   DWORD& section,
+                                   DWORD& offset)
+{
+    MEMORY_BASIC_INFORMATION mbi;
+
+    if ( !::VirtualQuery(addr, &mbi, sizeof(mbi)) )
+        return false;
+
+    DWORD hMod = (DWORD)mbi.AllocationBase;
+
+    if ( !::GetModuleFileName((HMODULE)hMod, szModule, len) )
+        return false;
+
+    // Point to the DOS header in memory
+    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
+
+    // From the DOS header, find the NT (PE) header
+    PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);
+
+    PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
+
+    DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address
+
+    // Iterate through the section table, looking for the one that encompasses
+    // the linear address.
+    const DWORD nSections = pNtHdr->FileHeader.NumberOfSections;
+    for ( DWORD i = 0; i < nSections; i++, pSection++ )
+    {
+        DWORD sectionStart = pSection->VirtualAddress;
+        DWORD sectionEnd = sectionStart
+                    + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);
+
+        // Is the address in this section?
+        if ( (rva >= sectionStart) && (rva <= sectionEnd) )
+        {
+            // Yes, address is in the section.  Calculate section and offset,
+            // and store in the "section" & "offset" params, which were
+            // passed by reference.
+            section = i + 1;
+            offset = rva - sectionStart;
+
+            return true;
+        }
+    }
+
+    // failed to map to logical address...
+    return false;
+}
+
+bool
+wxSEHReportImpl::OutputBasicContext(EXCEPTION_RECORD *pExceptionRecord,
+                                    CONTEXT *pCtx)
+{
+    // First print information about the type of fault
+    const DWORD dwCode = pExceptionRecord->ExceptionCode;
+    Output(_T("Exception code: %s (%#08x)\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);
+
+    // Show the registers
+#ifdef _M_IX86
+    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 true;
+}
+
+void wxSEHReportImpl::OutputStack(CONTEXT *pCtx)
+{
+    Output(_T("\r\nCall stack:\r\n"));
+
+    Output(_T("Address   Frame     Function            SourceFile\r\n"));
+
+    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       = pCtx->Eip;
+    sf.AddrPC.Mode         = AddrModeFlat;
+    sf.AddrStack.Offset    = pCtx->Esp;
+    sf.AddrStack.Mode      = AddrModeFlat;
+    sf.AddrFrame.Offset    = pCtx->Ebp;
+    sf.AddrFrame.Mode      = AddrModeFlat;
+
+    dwMachineType = IMAGE_FILE_MACHINE_I386;
+#endif // _M_IX86
+
+    const HANDLE hProcess = GetCurrentProcess();
+    const HANDLE hThread = GetCurrentThread();
+
+    for ( ;; )
+    {
+        // Get the next stack frame
+        if ( !StackWalk(dwMachineType,
+                        hProcess,
+                        hThread,
+                        &sf,
+                        pCtx,
+                        0,
+                        SymFunctionTableAccess,
+                        SymGetModuleBase,
+                        0) )
+        {
+            break;
+        }
+
+        // Basic sanity check to make sure the frame is OK.
+        if ( !sf.AddrFrame.Offset )
+            break;
+
+        Output(_T("%08x  %08x  "), 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;
+        DWORD64 symDisplacement = 0;    // Displacement of the input address,
+                                        // relative to the start of the symbol
+
+        if ( SymFromAddr(hProcess, sf.AddrPC.Offset,
+                         &symDisplacement,pSymbol) )
+        {
+            Output(_T("%hs() + %#08I64x"), 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); 
+        }
+
+        Output(_T("\r\n"));
+    }
+}
+
+bool wxSEHReportImpl::ResolveSymFunctions(const wxDynamicLibrary& dllDbgHelp)
+{
+    #define LOAD_SYM_FUNCTION(name)                                           \
+        name = (name ## _t) dllDbgHelp.GetSymbol(#name);                      \
+        if ( !name )                                                          \
+        {                                                                     \
+            Output(_T("\r\nFunction ") __XFILE__(#name)                       \
+                   _T("() not found.\r\n"));                                  \
+            return false;                                                     \
+        }
+
+    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);
+
+    #undef LOAD_SYM_FUNCTION
+
+    return true;
+}
+
+bool wxSEHReportImpl::Generate()
+{
+    if ( m_hFile == INVALID_HANDLE_VALUE )
+        return false;
+
+    PEXCEPTION_RECORD pExceptionRecord = wxGlobalSEInformation->ExceptionRecord;
+    PCONTEXT pCtx = wxGlobalSEInformation->ContextRecord;
+
+    if ( !OutputBasicContext(pExceptionRecord, pCtx) )
+        return false;
+
+    // for everything else we need dbghelp.dll
+    wxDynamicLibrary dllDbgHelp(_T("dbghelp.dll"), wxDL_VERBATIM);
+    if ( dllDbgHelp.IsLoaded() )
+    {
+        if ( ResolveSymFunctions(dllDbgHelp) )
+        {
+            SymSetOptions(SYMOPT_DEFERRED_LOADS);
+
+            // Initialize DbgHelp
+            if ( SymInitialize(GetCurrentProcess(), NULL, TRUE /* invade */) )
+            {
+                CONTEXT ctxCopy = *pCtx;
+
+                OutputStack(&ctxCopy);
+
+                return true;
+            }
+        }
+        else
+        {
+            Output(_T("Please update your dbghelp.dll version, "
+                      "at least version 6.0 is needed!\r\n"));
+        }
+    }
+    else
+    {
+        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"));
+
+    return true;
+}
+
+/* static */
+wxString wxSEHReportImpl::GetExceptionString(DWORD dwCode)
+{
+    wxString s;
+
+    #define CASE_EXCEPTION( x ) case EXCEPTION_##x: s = _T(#x); break
+
+    switch ( dwCode )
+    {
+        CASE_EXCEPTION(ACCESS_VIOLATION);
+        CASE_EXCEPTION(DATATYPE_MISALIGNMENT);
+        CASE_EXCEPTION(BREAKPOINT);
+        CASE_EXCEPTION(SINGLE_STEP);
+        CASE_EXCEPTION(ARRAY_BOUNDS_EXCEEDED);
+        CASE_EXCEPTION(FLT_DENORMAL_OPERAND);
+        CASE_EXCEPTION(FLT_DIVIDE_BY_ZERO);
+        CASE_EXCEPTION(FLT_INEXACT_RESULT);
+        CASE_EXCEPTION(FLT_INVALID_OPERATION);
+        CASE_EXCEPTION(FLT_OVERFLOW);
+        CASE_EXCEPTION(FLT_STACK_CHECK);
+        CASE_EXCEPTION(FLT_UNDERFLOW);
+        CASE_EXCEPTION(INT_DIVIDE_BY_ZERO);
+        CASE_EXCEPTION(INT_OVERFLOW);
+        CASE_EXCEPTION(PRIV_INSTRUCTION);
+        CASE_EXCEPTION(IN_PAGE_ERROR);
+        CASE_EXCEPTION(ILLEGAL_INSTRUCTION);
+        CASE_EXCEPTION(NONCONTINUABLE_EXCEPTION);
+        CASE_EXCEPTION(STACK_OVERFLOW);
+        CASE_EXCEPTION(INVALID_DISPOSITION);
+        CASE_EXCEPTION(GUARD_PAGE);
+        CASE_EXCEPTION(INVALID_HANDLE);
+
+        default:
+            // unknown exception, ask NTDLL for the name
+            if ( !::FormatMessage
+                    (
+                     FORMAT_MESSAGE_IGNORE_INSERTS |
+                     FORMAT_MESSAGE_FROM_HMODULE,
+                     ::GetModuleHandle(_T("NTDLL.DLL")),
+                     dwCode,
+                     0,
+                     wxStringBuffer(s, 1024),
+                     1024,
+                     0
+                    ) )
+            {
+                s = _T("UNKNOWN_EXCEPTION");
+            }
+    }
+
+    #undef CASE_EXCEPTION
+
+    return s;
+}
+
+// ----------------------------------------------------------------------------
+// wxSEHReport
+// ----------------------------------------------------------------------------
+
+/* static */
+void wxSEHReport::SetFileName(const wxChar *filename)
+{
+    wxStrncpy(gs_reportFilename, filename, WXSIZEOF(gs_reportFilename) - 1);
+    gs_reportFilename[WXSIZEOF(gs_reportFilename) - 1] = _T('\0');
+}
+
+/* static */
+const wxChar *wxSEHReport::GetFileName()
+{
+    return gs_reportFilename;
+}
+
+/* static */
+bool wxSEHReport::Generate()
+{
+    wxSEHReportImpl impl(gs_reportFilename);
+
+    return impl.Generate();
+}
+
+// ----------------------------------------------------------------------------
+// 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
+                         (
+                            _T("%s_%s_%lu.rpt"),
+                            wxTheApp ? wxTheApp->GetAppName().c_str()
+                                     : _T("wxwindows"),
+                            wxDateTime::Now().Format(_T("%Y%m%d")).c_str(),
+                            ::GetCurrentProcessId()
+                         );
+
+        wxStrncat(gs_reportFilename, fname,
+                  WXSIZEOF(gs_reportFilename) - strlen(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
+        wxTheApp->OnFatalException();
+
+        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
+