When I graduated from college with my Bachelors in CS, I knew next to nothing about successfully using Visual Studio, or any other visual debugger for that matter, for debugging. In fact, the majority of my debugging experiences involved liberal use of printf and reviewing output logs, which I suppose works to a degree, but it’s just not nearly as powerful as using Visual Studio.
Since then, from my experiences both at National Instruments, as well as working on the Visual Studio Platform team at Microsoft, I have learned a lot of neat tricks when using Visual Studio for debugging both managed and unmanaged code.
How to create and use a debugging project
I never knew you could do this until I joined Microsoft. If you want to create a “debugging project”, all you really have to do is File -> Open Project, and then find the executable you want to debug. Voila, when you hit F5, your “project” will automatically run the executable and attach to it. This is really handy for cases when you can’t attach to an already running process, because you need to attach as soon as possible before any loading takes place. If you edit your project properties you can customize the kind of debugging you do.
Attach to a running process
When you’re using Visual Studio to write, build, and run your application, you don’t have to do this, because you can just build your code and then run it like any normal project. However, if you’re debugging in a production environment, with a sizable codebase that doesn’t build in Visual Studio, you don’t really have this option. Instead, what you can do is load the source code you’re interested in, set breakpoints, and then attach to an already running application to do your debugging. This works remarkably well, and you don’t have to load the entire project into VS.
To do this, choose Debug -> Attach to Process, and find the name of the currently running executable that you want to debug. Make sure that you also click on the Select button to pick the specific type of debugging you want to do, namely Managed, Native, or both. I usually do both, but only because I usually debug mixed-mode applications. If your project is entirely managed or entirely native then you can improve debugging performance by picking whichever one applies to you.
Disable Just My Code
Just My Code is one of the many default settings that I can’t stand because I always want to turn it off, just like “Hide extensions for known file types” and “Insert tabs”. I guess the idea behind Just My Code is that the average programmer might not care if some low-level library is throwing exceptions, they only want to focus on their own code. But we don’t want to be average programmers, we want to be rockstar programmers, so turn it off and you can actually debug into the programs you’re attaching to as deep as you need to go. Trust me, turn this off.
The next step after disabling Just My Code is usually adjusting your symbol settings and making sure that your symbols are loaded. If you go to Debug -> Windows -> Modules after running or attaching to your executable, you can see a list of every assembly or DLL loaded by the application, and whether or not symbols have been loaded for that module, among other things. There are three things you usually need for debugging:
- Source code
- The assemblies or DLLs being debugged
- Program Database (pdb) files
The PDB files are the files built for Debug builds which actually contains the debugging information, otherwise known as symbols. These files are the link between your binary module and your textual source code, so loading them means that you can now set breakpoints in your source code and the Visual Studio debugger will honor those breakpoints and allow you to debug.
If your symbols aren’t being loaded for the module you’re interested in, it’s probably just because Visual Studio doesn’t know where to look for your PDBs. Right-click on the Modules window and choose Symbol Settings, and you can give Visual Studio a list of directories to look in for PDB files. If some of these symbols live on the network, it is also usually a good idea to choose a local directory as a symbol cache so that you only have to download symbols once. If “Load symbols using the updated settings when this dialog is closed” is checked, Visual Studio will attempt to load every single PDB it can find in the directories you specified. Sometimes that’s fine but other times you may wish to uncheck this so that you don’t load symbols for everything in the world when you only care about a single module.
Disabling Native Images
Native Images are basically managed assemblies compiled into processor-specific machine code. The .NET runtime can use these natives images, which are stored in the native image cache, instead of having to use the just-in-time compiler every time a managed assembly is loaded. Application developers can use ngen or the Native Image Service to generate these native images.
The bad thing about native images however is that, because they are compiled into processor-specific optimized machine code, they are much harder to debug. So in some cases you might want to disable these native images when running your Visual Studio debugger. The instructions are available here: Reference Source FAQ, but essentially what you have to do is set the environment variable COMPLUS_ZapDisable=1 in your environment before launching Visual Studio, and then disable the Visual Studio Hosting Process. This will cause Visual Studio to ignore the native images and use the managed assemblies instead, so now you can debug your previously optimized code.
Set Next Statement
Set Next Statement is a powerful debugging technique that I had ignored until I saw someone use it, and finally I realized why it might come in handy. When you’re debugging in Visual Studio, and the application is paused by the debugger, you can elect to skip the natural execution path of the program and choose the next statement your program will execute. All you have to do is right-click in the text editor on the next statement you want to run, select Set Next Statement, and Visual Studio automatically skips there next instead of executing the current line, changing the program’s instruction pointer.
Why would you want to do this? Maybe you want to see what would have happened if a certain condition was met and step into an if or else that wouldn’t have occurred otherwise. Maybe you want to see what would happen if you skip some code or execute it again. The possibilities are endless, and you don’t even have to recompile your code to do it.
Modify variables at runtime
This technique is kind of similar to the previous, in that you’re modifying the natural execution of a running program by changing variable values, or even values in memory, at runtime. Simply find the variable you’re interested in, either via Locals, Autos, or the Watch window, double-click on its value, and change it to whatever you want. Similarly, if you know the address of a pointer to memory you’d like to change, you can open up the Memory window, type in the hexadecimal address, and use the hex-editor to modify memory values.
Like the previous technique, you can use this to see what would have happened if a certain variable was set to something else. For example, you could be calling a function which is returning an incorrect value, but you want to double-check to make sure your code would work correctly if the correct value was actually being returned. Simply change the value after you call the function, and now you can find out.
Freeze and thaw threads
In multi-threaded applications, it’s extremely important to make sure that no matter what, your program cannot get into a deadlock situation where two threads block each other from being able to continue executing. Unfortunately, this is very difficult to prove, and simply running your program and verifying that it didn’t crash isn’t enough, because of the indeterministic nature of multi-threading. So how do you test/verify your code? Use the Threads window in Visual Studio and use it to freeze/thaw threads.
When you open the Threads window, and the program is paused, you will see a list of the currently executing threads for your process. Each thread has an ID, a category, a name, a location, and a priority. When you’re debugging a multithreaded application, you want to brainstorm ways in which to deterministically control which thread executes which code in which order, so after you’ve broken into your first breakpoint, you can freeze that thread and then let the other thread catch up to the point where you want to freeze it. If you pick the right freeze/thaw points for each thread, and your program has the possibility of deadlock, you should be able to force the program into it by controlling the execution timing. If you can successfully do this, your code is flawed and must be fixed.
Determine which DLL or assembly to load based on a virtual function pointer
I learned this technique very recently from a colleague at Microsoft. Sometimes you might be debugging an application, and you want to step into a particular virtual function call, but you can’t because Visual Studio hasn’t loaded the symbols for whatever module the function is located in. Also, you don’t actually know offhand which DLL it is, and you don’t want to just load every symbol in the application because it takes too long and slows you down too much.
So here’s what you can do, sometimes. Let’s say you have a pointer to a virtual class which implements the method you’re interested in. Use Intellisense to find out more about that pointer, until you find the virtual function table. Now you have a memory address for where a particular method implementation is located. Open up your Modules window in Visual Studio, and sort by Address. Find out which address range corresponds to the virtual function pointer, and that row is the module you’re looking for. Right-click on it and choose to Load Symbols, and now the next time you try to step into that function call, it will succeed because the symbols are now loaded. Here’s a screenshot of this in action:
So there you have it, some of my favorite Visual Studio debugging tricks. Since I’m pretty new to the Visual Studio Platform team I am sure I will learn new tricks as I go along, so I will be sure to post about them as I discover them.