In the past I worked on an application which used modules from different teams. Many of these modules raised and caught a ton of exceptions. So much so that performance data was showing that these exceptions were causing issues. So I had to figure out an easy way to programmatically find out these code and inform their owners that exception is for exceptional scenarios and shouldn’t be used for normal code-flow :)
Thankfully CLR provides an easy hook in the form of an AppDomain event. I just need to hook into the AppDomain.FirstChanceException event and CLR notifies upfront when the exception is raised. It does that even before any managed code gets a chance to handle it (and potentially suppresses it).
The following is a plugin which throws and catches exception.
namespace Plugins { public class FunkyPlugin { public static void ThrowingFunction() { try { Console.WriteLine("Just going to throw"); throw new Exception("Cool exception"); } catch (Exception ex) { Console.WriteLine("Caught a {0}", ex.Message); } } } }
In the main application I added code to subscribe to the FirstChanceException event before calling the plugins
using System;
using System.Runtime.ExceptionServices;
using System.Reflection;
namespace foo
{
public class Program
{
static void Main()
{
// Register handler
AppDomain.CurrentDomain.FirstChanceException += FirstChanceHandler;
Plugins.FunkyPlugin.ThrowingFunction();
}
static void FirstChanceHandler(object o,
FirstChanceExceptionEventArgs e)
{
MethodBase site = e.Exception.TargetSite;
Console.WriteLine("Thrown by : {0} {1}({2})", site.Module,
site.DeclaringType,
site.ToString());
Console.WriteLine("Stack: {0}", e.Exception.StackTrace);
}
}
}
The FirstChanceHandler just dumps out the name of the assembly and type that raises the exception. The output of this program is as follows
Just going to throw Thrown by : some.dll Plugins.FunkyPlugin(Void ThrowingFunction()) Stack: at Plugins.FunkyPlugin.ThrowingFunction() Caught a Cool exception
As you can see the handler runs even before the catch block executes and I have the full information of the assembly, type and method that throws the exception.
Behind the Scene
For most it might suffice to know that the event handler gets called before anyone gets a chance to handle the exception. However if you care about when this is fired, then its in the first pass (first chance) just after the runtime notifies the debugger/profiler.The managed exception system piggy backs on native OS exception handling system. Though the x86 exception handling (FS:0 based chaining) is significantly different from the x64 (PDATA) it has the same basic idea
- From outside a managed exception looks exactly like a native exception and hence the OSes normal exception handling mechanism kicks in
- Exception handling requires some mechanism to walk the thread callstack on which the exception is thrown. So that it can find an up-level catch block as well as call the finally block of all functions in-between the catch and the point of exception being thrown. The mechanism varies in between x86 and x64 but is not super relevant for our discussion. (a series of data-structures pushed onto the stack in case of x86 or a series of data-structure table registered with OS in x64).
- On an exception the OS walks the stack and for managed function frames calls into CLR’s registered personality routine (that's what its called :)). This routine knows how to handle managed exceptions
- This routine notifies the profiler then the debugger of this first-chance exception, so that debugger can potentially break on the exception and do other relevant operations. If debugger did not handle the first chance exception the processing of the exception continues
- If there is a registered handler for FirstChanceException that is called
- JIT is consulted to find appropriate catch block for the exception (none might be found)
- The CLR returns the right set of information to the OS indicating that indeed the exception will be processed
- The OS initiates the second-pass
- For every function in between the frame of exception and the found catch block the CLR’s handler routine is called and the CLR consults the JIT to find the appropriate finally blocks and proceeds to call them for cleanup. In this phase the stack actually starts unwinding
- This continues till the frame in which the catch was initially found is reached. CLR proceeds to execute the catch block.
- If all is well the exception has been caught and processed and peace is restored to the world.
PS: Please don’t throw an exception in the FirstChance handler :)
No comments:
Post a Comment