Viewing Exception Message string
Several people have asked how managed debugger tools can print more details about an exception, such as its message string. Just printing that an exception of type "BadArgumentException" was thrown is a nice start. Managed Exceptions are associated with a rich message string (via the Exception.Message property), which may say something more useful like "Argument 'abc' is outside the exception range.". So how do you print this?
ICorDebugValue is the interesting portion of the API. It's on my blog-list to write more details about this and how to use. I'll answer the specific issue of getting the Exception.Message property, and write about more about ICorDebugValue in general later. MDbg provides the MDbgValue class as a wrapper on top of ICorDebugValue that should make it easier to use.
About Exception.Message:
There are some key caveats:
1) Not all thrown exceptions necessarily derive from System.Exception (which motivates this FxCop rule)! The reasoning was to support languages like MC++ that let you throw any random object. This turns out to be very bad. I believe we "fixed" this late in V2.0 by wrapping non- System.Exception objects. More on this later. The key is that it's possible the Message property may not be available on your exception object.
2) Evaluating the Message property means doing a function evaluation (func-eval). However, func-eval is evil, so we'd like to get this information another way. Note that Exception.Message is virtual, so we need to make sure we evaluate the right instance.
3) An alternative to always doing a function evaluation of the Message property may be to inspect private state. For example, if the Message property is just returning a private field, the debugger could just read the private instead of executing the Message property. Yes, this is very implementation specific and fragile. However, tools are usually updated for the latest compilers, so this is arguably a reasonable optimization.
Getting Message via field:
It turns out the current (V2.0) implementation of System.Exception.get_Message does indeed return a private field, Exception._message.
Using the COM-class ICorDebug API, we get an ICorDebugValue for the exception object from ICorDebugThread::GetCurrentException. We can QI that for an ICorDebugObjectValue, and then we can call ICorDebugObjectValue::GetFieldValue() to try and get the "_message" field. If that succeeds, we get an ICorDebugValue representing the field. Now "_message" is a string, so we QI that for ICorDebugStringValue, and then that interface provides rich methods (GetLength, GetString) to get the contents of the string. What a pain.
Using MDbg's managed wrappers can simplify our lives. I showed a simple harness to print exceptions. That would print the exception's type, but not the message string. Here's some quick code using the wrappers to try and get the exception message string via a private field.
MDbgValue ex = t.CurrentException;
// Try to get the string message;
// Note the object may not actually derive from System.Exception.
// Ideally, we'd call Exception::get_Message. However, that involves a func-eval, which is evil.
// We could cheat and access a *private* field, "Exception::_message". That may update
// in future versions, but it's good enough for now.string msg = "<unknown>"; try
{
msg = ex.GetField( "_message").GetStringValue(false ) ;
}
catch (Exception e)
{
}
Console.WriteLine("Exception message:" + msg);
Empirically, this works very well. It does not try a func-eval on Exception.get_Message.
Comments
- Anonymous
October 02, 2005
This is only tangentially related, but I wrote up a few posts that walk through doing the same thing in WinDbg a few days ago. The first one is here, the rest follow it on my site:
http://mcfunley.com/blog/archive/2005/09/17/568.aspx