Microsoft.com Home

|

Site
Map

MSDN
Home

|

Developer
Centers

|

Library

|

Downloads

|

How to Buy

|

Subscribers

|

Worldwide

Search for

MSDN and KBMSDN
LibraryCode and
DownloadsProduct
InformationKnowledge Base
(KB)

Advanced
Search

Visual Studio
Home

Visual
Studio Team System

Visual
Studio Express

Learn
Visual Studio

Reference

Downloads

Support

Community

Products

How to
Buy

Why
Visual Studio

Customer
Evidence

Newsroom

Partners

Extensibility
Center

Resources for
Java Developers

Previous
Versions

Future
Versions

MSDN Home > Visual
Studio Home

See
This in the MSDN Library

Page Options

Customizing the Visual
Studio Debugger Display of Your Data

Calvin Hsia
Microsoft Corporation

June 2006

Applies to:
Visual Studio
2005
Visual Studio .NET
2003
Visual Studio 7.0

Summary: Illustrates ways to customize the Visual Studio
2005 debugger to get the most out of your debugging time. (6 printed
pages)

As a software developer, I spend much of my time looking at code,
learning how it works, and figuring out how to modify or fix it. A
very good tool to help examine code is the Visual Studio
debugger.

(Even if you're not a hard core programmer, the following
tutorial shows some of the power of the Visual Studio components—for
example, the project system, build system, and debugger—working
together.)

At a breakpoint, I can examine local variables in the Watch,
Auto, or Locals window to see their values and types. If it's a
class or structure, the debugger will show a plus sign (+), indicating that it can be expanded, and the
first couple members of that structure. Structures' submembers or
inherited values can be examined. These structures can get
very deep. Sometimes I need to inspect a value that's dozens
of levels down in a hierarchy. That's a lot of complicated tree
navigation in the debugger. Other times, I need to take a local
variable name (or a member of that variable if it's a
structure/class), drag and drop it to a new line in the Watch
window, and then typecast it to a value that's more meaningful. As I
step through the code, the variable might go out of scope, or it
might have a different name in a subroutine, so I'd have to repeat
the typecasting steps in the Watch window with the different
variable name.

For example, suppose that one of the variables is called
VBLine, and that it is an internal representation of a line
of Visual Basic .NET code. It's much more meaningful to see Dim MyVar As String than a bunch of hex numbers in
the debugger. I drag and drop it to the Watch window, typecast it to
a DIM statement, and expand/navigate the results to find
MyVar. Then, I step into the next called function, with
VBLine passed as an argument. The receiving function names
the parameter VBStatement, so my Watch window drilldown needs
to be modified to use the different variable name.

This gets very cumbersome. Let's improve it!

Here's a simple demonstration of how you can control what the
debugger displays.

Start Visual Studio 2005 or 2003. (It also works in Visual
Studio 7, although the steps might be slightly different.)

Click File > New >
Projects.

Select Visual C++, and then Win32 Console
Application, and name it Test.

Click Finish in the wizard.

Paste in some sample code to debug.
#include "windows.h"
int _tmain(int argc, _TCHAR* argv[])
{
OSVERSIONINFOEX osinfo; // Declare a structure
ZeroMemory(&osinfo, sizeof(osinfo)); // init it to 0
osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); // set the size
GetVersionEx((LPOSVERSIONINFO) &osinfo); // call WinAPI to fill it in

WIN32_FIND_DATA FAR ffd; // Declare a structure
FindFirstFile("c:\\windows\\system32\\k*.exe",&ffd); // Find the first file starting with "k"

return 0; //set bpt here
}

This sample code just calls the Windows API functions GetVersionEx
and FindFirstFile,
which fill structures that we can examine in the debugger.

To make things simple, let's use ANSI rather than Unicode
characters (Visual Studio 2005 defaults to Unicode):

Click Project > Properties >
Configuration Properties >
General > Character Set.

Change the character set from Use Unicode Character
Set to Use Multi-Byte Character Set.

Let's also remove the check for 64-bit portability issues:

Click Project >
Properties > Configuration
Properties > C++ > General.

Clear Detect 64 bit portability issues.

Press F9 on the return line to set a breakpoint, and
then press F5 to build and run the project.

When the breakpoint hits, the Debug window shows the
following.

