The C++ logo, by Jeremy Kratz, licensed under CC0 1.0 Universal

Natvis files


8 - 10 minutes read, 2032 words
Categories: c c++ windows
Keywords: c c++ debug natvis visual studio windows

Microsoft STL Visualizers are on GitHub

A couple of days ago, Microsoft announced that it published STL visualizers on Github. While it has been always possible to provide own .natvis files for structures and classes, it is not something I thought too much about.

As it is now easier to find some examples, I looked at the documentation and decided to write down some.

Support in CMake

First of all, I checked how easy it is to integrate that information into a project. If using CMake, support has been added in version 3.7.

It is as simple as adding the relevant files to the library where the class belongs

add_library(foo
    <other source files>
    datastructure.cpp
    datastructure.natvis
)

or to the executable, if the data structure is not part of any library

add_executable(foo
    <other source files>
    datastructure.cpp
    datastructure.natvis
)

Array-like containers

Suppose you have implemented something like std::array, std::span or ct_map.

Without a .natvis file, Visual Studio will show the "raw" data structure, which, per se, is good enough for most use-cases.

cmap                        {data=0x0113dad0 {('a', -2), ('b', -1)} }
  m_data 0x0113dad0         {('a', -2), ('b', -1)}
    [0]                     ('a', -2)
    [1]                     ('b', -1)

With the following .natvis file

<?xml version="1.0" encoding="utf-8"?>

<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    <Type Name="ct_map&lt;*&gt;">
        <DisplayString>{{ size={$T3} }}</DisplayString>
        <Expand>
            <Item Name="[size]">$T3</Item>
            <ArrayItems>
                <Size>$T3</Size>
                <ValuePointer>data</ValuePointer>
            </ArrayItems>
        </Expand>
    </Type>
</AutoVisualizer>

the output looks like

cmap                        { size=2 }
  [size]                    2
  [0]                       ('a', -2)
  [1]                       ('b', -1)
  [Raw View]                {...}

"Raw view" gives the possibility to looks at the content as if there would be no .natvis file.

The address of data is not shown if not in Raw view, because it is not logically part of the content of the class. The class is fully represented by m_data, so there is no need to show it as a separate/member variable.

In this case, the hard work is done by <ArrayItems>, which has the nodes <Size> and <ValuePointer>. Both are self-explanatory, but for ct_map I would have liked to get a view more similar to std::map, which shows the key in the name column, and the value in the Value column:

map                         { size=2 }
  ['a']                     -2
  ['b']                     -1
  [Raw View]                {...}

std::map accomplishes this by using TreeItems and a view helper:

...

    <TreeItems>
        <Size>_Mypair._Myval2._Myval2._Mysize</Size>
        <HeadPointer>_Mypair._Myval2._Myval2._Myhead-&gt;_Parent</HeadPointer>
        <LeftPointer>_Left</LeftPointer>
        <RightPointer>_Right</RightPointer>
        <ValueNode Condition="_Isnil == 0" Name="[{_Myval.first}]">_Myval,view(MapHelper)</ValueNode>
      </TreeItems>

...

  <Type Name="std::pair&lt;*, *&gt;" IncludeView="MapHelper">
    <DisplayString>{second}</DisplayString>
  </Type>

So ValueNode seems to support a Name attribute, just like Item. I could not find anything about it in the documentation, and I was not able to get it to work.

<?xml version="1.0" encoding="utf-8"?>

<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    <Type Name="ct_map&lt;*&gt;">
        <DisplayString>{{ size={$T3} }}</DisplayString>
        <Expand>
            <Item Name="[size]">$T3</Item>
                <IndexListItems>
                <Size>$T3</Size>
                <ValueNode>data[$i]</ValueNode>
            </IndexListItems>
        </Expand>
    </Type>
</AutoVisualizer>

Replacing <ValueNode>data[$i]</ValueNode> with <ValueNode Name="[data[$i].first]">data[$i].second</ValueNode> or even <ValueNode Name="[dummy]">data[$i].second</ValueNode> make the debugger ignore the declaration in the .natvis, which is a good indicator that something is wrong.

Maybe the Name attribute works only for nodes of TreeItems. Unfortunately, I did not find out how to map it to an array.

In the meantime I’m happy enough with the current .natvis file, it’s already an improvement.

string-like containers

An instance of something like string_views, std::string_view or even std::string would look similar to

ss                          {m_data=0x00d6da44 "123" }
  m_data                    0x00d6da44 "123"
    [0]                     49 '1'
    [1]                     50 '2'
    [2]                     51 '3'
    [3]                     0 '\0'

With a corresponding .natvis, it can be changed too

ss                          "123"
  [size]                    3
  [0]                       49 '1'
  [1]                       50 '2'
  [2]                       51 '3'
  [3]                       0 '\0'
  [Raw View]                {m_data=0x00d6da44 "123" }

Also, thank to <StringView> it is now possible to copy directly the string as-is, while before it had to be done from m_data.

