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 // Copyright:   (c) 2003-2005 Vadim Zeitlin <vadim@wxwindows.org>
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 // ============================================================================
12 // declarations
13 // ============================================================================
14 
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18 
19 #include "wx/wxprec.h"
20 
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24 
25 #include "wx/msw/debughlp.h"
26 
27 #if wxUSE_DBGHELP && wxUSE_DYNLIB_CLASS
28 
29 // ----------------------------------------------------------------------------
30 // constants
31 // ----------------------------------------------------------------------------
32 
33 // to prevent recursion which could result from corrupted data we limit
34 // ourselves to that many levels of embedded fields inside structs
35 static const unsigned MAX_DUMP_DEPTH = 20;
36 
37 // ----------------------------------------------------------------------------
38 // globals
39 // ----------------------------------------------------------------------------
40 
41 // error message from Init()
42 static wxString gs_errMsg;
43 
44 // ============================================================================
45 // wxDbgHelpDLL implementation
46 // ============================================================================
47 
48 // ----------------------------------------------------------------------------
49 // static members
50 // ----------------------------------------------------------------------------
51 
52 #define DEFINE_SYM_FUNCTION(func, name) \
53     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(func, name)                                     \
68         wxDbgHelpDLL::func = (wxDbgHelpDLL::func ## _t)                       \
69             dllDbgHelp.GetSymbol(wxT(#name));                                 \
70         if ( !wxDbgHelpDLL::func )                                            \
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
DoInit()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 */
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(wxT("dbghelp: %s() failed: %s\r\n"),
148                         func, wxSysErrorMsg(::GetLastError())).t_str());
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 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", *(wxLongLong_t *)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 = 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
DumpUDT(PSYMBOL_INFO pSym,void * pVariable,unsigned level)390 wxDbgHelpDLL::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 */
488 wxDbgHelpDLL::SymbolTag
DereferenceSymbol(PSYMBOL_INFO pSym,void ** ppData)489 wxDbgHelpDLL::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
DumpSymbol(PSYMBOL_INFO pSym,void * pVariable)526 wxDbgHelpDLL::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 
563 static 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 
612 static 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 
640 static 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 
661 static 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
714 extern "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
782