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: debughlp.cpp 37037 2006-01-21 16:47:30Z JS $
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 
BindDbgHelpFunctions(const wxDynamicLibrary & dllDbgHelp)65 static 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
DoInit()84 static 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 */
Init()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 */
GetErrorMessage()139 const wxString& wxDbgHelpDLL::GetErrorMessage()
140 {
141     return gs_errMsg;
142 }
143 
144 /* static */
LogError(const wxChar * func)145 void 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 
155 static inline
156 bool
DoGetTypeInfo(DWORD64 base,ULONG ti,IMAGEHLP_SYMBOL_TYPE_INFO type,void * rc)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
DoGetTypeInfo(PSYMBOL_INFO pSym,IMAGEHLP_SYMBOL_TYPE_INFO type,void * rc)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
GetBasicType(PSYMBOL_INFO pSym)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 */
GetSymbolName(PSYMBOL_INFO pSym)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
DumpBaseType(BasicType bt,DWORD64 length,PVOID pAddress)211 wxDbgHelpDLL::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 
291 wxString
DumpField(PSYMBOL_INFO pSym,void * pVariable,unsigned level)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 = _T("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) + _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
DumpUDT(PSYMBOL_INFO pSym,void * pVariable,unsigned level)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_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;
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 << _T("(\"") << (p ? p : _T("???")) << _T(")\"");
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 */
479 wxDbgHelpDLL::SymbolTag
DereferenceSymbol(PSYMBOL_INFO pSym,void ** ppData)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
DumpSymbol(PSYMBOL_INFO pSym,void * pVariable)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         _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 
598 static 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 
626 static 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 
647 static 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
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(_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"),
724                       nested ? _T("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(_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 
762     OutputDebugString(_T("\r\n"));
763 }
764 
765 #endif // NDEBUG
766 
767 #endif // wxUSE_DBGHELP
768