Partager via


Managed Debug Assistants (MDAs) are cool.

Whidbey has has added Manage Debug Assistants (MDAs), which are like advanced log messages for diagnostic information. These things are awesome. There appears to be very little written about them. You can enable them in the VS exception dialog.
You can read more about them on MSDN here.

From my perspective, the motivation for MDAs is purely selfish. There's a set of user problems which manifest as CLR bugs. For example, if the user makes a marshalling error in some PInvoke signature, that may manifest as an Access Violation in the CLR. That meant the CLR folks were getting hit with a high support cost for user bugs. Clearly, we need to make it easier for users to debug their own problems rather than rely on us.

So each MDA is some check built into the CLR to help diagnose common low-level problems like these. Other MDAs include things like calling code under the loader lock and failed QI in com-interop components. Currently, only the CLR can add MDAs. In the future, I hope we extend it to allow custom libaries to add them.

MDAs vs. Log Messages:
MDAs are like very rich log messages. OutputDebugString or Debugger.Log just give you back a string. MDAs give you back an unique identifier (which you can use to search for more information about it here), a description string, and rich xml data. Because MDAs map to unique error codes, there's an obvious place to go and get more detailed help on them. Also, their data is structured to a schema, a debugger extension could actually intelligently interpret it and do useful things. 

MDAs vs. Exceptions:
People sometimes use exceptions as notifications.  It's not that they like exceptions, but rather they like the dialog box that most debuggers will popup with an exception. But exceptions are for control flow and shouldn't be used for just debugging notifications.

Part of the problem was that users were adding code that would check if it was under a debugger, and if so, do extra checks and throw an exception. Winforms has a notorious check where it would use a separate window proc under the debugger that wrap handlers in a try-catch block  That handler would then notify the user. This was very bad because running under a debugger isn't supposed to change behavior. So adding debug-only checks that throw exceptions is very bad here.

MDAs are a lot like a "zero-chance exception". They provide the debugger with an opportunity to make an intelligent notification, but they don't actually change control flow. 

VS exposes MDAs through its exception dialog and its UI treats MDAs like exceptions. This lets MDAs have the same granularity for stopping as exceptions do.

MDAs and ICorDebug
We've added first-class support for MDA into ICorDebug.

We've added a new managed debug event (onto ICorDebugManagedCallback2 ) specifically for MDAs. You may notice the parameters have sufficient flexibility to avoid the design flaw that we hit with similar notifications like Exception and LogMessage.

     // Notification that an Managed Debug Assistant (MDA) was hit in the debuggee process.
    // - MDAs are heuristic warnings and do not require any explicit debugger action (other than continue, of course) for proper functionality.
    // - The CLR can change what MDAs are fired (and what data is in any given MDA) at any point.
    // - Therefore, debuggers should not build any specific functionality requiring specific MDAs patterns.
    // - MDAs may be queued and fired "after the fact". This could happen if the runtime needs to slip from when an
    //   MDA occurs to get to a safe point for firing it. It also means the runtime reserves the right to fire a bunch of MDAs
    //   in a single set of callback queue (similar for what we do w/ attach events).
    //
    // See the MDA documentation for how to enable / disable notifications.
    //
    // Parameters:
    // - pController is the controller object (process or appdomain) that the MDA occurred in.
    //     Clients should not make any assumption about whether the controller is a process or appdomain (though they can
    //     always QI to find out).
    //     Call continue on this to resume the debuggee.
    // - pThread - managed thread on which the debug event occured. If the MDA occurred on an unmanaged thread then
    //     this will be null. Get the OS thread ID from the MDA object itself.
    // - pMDA is an object containing MDA information.
    //    Suggested usage is that the client does not keep a reference to the MDA object after returning from this callback
    //    because that lets the CLR quickly recycle the MDA's memory. This could be a performance win if there are
    //    lots of MDAs firing.
    HRESULT MDANotification(
        [in] ICorDebugController * pController,
        [in] ICorDebugThread *pThread,
        [in] ICorDebugMDA * pMDA
    );
 That returns this rich object:
 interface ICorDebugMDA : IUnknown
{
    // Get the string for the type of the MDA. Never empty.
    // This is a convenient performant alternative to getting the XML stream and extracting
    // the type from that based off the schema.
    HRESULT GetName(
        [in] ULONG32 cchName,
        [out] ULONG32 * pcchName,
        [out, size_is(cchName), length_is(*pcchName)] WCHAR szName[]);

