1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: helpers for structured exception handling (SEH)
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
13 The code in this file is heavily based on Matt Pietrek's column from
14 the 2002 issue of MSDN Magazine.
17 // ============================================================================
19 // ============================================================================
21 // ----------------------------------------------------------------------------
23 // ----------------------------------------------------------------------------
25 // For compilers that support precompilation, includes "wx.h".
26 #include "wx/wxprec.h"
32 #if wxUSE_ON_FATAL_EXCEPTION
37 #include "wx/datetime.h"
38 #include "wx/dynload.h"
40 #include "wx/msw/seh.h"
44 #include "wx/msw/private.h"
46 // ----------------------------------------------------------------------------
47 // types of imagehlp.h functions
48 // ----------------------------------------------------------------------------
50 typedef DWORD (WINAPI
*SymSetOptions_t
)(DWORD
);
51 typedef BOOL (WINAPI
*SymInitialize_t
)(HANDLE
, LPSTR
, BOOL
);
52 typedef BOOL (WINAPI
*StackWalk_t
)(DWORD
, HANDLE
, HANDLE
, LPSTACKFRAME
,
53 LPVOID
, PREAD_PROCESS_MEMORY_ROUTINE
,
54 PFUNCTION_TABLE_ACCESS_ROUTINE
,
55 PGET_MODULE_BASE_ROUTINE
,
56 PTRANSLATE_ADDRESS_ROUTINE
);
57 typedef BOOL (WINAPI
*SymFromAddr_t
)(HANDLE
, DWORD64
, PDWORD64
, PSYMBOL_INFO
);
58 typedef LPVOID (WINAPI
*SymFunctionTableAccess_t
)(HANDLE
, DWORD
);
59 typedef DWORD (WINAPI
*SymGetModuleBase_t
)(HANDLE
, DWORD
);
60 typedef BOOL (WINAPI
*SymGetLineFromAddr_t
)(HANDLE
, DWORD
,
61 PDWORD
, PIMAGEHLP_LINE
);
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
67 // the real crash report generator
71 wxSEHReportImpl(const wxChar
*filename
);
77 if ( m_hFile
!= INVALID_HANDLE_VALUE
)
79 ::CloseHandle(m_hFile
);
84 // formatted output to m_hFile
85 void Output(const wxChar
*format
, ...);
87 // translate exception code to its symbolic name
88 static wxString
GetExceptionString(DWORD dwCode
);
90 // load all the functions we need from dbghelp.dll, return true if all ok
91 bool ResolveSymFunctions(const wxDynamicLibrary
& dllDbgHelp
);
93 // map address to module (and also section:offset), retunr true if ok
94 static bool GetLogicalAddress(PVOID addr
,
101 // show the general information about exception which should be always
103 bool OutputBasicContext(EXCEPTION_RECORD
*pExceptionRecord
, CONTEXT
*pCtx
);
105 // output the call stack (pCtx may be modified, make copy before call!)
106 void OutputStack(CONTEXT
*pCtx
);
109 // the handle of the report file
112 // dynamically loaded dbghelp.dll functions
113 #define DECLARE_SYM_FUNCTION(func) func ## _t func
115 DECLARE_SYM_FUNCTION(SymSetOptions
);
116 DECLARE_SYM_FUNCTION(SymInitialize
);
117 DECLARE_SYM_FUNCTION(StackWalk
);
118 DECLARE_SYM_FUNCTION(SymFromAddr
);
119 DECLARE_SYM_FUNCTION(SymFunctionTableAccess
);
120 DECLARE_SYM_FUNCTION(SymGetModuleBase
);
121 DECLARE_SYM_FUNCTION(SymGetLineFromAddr
);
124 // ----------------------------------------------------------------------------
126 // ----------------------------------------------------------------------------
128 // global pointer to exception information, only valid inside OnFatalException
129 extern WXDLLIMPEXP_BASE EXCEPTION_POINTERS
*wxGlobalSEInformation
= NULL
;
132 // flag telling us whether the application wants to handle exceptions at all
133 static bool gs_handleExceptions
= false;
135 // the file name where the report about exception is written
136 static wxChar gs_reportFilename
[MAX_PATH
];
138 // ============================================================================
140 // ============================================================================
142 // ----------------------------------------------------------------------------
144 // ----------------------------------------------------------------------------
146 wxSEHReportImpl::wxSEHReportImpl(const wxChar
*filename
)
148 m_hFile
= ::CreateFile
153 NULL
, // default security
155 FILE_FLAG_WRITE_THROUGH
,
156 NULL
// no template file
160 void wxSEHReportImpl::Output(const wxChar
*format
, ...)
163 va_start(argptr
, format
);
167 wxString s
= wxString::FormatV(format
, argptr
);
168 ::WriteFile(m_hFile
, s
, s
.length() * sizeof(wxChar
), &cbWritten
, 0);
174 wxSEHReportImpl::GetLogicalAddress(PVOID addr
,
180 MEMORY_BASIC_INFORMATION mbi
;
182 if ( !::VirtualQuery(addr
, &mbi
, sizeof(mbi
)) )
185 DWORD hMod
= (DWORD
)mbi
.AllocationBase
;
187 if ( !::GetModuleFileName((HMODULE
)hMod
, szModule
, len
) )
190 // Point to the DOS header in memory
191 PIMAGE_DOS_HEADER pDosHdr
= (PIMAGE_DOS_HEADER
)hMod
;
193 // From the DOS header, find the NT (PE) header
194 PIMAGE_NT_HEADERS pNtHdr
= (PIMAGE_NT_HEADERS
)(hMod
+ pDosHdr
->e_lfanew
);
196 PIMAGE_SECTION_HEADER pSection
= IMAGE_FIRST_SECTION( pNtHdr
);
198 DWORD rva
= (DWORD
)addr
- hMod
; // RVA is offset from module load address
200 // Iterate through the section table, looking for the one that encompasses
201 // the linear address.
202 const DWORD nSections
= pNtHdr
->FileHeader
.NumberOfSections
;
203 for ( DWORD i
= 0; i
< nSections
; i
++, pSection
++ )
205 DWORD sectionStart
= pSection
->VirtualAddress
;
206 DWORD sectionEnd
= sectionStart
207 + max(pSection
->SizeOfRawData
, pSection
->Misc
.VirtualSize
);
209 // Is the address in this section?
210 if ( (rva
>= sectionStart
) && (rva
<= sectionEnd
) )
212 // Yes, address is in the section. Calculate section and offset,
213 // and store in the "section" & "offset" params, which were
214 // passed by reference.
216 offset
= rva
- sectionStart
;
222 // failed to map to logical address...
227 wxSEHReportImpl::OutputBasicContext(EXCEPTION_RECORD
*pExceptionRecord
,
230 // First print information about the type of fault
231 const DWORD dwCode
= pExceptionRecord
->ExceptionCode
;
232 Output(_T("Exception code: %s (%#08x)\r\n"),
233 GetExceptionString(dwCode
).c_str(), dwCode
);
235 // Now print information about where the fault occured
236 TCHAR szFaultingModule
[MAX_PATH
];
239 void * const pExceptionAddress
= pExceptionRecord
->ExceptionAddress
;
240 if ( !GetLogicalAddress(pExceptionAddress
,
242 WXSIZEOF(szFaultingModule
),
248 wxStrcpy(szFaultingModule
, _T("<< unknown >>"));
251 Output(_T("Fault address: %08x %02x:%08x %s\r\n"),
252 pExceptionAddress
, section
, offset
, szFaultingModule
);
254 // Show the registers
256 Output( _T("\r\nRegisters:\r\n") );
258 Output(_T("EAX: %08x EBX: %08x ECX: %08x EDX: %08x ESI: %08x EDI: %08x\r\n"),
259 pCtx
->Eax
, pCtx
->Ebx
, pCtx
->Ecx
, pCtx
->Edx
, pCtx
->Esi
, pCtx
->Edi
);
261 Output(_T("CS:EIP: %04x:%08x SS:ESP: %04x:%08x EBP: %08x\r\n"),
262 pCtx
->SegCs
, pCtx
->Eip
, pCtx
->SegSs
, pCtx
->Esp
, pCtx
->Ebp
);
263 Output(_T("DS: %04x ES: %04x FS: %04x GS: %04x\r\n"),
264 pCtx
->SegDs
, pCtx
->SegEs
, pCtx
->SegFs
, pCtx
->SegGs
);
265 Output(_T("Flags: %08x\r\n"), pCtx
->EFlags
);
271 void wxSEHReportImpl::OutputStack(CONTEXT
*pCtx
)
273 Output(_T("\r\nCall stack:\r\n"));
275 Output(_T("Address Frame Function SourceFile\r\n"));
277 DWORD dwMachineType
= 0;
283 // Initialize the STACKFRAME structure for the first call. This is only
284 // necessary for Intel CPUs, and isn't mentioned in the documentation.
285 sf
.AddrPC
.Offset
= pCtx
->Eip
;
286 sf
.AddrPC
.Mode
= AddrModeFlat
;
287 sf
.AddrStack
.Offset
= pCtx
->Esp
;
288 sf
.AddrStack
.Mode
= AddrModeFlat
;
289 sf
.AddrFrame
.Offset
= pCtx
->Ebp
;
290 sf
.AddrFrame
.Mode
= AddrModeFlat
;
292 dwMachineType
= IMAGE_FILE_MACHINE_I386
;
295 const HANDLE hProcess
= GetCurrentProcess();
296 const HANDLE hThread
= GetCurrentThread();
300 // Get the next stack frame
301 if ( !StackWalk(dwMachineType
,
307 SymFunctionTableAccess
,
314 // Basic sanity check to make sure the frame is OK.
315 if ( !sf
.AddrFrame
.Offset
)
318 Output(_T("%08x %08x "), sf
.AddrPC
.Offset
, sf
.AddrFrame
.Offset
);
320 // Get the name of the function for this stack frame entry
321 BYTE symbolBuffer
[ sizeof(SYMBOL_INFO
) + 1024 ];
322 PSYMBOL_INFO pSymbol
= (PSYMBOL_INFO
)symbolBuffer
;
323 pSymbol
->SizeOfStruct
= sizeof(symbolBuffer
);
324 pSymbol
->MaxNameLen
= 1024;
325 DWORD64 symDisplacement
= 0; // Displacement of the input address,
326 // relative to the start of the symbol
328 if ( SymFromAddr(hProcess
, sf
.AddrPC
.Offset
,
329 &symDisplacement
,pSymbol
) )
331 Output(_T("%hs() + %#08I64x"), pSymbol
->Name
, symDisplacement
);
333 else // No symbol found. Print out the logical address instead.
335 TCHAR szModule
[MAX_PATH
];
339 if ( !GetLogicalAddress((PVOID
)sf
.AddrPC
.Offset
,
340 szModule
, sizeof(szModule
),
343 szModule
[0] = _T('\0');
348 Output(_T("%04x:%08x %s"), section
, offset
, szModule
);
351 // Get the source line for this stack frame entry
352 IMAGEHLP_LINE lineInfo
= { sizeof(IMAGEHLP_LINE
) };
353 DWORD dwLineDisplacement
;
354 if ( SymGetLineFromAddr(hProcess
, sf
.AddrPC
.Offset
,
355 &dwLineDisplacement
, &lineInfo
))
357 Output(_T(" %s line %u"), lineInfo
.FileName
, lineInfo
.LineNumber
);
364 bool wxSEHReportImpl::ResolveSymFunctions(const wxDynamicLibrary
& dllDbgHelp
)
366 #define LOAD_SYM_FUNCTION(name) \
367 name = (name ## _t) dllDbgHelp.GetSymbol(#name); \
370 Output(_T("\r\nFunction ") __XFILE__(#name) \
371 _T("() not found.\r\n")); \
375 LOAD_SYM_FUNCTION(SymSetOptions
);
376 LOAD_SYM_FUNCTION(SymInitialize
);
377 LOAD_SYM_FUNCTION(StackWalk
);
378 LOAD_SYM_FUNCTION(SymFromAddr
);
379 LOAD_SYM_FUNCTION(SymFunctionTableAccess
);
380 LOAD_SYM_FUNCTION(SymGetModuleBase
);
381 LOAD_SYM_FUNCTION(SymGetLineFromAddr
);
383 #undef LOAD_SYM_FUNCTION
388 bool wxSEHReportImpl::Generate()
390 if ( m_hFile
== INVALID_HANDLE_VALUE
)
393 PEXCEPTION_RECORD pExceptionRecord
= wxGlobalSEInformation
->ExceptionRecord
;
394 PCONTEXT pCtx
= wxGlobalSEInformation
->ContextRecord
;
396 if ( !OutputBasicContext(pExceptionRecord
, pCtx
) )
399 // for everything else we need dbghelp.dll
400 wxDynamicLibrary
dllDbgHelp(_T("dbghelp.dll"), wxDL_VERBATIM
);
401 if ( dllDbgHelp
.IsLoaded() )
403 if ( ResolveSymFunctions(dllDbgHelp
) )
405 SymSetOptions(SYMOPT_DEFERRED_LOADS
);
407 // Initialize DbgHelp
408 if ( SymInitialize(GetCurrentProcess(), NULL
, TRUE
/* invade */) )
410 CONTEXT ctxCopy
= *pCtx
;
412 OutputStack(&ctxCopy
);
419 Output(_T("Please update your dbghelp.dll version, "
420 "at least version 6.0 is needed!\r\n"));
425 Output(_T("Please install dbghelp.dll available free of charge ")
426 _T("from Microsoft to get more detailed crash information!"));
429 Output(_T("\r\nLatest dbghelp.dll is available at "
430 "http://www.microsoft.com/whdc/ddk/debugging/\r\n"));
436 wxString
wxSEHReportImpl::GetExceptionString(DWORD dwCode
)
440 #define CASE_EXCEPTION( x ) case EXCEPTION_##x: s = _T(#x); break
444 CASE_EXCEPTION(ACCESS_VIOLATION
);
445 CASE_EXCEPTION(DATATYPE_MISALIGNMENT
);
446 CASE_EXCEPTION(BREAKPOINT
);
447 CASE_EXCEPTION(SINGLE_STEP
);
448 CASE_EXCEPTION(ARRAY_BOUNDS_EXCEEDED
);
449 CASE_EXCEPTION(FLT_DENORMAL_OPERAND
);
450 CASE_EXCEPTION(FLT_DIVIDE_BY_ZERO
);
451 CASE_EXCEPTION(FLT_INEXACT_RESULT
);
452 CASE_EXCEPTION(FLT_INVALID_OPERATION
);
453 CASE_EXCEPTION(FLT_OVERFLOW
);
454 CASE_EXCEPTION(FLT_STACK_CHECK
);
455 CASE_EXCEPTION(FLT_UNDERFLOW
);
456 CASE_EXCEPTION(INT_DIVIDE_BY_ZERO
);
457 CASE_EXCEPTION(INT_OVERFLOW
);
458 CASE_EXCEPTION(PRIV_INSTRUCTION
);
459 CASE_EXCEPTION(IN_PAGE_ERROR
);
460 CASE_EXCEPTION(ILLEGAL_INSTRUCTION
);
461 CASE_EXCEPTION(NONCONTINUABLE_EXCEPTION
);
462 CASE_EXCEPTION(STACK_OVERFLOW
);
463 CASE_EXCEPTION(INVALID_DISPOSITION
);
464 CASE_EXCEPTION(GUARD_PAGE
);
465 CASE_EXCEPTION(INVALID_HANDLE
);
468 // unknown exception, ask NTDLL for the name
469 if ( !::FormatMessage
471 FORMAT_MESSAGE_IGNORE_INSERTS
|
472 FORMAT_MESSAGE_FROM_HMODULE
,
473 ::GetModuleHandle(_T("NTDLL.DLL")),
476 wxStringBuffer(s
, 1024),
481 s
= _T("UNKNOWN_EXCEPTION");
485 #undef CASE_EXCEPTION
490 // ----------------------------------------------------------------------------
492 // ----------------------------------------------------------------------------
495 void wxSEHReport::SetFileName(const wxChar
*filename
)
497 wxStrncpy(gs_reportFilename
, filename
, WXSIZEOF(gs_reportFilename
) - 1);
498 gs_reportFilename
[WXSIZEOF(gs_reportFilename
) - 1] = _T('\0');
502 const wxChar
*wxSEHReport::GetFileName()
504 return gs_reportFilename
;
508 bool wxSEHReport::Generate()
510 wxSEHReportImpl
impl(gs_reportFilename
);
512 return impl
.Generate();
515 // ----------------------------------------------------------------------------
516 // wxApp::OnFatalException() support
517 // ----------------------------------------------------------------------------
519 bool wxHandleFatalExceptions(bool doit
)
521 // assume this can only be called from the main thread
522 gs_handleExceptions
= doit
;
526 // try to find a place where we can put out report file later
529 WXSIZEOF(gs_reportFilename
),
533 wxLogLastError(_T("GetTempPath"));
535 // when all else fails...
536 wxStrcpy(gs_reportFilename
, _T("c:\\"));
539 // use PID and date to make the report file name more unique
540 wxString fname
= wxString::Format
543 wxTheApp
? wxTheApp
->GetAppName().c_str()
545 wxDateTime::Now().Format(_T("%Y%m%d")).c_str(),
546 ::GetCurrentProcessId()
549 wxStrncat(gs_reportFilename
, fname
,
550 WXSIZEOF(gs_reportFilename
) - strlen(gs_reportFilename
) - 1);
556 extern unsigned long wxGlobalSEHandler(EXCEPTION_POINTERS
*pExcPtrs
)
558 if ( gs_handleExceptions
&& wxTheApp
)
560 // store the pointer to exception info
561 wxGlobalSEInformation
= pExcPtrs
;
563 // give the user a chance to do something special about this
564 wxTheApp
->OnFatalException();
566 wxGlobalSEInformation
= NULL
;
568 // this will execute our handler and terminate the process
569 return EXCEPTION_EXECUTE_HANDLER
;
572 return EXCEPTION_CONTINUE_SEARCH
;
575 #else // !wxUSE_ON_FATAL_EXCEPTION
577 bool wxHandleFatalExceptions(bool WXUNUSED(doit
))
579 wxFAIL_MSG(_T("set wxUSE_ON_FATAL_EXCEPTION to 1 to use this function"));
584 #endif // wxUSE_ON_FATAL_EXCEPTION/!wxUSE_ON_FATAL_EXCEPTION