Using IL exception filters for a better debugger experience
Visual Studio 2005 brings with it a new technology -- Just My Code. One nice feature that comes with Just My Code is the ability to stop on exceptions that you care about, without stopping on all exceptions. Usually this just works for you because something in a library that you are using (say WinForms), is eating all the exceptions that you want to stop on. Since your code didn't handle the exception, the debugger will stop.
However, sometimes your program might want to be able to tell the debugger when to stop. It turns out that it is possible to do this by using IL exception filters. With exception filters, you can divide the world into critical exceptions (which you want to stop on), and expected exceptions (which you don't want to stop on).
I think its easiest to show this with an example. Suppose you have some C# code, and you always want to stop on NullReferenceExceptions, but you don't actually care about anything else. Here is how you could do this.
enum DebuggerExceptionDisposition
{
DebuggerStop,
DebuggerIgnore
};
delegate void UserRoutine();
delegate DebuggerExceptionDisposition DebuggerExceptionFilter(Exception e);
[DebuggerNonUserCode]
static class ExceptionBoundry
{
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.ForwardRef)]
static public extern Exception Invoke(UserRoutine routine, DebuggerExceptionFilter filter);
};
class Program
{
static void Main(string[] args)
{
UserRoutine userRoutine = delegate()
{
//throw new ArgumentException();
throw new System.IO.FileNotFoundException();
};
DebuggerExceptionFilter filter = delegate(Exception e)
{
if (e is NullReferenceException)
{
return DebuggerExceptionDisposition.DebuggerStop;
}
return DebuggerExceptionDisposition.DebuggerIgnore;
};
try
{
ExceptionBoundry.Invoke(userRoutine, filter);
}
catch
{
}
}
}
The idea behind ExceptionBoundry.Invoke is that non-user code that will catch any exception that 'filter' tells it to catch (by returning DebuggerStop).
Here is the implementation for ExceptionBoundry.Invoke:
.class private abstract auto ansi sealed beforefieldinit cs.ExceptionBoundry
extends [mscorlib]System.Object
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 )
.method public hidebysig static class [mscorlib]System.Exception
Invoke(class cs.UserRoutine routine,
class cs.DebuggerExceptionFilter 'filter') cil managed
{
.maxstack 2
.locals init ([0] class [mscorlib]System.Exception e)
.try
{
ldarg.0
callvirt instance void cs.UserRoutine::Invoke()
leave.s EOF
} // end .try
filter
{
castclass class [mscorlib]System.Exception
stloc.0
ldarg.1
ldloc.0
callvirt instance valuetype cs.DebuggerExceptionDisposition cs.DebuggerExceptionFilter::Invoke(class [mscorlib]System.Exception)
ldc.i4.0
ceq
endfilter
}
{
leave.s EOF
} // end handler
EOF: ldloc.0 // return the first local
ret
} // end of method ExceptionBoundry::Invoke
} // end of class cs.ExceptionBoundry
To add it to your program, you could:
- Put it in another assembly
-or- - Add a post build step that will disassembly your program, add the new IL, and reassemble your program
To disassemble your program:
"<Visual Studio Directory>\SDK\v2.0\Bin\ildasm.exe" ..\cs\bin\debug\cs.exe /out=program.il /linenum
To assemble your program:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ilasm.exe /quiet /exe /debug=impl /OUTPUT=..\ILExceptionFilter.exe program.il
Comments
- Anonymous
March 23, 2006
Nice. I often wondered if there was an easy way to add filters to C#. - Anonymous
May 24, 2006
A vectored exception handler (see kernel32!AddVectoredExceptionHandler) lets you add to a global list... - Anonymous
December 22, 2008
Often, when an unexpected exception occurs in production code, applications want to generate (and potentially