As you may or may not know, for a couple years in addition to running .NET code on the 'Desktop' runtime that ships with the Windows operating system, the .NET Runtime team also ships a version of the runtime called .NET Core which can be installed on various favors of Linux as well as Windows. In addition to being cross platform, it is also possible to ship your application 'with the runtime' which means that your app runs the same regardless of what version of Windows / Desktop .NET is on the target machine. See 'choosing between .NET Core and .NET Framework' for more on when to use .NET Core.
Visual Studio (currently 2017) is the suggested editor/debugger, for making .NET Core applications, and there is a free download for the Community Edition, so there reason not to use this excellent debugger/IDE.
One of the nice aspects of the .NET Core runtime is that it is open source. The source code for what ships in the runtime mostly lives in CoreCLR repo (for very fundamental things) CoreFX repo (generally the rest of System.*) or one of the ASP.NET repos (for *AspNet* dlls).
With all the source code available, it is possible to have a excellent debugging experience with framework code. Basically when you need source code from the framework it can be fetched by the debugger from GitHub and presented just like local source code. Visual Studio 2017 supports the SourceLink standard that enables this as of V2.0.3 of the .NET Runtime the team has published all the right symbolic information to the Microsoft public symbol server to make this experience 'just work'.
Stepping Into the .NET Framework
Cutting to the chase: if you are running on Windows, Have Visual Studio 2017 or later Installed, and have V2.0.3 of the .NET Core SDK installed (Shipped 12/15/2017). then you only need to do two things in Visual Studio to enable the experience of being able to step into the source code for the .NET Runtime (and ASP.NET)
- Uncheck the checkbox: Tools -> Options -> Debugging -> General -> Enable Just My Code. This tells Visual Studio that you WANT to look at framework code.
- Check the checkbox: Tools -> Options -> Debugging -> Symbols -> Microsoft Symbol Servers. This tells Visual Studio where to get the symbolic information for framework code.
That is it! you can now step into calls to framework code, or set breakpoints in framework methods and Visual Studio will automatically fetch the appropriate symbolic information and source code from GitHub and present it to you so you can step through it. Note that Visual Studio is pretty aggressive about downloading symbolic information so it can definitely slow down debugging (there will be a dialog box as it does its work), so you might want to turn off uncheck the use of 'Microsoft Symbol Servers' when you don't need it. Note that Visual Studio caches the symbolic information locally, so even after you no longer are probing the Microsoft Symbol Server, you still will be able to step into framework code, as long as the information has already been downloaded (likely if you are debugging the same scenario).
Disabling Framework Optimization (Seeing All Local Variables)
Enabling stepping is often enough (you can follow the flow of execution, however, because the framework code was precompiled as OPTIMIZED code, the debugger often will not have enough information to display local variables. Thus Visual Studio will show a 'Cannot obtain value' message in these cases. This can be frustrating if that variable was very important. By doing two more tweeks, you can solve this problem as well.
- Right click on the Project File in the solution explorer -> Properties -> Debug. Then press the 'Add' button in the 'Environment Variables', and add the environment variable 'COMPLUS_ZapDisable' with the value of 1. Be sure to type 'Ctrl-S' (save) after you make the edit because changes to the property page will NOT take effect by simply running the program, you have to explicitly save it. When you are done the property page should look like this.
- Check the checkbox: Tools -> Options -> Debugging -> General -> Suppress JIT optimization on module load.
The environment variable tells the runtime to disable the use of precompiled images (called 'Zap'ing). This forces the runtime to Just In Time compile framework code. By setting the 'Supress JIT optimization' the debugger then forces the runtime to not optimize this code, which allows the debugger to do a good job viewing local variables.
So happy debugging through the framework! Here are some additional resources
- Dotnet Core Issue 897 - is a gitub issue that is the 'official' source of all the information presented here. It also talks about how to do this on V2.0 (earlier) version of the runtime. It is related to AspNet Issue 4155 which basically asked the same question about ASP.NET framework code. This later issue has a bunch of screenshots that can be useful if the terse instructions above are not enough.
Sadly, things are not quite so automatic if you are developing on Linux rather than Windows. Among other things Visual Studio does not run on Linux (but VSCode does). We are working on making this experience good as well and I hope to write a blog post about how to step through source on non-Windows platforms relatively soon.