]> git.saurik.com Git - wxWidgets.git/blob - src/msw/debughlp.cpp
Fix wxSTC compilation without wxUSE_DRAG_AND_DROP after r65827.
[wxWidgets.git] / src / msw / debughlp.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/debughlp.cpp
3 // Purpose: various Win32 debug helpers
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 2005-01-08 (extracted from crashrpt.cpp)
7 // RCS-ID: $Id$
8 // Copyright: (c) 2003-2005 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #include "wx/msw/debughlp.h"
27
28 #if wxUSE_DBGHELP && wxUSE_DYNLIB_CLASS
29
30 // ----------------------------------------------------------------------------
31 // constants
32 // ----------------------------------------------------------------------------
33
34 // to prevent recursion which could result from corrupted data we limit
35 // ourselves to that many levels of embedded fields inside structs
36 static const unsigned MAX_DUMP_DEPTH = 20;
37
38 // ----------------------------------------------------------------------------
39 // globals
40 // ----------------------------------------------------------------------------
41
42 // error message from Init()
43 static wxString gs_errMsg;
44
45 // ============================================================================
46 // wxDbgHelpDLL implementation
47 // ============================================================================
48
49 // ----------------------------------------------------------------------------
50 // static members
51 // ----------------------------------------------------------------------------
52
53 #define DEFINE_SYM_FUNCTION(func) wxDbgHelpDLL::func ## _t wxDbgHelpDLL::func = 0
54
55 wxDO_FOR_ALL_SYM_FUNCS(DEFINE_SYM_FUNCTION);
56
57 #undef DEFINE_SYM_FUNCTION
58
59 // ----------------------------------------------------------------------------
60 // initialization methods
61 // ----------------------------------------------------------------------------
62
63 // load all function we need from the DLL
64
65 static bool BindDbgHelpFunctions(const wxDynamicLibrary& dllDbgHelp)
66 {
67 #define LOAD_SYM_FUNCTION(name) \
68 wxDbgHelpDLL::name = (wxDbgHelpDLL::name ## _t) \
69 dllDbgHelp.GetSymbol(wxT(#name)); \
70 if ( !wxDbgHelpDLL::name ) \
71 { \
72 gs_errMsg += wxT("Function ") wxT(#name) wxT("() not found.\n"); \
73 return false; \
74 }
75
76 wxDO_FOR_ALL_SYM_FUNCS(LOAD_SYM_FUNCTION);
77
78 #undef LOAD_SYM_FUNCTION
79
80 return true;
81 }
82
83 // called by Init() if we hadn't done this before
84 static bool DoInit()
85 {
86 wxDynamicLibrary dllDbgHelp(wxT("dbghelp.dll"), wxDL_VERBATIM);
87 if ( dllDbgHelp.IsLoaded() )
88 {
89 if ( BindDbgHelpFunctions(dllDbgHelp) )
90 {
91 // turn on default options
92 DWORD options = wxDbgHelpDLL::SymGetOptions();
93
94 options |= SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_DEBUG;
95
96 wxDbgHelpDLL::SymSetOptions(options);
97
98 dllDbgHelp.Detach();
99 return true;
100 }
101
102 gs_errMsg += wxT("\nPlease update your dbghelp.dll version, ")
103 wxT("at least version 5.1 is needed!\n")
104 wxT("(if you already have a new version, please ")
105 wxT("put it in the same directory where the program is.)\n");
106 }
107 else // failed to load dbghelp.dll
108 {
109 gs_errMsg += wxT("Please install dbghelp.dll available free of charge ")
110 wxT("from Microsoft to get more detailed crash information!");
111 }
112
113 gs_errMsg += wxT("\nLatest dbghelp.dll is available at ")
114 wxT("http://www.microsoft.com/whdc/ddk/debugging/\n");
115
116 return false;
117 }
118
119 /* static */
120 bool wxDbgHelpDLL::Init()
121 {
122 // this flag is -1 until Init() is called for the first time, then it's set
123 // to either false or true depending on whether we could load the functions
124 static int s_loaded = -1;
125
126 if ( s_loaded == -1 )
127 {
128 s_loaded = DoInit();
129 }
130
131 return s_loaded != 0;
132 }
133
134 // ----------------------------------------------------------------------------
135 // error handling
136 // ----------------------------------------------------------------------------
137
138 /* static */
139 const wxString& wxDbgHelpDLL::GetErrorMessage()
140 {
141 return gs_errMsg;
142 }
143
144 /* static */
145 void wxDbgHelpDLL::LogError(const wxChar *func)
146 {
147 ::OutputDebugString(wxString::Format(wxT("dbghelp: %s() failed: %s\r\n"),
148 func, wxSysErrorMsg(::GetLastError())).wx_str());
149 }
150
151 // ----------------------------------------------------------------------------
152 // data dumping
153 // ----------------------------------------------------------------------------
154
155 static inline
156 bool
157 DoGetTypeInfo(DWORD64 base, ULONG ti, IMAGEHLP_SYMBOL_TYPE_INFO type, void *rc)
158 {
159 static HANDLE s_hProcess = ::GetCurrentProcess();
160
161 return wxDbgHelpDLL::SymGetTypeInfo
162 (
163 s_hProcess,
164 base,
165 ti,
166 type,
167 rc
168 ) != 0;
169 }
170
171 static inline
172 bool
173 DoGetTypeInfo(PSYMBOL_INFO pSym, IMAGEHLP_SYMBOL_TYPE_INFO type, void *rc)
174 {
175 return DoGetTypeInfo(pSym->ModBase, pSym->TypeIndex, type, rc);
176 }
177
178 static inline
179 wxDbgHelpDLL::BasicType GetBasicType(PSYMBOL_INFO pSym)
180 {
181 wxDbgHelpDLL::BasicType bt;
182 return DoGetTypeInfo(pSym, TI_GET_BASETYPE, &bt)
183 ? bt
184 : wxDbgHelpDLL::BASICTYPE_NOTYPE;
185 }
186
187 /* static */
188 wxString wxDbgHelpDLL::GetSymbolName(PSYMBOL_INFO pSym)
189 {
190 wxString s;
191
192 WCHAR *pwszTypeName;
193 if ( SymGetTypeInfo
194 (
195 GetCurrentProcess(),
196 pSym->ModBase,
197 pSym->TypeIndex,
198 TI_GET_SYMNAME,
199 &pwszTypeName
200 ) )
201 {
202 s = wxConvCurrent->cWC2WX(pwszTypeName);
203
204 ::LocalFree(pwszTypeName);
205 }
206
207 return s;
208 }
209
210 /* static */ wxString
211 wxDbgHelpDLL::DumpBaseType(BasicType bt, DWORD64 length, PVOID pAddress)
212 {
213 if ( !pAddress )
214 {
215 return wxT("null");
216 }
217
218 if ( ::IsBadReadPtr(pAddress, length) != 0 )
219 {
220 return wxT("BAD");
221 }
222
223
224 wxString s;
225 s.reserve(256);
226
227 if ( length == 1 )
228 {
229 const BYTE b = *(PBYTE)pAddress;
230
231 if ( bt == BASICTYPE_BOOL )
232 s = b ? wxT("true") : wxT("false");
233 else
234 s.Printf(wxT("%#04x"), b);
235 }
236 else if ( length == 2 )
237 {
238 s.Printf(bt == BASICTYPE_UINT ? wxT("%#06x") : wxT("%d"),
239 *(PWORD)pAddress);
240 }
241 else if ( length == 4 )
242 {
243 bool handled = false;
244
245 if ( bt == BASICTYPE_FLOAT )
246 {
247 s.Printf(wxT("%f"), *(PFLOAT)pAddress);
248
249 handled = true;
250 }
251 else if ( bt == BASICTYPE_CHAR )
252 {
253 // don't take more than 32 characters of a string
254 static const size_t NUM_CHARS = 64;
255
256 const char *pc = *(PSTR *)pAddress;
257 if ( ::IsBadStringPtrA(pc, NUM_CHARS) == 0 )
258 {
259 s += wxT('"');
260 for ( size_t n = 0; n < NUM_CHARS && *pc; n++, pc++ )
261 {
262 s += *pc;
263 }
264 s += wxT('"');
265
266 handled = true;
267 }
268 }
269
270 if ( !handled )
271 {
272 // treat just as an opaque DWORD
273 s.Printf(wxT("%#x"), *(PDWORD)pAddress);
274 }
275 }
276 else if ( length == 8 )
277 {
278 if ( bt == BASICTYPE_FLOAT )
279 {
280 s.Printf(wxT("%lf"), *(double *)pAddress);
281 }
282 else // opaque 64 bit value
283 {
284 s.Printf("%#" wxLongLongFmtSpec "x", *(PDWORD *)pAddress);
285 }
286 }
287
288 return s;
289 }
290
291 wxString
292 wxDbgHelpDLL::DumpField(PSYMBOL_INFO pSym, void *pVariable, unsigned level)
293 {
294 wxString s;
295
296 // avoid infinite recursion
297 if ( level > MAX_DUMP_DEPTH )
298 {
299 return s;
300 }
301
302 SymbolTag tag = SYMBOL_TAG_NULL;
303 if ( !DoGetTypeInfo(pSym, TI_GET_SYMTAG, &tag) )
304 {
305 return s;
306 }
307
308 switch ( tag )
309 {
310 case SYMBOL_TAG_UDT:
311 case SYMBOL_TAG_BASE_CLASS:
312 s = DumpUDT(pSym, pVariable, level);
313 break;
314
315 case SYMBOL_TAG_DATA:
316 if ( !pVariable )
317 {
318 s = wxT("NULL");
319 }
320 else // valid location
321 {
322 wxDbgHelpDLL::DataKind kind;
323 if ( !DoGetTypeInfo(pSym, TI_GET_DATAKIND, &kind) ||
324 kind != DATA_MEMBER )
325 {
326 // maybe it's a static member? we're not interested in them...
327 break;
328 }
329
330 // get the offset of the child member, relative to its parent
331 DWORD ofs = 0;
332 if ( !DoGetTypeInfo(pSym, TI_GET_OFFSET, &ofs) )
333 break;
334
335 pVariable = (void *)((DWORD_PTR)pVariable + ofs);
336
337
338 // now pass to the type representing the type of this member
339 SYMBOL_INFO sym = *pSym;
340 if ( !DoGetTypeInfo(pSym, TI_GET_TYPEID, &sym.TypeIndex) )
341 break;
342
343 ULONG64 size;
344 DoGetTypeInfo(&sym, TI_GET_LENGTH, &size);
345
346 switch ( DereferenceSymbol(&sym, &pVariable) )
347 {
348 case SYMBOL_TAG_BASE_TYPE:
349 {
350 BasicType bt = GetBasicType(&sym);
351 if ( bt )
352 {
353 s = DumpBaseType(bt, size, pVariable);
354 }
355 }
356 break;
357
358 case SYMBOL_TAG_UDT:
359 case SYMBOL_TAG_BASE_CLASS:
360 s = DumpUDT(&sym, pVariable, level);
361 break;
362 }
363 }
364
365 if ( !s.empty() )
366 {
367 s = GetSymbolName(pSym) + wxT(" = ") + s;
368 }
369 break;
370 }
371
372 if ( !s.empty() )
373 {
374 s = wxString(wxT('\t'), level + 1) + s + wxT('\n');
375 }
376
377 return s;
378 }
379
380 /* static */ wxString
381 wxDbgHelpDLL::DumpUDT(PSYMBOL_INFO pSym, void *pVariable, unsigned level)
382 {
383 wxString s;
384
385 // we have to limit the depth of UDT dumping as otherwise we get in
386 // infinite loops trying to dump linked lists... 10 levels seems quite
387 // reasonable, full information is in minidump file anyhow
388 if ( level > 10 )
389 return s;
390
391 s.reserve(512);
392 s = GetSymbolName(pSym);
393
394 #if !wxUSE_STD_STRING
395 // special handling for ubiquitous wxString: although the code below works
396 // for it as well, it shows the wxStringBase class and takes 4 lines
397 // instead of only one as this branch
398 if ( s == wxT("wxString") )
399 {
400 wxString *ps = (wxString *)pVariable;
401
402 // we can't just dump wxString directly as it could be corrupted or
403 // invalid and it could also be locked for writing (i.e. if we're
404 // between GetWriteBuf() and UngetWriteBuf() calls) and assert when we
405 // try to access it contents using public methods, so instead use our
406 // knowledge of its internals
407 const wxChar *p = NULL;
408 if ( !::IsBadReadPtr(ps, sizeof(wxString)) )
409 {
410 p = ps->data();
411 wxStringData *data = (wxStringData *)p - 1;
412 if ( ::IsBadReadPtr(data, sizeof(wxStringData)) ||
413 ::IsBadReadPtr(p, sizeof(wxChar *)*data->nAllocLength) )
414 {
415 p = NULL; // don't touch this pointer with 10 feet pole
416 }
417 }
418
419 s << wxT("(\"") << (p ? p : wxT("???")) << wxT(")\"");
420 }
421 else // any other UDT
422 #endif // !wxUSE_STD_STRING
423 {
424 // Determine how many children this type has.
425 DWORD dwChildrenCount = 0;
426 DoGetTypeInfo(pSym, TI_GET_CHILDRENCOUNT, &dwChildrenCount);
427
428 // Prepare to get an array of "TypeIds", representing each of the children.
429 TI_FINDCHILDREN_PARAMS *children = (TI_FINDCHILDREN_PARAMS *)
430 malloc(sizeof(TI_FINDCHILDREN_PARAMS) +
431 (dwChildrenCount - 1)*sizeof(ULONG));
432 if ( !children )
433 return s;
434
435 children->Count = dwChildrenCount;
436 children->Start = 0;
437
438 // Get the array of TypeIds, one for each child type
439 if ( !DoGetTypeInfo(pSym, TI_FINDCHILDREN, children) )
440 {
441 free(children);
442 return s;
443 }
444
445 s << wxT(" {\n");
446
447 // Iterate through all children
448 SYMBOL_INFO sym;
449 wxZeroMemory(sym);
450 sym.ModBase = pSym->ModBase;
451 for ( unsigned i = 0; i < dwChildrenCount; i++ )
452 {
453 sym.TypeIndex = children->ChildId[i];
454
455 // children here are in lexicographic sense, i.e. we get all our nested
456 // classes and not only our member fields, but we can't get the values
457 // for the members of the nested classes, of course!
458 DWORD nested;
459 if ( DoGetTypeInfo(&sym, TI_GET_NESTED, &nested) && nested )
460 continue;
461
462 // avoid infinite recursion: this does seem to happen sometimes with
463 // complex typedefs...
464 if ( sym.TypeIndex == pSym->TypeIndex )
465 continue;
466
467 s += DumpField(&sym, pVariable, level + 1);
468 }
469
470 free(children);
471
472 s << wxString(wxT('\t'), level + 1) << wxT('}');
473 }
474
475 return s;
476 }
477
478 /* static */
479 wxDbgHelpDLL::SymbolTag
480 wxDbgHelpDLL::DereferenceSymbol(PSYMBOL_INFO pSym, void **ppData)
481 {
482 SymbolTag tag = SYMBOL_TAG_NULL;
483 for ( ;; )
484 {
485 if ( !DoGetTypeInfo(pSym, TI_GET_SYMTAG, &tag) )
486 break;
487
488 if ( tag != SYMBOL_TAG_POINTER_TYPE )
489 break;
490
491 ULONG tiNew;
492 if ( !DoGetTypeInfo(pSym, TI_GET_TYPEID, &tiNew) ||
493 tiNew == pSym->TypeIndex )
494 break;
495
496 pSym->TypeIndex = tiNew;
497
498 // remove one level of indirection except for the char strings: we want
499 // to dump "char *" and not a single "char" for them
500 if ( ppData && *ppData && GetBasicType(pSym) != BASICTYPE_CHAR )
501 {
502 DWORD_PTR *pData = (DWORD_PTR *)*ppData;
503
504 if ( ::IsBadReadPtr(pData, sizeof(DWORD_PTR *)) )
505 {
506 break;
507 }
508
509 *ppData = (void *)*pData;
510 }
511 }
512
513 return tag;
514 }
515
516 /* static */ wxString
517 wxDbgHelpDLL::DumpSymbol(PSYMBOL_INFO pSym, void *pVariable)
518 {
519 wxString s;
520 SYMBOL_INFO symDeref = *pSym;
521 switch ( DereferenceSymbol(&symDeref, &pVariable) )
522 {
523 case SYMBOL_TAG_UDT:
524 // show UDT recursively
525 s = DumpUDT(&symDeref, pVariable);
526 break;
527
528 case SYMBOL_TAG_BASE_TYPE:
529 // variable of simple type, show directly
530 BasicType bt = GetBasicType(&symDeref);
531 if ( bt )
532 {
533 s = DumpBaseType(bt, pSym->Size, pVariable);
534 }
535 break;
536 }
537
538 return s;
539 }
540
541 // ----------------------------------------------------------------------------
542 // debugging helpers
543 // ----------------------------------------------------------------------------
544
545 // this code is very useful when debugging debughlp.dll-related code but
546 // probably not worth having compiled in normally, please do not remove it!
547 #if 0 // ndef NDEBUG
548
549 static wxString TagString(wxDbgHelpDLL::SymbolTag tag)
550 {
551 static const wxChar *tags[] =
552 {
553 wxT("null"),
554 wxT("exe"),
555 wxT("compiland"),
556 wxT("compiland details"),
557 wxT("compiland env"),
558 wxT("function"),
559 wxT("block"),
560 wxT("data"),
561 wxT("annotation"),
562 wxT("label"),
563 wxT("public symbol"),
564 wxT("udt"),
565 wxT("enum"),
566 wxT("function type"),
567 wxT("pointer type"),
568 wxT("array type"),
569 wxT("base type"),
570 wxT("typedef"),
571 wxT("base class"),
572 wxT("friend"),
573 wxT("function arg type"),
574 wxT("func debug start"),
575 wxT("func debug end"),
576 wxT("using namespace"),
577 wxT("vtable shape"),
578 wxT("vtable"),
579 wxT("custom"),
580 wxT("thunk"),
581 wxT("custom type"),
582 wxT("managed type"),
583 wxT("dimension"),
584 };
585
586 wxCOMPILE_TIME_ASSERT( WXSIZEOF(tags) == wxDbgHelpDLL::SYMBOL_TAG_MAX,
587 SymbolTagStringMismatch );
588
589 wxString s;
590 if ( tag < WXSIZEOF(tags) )
591 s = tags[tag];
592 else
593 s.Printf(wxT("unrecognized tag (%d)"), tag);
594
595 return s;
596 }
597
598 static wxString KindString(wxDbgHelpDLL::DataKind kind)
599 {
600 static const wxChar *kinds[] =
601 {
602 wxT("unknown"),
603 wxT("local"),
604 wxT("static local"),
605 wxT("param"),
606 wxT("object ptr"),
607 wxT("file static"),
608 wxT("global"),
609 wxT("member"),
610 wxT("static member"),
611 wxT("constant"),
612 };
613
614 wxCOMPILE_TIME_ASSERT( WXSIZEOF(kinds) == wxDbgHelpDLL::DATA_MAX,
615 DataKindStringMismatch );
616
617 wxString s;
618 if ( kind < WXSIZEOF(kinds) )
619 s = kinds[kind];
620 else
621 s.Printf(wxT("unrecognized kind (%d)"), kind);
622
623 return s;
624 }
625
626 static wxString UdtKindString(wxDbgHelpDLL::UdtKind kind)
627 {
628 static const wxChar *kinds[] =
629 {
630 wxT("struct"),
631 wxT("class"),
632 wxT("union"),
633 };
634
635 wxCOMPILE_TIME_ASSERT( WXSIZEOF(kinds) == wxDbgHelpDLL::UDT_MAX,
636 UDTKindStringMismatch );
637
638 wxString s;
639 if ( kind < WXSIZEOF(kinds) )
640 s = kinds[kind];
641 else
642 s.Printf(wxT("unrecognized UDT (%d)"), kind);
643
644 return s;
645 }
646
647 static wxString TypeString(wxDbgHelpDLL::BasicType bt)
648 {
649 static const wxChar *types[] =
650 {
651 wxT("no type"),
652 wxT("void"),
653 wxT("char"),
654 wxT("wchar"),
655 wxT(""),
656 wxT(""),
657 wxT("int"),
658 wxT("uint"),
659 wxT("float"),
660 wxT("bcd"),
661 wxT("bool"),
662 wxT(""),
663 wxT(""),
664 wxT("long"),
665 wxT("ulong"),
666 wxT(""),
667 wxT(""),
668 wxT(""),
669 wxT(""),
670 wxT(""),
671 wxT(""),
672 wxT(""),
673 wxT(""),
674 wxT(""),
675 wxT(""),
676 wxT("CURRENCY"),
677 wxT("DATE"),
678 wxT("VARIANT"),
679 wxT("complex"),
680 wxT("bit"),
681 wxT("BSTR"),
682 wxT("HRESULT"),
683 };
684
685 wxCOMPILE_TIME_ASSERT( WXSIZEOF(types) == wxDbgHelpDLL::BASICTYPE_MAX,
686 BasicTypeStringMismatch );
687
688 wxString s;
689 if ( bt < WXSIZEOF(types) )
690 s = types[bt];
691
692 if ( s.empty() )
693 s.Printf(wxT("unrecognized type (%d)"), bt);
694
695 return s;
696 }
697
698 // this function is meant to be called from under debugger to see the
699 // proprieties of the given type id
700 extern "C" void DumpTI(ULONG ti)
701 {
702 SYMBOL_INFO sym = { sizeof(SYMBOL_INFO) };
703 sym.ModBase = 0x400000; // it's a constant under Win32
704 sym.TypeIndex = ti;
705
706 wxDbgHelpDLL::SymbolTag tag = wxDbgHelpDLL::SYMBOL_TAG_NULL;
707 DoGetTypeInfo(&sym, TI_GET_SYMTAG, &tag);
708 DoGetTypeInfo(&sym, TI_GET_TYPEID, &ti);
709
710 OutputDebugString(wxString::Format(wxT("Type 0x%x: "), sym.TypeIndex));
711 wxString name = wxDbgHelpDLL::GetSymbolName(&sym);
712 if ( !name.empty() )
713 {
714 OutputDebugString(wxString::Format(wxT("name=\"%s\", "), name.c_str()));
715 }
716
717 DWORD nested;
718 if ( !DoGetTypeInfo(&sym, TI_GET_NESTED, &nested) )
719 {
720 nested = FALSE;
721 }
722
723 OutputDebugString(wxString::Format(wxT("tag=%s%s"),
724 nested ? wxT("nested ") : wxEmptyString,
725 TagString(tag).c_str()));
726 if ( tag == wxDbgHelpDLL::SYMBOL_TAG_UDT )
727 {
728 wxDbgHelpDLL::UdtKind udtKind;
729 if ( DoGetTypeInfo(&sym, TI_GET_UDTKIND, &udtKind) )
730 {
731 OutputDebugString(wxT(" (") + UdtKindString(udtKind) + wxT(')'));
732 }
733 }
734
735 wxDbgHelpDLL::DataKind kind = wxDbgHelpDLL::DATA_UNKNOWN;
736 if ( DoGetTypeInfo(&sym, TI_GET_DATAKIND, &kind) )
737 {
738 OutputDebugString(wxString::Format(
739 wxT(", kind=%s"), KindString(kind).c_str()));
740 if ( kind == wxDbgHelpDLL::DATA_MEMBER )
741 {
742 DWORD ofs = 0;
743 if ( DoGetTypeInfo(&sym, TI_GET_OFFSET, &ofs) )
744 {
745 OutputDebugString(wxString::Format(wxT(" (ofs=0x%x)"), ofs));
746 }
747 }
748 }
749
750 wxDbgHelpDLL::BasicType bt = GetBasicType(&sym);
751 if ( bt )
752 {
753 OutputDebugString(wxString::Format(wxT(", type=%s"),
754 TypeString(bt).c_str()));
755 }
756
757 if ( ti != sym.TypeIndex )
758 {
759 OutputDebugString(wxString::Format(wxT(", next ti=0x%x"), ti));
760 }
761
762 OutputDebugString(wxT("\r\n"));
763 }
764
765 #endif // NDEBUG
766
767 #endif // wxUSE_DBGHELP