]> git.saurik.com Git - wxWidgets.git/blame - src/msw/debughlp.cpp
added explicit wx/dynlib.h include
[wxWidgets.git] / src / msw / debughlp.cpp
CommitLineData
3d8b5d85
VZ
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
29
97799a96
VZ
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
3d8b5d85
VZ
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(_T(#name)); \
70 if ( !wxDbgHelpDLL::name ) \
71 { \
72 gs_errMsg += _T("Function ") _T(#name) _T("() 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(_T("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 += _T("\nPlease update your dbghelp.dll version, ")
103 _T("at least version 5.1 is needed!\n")
104 _T("(if you already have a new version, please ")
105 _T("put it in the same directory where the program is.)\n");
106 }
107 else // failed to load dbghelp.dll
108 {
109 gs_errMsg += _T("Please install dbghelp.dll available free of charge ")
110 _T("from Microsoft to get more detailed crash information!");
111 }
112
113 gs_errMsg += _T("\nLatest dbghelp.dll is available at ")
114 _T("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(_T("dbghelp: %s() failed: %s\r\n"),
148 func, wxSysErrorMsg(::GetLastError())));
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 _T("null");
216 }
217
218 if ( ::IsBadReadPtr(pAddress, length) != 0 )
219 {
220 return _T("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 ? _T("true") : _T("false");
233 else
234 s.Printf(_T("%#04x"), b);
235 }
236 else if ( length == 2 )
237 {
238 s.Printf(bt == BASICTYPE_UINT ? _T("%#06x") : _T("%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(_T("%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 += _T('"');
260 for ( size_t n = 0; n < NUM_CHARS && *pc; n++, pc++ )
261 {
262 s += *pc;
263 }
264 s += _T('"');
265
266 handled = true;
267 }
268 }
269
270 if ( !handled )
271 {
272 // treat just as an opaque DWORD
273 s.Printf(_T("%#x"), *(PDWORD)pAddress);
274 }
275 }
276 else if ( length == 8 )
277 {
278 if ( bt == BASICTYPE_FLOAT )
279 {
280 s.Printf(_T("%lf"), *(double *)pAddress);
281 }
282 else // opaque 64 bit value
283 {
284 s.Printf(_T("%#" wxLongLongFmtSpec _T("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
97799a96 297 if ( level > MAX_DUMP_DEPTH )
3d8b5d85
VZ
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:
120678ee 316 if ( !pVariable )
3d8b5d85 317 {
120678ee 318 s = _T("NULL");
3d8b5d85 319 }
120678ee
VZ
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 }
3d8b5d85 329
120678ee
VZ
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;
3d8b5d85 334
120678ee 335 pVariable = (void *)((DWORD_PTR)pVariable + ofs);
3d8b5d85
VZ
336
337
120678ee
VZ
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;
3d8b5d85 342
120678ee
VZ
343 ULONG64 size;
344 DoGetTypeInfo(&sym, TI_GET_LENGTH, &size);
3d8b5d85 345
120678ee
VZ
346 switch ( DereferenceSymbol(&sym, &pVariable) )
347 {
348 case SYMBOL_TAG_BASE_TYPE:
3d8b5d85 349 {
120678ee
VZ
350 BasicType bt = GetBasicType(&sym);
351 if ( bt )
352 {
353 s = DumpBaseType(bt, size, pVariable);
354 }
3d8b5d85 355 }
120678ee 356 break;
3d8b5d85 357
120678ee
VZ
358 case SYMBOL_TAG_UDT:
359 case SYMBOL_TAG_BASE_CLASS:
360 s = DumpUDT(&sym, pVariable, level);
361 break;
362 }
3d8b5d85
VZ
363 }
364
365 if ( !s.empty() )
366 {
367 s = GetSymbolName(pSym) + _T(" = ") + s;
368 }
369 break;
370 }
371
372 if ( !s.empty() )
373 {
374 s = wxString(_T('\t'), level + 1) + s + _T('\n');
375 }
376
377 return s;
378}
379
380/* static */ wxString
381wxDbgHelpDLL::DumpUDT(PSYMBOL_INFO pSym, void *pVariable, unsigned level)
382{
383 wxString s;
737c443b
VZ
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
3d8b5d85
VZ
391 s.reserve(512);
392 s = GetSymbolName(pSym);
393
394#if !wxUSE_STL
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 == _T("wxString") )
399 {
400 wxString *ps = (wxString *)pVariable;
b903802e 401
e1f2de5a
VZ
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
b0c4316e
VZ
407 const wxChar *p = NULL;
408 if ( !::IsBadReadPtr(ps, sizeof(wxString)) )
e1f2de5a 409 {
b0c4316e
VZ
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 }
e1f2de5a
VZ
417 }
418
b0c4316e 419 s << _T("(\"") << (p ? p : _T("???")) << _T(")\"");
3d8b5d85
VZ
420 }
421 else // any other UDT
422#endif // !wxUSE_STL
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 << _T(" {\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(_T('\t'), level + 1) << _T('}');
473 }
474
475 return s;
476}
477
478/* static */
479wxDbgHelpDLL::SymbolTag
480wxDbgHelpDLL::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 )
120678ee
VZ
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 }
3d8b5d85
VZ
511 }
512
513 return tag;
514}
515
516/* static */ wxString
517wxDbgHelpDLL::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
e84ba59e
VZ
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
3d8b5d85
VZ
548
549static wxString TagString(wxDbgHelpDLL::SymbolTag tag)
550{
551 static const wxChar *tags[] =
552 {
553 _T("null"),
554 _T("exe"),
555 _T("compiland"),
556 _T("compiland details"),
557 _T("compiland env"),
558 _T("function"),
559 _T("block"),
560 _T("data"),
561 _T("annotation"),
562 _T("label"),
563 _T("public symbol"),
564 _T("udt"),
565 _T("enum"),
566 _T("function type"),
567 _T("pointer type"),
568 _T("array type"),
569 _T("base type"),
570 _T("typedef"),
571 _T("base class"),
572 _T("friend"),
573 _T("function arg type"),
574 _T("func debug start"),
575 _T("func debug end"),
576 _T("using namespace"),
577 _T("vtable shape"),
578 _T("vtable"),
579 _T("custom"),
580 _T("thunk"),
581 _T("custom type"),
582 _T("managed type"),
583 _T("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(_T("unrecognized tag (%d)"), tag);
594
595 return s;
596}
597
598static wxString KindString(wxDbgHelpDLL::DataKind kind)
599{
600 static const wxChar *kinds[] =
601 {
602 _T("unknown"),
603 _T("local"),
604 _T("static local"),
605 _T("param"),
606 _T("object ptr"),
607 _T("file static"),
608 _T("global"),
609 _T("member"),
610 _T("static member"),
611 _T("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(_T("unrecognized kind (%d)"), kind);
622
623 return s;
624}
625
626static wxString UdtKindString(wxDbgHelpDLL::UdtKind kind)
627{
628 static const wxChar *kinds[] =
629 {
630 _T("struct"),
631 _T("class"),
632 _T("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(_T("unrecognized UDT (%d)"), kind);
643
644 return s;
645}
646
647static wxString TypeString(wxDbgHelpDLL::BasicType bt)
648{
649 static const wxChar *types[] =
650 {
651 _T("no type"),
652 _T("void"),
653 _T("char"),
654 _T("wchar"),
655 _T(""),
656 _T(""),
657 _T("int"),
658 _T("uint"),
659 _T("float"),
660 _T("bcd"),
661 _T("bool"),
662 _T(""),
663 _T(""),
664 _T("long"),
665 _T("ulong"),
666 _T(""),
667 _T(""),
668 _T(""),
669 _T(""),
670 _T(""),
671 _T(""),
672 _T(""),
673 _T(""),
674 _T(""),
675 _T(""),
676 _T("CURRENCY"),
677 _T("DATE"),
678 _T("VARIANT"),
679 _T("complex"),
680 _T("bit"),
681 _T("BSTR"),
682 _T("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(_T("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
700extern "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(_T("Type 0x%x: "), sym.TypeIndex));
711 wxString name = wxDbgHelpDLL::GetSymbolName(&sym);
712 if ( !name.empty() )
713 {
714 OutputDebugString(wxString::Format(_T("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(_T("tag=%s%s"),
f31a4098 724 nested ? _T("nested ") : wxEmptyString,
3d8b5d85
VZ
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(_T(" (") + UdtKindString(udtKind) + _T(')'));
732 }
733 }
734
735 wxDbgHelpDLL::DataKind kind = wxDbgHelpDLL::DATA_UNKNOWN;
736 if ( DoGetTypeInfo(&sym, TI_GET_DATAKIND, &kind) )
737 {
738 OutputDebugString(wxString::Format(
739 _T(", 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(_T(" (ofs=0x%x)"), ofs));
746 }
747 }
748 }
749
750 wxDbgHelpDLL::BasicType bt = GetBasicType(&sym);
751 if ( bt )
752 {
753 OutputDebugString(wxString::Format(_T(", type=%s"),
754 TypeString(bt).c_str()));
755 }
756
757 if ( ti != sym.TypeIndex )
758 {
759 OutputDebugString(wxString::Format(_T(", next ti=0x%x"), ti));
760 }
761
e84ba59e 762 OutputDebugString(_T("\r\n"));
3d8b5d85
VZ
763}
764
765#endif // NDEBUG
766
767#endif // wxUSE_DBGHELP