In this case, the .natvis file looks like

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">

    <Type Name="string_views::basic_string_views&lt;char,*&gt;">
        <DisplayString>{m_data,[m_size] s8}</DisplayString>
        <StringView>m_data,[m_size] s8b</StringView>
        <Expand>
            <Item Name="[size]">m_size</Item>
            <Item Name="[data]">m_data</Item>
        </Expand>
    </Type>
    <Type Name="string_views::basic_string_views&lt;wchar_t,*&gt;">
        <DisplayString>{m_data,[m_size] su}</DisplayString>
        <StringView>m_data,[m_size] sub</StringView>
        <Expand>
            <Item Name="[size]">m_size</Item>
            <Item Name="[data]">m_data</Item>
        </Expand>
    </Type>

    <Type Name="string_views::basic_string_views&lt;*&gt;">
        <DisplayString>"{m_data,[m_size]}"</DisplayString>
        <StringView>m_data,[m_size]</StringView>
        <Expand>
            <Item Name="[size]">m_size</Item>
            <Item Name="[data]">m_data</Item>
        </Expand>
    </Type>

</AutoVisualizer>

A first approach used <DisplayString>{m_data}</DisplayString> instead of <DisplayString>{m_data,[m_size] s8}</DisplayString>, and did not distinguish between char and wchar_t.

The first issue is that if the size is not specified, the debugger cannot know how long the string is. It can assume that it is \0-terminated, but this is not true for std::string_view and is generally problematic if there can be embedded \0, like in std::string.

The issue is simple to fix; just add the size [m_size]. The resulting <DisplayString> would work both for char and wchar_t.

I later noticed that it is beneficial to distinguish between the two types if the content happens to be non-ASCII. The distinction of the two types is needed because in one case I use the format specifier s8, and in the other su. Both are for specifying that the content is Unicode, but one is for UTF-8 encoding, and the other for UTF-16. If the string contains binary data or data in another encoding…​. then the string displayed in the debugger will be garbled.

PIMPL classes

Another benefit of .natvis file is giving a better view over a PIMPL class.

PIMPL is a technique for hiding implementation details and reducing compilation times, but it has several drawbacks, and one of them is adding another layer of indirection while debugging.

Suppose you have defined a class like

// header1
#include <memory>

struct foo;

struct bar{
		bar();
		~bar();
		void increment();
		int value() const;
	private:
		std::unique_ptr<foo> impl;
};

and a possible implementation would be

// implementation file

struct foo {
	int data = 42; // whatever data is part of the implementation of bar
};

bar::bar() : impl(std::make_unique<foo>()){
}
bar::~bar() = default;

void bar::increment(){
	++(impl->data);
}
int bar::value() const {
	return impl->data;
}

then one could define following natvis file

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
	<Type Name="bar">
		<DisplayString>{*impl}</DisplayString>
	</Type>
</AutoVisualizer>

Some classes might avoid wanting to allocate in it’s default constructor. In those cases impl can be nullptr (at the possible expense of a more complex implementation), but it is possible to create an appropriate .natvis file, that shows the intended behavior of the class.

namespace{
	constexpr const int default_data = 42;
}
struct foo {
	int data = default_data; // whatever data is part of the implementation of bar
};

bar::bar() = default;
bar::~bar() = default;

void bar::increment(){
	if (impl == nullptr){
		impl = std::make_unique<foo>();
	}
	++(impl->data);
}
int bar::value() const {
	return impl == nullptr ? default_data : impl->data;
}

then something like the following .natvis might be more appropriate

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
	<Type Name="bar">
		<DisplayString Condition="impl==nullptr">data = 42</DisplayString>
		<DisplayString>data = {impl->data}</DisplayString>
	</Type>
</AutoVisualizer>

Another issue is that the debugger has apparently issues showing the data hold by foo if this class is only forward-declared. Maybe there is a way to automatically overcome this limitation, at the moment I could not find only following workaround

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
	<Type Name="bar">
		<DisplayString Condition="impl==nullptr">{{data = 42}}</DisplayString>
		<DisplayString>{*impl}</DisplayString>
	</Type>
	<Type Name="foo">
		<DisplayString>{*(file.dll!foo*)this,!}</DisplayString>
	</Type>
</AutoVisualizer>

Which work, but is fragile, as one needs to add the library (or executable) name to the natvis file.

It also does not work in case of anonymous namespaces.

Other classes

Showing all member variables is probably good enough for most use-cases. But with .natvis files it is possible to enrich the information shown. For example, a class could have a valid/invalid state, this could be shown directly in the debugger.

It is possible to specify conditionals and call functions, I’m looking forward to using those features in the future.

Issues

The documentation is unfortunately scarce, and of course http://schemas.microsoft.com/vstudio/debugger/natvis/2010 returns a 404.

In the example I made above I relied on the implementation details of the classes, instead of, for example, using the member function size() for getting the size. This is because one has to enable specific settings in Visual Studio, otherwise, functions are not executed. As functions could have side effects, this is a good thing but makes such .natvis files less portable and harder to reuse.

Another "issue" is that it is not possible to add those debug information in the C++ source code directly, thus one has to manually ensure that the source code and .natvis file are synchronized.

On the other hand, one can edit the .natvis file with Visual Studio while debugging in real-time, and the debugger will update the view at every save. If those debug information would be stored in a source file, the debugger might have issues keeping the executing code and source file in sync. With the current approach, one does not need to recompile anything if the .natvis file get changed, which makes it more flexible when debugging, even if I doubt this is an operation that happens often.


Do you want to share your opinion? Or is there an error, same parts that are not clear enough?

You can contact me here.