Home > Articles > Programming > C#

.NET Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Managed Debugging Assistants in .NET 2.0

Last updated Mar 14, 2003.

One of the disadvantages of working in a purely managed environment is that you relinquish some level of control to the framework. In a native environment you can use your debugger to single-step the machine code and set breakpoints at any point in the executing program, whether it be your code or the operating system kernel. You can’t do that in a managed environment and as a result things can be a lot more difficult to debug.

In .NET programming, the ability to see what’s going on at the machine level usually poses a problem only when we’re working with low-level functionality like loading assemblies or interoperating with unmanaged code. This isn’t surprising, as it’s usually the "edges"--the interfaces between components--that usually end up being the problematic parts of any significant program.

.NET 2.0 introduced Managed Debugging Assistants, MDAs, which are probes into the common language runtime (CLR) and base class libraries (BCL). These probes, which you can turn on and off with configuration files, provide information on the CLR’s current state and on events--information that you could not normally access. Some MDAs also modify behavior to help expose bugs that would otherwise be very difficult to isolate.

Currently there are 42 MDAs, which fall into three categories: Detection, Behavioral, and Informational. Informational MDAs provide information about the current state of the system and are off by default. Behavioral MDAs, which also are turned off by default, modify the behavior of the CLR in an attempt to highlight specific bugs in a developer’s code. Detection MDAs directly identify error conditions that are occurring--they’re more like asserts. Some detection MDAs are enabled by default and others are easily enabled depending on the environment.

What MDAs Are Available?

Of the 42 available MDAs, you’ll likely find some much more useful than others. CallbackOnCollectedDelegate, for example, is very useful if you have a delegate that’s being called by unmanaged code. Very often, those delegates get collected by the garbage collector and result in what is essentially a null pointer reference. That bug is very difficult to track down. Enabling the CallbackOnCollectedDelegate MDA prevents the delegate from being deleted when it’s collected. Instead, it’s redirected to display some information that tells you why your program is about to die.

StreamWriteBufferedDataLost is a very handy little tool that will notify you if you destroy a StreamWriter before calling Flush or Close on it. Failing to flush or close the file after you’ve written to it can cause you to lose data. You’d be surprised by how many programmers think that the finalizer will automatically flush the file.

If you do a lot of interoperating with unmanaged code, you probably want to enable GcManagedToUnmanaged and GcUnmanagedToManaged. Both of these force garbage collections at the boundary between managed and unmanaged code, which will help to identify memory errors like heap corruption and trying to access collected items.

That’s just a handful of the MDAs that I’ve found useful. You should review all of them just to see what’s available so that you’ll be prepared for your next debugging session.

Configuring MDAs

In Visual Studio 2005, you can enable and disable the Detection MDAs through the Exceptions dialog (select Exceptions from the Debug menu). The Exceptions dialog, with the MDA list scrolled into view, is shown in Figure 70.

Figure 70

Figure 70 - Enabling Detection MDAs with the Visual Studio 2005 Exceptions Dialog

If an enabled MDA is encountered at runtime, Visual Studio will halt debugging with an Exception Assistant dialog.

If you’re running under an unmanaged debugger (i.e. not in Visual Studio), then only two of the MDAs are enabled by default. In order to configure the MDAs, you need to create a configuration file and either update the registry or set an environment variable.

MDA configuration is stored in a file called AppName.exe.mda.config, where AppName is replaced by the name of the application. For example, if the program is foo.exe, then the MDA configuration file is foo.exe.mda.config.

The runtime must be instructed to look for the MDA configuration file. You can either set the Registry key HKEY_LOCAL_MACHINE\Software\Microsoft\.NetFramework\MDA to "1", or set the environment variable COMPLUS_MDA equal to 1. It’s more convenient to set the registry key, of course, but that requires administrator privileges and affects all applications on the system. Enabling the MDAs on a per-application basis using the COMPLUS_MDA environment variable is a better idea.

The format of the MDA configuration file is very similar to the application configuration file. Here’s an example that enables a handful of MDAs.

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

  <callbackOnCollectedDelegate />
  <gcUnmanagedToManaged />
  <invalidFunctionPointerInDelegate />
  <loadFromContext />
  <pInvokeLog />
  <pInvokeStackImbalance />

  <streamWriterBufferedDataLost />

Some MDAs allow configuration parameters. See the SDK documentation on the individual MDAs for full configuration information.

To run a program with the MDAs enabled, make sure that you’ve created the configuration file correctly, set the COMPLUS_MDA environment variable to 1, and execute the program. It should read the configuration file and begin executing with whatever MDAs you enabled.