+ osinfo {dwOSVersionInfoSize=284 dwMajorVersion=5 dwMinorVersion=1 ...} _OSVERSIONINFOEXA
+ ffd {dwFileAttributes=32 ftCreationTime={...} ftLastAccessTime={...} ...} _WIN32_FIND_DATAA

Now, let's control the string displayed for a given type:

Open the file called AutoExp.dat (which installs with Visual
Studio) in the Visual Studio editor:

Click File > Open >
File.

Locate the AutoExp.dat file. On my machine, it's at
c:\Program Files\Microsoft Visual Studio
8\Common7\Packages\Debugger\Autoexp.dat.

This file describes how to customize the output of the Debug
Watch, Locals, and Auto windows. It's formatted like an INI file.

Add the following line to the AutoExp.dat file, in the
[AutoExpand] section.
_OSVERSIONINFOEXA = Hi there <szCSDVersion> Build number = <dwBuildNumber>

Press F5 to go to the breakpoint.

Now the Watch window shows the following.

+ osinfo {Hi there 0x0013fe58 "Service Pack 2" Build number = 2600} _OSVERSIONINFOEXA

This is a big improvement: we've told the debugger which members
of the structure to show, and how to format them! We can still click
the + to drill down the member hierarchy.

When starting a debug session, the debugger reads the AutoExpand
file, and if the left of the equal sign matches the type in the Type
column of the Locals/Watch/Auto window, the right side will direct
how to format the displayed string. The comments at the beginning of
AutoExp.dat give more details, including more formatting
options.

This is great, but it's nothing compared to what we'll do
next!

You can write code that executes in the debugger process, and
that can read the memory of the debugee! AutoExp.dat controls this
feature too:

In the [AutoExpand] section of the AutoExp.dat file,
replace the line that you added in Step 2 above with the
following three lines.
_OSVERSIONINFOEXA= $ADDIN(MyDbgEE.dll,?EE_OSVERSIONINFOEXA@@YGJKPAUtagDEBUGHELPER@@HHPADIK@Z)
_WIN32_FIND_DATAA =$ADDIN(MyDbgEE.dll,?EE_WIN32_FIND_DATAA@@YGJKPAUtagDEBUGHELPER@@HHPADIK@Z)
MyClass = $ADDIN(MyDbgEE.dll,?EE_MyClass@@YGJKPAUtagDEBUGHELPER@@HHPADIK@Z)

The $ADDIN(DllName,FunctionName) syntax means that the DLL
that is named will be loaded, and the FunctionName export in
the DLL will be called. (Ignore the gobbledygook: it's just C++
name decorating, indicating the calling convention, the
parameters, and so on.) If any error occurs—for example, the DLL
can't be found, the export can't be found, or the DLL caused an
exception, the displayed string will be {???}.

Now, let's create the project that will build MyDbg.DLL, and add
it to the current solution:

Click File > New >
Project > Visual C++ Win32 Project.

Name the project MyDbgEE, and select Add to
Solution (rather than Create New Solution).

In the Win32 App Wizard that appears, change the application
type to a DLL.

Change the project properties as above to non-Unicode and no
64-bit issues.

Add the following lines.
#define ADDIN_API __declspec(dllexport)

