Show the desired data format to the vs Debugger

Source: Internet
Author: User

Address: http://msdn.microsoft.com/en-us/library/aa730838%28v=vs.80%29.aspx

Calvin Hsia
Microsoft Corporation

Jun 2006

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

Summary:When strates 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 getVeryDeep. 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 calledVbline, And that it is an internal representation of a line of Visual Basic. Net code. It's much more meaningful to seeDim MyVar As StringThan a bunch of hex
Numbers in the debugger. I drag and drop it to the watch window, typecast it toDimStatement, and expand/navigate the results to findMyvar. Then, I step into the next called function,VblinePassed
An argument. The parameter ing function names the ParameterVbstatement, 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.

  1. Start Visual Studio 2005 or 2003. (It also works in Visual Studio 7, although the steps might be slightly different .)
  2. ClickFile>New>Projects.
  3. SelectVisual c ++, And thenWin32 console application, And name itTest.
  4. ClickFinishIn the wizard.
  5. 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 cballs the Windows API functions
    Getversionex and
    Findfirstfile, which fill structures that we can examine in the debugger.

  6. To make things simple, let's use ANSI rather than Unicode characters (Visual Studio 2005 defaults to Unicode ):
    1. ClickProject>Properties>Configuration Properties>General>Character Set.
    2. Change the character set fromUse Unicode Character SetTo
      Use multi-Byte Character Set
      .
  7. Let's also remove the check for 64-bit portability issues:
    1. ClickProject>Properties>Configuration Properties>C ++>General.
    2. ClearDetect 64 bit portability issues.
  8. Press F9 onReturnLine 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:

  1. Open the file called autoexp. dat (which installwith Visual Studio) in the Visual Studio Editor:

    1. ClickFile>Open>File.
    2. 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.

  2. Add the following line to the autoexp. dat file, in[Autoexpand]Section.
    _OSVERSIONINFOEXA = Hi there <szCSDVersion> Build number = <dwBuildNumber>
  3. 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+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
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[Autoexpand]Section of the autoexp. dat file, replace the line that you added in step 2 abve 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, andFunctionnameExport in the DLL will be called. (ignore the gobbledygook: It's justc ++
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:

  1. ClickFile>New>Project>Visual c ++ Win32 Project.
  2. Name the projectMydbgee, And selectAdd to Solution(RatherCreate New Solution).
  3. In the Win32 App Wizard that appears, change the application type to a DLL.
  4. Change the project properties as abve to non-Unicode and no 64-bit issues.
  5. 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:

  1. For the DLL project, clickProject>Properties>Configuration Properties>Build Events>
    Post build event>Command Line.
  2. EnterCopy $ (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.
  3. 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.

  1. Add the following code after# 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};
  2. Add the following code to just beforeReturnStatement.
       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 instanceMyclassThat contains the desired debug display value.

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

  1. Copy the same structure definition as above into the debug DLL Code. (typically, these definitions will be in a shared # include file .)
  2. 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;}
  3. 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.

In addition, I found some explanations in the msdn help document:

To extend the expression evaluator for a custom data type, you write a custom viewer function in the expression evaluator add-in DLL. the function uses a pointer to an object in the memory space of the program being debugged (not the memory space of
Expression Evaluator you are extending). You cannot use normal casts with this pointer. You must read it and the data it points to using a callback function. A callback pointer of Type
Debughelper * points to an object with various methods.

The syntax looks like this:

HRESULT WINAPI CustomViewer(   DWORD dwAddress,       // low 32-bits of address   DEBUGHELPER *pHelper,  // callback pointer to access helper functions   int nBase,             // decimal or hex   BOOL bIgnore,          // not used   char *pResult,         // where the result needs to go   size_t max,            // how large the above buffer is   DWORD dwReserved       // always pass zero)

The sample has two implementations of this type of function,
Addin_systemtime and addin_filetime in timeaddin. cpp.
Debughelper struct (defined in custview. h) consists of function pointers that can assist you in writing your extension. This pointer is passed to your
Customviewer function, and you can use it to call the helper functions.

You can get the processor type with pHelper-> getprocessortype. There are two methods for reading memory,
PHelper-> readdebuggeememory and pHelper-> readdebuggeememoryex.
Readdebuggeememoryex handles 64-bit addresses and is supported by the Visual Studio. NET debugger.
Readdebuggeememory does not handle 64-bit addresses and is supported by the Visual Studio. net and Visual C ++ 6.0 debuggers. if your add-in is designed for the Visual Studio. net debugger only, you can use
Readdebuggeememoryex. If your add-in needs to work with Visual C ++ 6.0 also, you must check
Dwversion field and avoid calling readdebuggeememoryex for Visual C ++ 6.0.

The following code works with both debuggers and reads the contents ofLocalobject(Whose type is
Mytype) from the program being debugged:

DWORDLONG qwRealAddress;DWORD dwGot;MyType localobject;if (pHelper->dwVersion<0x20000){   // Visual C++ 6.0 version   qwRealAddress = dwAddress;   pHelper->ReadDebuggeeMemory( pHelper, dwAddress,       sizeof(localobject), &localobject, &dwGot );}else{   qwRealAddress = pHelper->GetRealAddress(pHelper);   pHelper->ReadDebuggeeMemoryEx( pHelper, qwRealAddress,       sizeof(localobject), &localobject, &dwGot );}// TODO: display localobject here

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.