]> git.saurik.com Git - wxWidgets.git/blob - src/msw/seh.cpp
1e5e336659260e476b2dfc8bba3f853e2c892afe
[wxWidgets.git] / src / msw / seh.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/seh.cpp
3 // Purpose: helpers for structured exception handling (SEH)
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 13.07.03
7 // RCS-ID: $Id$
8 // Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 /*
13 The code in this file is heavily based on Matt Pietrek's column from
14 the 2002 issue of MSDN Magazine.
15 */
16
17 // ============================================================================
18 // declarations
19 // ============================================================================
20
21 // ----------------------------------------------------------------------------
22 // headers
23 // ----------------------------------------------------------------------------
24
25 // For compilers that support precompilation, includes "wx.h".
26 #include "wx/wxprec.h"
27
28 #ifdef __BORLANDC__
29 #pragma hdrstop
30 #endif
31
32 #if wxUSE_ON_FATAL_EXCEPTION
33
34 #ifndef WX_PRECOMP
35 #endif //WX_PRECOMP
36
37 #include "wx/datetime.h"
38 #include "wx/dynload.h"
39
40 #include "wx/msw/seh.h"
41
42 #include <windows.h>
43 #include <imagehlp.h>
44 #include "wx/msw/private.h"
45
46 // ----------------------------------------------------------------------------
47 // types of imagehlp.h functions
48 // ----------------------------------------------------------------------------
49
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);
62
63 // ----------------------------------------------------------------------------
64 // classes
65 // ----------------------------------------------------------------------------
66
67 // the real crash report generator
68 class wxSEHReportImpl
69 {
70 public:
71 wxSEHReportImpl(const wxChar *filename);
72
73 bool Generate();
74
75 ~wxSEHReportImpl()
76 {
77 if ( m_hFile != INVALID_HANDLE_VALUE )
78 {
79 ::CloseHandle(m_hFile);
80 }
81 }
82
83 private:
84 // formatted output to m_hFile
85 void Output(const wxChar *format, ...);
86
87 // translate exception code to its symbolic name
88 static wxString GetExceptionString(DWORD dwCode);
89
90 // load all the functions we need from dbghelp.dll, return true if all ok
91 bool ResolveSymFunctions(const wxDynamicLibrary& dllDbgHelp);
92
93 // map address to module (and also section:offset), retunr true if ok
94 static bool GetLogicalAddress(PVOID addr,
95 PTSTR szModule,
96 DWORD len,
97 DWORD& section,
98 DWORD& offset);
99
100
101 // show the general information about exception which should be always
102 // available
103 bool OutputBasicContext(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pCtx);
104
105 // output the call stack (pCtx may be modified, make copy before call!)
106 void OutputStack(CONTEXT *pCtx);
107
108
109 // the handle of the report file
110 HANDLE m_hFile;
111
112 // dynamically loaded dbghelp.dll functions
113 #define DECLARE_SYM_FUNCTION(func) func ## _t func
114
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);
122 };
123
124 // ----------------------------------------------------------------------------
125 // globals
126 // ----------------------------------------------------------------------------
127
128 // global pointer to exception information, only valid inside OnFatalException
129 extern WXDLLIMPEXP_BASE EXCEPTION_POINTERS *wxGlobalSEInformation = NULL;
130
131
132 // flag telling us whether the application wants to handle exceptions at all
133 static bool gs_handleExceptions = false;
134
135 // the file name where the report about exception is written
136 static wxChar gs_reportFilename[MAX_PATH];
137
138 // ============================================================================
139 // implementation
140 // ============================================================================
141
142 // ----------------------------------------------------------------------------
143 // wxSEHReport
144 // ----------------------------------------------------------------------------
145
146 wxSEHReportImpl::wxSEHReportImpl(const wxChar *filename)
147 {
148 m_hFile = ::CreateFile
149 (
150 filename,
151 GENERIC_WRITE,
152 0, // no sharing
153 NULL, // default security
154 CREATE_ALWAYS,
155 FILE_FLAG_WRITE_THROUGH,
156 NULL // no template file
157 );
158 }
159
160 void wxSEHReportImpl::Output(const wxChar *format, ...)
161 {
162 va_list argptr;
163 va_start(argptr, format);
164
165 DWORD cbWritten;
166
167 wxString s = wxString::FormatV(format, argptr);
168 ::WriteFile(m_hFile, s, s.length() * sizeof(wxChar), &cbWritten, 0);
169
170 va_end(argptr);
171 }
172
173 bool
174 wxSEHReportImpl::GetLogicalAddress(PVOID addr,
175 PTSTR szModule,
176 DWORD len,
177 DWORD& section,
178 DWORD& offset)
179 {
180 MEMORY_BASIC_INFORMATION mbi;
181
182 if ( !::VirtualQuery(addr, &mbi, sizeof(mbi)) )
183 return false;
184
185 DWORD hMod = (DWORD)mbi.AllocationBase;
186
187 if ( !::GetModuleFileName((HMODULE)hMod, szModule, len) )
188 return false;
189
190 // Point to the DOS header in memory
191 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
192
193 // From the DOS header, find the NT (PE) header
194 PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);
195
196 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
197
198 DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address
199
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++ )
204 {
205 DWORD sectionStart = pSection->VirtualAddress;
206 DWORD sectionEnd = sectionStart
207 + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);
208
209 // Is the address in this section?
210 if ( (rva >= sectionStart) && (rva <= sectionEnd) )
211 {
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.
215 section = i + 1;
216 offset = rva - sectionStart;
217
218 return true;
219 }
220 }
221
222 // failed to map to logical address...
223 return false;
224 }
225
226 bool
227 wxSEHReportImpl::OutputBasicContext(EXCEPTION_RECORD *pExceptionRecord,
228 CONTEXT *pCtx)
229 {
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);
234
235 // Now print information about where the fault occured
236 TCHAR szFaultingModule[MAX_PATH];
237 DWORD section,
238 offset;
239 void * const pExceptionAddress = pExceptionRecord->ExceptionAddress;
240 if ( !GetLogicalAddress(pExceptionAddress,
241 szFaultingModule,
242 WXSIZEOF(szFaultingModule),
243 section, offset) )
244 {
245 section =
246 offset = 0;
247
248 wxStrcpy(szFaultingModule, _T("<< unknown >>"));
249 }
250
251 Output(_T("Fault address: %08x %02x:%08x %s\r\n"),
252 pExceptionAddress, section, offset, szFaultingModule);
253
254 // Show the registers
255 #ifdef _M_IX86
256 Output( _T("\r\nRegisters:\r\n") );
257
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);
260
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 );
266 #endif // _M_IX86
267
268 return true;
269 }
270
271 void wxSEHReportImpl::OutputStack(CONTEXT *pCtx)
272 {
273 Output(_T("\r\nCall stack:\r\n"));
274
275 Output(_T("Address Frame Function SourceFile\r\n"));
276
277 DWORD dwMachineType = 0;
278
279 STACKFRAME sf;
280 wxZeroMemory(sf);
281
282 #ifdef _M_IX86
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;
291
292 dwMachineType = IMAGE_FILE_MACHINE_I386;
293 #endif // _M_IX86
294
295 const HANDLE hProcess = GetCurrentProcess();
296 const HANDLE hThread = GetCurrentThread();
297
298 for ( ;; )
299 {
300 // Get the next stack frame
301 if ( !StackWalk(dwMachineType,
302 hProcess,
303 hThread,
304 &sf,
305 pCtx,
306 0,
307 SymFunctionTableAccess,
308 SymGetModuleBase,
309 0) )
310 {
311 break;
312 }
313
314 // Basic sanity check to make sure the frame is OK.
315 if ( !sf.AddrFrame.Offset )
316 break;
317
318 Output(_T("%08x %08x "), sf.AddrPC.Offset, sf.AddrFrame.Offset);
319
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
327
328 if ( SymFromAddr(hProcess, sf.AddrPC.Offset,
329 &symDisplacement,pSymbol) )
330 {
331 Output(_T("%hs() + %#08I64x"), pSymbol->Name, symDisplacement);
332 }
333 else // No symbol found. Print out the logical address instead.
334 {
335 TCHAR szModule[MAX_PATH];
336 DWORD section,
337 offset;
338
339 if ( !GetLogicalAddress((PVOID)sf.AddrPC.Offset,
340 szModule, sizeof(szModule),
341 section, offset) )
342 {
343 szModule[0] = _T('\0');
344 section =
345 offset = 0;
346 }
347
348 Output(_T("%04x:%08x %s"), section, offset, szModule);
349 }
350
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 ))
356 {
357 Output(_T(" %s line %u"), lineInfo.FileName, lineInfo.LineNumber);
358 }
359
360 Output(_T("\r\n"));
361 }
362 }
363
364 bool wxSEHReportImpl::ResolveSymFunctions(const wxDynamicLibrary& dllDbgHelp)
365 {
366 #define LOAD_SYM_FUNCTION(name) \
367 name = (name ## _t) dllDbgHelp.GetSymbol(#name); \
368 if ( !name ) \
369 { \
370 Output(_T("\r\nFunction ") __XFILE__(#name) \
371 _T("() not found.\r\n")); \
372 return false; \
373 }
374
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);
382
383 #undef LOAD_SYM_FUNCTION
384
385 return true;
386 }
387
388 bool wxSEHReportImpl::Generate()
389 {
390 if ( m_hFile == INVALID_HANDLE_VALUE )
391 return false;
392
393 PEXCEPTION_RECORD pExceptionRecord = wxGlobalSEInformation->ExceptionRecord;
394 PCONTEXT pCtx = wxGlobalSEInformation->ContextRecord;
395
396 if ( !OutputBasicContext(pExceptionRecord, pCtx) )
397 return false;
398
399 // for everything else we need dbghelp.dll
400 wxDynamicLibrary dllDbgHelp(_T("dbghelp.dll"), wxDL_VERBATIM);
401 if ( dllDbgHelp.IsLoaded() )
402 {
403 if ( ResolveSymFunctions(dllDbgHelp) )
404 {
405 SymSetOptions(SYMOPT_DEFERRED_LOADS);
406
407 // Initialize DbgHelp
408 if ( SymInitialize(GetCurrentProcess(), NULL, TRUE /* invade */) )
409 {
410 CONTEXT ctxCopy = *pCtx;
411
412 OutputStack(&ctxCopy);
413
414 return true;
415 }
416 }
417 else
418 {
419 Output(_T("Please update your dbghelp.dll version, "
420 "at least version 6.0 is needed!\r\n"));
421 }
422 }
423 else
424 {
425 Output(_T("Please install dbghelp.dll available free of charge ")
426 _T("from Microsoft to get more detailed crash information!"));
427 }
428
429 Output(_T("\r\nLatest dbghelp.dll is available at "
430 "http://www.microsoft.com/whdc/ddk/debugging/\r\n"));
431
432 return true;
433 }
434
435 /* static */
436 wxString wxSEHReportImpl::GetExceptionString(DWORD dwCode)
437 {
438 wxString s;
439
440 #define CASE_EXCEPTION( x ) case EXCEPTION_##x: s = _T(#x); break
441
442 switch ( dwCode )
443 {
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);
466
467 default:
468 // unknown exception, ask NTDLL for the name
469 if ( !::FormatMessage
470 (
471 FORMAT_MESSAGE_IGNORE_INSERTS |
472 FORMAT_MESSAGE_FROM_HMODULE,
473 ::GetModuleHandle(_T("NTDLL.DLL")),
474 dwCode,
475 0,
476 wxStringBuffer(s, 1024),
477 1024,
478 0
479 ) )
480 {
481 s = _T("UNKNOWN_EXCEPTION");
482 }
483 }
484
485 #undef CASE_EXCEPTION
486
487 return s;
488 }
489
490 // ----------------------------------------------------------------------------
491 // wxSEHReport
492 // ----------------------------------------------------------------------------
493
494 /* static */
495 void wxSEHReport::SetFileName(const wxChar *filename)
496 {
497 wxStrncpy(gs_reportFilename, filename, WXSIZEOF(gs_reportFilename) - 1);
498 gs_reportFilename[WXSIZEOF(gs_reportFilename) - 1] = _T('\0');
499 }
500
501 /* static */
502 const wxChar *wxSEHReport::GetFileName()
503 {
504 return gs_reportFilename;
505 }
506
507 /* static */
508 bool wxSEHReport::Generate()
509 {
510 wxSEHReportImpl impl(gs_reportFilename);
511
512 return impl.Generate();
513 }
514
515 // ----------------------------------------------------------------------------
516 // wxApp::OnFatalException() support
517 // ----------------------------------------------------------------------------
518
519 bool wxHandleFatalExceptions(bool doit)
520 {
521 // assume this can only be called from the main thread
522 gs_handleExceptions = doit;
523
524 if ( doit )
525 {
526 // try to find a place where we can put out report file later
527 if ( !::GetTempPath
528 (
529 WXSIZEOF(gs_reportFilename),
530 gs_reportFilename
531 ) )
532 {
533 wxLogLastError(_T("GetTempPath"));
534
535 // when all else fails...
536 wxStrcpy(gs_reportFilename, _T("c:\\"));
537 }
538
539 // use PID and date to make the report file name more unique
540 wxString fname = wxString::Format
541 (
542 _T("%s_%s_%lu.rpt"),
543 wxTheApp ? wxTheApp->GetAppName().c_str()
544 : _T("wxwindows"),
545 wxDateTime::Now().Format(_T("%Y%m%d")).c_str(),
546 ::GetCurrentProcessId()
547 );
548
549 wxStrncat(gs_reportFilename, fname,
550 WXSIZEOF(gs_reportFilename) - strlen(gs_reportFilename) - 1);
551 }
552
553 return true;
554 }
555
556 extern unsigned long wxGlobalSEHandler(EXCEPTION_POINTERS *pExcPtrs)
557 {
558 if ( gs_handleExceptions && wxTheApp )
559 {
560 // store the pointer to exception info
561 wxGlobalSEInformation = pExcPtrs;
562
563 // give the user a chance to do something special about this
564 wxTheApp->OnFatalException();
565
566 wxGlobalSEInformation = NULL;
567
568 // this will execute our handler and terminate the process
569 return EXCEPTION_EXECUTE_HANDLER;
570 }
571
572 return EXCEPTION_CONTINUE_SEARCH;
573 }
574
575 #else // !wxUSE_ON_FATAL_EXCEPTION
576
577 bool wxHandleFatalExceptions(bool WXUNUSED(doit))
578 {
579 wxFAIL_MSG(_T("set wxUSE_ON_FATAL_EXCEPTION to 1 to use this function"));
580
581 return false;
582 }
583
584 #endif // wxUSE_ON_FATAL_EXCEPTION/!wxUSE_ON_FATAL_EXCEPTION
585