Custom Debug Visualizers

When working with custom data structures, the debugger can show too much information layered through how you’ve constructed it. To make it easier to debug, you can create a custom visualizer which will present only the information you want, to make it easier to debug.

Here’s an example of a custom *.natvis file:


<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="__node">
    <DisplayString Condition="nType==0">Unknown | {psAName,na} | {psWName,na}</DisplayString>
    <DisplayString Condition="nType==1">String A | {psAName,na} | {psWName,na} | {psAValue,na}</DisplayString>
    <DisplayString Condition="nType==2">Int32 | {psAName,na} | {psWName,na} | {nValue}</DisplayString>
    <DisplayString Condition="nType==3">Double | {psAName,na} | {psWName,na} | {dfValue}</DisplayString>
    <DisplayString Condition="nType==4">Linked List | {psAName,na} | {psWName,na}</DisplayString>
    <DisplayString Condition="nType==5">Hash | {psAName,na} | {psWName,na}</DisplayString>
    <DisplayString Condition="nType==6">Int64 | {psAName,na} | {psWName,na} | {n64Value}</DisplayString>
    <DisplayString Condition="nType==8">Binary Data | {psAName,na} | {psWName,na}</DisplayString>
    <DisplayString Condition="nType==9">String W | {psAName,na} | {psWName,na} | {psWValue,na}</DisplayString>
    <DisplayString>Uninitialized</DisplayString>
    <Expand>
      <Item Name="Name A">psAName,na</Item>
      <Item Name="Name W">psWName,na</Item>
      <Item Name="Value" Condition="nType==1">psAValue,na</Item>
      <Item Name="Value" Condition="nType==2">nValue</Item>
      <Item Name="Value" Condition="nType==3">dfValue</Item>
      <Item Name="Value" Condition="nType==6">n64Value</Item>
      <Item Name="Value" Condition="nType==9">psWValue,na</Item>
      
      <LinkedListItems Condition="nType==4">
        <HeadPointer>pnListHead</HeadPointer>
        <NextPointer>pnNext</NextPointer>
        <ValueNode>this</ValueNode>
      </LinkedListItems>
      <CustomListItems Condition="nType==5">
        <Variable Name="bucket" InitialValue="ppnHashHeads" />
        <Variable Name="bucketCount" InitialValue="nHashBuckets" />
        <Variable Name="bucketI" InitialValue="0" />
        <Variable Name="element" InitialValue="bucket[0]" />
        <Loop>
          <Break Condition="bucketI>=bucketCount" />
          <Exec>element = bucket[bucketI]</Exec>
          <Loop>
            <Break Condition="element==0"/>
            <Item>element</Item>
            <Exec>element = element->pnNext</Exec>
          </Loop>
          <Exec>bucketI++</Exec>
        </Loop>
      </CustomListItems>
      <Synthetic Name="Buckets" Condition="nType==5">
        <Expand>
          <ArrayItems>
            <Size>nHashBuckets</Size>
            <ValuePointer>ppnHashHeads</ValuePointer>
          </ArrayItems>
        </Expand>
      </Synthetic>
      
      <Item Name="Next">pnNext,na</Item>
    </Expand>
  </Type>
</AutoVisualizer>

Let’s look at a few elements here:

  • The DisplayString commands are the synopsis when the structure is closed in the debugger. In this example we have differing views of what should be shown depending on the state of the object. Curly brackets are used to induce values directly into the description, with the na flag removing pointer addresses to make it easier to read.
  • The Expand section gives the full details of what should be shown when you want to expand and see all items in the structure.
  • Items and LinkedListItems are predefined sets which allow you to specify what you want shown, without having to describe the how to do it.
  • If there is no predefined set, you can write a custom script to do it. This example works for a bucketed hash, so you can see all the objects inside it.

If you’re using a previous version of Visual Studio, you’ll need to modify your autoexp.dat file to achieve the same results.


;node_t* visualizer
__node {
    preview(
        #switch($e.nType)
        #case 0(#( ;Unknown
            "Unknown | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 1(#( ;String A
            "String A | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 2(#( ;Int32
            "Int32 | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 3(#( ;Double
            "Double | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 4(#( ;Linked List
            "Linked List | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 5(#( ;Hash  of name->value
            "Hash | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 6(#( ;Int64
            "Int64 | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 8(#( ;Binary Data
            "Binary Data | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #case 9(#( ;String W
            "String W | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
        #default(#( ;other/undefined/unitialized/corrupted
            "other/undefined/unitialized/corrupted | ",
            [$c.psAName, s],
            " | ",
            [$c.psWName, su]
        ))
    )
    children(
        #switch($e.nType)
        #case 0(#( ;Unknown
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 1(#( ;String A
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            Value: [$c.psAValue],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 2(#( ;Int32
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            Value: [$c.nValue],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 3(#( ;Double
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            Value: [$c.dfValue],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 4(#( ;Linked List
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            List Size: [$c.nListElements],
            [actual members]: [$e,!],
            #list(
                head: $c.pnListHead,
                size: $c.nListElements,
                next: pnNext
            )
        ))
        #case 5(#( ;Hash  of name->value
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            Number of Buckets: [$c.nHashBuckets],
            Number of Elements: [$c.nHashElements],
            [actual members]: [$e,!],
            #array(
                expr: $c.ppnHashHeads[$i],
                size: $c.nHashBuckets
            ),
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 6(#( ;Int64
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            Value: [$c.n64Value],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 8(#( ;Binary Data
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #case 9(#( ;String W
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            Value: [$c.psWValue],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
        #default(#( ;other/undefined/unitialized/corrupted
            Type: [$c.nType],
            Name A: [$c.psAName],
            Name W: [$c.psWName],
            [actual members]: [$e,!],
            #list(
                head: $c.pnNext,
                next: pnNext
            )
        ))
    )
}

Further Reading:

Leave a Reply

Your email address will not be published. Required fields are marked *

*