Files
wxWidgets/src/msw/crashrpt.cpp
Julian Smart 655719367a Use old licence name
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@27408 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2004-05-23 20:53:33 +00:00

1338 lines
40 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: msw/crashrpt.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 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
// ============================================================================
// ----------------------------------------------------------------------------
// 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
/*
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"
#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
// ----------------------------------------------------------------------------
// 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
{
public:
wxCrashReportImpl(const wxChar *filename);
bool Generate(int flags);
~wxCrashReportImpl()
{
if ( m_hFile != INVALID_HANDLE_VALUE )
{
::CloseHandle(m_hFile);
}
}
private:
// formatted output to m_hFile
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
// 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
static wxChar gs_reportFilename[MAX_PATH];
// ============================================================================
// 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,
GENERIC_WRITE,
0, // no sharing
NULL, // default security
CREATE_ALWAYS,
FILE_FLAG_WRITE_THROUGH,
NULL // no template file
);
}
void wxCrashReportImpl::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);
}
#if wxUSE_DBGHELP
#if !wxUSE_MINIDUMP
bool
wxCrashReportImpl::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;
}
/* static */ BasicType
wxCrashReportImpl::GetBasicType(DWORD64 modBase, DWORD typeIndex)
{
const HANDLE hProcess = GetCurrentProcess();
// try the index we have
BasicType bt;
if ( SymGetTypeInfo(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &bt) )
{
return bt;
}
// 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)) )
{
return bt;
}
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 )
{
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 )
{
// treat just as an opaque DWORD
s.Printf(_T("%#x"), *(PDWORD)pAddress);
}
}
else if ( length == 8 )
{
if ( bt == BASICTYPE_FLOAT )
{
s.Printf(_T("%lf"), *(double *)pAddress);
}
else // opaque 64 bit value
{
s.Printf(_T("%#" wxLongLongFmtSpec _T("x")), *(PDWORD *)pAddress);
}
}
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;
}
// 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 )
{
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;
}
if ( s.empty() )
{
// don't show if no value -- what for?
return s;
}
return wxString(_T('\t'), level + 1) +
GetSymbolName(modBase, dwTypeIndex) +
_T(" = ") + s + _T("\r\n");
}
// 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;
}
// return the string containing the symbol of the given symbol
/* static */ wxString
wxCrashReportImpl::FormatAnyValue(PSYMBOL_INFO pSym, void *pVariable)
{
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;
}
// display contents and type of the given variable
/* static */ wxString
wxCrashReportImpl::FormatSymbol(PSYMBOL_INFO pSym, STACKFRAME *sf)
{
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;
}
void
wxCrashReportImpl::OutputSymbol(PSYMBOL_INFO pSymInfo, STACKFRAME *sf)
{
wxString s = FormatSymbol(pSymInfo, sf);
if ( !s.empty() )
{
Output(_T("%s\r\n"), s.c_str());
}
//else: not an interesting symbol
}
// callback for SymEnumSymbols()
/* static */
BOOL CALLBACK
wxCrashReportImpl::EnumerateSymbolsCallback(PSYMBOL_INFO pSymInfo,
ULONG WXUNUSED(SymbolSize),
PVOID UserContext)
{
wxCrashReportImpl *self = (wxCrashReportImpl *)UserContext;
__try
{
self->OutputSymbol(pSymInfo, self->m_sfCurrent);
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
self->Output(_T("Can't process symbol %hs\r\n"), pSymInfo->Name);
}
// 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);
}
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
bool wxCrashReportImpl::BindDbgHelpFunctions(const wxDynamicLibrary& dllDbgHelp)
{
#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;
}
#if !wxUSE_MINIDUMP
/* static */
wxString wxCrashReportImpl::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;
}
#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