Wednesday, 13 January 2021

C++ - Using NatVis files with UE4 & Visual Studio + WinDbg Funtimes

Story time
Recently at work I was debugging through a dump that had crashed in the TickTaskManger. It's the part of the engine that pretty much handles all the ticks in a frame. An object had recently spawned late in the frame, causing it's tick to be added to the NewlySpawnedTickFunctions container. The problem is that this container is a custom UE4 one known as a TSet. Unreal explains what a TSet is here:
https://docs.unrealengine.com/en-US/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/TSet/index.html

It's kind of like a map but instead of a <key, value> pair, the data itself is both key and value. When debugging through it, the only data available to me in the watch window was the LevelList (an array of TickTaskLevels for every level in the world). 

A TickTaskLevel contains a few containers itself, including the NewlySpawnedTickFunctions. When ending the frame, there is an assert to make sure that this TSet is empty, however, one of the TickTaskLevels NewlySpawnedTickFunctions had 1 element. It should've been very easy to see what tick function had added itself late in the frame by just looking at the Target member variable on the TickFunction added to the container. Instead, I was left staring at pointers, padding and random characters.

My manager easily identified what the offending UObject was that was trying to spawn and I was sat there staring at my garbage results like "but how??". Turns out that Visual Studio just had no idea how to display the information in a TSet properly and was doing it the best way it knew how. 

Enter NatVis. I had never heard of this before, there's a great post on it here:

Basically, you create a small XML file that tells VS how to display the custom container. Epic provides an XML file with their custom containers. If you have a code version of UE4 installed you can find this file on your pc around here:
[UE4Root]/Engine/Extras/VisualStudioDebugging/UE4.natvis

You need to copy this file into Visual Studio's visualisers folder. Usually located (but not always) in
C:/Users/You/Documents/Visual Studio Version/Visualizers

Or follow some of the steps below on how to add it to your project directly.

We have a tool that automatically does this for you. It's supposed to do it every time you generate project files but some reason it didn't for me. After this, TSet immediately showed me the offending owner of the tick function (it was a particle...).

Enabling NatVis in Your Own Projects
So the above Microsoft post is really straight forward...if you have debugging tools enabled and you know how to use WinDbg. I apparently did not have debugging tools enabled on my personal PC and I've used WinDbg once at work. I went down a rabbit hole trying to figure out how to follow the "straight forward" post. I eventually figured out what to do from this post:

First go to Apps & Features:

I had a few dev kits installed so I chose the newest one. Click on it and press Modify.


Choose change and then press next.


Tick the Debugging Tools For Windows box and hit change. It will then go and install it.

A Visualizers folder will have now appeared. Mine was located around here:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers

Now, for me Visual Studio had no problem displaying this type without help but this is some good practice with using WinDbg; the Windows 95 looking memory tracker.

I'm following this tutorial here:

In WinDbg
Open up WinDbg, then File->Open Executable and search for the .exe created when compiling the dog example program from the first link in this post.

In the command line enter:
.symfix
.symfix + (the location of the debug folder of your application)

UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes
It will show *BUSY* for a few moments in the bottom left and then symbols will have been loaded for your program.

Next type in:
.reload
bu AppName!main        // you may get a warning after this one about verifying checksum, ignore it
g

WinDbg will now breakpoint into the program at main:
UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes

Hit Step Into at the top (or press F11)
UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes

Step until MyDog has been initialised. Now, even though I had added the natvis file to the visualizations folder it still didn't understand what to do. Eventually I found a command to make it load a specific natvis file from anywhere:
.nvload {filelocation}
UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes

If you type in ??MyDog and hit enter it will show you the contents. It's readable but it could be better. Type the following
dx -r1 MyDog

and WinDbg will show the contents in our nice new readable way!

UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes

That's great but WinDbg is kind of a pain and only useful for heavyweight developing. There is a way to add natvis files to Visual Studio.

In Visual Studio
Right click on the project in Solution Explorer and go to add new-> new item.
UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes

Go to Visual C++->Utility and choose Debugger visualization file (.natvis)
UE4 C++ - Using Visual Studio's NatVis with UE4 (and your own projects) + WinDbg Funtimes

This will create a basic natvis file for you to add code to. When you run the program and breakpoint; there will be no difference when using the MyDog example but Microsoft gives some pretty good examples of when custom natvis files are useful here:
https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/debugger/create-custom-views-of-native-objects?view=vs-2015&redirectedfrom=MSDN

It also builds the natvis file into the pdb (symbols) of the project so if you do use WinDbg you don't have to mess around doing the above steps (but now you know how :) ).

No comments:

Post a Comment