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