    // Get a string description of the MDA. This may be empty (0-length).
    HRESULT GetDescription(
        [in] ULONG32 cchName,
        [out] ULONG32 * pcchName,
        [out, size_is(cchName), length_is(*pcchName)] WCHAR szName[]);

    // Get the full associated XML for the MDA. This may be empty.
    // This could be a potentially expensive operation if the xml stream is large.
    // See the MDA documentation for the schema for this XML stream.
    HRESULT GetXML(
        [in] ULONG32 cchName,
        [out] ULONG32 * pcchName,
        [out, size_is(cchName), length_is(*pcchName)] WCHAR szName[]);

    // Get the flags associated w/ the MDA. New flags may be added in future versions.
    typedef enum CorDebugMDAFlags
    {
    // If this flag is high, then the thread may have slipped since the MDA was fired. 
 MDA_FLAG_SLIP = 0x2
    } CorDebugMDAFlags;
    HRESULT GetFlags([in] CorDebugMDAFlags * pFlags);

    // Thread that the MDA is fired on. We use the os tid instead of an ICDThread in case an MDA is fired on a
    // native thread (or a managed thread that hasn't yet entered managed code and so we don't have a ICDThread
    // object for it yet)
    HRESULT GetOSThreadId([out] DWORD * pOsTid);
};

One of the reasons I decided on making the MDA event hand out an ICorDebugMDA object instead of passing back the data directly in the event notification was flexibility:
1) The XML string may be large. Since it's a field off ICorDebugMDA, we can retrieve it lazily. This lets a debugger do filtering off the name (a small string) and continue without having to read in the the potentially large XML or Description strings.
2) There's a lot of data on the MDA object to jam into callback parameters.
3) This lets us add more data to an MDA, by letting you QI for ICorDebugMDA2, without requiring a new callback method.

Activating MDAs:
Some MDAs are always on under the debugger. This set was chosen very carefully to ensure that they wouldn't change behavior. Some MDAs are implemented by adding additional checks, which means there may be a minor perf hit. Other MDAs must be explicitly enabled. See here for details on how to enable MDAs.

The MDA must be enabled in order for it to fire a debug event. Even once the debug event fires, the debugger still has policy about whether it actually shows the user the MDA. This is very similar to notifications on first-chance exceptions, and VS exposes them both the same way by using the same exception dialog. I think this makes a lot of sense.

I'd recommend turning MDAs on and seeing what they catch. If there are any that are too noisy, you can always turn individual ones off.

Comments

  • Anonymous
    November 10, 2005
    Can you create your own MDAs for your own class library/runtime?

  • Anonymous
    November 10, 2005
    Yup they are really cool. You guess it I want to add my own. How could I deploy them? Or do I have to sign a NDA with MS?

  • Anonymous
    November 10, 2005
    Unfortunately, you can't create your own MDAs. The current ones are very intimate with the CLR.
    We hope to extend it to be a more general mechanism in a future release.

  • Anonymous
    January 30, 2006
    The comment has been removed

  • Anonymous
    January 30, 2006
    The env var is the easiest way to disable MDAs.
    Are you setting COMPLUS_MDA in a way that ensures that the debuggee will inherit that environment var?
    Are you launching your debuggee from VS, or attaching to it under VS?

  • Anonymous
    February 07, 2006
    The ICorDebug API (the API for debugging managed apps) is about 70 total interfaces.  Here is how...

  • Anonymous
    March 21, 2006
    When I wrote a C# program which calls a library which is a wrapper of COM ojbect and debugging...

  • Anonymous
    March 31, 2006
    Stephen Toub writes about MDAs in MSDN Magazine (see here). Whereas I focuses more on the platform implications,...

  • Anonymous
    October 20, 2006
    When I read through the responses for the questionnaire below I saw that many users were not aware of

  • Anonymous
    December 27, 2006
    When you attach to a managed debuggee (via ICorDebug::DebugActiveProcess), ICorDebug generates a set