]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/msw/debughlp.cpp
Several corrections to wxDocManager fields documentation.
[wxWidgets.git] / src / msw / debughlp.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/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
36static const unsigned MAX_DUMP_DEPTH = 20;
37
38// ----------------------------------------------------------------------------
39// globals
40// ----------------------------------------------------------------------------
41
42// error message from Init()
43static 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
55wxDO_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
65static 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
84static 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 */
120bool 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 */
139const wxString& wxDbgHelpDLL::GetErrorMessage()
140{
141 return gs_errMsg;
142}
143
144/* static */
145void 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
155static inline
156bool
157DoGetTypeInfo(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
171static inline
172bool
173DoGetTypeInfo(PSYMBOL_INFO pSym, IMAGEHLP_SYMBOL_TYPE_INFO type, void *rc)
174{
175 return DoGetTypeInfo(pSym->ModBase, pSym->TypeIndex, type, rc);
176}
177
178static inline
179wxDbgHelpDLL::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 */
188wxString 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
211wxDbgHelpDLL::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
291wxString
292wxDbgHelpDLL::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 default:
364 // Suppress gcc warnings about unhandled enum values.
365 break;
366 }
367 }
368
369 if ( !s.empty() )
370 {
371 s = GetSymbolName(pSym) + wxT(" = ") + s;
372 }
373 break;
374
375 default:
376 // Suppress gcc warnings about unhandled enum values, don't assert
377 // to avoid problems during fatal crash generation.
378 break;
379 }
380
381 if ( !s.empty() )
382 {
383 s = wxString(wxT('\t'), level + 1) + s + wxT('\n');
384 }
385
386 return s;
387}
388
389/* static */ wxString
390wxDbgHelpDLL::DumpUDT(PSYMBOL_INFO pSym, void *pVariable, unsigned level)
391{
392 wxString s;
393
394 // we have to limit the depth of UDT dumping as otherwise we get in
395 // infinite loops trying to dump linked lists... 10 levels seems quite
396 // reasonable, full information is in minidump file anyhow
397 if ( level > 10 )
398 return s;
399
400 s.reserve(512);
401 s = GetSymbolName(pSym);
402
403#if !wxUSE_STD_STRING
404 // special handling for ubiquitous wxString: although the code below works
405 // for it as well, it shows the wxStringBase class and takes 4 lines
406 // instead of only one as this branch
407 if ( s == wxT("wxString") )
408 {
409 wxString *ps = (wxString *)pVariable;
410
411 // we can't just dump wxString directly as it could be corrupted or
412 // invalid and it could also be locked for writing (i.e. if we're
413 // between GetWriteBuf() and UngetWriteBuf() calls) and assert when we
414 // try to access it contents using public methods, so instead use our
415 // knowledge of its internals
416 const wxChar *p = NULL;
417 if ( !::IsBadReadPtr(ps, sizeof(wxString)) )
418 {
419 p = ps->data();
420 wxStringData *data = (wxStringData *)p - 1;
421 if ( ::IsBadReadPtr(data, sizeof(wxStringData)) ||
422 ::IsBadReadPtr(p, sizeof(wxChar *)*data->nAllocLength) )
423 {
424 p = NULL; // don't touch this pointer with 10 feet pole
425 }
426 }
427
428 s << wxT("(\"") << (p ? p : wxT("???")) << wxT(")\"");
429 }
430 else // any other UDT
431#endif // !wxUSE_STD_STRING
432 {
433 // Determine how many children this type has.
434 DWORD dwChildrenCount = 0;
435 DoGetTypeInfo(pSym, TI_GET_CHILDRENCOUNT, &dwChildrenCount);
436
437 // Prepare to get an array of "TypeIds", representing each of the children.
438 TI_FINDCHILDREN_PARAMS *children = (TI_FINDCHILDREN_PARAMS *)
439 malloc(sizeof(TI_FINDCHILDREN_PARAMS) +
440 (dwChildrenCount - 1)*sizeof(ULONG));
441 if ( !children )
442 return s;
443
444 children->Count = dwChildrenCount;
445 children->Start = 0;
446
447 // Get the array of TypeIds, one for each child type
448 if ( !DoGetTypeInfo(pSym, TI_FINDCHILDREN, children) )
449 {
450 free(children);
451 return s;
452 }
453
454 s << wxT(" {\n");
455
456 // Iterate through all children
457 SYMBOL_INFO sym;
458 wxZeroMemory(sym);
459 sym.ModBase = pSym->ModBase;
460 for ( unsigned i = 0; i < dwChildrenCount; i++ )
461 {
462 sym.TypeIndex = children->ChildId[i];
463
464 // children here are in lexicographic sense, i.e. we get all our nested
465 // classes and not only our member fields, but we can't get the values
466 // for the members of the nested classes, of course!
467 DWORD nested;
468 if ( DoGetTypeInfo(&sym, TI_GET_NESTED, &nested) && nested )
469 continue;
470
471 // avoid infinite recursion: this does seem to happen sometimes with
472 // complex typedefs...
473 if ( sym.TypeIndex == pSym->TypeIndex )
474 continue;
475
476 s += DumpField(&sym, pVariable, level + 1);
477 }
478
479 free(children);
480
481 s << wxString(wxT('\t'), level + 1) << wxT('}');
482 }
483
484 return s;
485}
486
487/* static */
488wxDbgHelpDLL::SymbolTag
489wxDbgHelpDLL::DereferenceSymbol(PSYMBOL_INFO pSym, void **ppData)
490{
491 SymbolTag tag = SYMBOL_TAG_NULL;
492 for ( ;; )
493 {
494 if ( !DoGetTypeInfo(pSym, TI_GET_SYMTAG, &tag) )
495 break;
496
497 if ( tag != SYMBOL_TAG_POINTER_TYPE )
498 break;
499
500 ULONG tiNew;
501 if ( !DoGetTypeInfo(pSym, TI_GET_TYPEID, &tiNew) ||
502 tiNew == pSym->TypeIndex )
503 break;
504
505 pSym->TypeIndex = tiNew;
506
507 // remove one level of indirection except for the char strings: we want
508 // to dump "char *" and not a single "char" for them
509 if ( ppData && *ppData && GetBasicType(pSym) != BASICTYPE_CHAR )
510 {
511 DWORD_PTR *pData = (DWORD_PTR *)*ppData;
512
513 if ( ::IsBadReadPtr(pData, sizeof(DWORD_PTR *)) )
514 {
515 break;
516 }
517
518 *ppData = (void *)*pData;
519 }
520 }
521
522 return tag;
523}
524
525/* static */ wxString
526wxDbgHelpDLL::DumpSymbol(PSYMBOL_INFO pSym, void *pVariable)
527{
528 wxString s;
529 SYMBOL_INFO symDeref = *pSym;
530 switch ( DereferenceSymbol(&symDeref, &pVariable) )
531 {
532 default:
533 // Suppress gcc warnings about unhandled enum values, don't assert
534 // to avoid problems during fatal crash generation.
535 break;
536
537 case SYMBOL_TAG_UDT:
538 // show UDT recursively
539 s = DumpUDT(&symDeref, pVariable);
540 break;
541
542 case SYMBOL_TAG_BASE_TYPE:
543 // variable of simple type, show directly
544 BasicType bt = GetBasicType(&symDeref);
545 if ( bt )
546 {
547 s = DumpBaseType(bt, pSym->Size, pVariable);
548 }
549 break;
550 }
551
552 return s;
553}
554
555// ----------------------------------------------------------------------------
556// debugging helpers
557// ----------------------------------------------------------------------------
558
559// this code is very useful when debugging debughlp.dll-related code but
560// probably not worth having compiled in normally, please do not remove it!
561#if 0 // ndef NDEBUG
562
563static wxString TagString(wxDbgHelpDLL::SymbolTag tag)
564{
565 static const wxChar *tags[] =
566 {
567 wxT("null"),
568 wxT("exe"),
569 wxT("compiland"),
570 wxT("compiland details"),
571 wxT("compiland env"),
572 wxT("function"),
573 wxT("block"),
574 wxT("data"),
575 wxT("annotation"),
576 wxT("label"),
577 wxT("public symbol"),
578 wxT("udt"),
579 wxT("enum"),
580 wxT("function type"),
581 wxT("pointer type"),
582 wxT("array type"),
583 wxT("base type"),
584 wxT("typedef"),
585 wxT("base class"),
586 wxT("friend"),
587 wxT("function arg type"),
588 wxT("func debug start"),
589 wxT("func debug end"),
590 wxT("using namespace"),
591 wxT("vtable shape"),
592 wxT("vtable"),
593 wxT("custom"),
594 wxT("thunk"),
595 wxT("custom type"),
596 wxT("managed type"),
597 wxT("dimension"),
598 };
599
600 wxCOMPILE_TIME_ASSERT( WXSIZEOF(tags) == wxDbgHelpDLL::SYMBOL_TAG_MAX,
601 SymbolTagStringMismatch );
602
603 wxString s;
604 if ( tag < WXSIZEOF(tags) )
605 s = tags[tag];
606 else
607 s.Printf(wxT("unrecognized tag (%d)"), tag);
608
609 return s;
610}
611
612static wxString KindString(wxDbgHelpDLL::DataKind kind)
613{
614 static const wxChar *kinds[] =
615 {
616 wxT("unknown"),
617 wxT("local"),
618 wxT("static local"),
619 wxT("param"),
620 wxT("object ptr"),
621 wxT("file static"),
622 wxT("global"),
623 wxT("member"),
624 wxT("static member"),
625 wxT("constant"),
626 };
627
628 wxCOMPILE_TIME_ASSERT( WXSIZEOF(kinds) == wxDbgHelpDLL::DATA_MAX,
629 DataKindStringMismatch );
630
631 wxString s;
632 if ( kind < WXSIZEOF(kinds) )
633 s = kinds[kind];
634 else
635 s.Printf(wxT("unrecognized kind (%d)"), kind);
636
637 return s;
638}
639
640static wxString UdtKindString(wxDbgHelpDLL::UdtKind kind)
641{
642 static const wxChar *kinds[] =
643 {
644 wxT("struct"),
645 wxT("class"),
646 wxT("union"),
647 };
648
649 wxCOMPILE_TIME_ASSERT( WXSIZEOF(kinds) == wxDbgHelpDLL::UDT_MAX,
650 UDTKindStringMismatch );
651
652 wxString s;
653 if ( kind < WXSIZEOF(kinds) )
654 s = kinds[kind];
655 else
656 s.Printf(wxT("unrecognized UDT (%d)"), kind);
657
658 return s;
659}
660
661static wxString TypeString(wxDbgHelpDLL::BasicType bt)
662{
663 static const wxChar *types[] =
664 {
665 wxT("no type"),
666 wxT("void"),
667 wxT("char"),
668 wxT("wchar"),
669 wxT(""),
670 wxT(""),
671 wxT("int"),
672 wxT("uint"),
673 wxT("float"),
674 wxT("bcd"),
675 wxT("bool"),
676 wxT(""),
677 wxT(""),
678 wxT("long"),
679 wxT("ulong"),
680 wxT(""),
681 wxT(""),
682 wxT(""),
683 wxT(""),
684 wxT(""),
685 wxT(""),
686 wxT(""),
687 wxT(""),
688 wxT(""),
689 wxT(""),
690 wxT("CURRENCY"),
691 wxT("DATE"),
692 wxT("VARIANT"),
693 wxT("complex"),
694 wxT("bit"),
695 wxT("BSTR"),
696 wxT("HRESULT"),
697 };
698
699 wxCOMPILE_TIME_ASSERT( WXSIZEOF(types) == wxDbgHelpDLL::BASICTYPE_MAX,
700 BasicTypeStringMismatch );
701
702 wxString s;
703 if ( bt < WXSIZEOF(types) )
704 s = types[bt];
705
706 if ( s.empty() )
707 s.Printf(wxT("unrecognized type (%d)"), bt);
708
709 return s;
710}
711
712// this function is meant to be called from under debugger to see the
713// proprieties of the given type id
714extern "C" void DumpTI(ULONG ti)
715{
716 SYMBOL_INFO sym = { sizeof(SYMBOL_INFO) };
717 sym.ModBase = 0x400000; // it's a constant under Win32
718 sym.TypeIndex = ti;
719
720 wxDbgHelpDLL::SymbolTag tag = wxDbgHelpDLL::SYMBOL_TAG_NULL;
721 DoGetTypeInfo(&sym, TI_GET_SYMTAG, &tag);
722 DoGetTypeInfo(&sym, TI_GET_TYPEID, &ti);
723
724 OutputDebugString(wxString::Format(wxT("Type 0x%x: "), sym.TypeIndex));
725 wxString name = wxDbgHelpDLL::GetSymbolName(&sym);
726 if ( !name.empty() )
727 {
728 OutputDebugString(wxString::Format(wxT("name=\"%s\", "), name.c_str()));
729 }
730
731 DWORD nested;
732 if ( !DoGetTypeInfo(&sym, TI_GET_NESTED, &nested) )
733 {
734 nested = FALSE;
735 }
736
737 OutputDebugString(wxString::Format(wxT("tag=%s%s"),
738 nested ? wxT("nested ") : wxEmptyString,
739 TagString(tag).c_str()));
740 if ( tag == wxDbgHelpDLL::SYMBOL_TAG_UDT )
741 {
742 wxDbgHelpDLL::UdtKind udtKind;
743 if ( DoGetTypeInfo(&sym, TI_GET_UDTKIND, &udtKind) )
744 {
745 OutputDebugString(wxT(" (") + UdtKindString(udtKind) + wxT(')'));
746 }
747 }
748
749 wxDbgHelpDLL::DataKind kind = wxDbgHelpDLL::DATA_UNKNOWN;
750 if ( DoGetTypeInfo(&sym, TI_GET_DATAKIND, &kind) )
751 {
752 OutputDebugString(wxString::Format(
753 wxT(", kind=%s"), KindString(kind).c_str()));
754 if ( kind == wxDbgHelpDLL::DATA_MEMBER )
755 {
756 DWORD ofs = 0;
757 if ( DoGetTypeInfo(&sym, TI_GET_OFFSET, &ofs) )
758 {
759 OutputDebugString(wxString::Format(wxT(" (ofs=0x%x)"), ofs));
760 }
761 }
762 }
763
764 wxDbgHelpDLL::BasicType bt = GetBasicType(&sym);
765 if ( bt )
766 {
767 OutputDebugString(wxString::Format(wxT(", type=%s"),
768 TypeString(bt).c_str()));
769 }
770
771 if ( ti != sym.TypeIndex )
772 {
773 OutputDebugString(wxString::Format(wxT(", next ti=0x%x"), ti));
774 }
775
776 OutputDebugString(wxT("\r\n"));
777}
778
779#endif // NDEBUG
780
781#endif // wxUSE_DBGHELP