Build complete business applications in XAML.
Debugging Cocktail Applications
I am occasionally being asked by developers how they can step into the Cocktail source code while debugging their own applications. There are some of you out there with a desire to understand what’s happening under the hood, or are chasing an obscure bug and seeing what’s happening in Cocktail may shed some more light on the issue. I can relate to that. There also seems to be a lack of knowledge around how to debug third-party open source libraries in general. We get to that in the section about debugging with a symbol server.
Let’s look at some of the basic concepts behind debugging in .NET. When building a .NET assembly you may have noticed that the output of the build is at a minimum two files. There is the assembly itself and then there is a second file with the same name as the assembly, but with extension pdb. That second file is what makes debugging of the assembly possible. The pdb extension stands for Program Debug Database and it contains two vital pieces of information. First, it contains the so called debug symbols. A debug symbol is information that expresses which programming-language constructs generated a specific piece of IL (Intermediate Language) in a given assembly. This is the crucial information the Visual Studio debugger needs in order to tie the current executing code back to the correct location in the source code. The second piece of information in the pdb file is the location of the corresponding source code. This, if you built the assembly yourself, is the path to where you stored the source code on your local disk at the time of the build.
Difference between Debug and Release builds
In native languages such as C++, where the output of the build is actual machine code instead of IL, there is a significant difference between a debug build and a release build and it is generally not possible to debug a release build. During a release build, the compiler optimizes the emitted machine code, often optimizing away entire sections such that the machine code can no longer be related to the programming-language constructs that generated it. In other words the compiler optimizes the machine code for performance instead of ease of debugging.
With managed code, this optimization step happens at runtime. The release assembly simply contains a flag telling the JIT (Just-in-time) compiler to optimize the code at runtime and you will still find the pdb file alongside a release assembly. In addition the runtime optimizations can be disabled at any point.
The main difference comes down to the use of conditional compiling where sections of the code are deliberately marked to only be included under a debug build configuration. Such sections will naturally be missing from a release build and as such can’t be debugged. In particular for Cocktail, the out-of-the-box logging of diagnostics information is disabled in the release build. At any point a release assembly can simply be swapped out for the corresponding debug assembly if required.
Referencing the project
The most common approach developers take to debugging an open source third-party library is to reference the project instead of the assembly. That’s easy to do. Simply add the project to one’s solution and then reference it from other projects.
One of the issues with this approach in my opinion is the solution bloat. It adds additional projects to the probably already big solution. Moreover though, if one of the open source third-party libraries references another open source third-party library, you’ll find yourself in a little bit of a pickle.
For example Cocktail references Caliburn.Micro. If you add both the Cocktail project and the Caliburn.Micro project to your solution you’ll find yourself having to modify the Cocktail project so that it references the Caliburn.Micro project in your solution instead of the Caliburn.Micro assembly that ships with Cocktail. This will in turn complicate your upgrade path and may lead to incompatibilities if you are not using the same Caliburn.Micro revision that Cocktail was tested with.
On the upside, with this approach you’ll be able to debug your application and step into Cocktail as well as Caliburn.Micro and inspect all the source code to your heart’s content.
Another benefit of this approach is that when you build your application in debug mode, all the third-party libraries will also be built in debug mode and similarly if you make a release build of your application.
Build assemblies yourself
Instead of referencing the projects you can instead build the projects on your local machine and then reference the built assemblies. Remember that the pdb file contains the location of the source code and in this case that will be the location on your local machine.
All you have to make sure is that you put the pdb file in the same folder as the referenced assembly. Visual Studio will inspect the pdb file and load the source code from the location in the file.
The downside of this approach is in a team of developers you need to make sure that the source code location in the pdb is the correct location on every developer’s machine. You either have to build from a common location or you enforce a certain local directory structure for the entire team.
Taking advantage of symbol servers
I personally don’t like either of the above approaches, because of where everything is going with NuGet. I’ve been bitten by the NuGet bug and so has Microsoft. Future versions of .NET will have more and more of the APIs distributed as individual NuGet packages instead of one big installer for the entire framework. NuGet is already the only way to get the latest version of Entity Framework. MEF (Managed Extensibility Framework) which both Cocktail and DevForce depend on will not be part of Windows Runtime and instead will be distributed via NuGet for those who want to use it.
I prefer to manage my third-party dependencies with NuGet. All NuGet packages go into a packages folder that sits alongside the solution and assemblies distributed via NuGet are not put in the GAC avoiding all the issues with having assemblies in the GAC, often referred to as the new DLL hell. NuGet Package Restore automatically downloads all the necessary dependencies without having to commit those binaries to source control. Furthermore, using the Package Manager is a breeze to upgrade individual libraries to newer versions for the entire solution.
Cocktail as you may know is in fact being distributed via NuGet and it is our recommended way of adding it to your own projects. Since a NuGet package can itself have dependencies to other NuGet packages, this approach ensures that all necessary dependencies are added to your projects. This will become even more important moving forward as Microsoft continues to break up the .NET Framework into individual packages.
Having said all that, how does one debug or browse the source code of third-party libraries in this case? The answer is by using symbol servers, provided the author of the libraries publishes the symbols. The good news is that I as the author of Cocktail publish the Cocktail symbols to SymbolSource.org. As of this writing, the Caliburn.Micro symbols are not being published just yet, but I’m working with Rob Eisenberg to get this fixed.
So, what is a symbol server? A symbol server hosts the entire source code for a particular third-party library along with the pdb files. With a little bit of configuration, Visual Studio automatically downloads the debug symbols and source code from the appropriate symbol server when necessary. This happens whenever you step into third-party code or use the object browser to look at a third-party class.
We probably all wishfully think that we always write bug free code, but at least once in a while even the best have to debug our own or somebody else’s bug. I hope this information has brought you some new-found enthusiasm for debugging and maybe uncovered some new features in the tools you already use. While this post is primarily about debugging, I hope that if you don’t already do you’ll soon use NuGet to manage your dependencies and of course take advantage of symbol servers.