typedef struct tagDEBUGHELPER
{
DWORD dwVersion;
BOOL (WINAPI *ReadDebuggeeMemory)( struct tagDEBUGHELPER *pThis, DWORD dwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
// from here only when dwVersion >= 0x20000
DWORDLONG (WINAPI *GetRealAddress)( struct tagDEBUGHELPER *pThis );
BOOL (WINAPI *ReadDebuggeeMemoryEx)( struct tagDEBUGHELPER *pThis, DWORDLONG qwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
int (WINAPI *GetProcessorType)( struct tagDEBUGHELPER *pThis );
} DEBUGHELPER;

ADDIN_API HRESULT WINAPI EE_OSVERSIONINFOEXA( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved )
{
wsprintf(pResult,"Testing Addr = %x Uni = %d base = %d %x",dwAddress,bUniStrings, nBase, *(DWORD *)dwAddress);
return S_OK;
}

ADDIN_API HRESULT WINAPI EE_WIN32_FIND_DATAA( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved )
{
WIN32_FIND_DATA FAR ffd;
DWORD nGot=0;
pHelper->ReadDebuggeeMemory(pHelper,dwAddress,sizeof(ffd),&ffd,&nGot);
wsprintf(pResult,"FindData found file '%s' DBG Process ID = %d",ffd.cFileName, GetCurrentProcessId());
return S_OK;
}

Now, we need to tell Visual Studio where to put the built DLL, so
that the debugger can find it. We can use the build events in the
project:

For the DLL project, click Project >
Properties > Configuration
Properties > Build Events > Post
Build Event > Command Line.

Enter copy $(TargetPath) "$(DevEnvDir)". Make sure that
you have the quotation marks and parentheses right. If you put in
a description string, that string will be echoed to the Output
window when building. Now, when you rebuild, the debug DLL will be
copied to the same directory as Devenv.exe.

Press F5 and see the values in the Debug window! Bring up the
Task Manager and notice that the Process ID shown is the same as
that of the Devenv.exe debugger process.

To make things more interesting, let's see how our debug code can
read the debugger memory. We'll add some code to obscure a desired
value, but we'll dig for it in the debug DLL.

Add the following code after the #include "windows.h"
line in the main Test code.
struct MyClass {// normally this will go in #include file
int mymem1; // make the 1st few members irrelevant, so debugger won't show interesting info
int mymem2;
int mymem3;
int mymem4;
int mymem5;
short *str; // make this not a string, so debugger won't show it as a string
MyClass * m_pNextClass; // self referential, perhaps like a linklist
};

Add the following code to just before the return
statement.
MyClass * pMyClass = new MyClass(); // declare a new instance of MyClass
pMyClass->str = new short(8); // create a heap allocated byte array
memcpy(pMyClass->str,"NotMe!",7); // desired value to see in debugger

pMyClass->m_pNextClass = new MyClass(); // make a submember instance
pMyClass->m_pNextClass->str = new short(8); // heap allocated submember string
memcpy(pMyClass->m_pNextClass ->str,"Bingo!",7); // desired value to see in pMyClass

This code creates a class, MyClass, with a pointer to
another instance of MyClass that contains the desired debug
display value.

Now, we need to modify the debug DLL to dig for the value.

Copy the same structure definition as above into the debug DLL
code. (Typically, these definitions will be in a shared #include
file.)

Add the following code.
ADDIN_API HRESULT WINAPI EE_MyClass( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved )
{
DWORD nGot=0;
MyClass oMyClass;
pHelper->ReadDebuggeeMemory(pHelper,dwAddress,sizeof(oMyClass),&oMyClass,&nGot); // read the debuggee's structure
char szMainStr[100];
char szMemberStr[100];
*szMemberStr=0; // init to empty string
if (oMyClass.m_pNextClass) // if there's a sub member
{
MyClass oNextClass;
pHelper->ReadDebuggeeMemory(pHelper,(DWORD)oMyClass.m_pNextClass,sizeof(oNextClass),&oNextClass,&nGot); // read it
pHelper->ReadDebuggeeMemory(pHelper,(DWORD)oNextClass.str,sizeof(szMemberStr),&szMemberStr,&nGot); // read it's string
}
pHelper->ReadDebuggeeMemory(pHelper,(DWORD)oMyClass.str,sizeof(szMainStr),szMainStr,&nGot); // read the string of the main struct
wsprintf(pResult,"MyClass string is '%s'. Submem = '%s'",szMainStr,szMemberStr);
return S_OK;
}

Press F5, and Bingo! You can still drill down into the class
manually as before, so you haven't lost any functionality.

The debug DLL can be rebuilt even while debugging: it's
loaded/unloaded as needed by the debugger. This means that
persisting values might be cumbersome. I've used custom registry
keys for persisting values, such as global variables.

I've been using this debug expression evaluator architecture for
years for huge projects, including Visual Foxpro and Visual
Basic.NET, and I find it indispensable and a huge time saver.

See also:

EEAddIn:
Uses the Expression Evaluator Add-In API to extend the native
debugger expression evaluator.

Very
Advanced Debugging Tips.

Manage Your Profile
|Legal |Contact
Us |MSDN Flash Newsletter

© 2006 Microsoft Corporation. All
rights reserved. Terms of Use
|Trademarks
|Privacy
Statement