Udostępnij za pośrednictwem


.NET: Figuring out if your application is exception heavy

Ocean beach

In the past I worked on a 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 codeflow :)

Thankfully CLR provides an easy hook in the form of an AppDomain event. I just need to hook into the AppDomain’s FirstChanceException event and CLR notifies me 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 immediately catches an 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);
        }
    }
}

Line 11 is the event subscription and 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/profiller.

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

  1. From outside a managed exception looks exactly like a native exception and hence the OSes normal exception handling mechanism kicks in
  2. Exception handling requires some mechanism to walk the thread callstack on which the exception is thrown. So that it can find an uplevel 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).
  3. 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
  4. 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
  5. If there is a registered handler for FirstChanceException that is called
  6. JIT is consulted to find appropriate catch block for the exception (none might be found)
  7. The CLR returns the right set of information to the OS indicating that indeed the exception will be processed
  8. The OS initiates the second-pass
  9. 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
  10. This continues till the frame in which the catch was initially found is reached. CLR proceeds to execute the catch block.
  11. If all is well the exception has been caught and processed and peace is restored to the world.

As it should be evident from the above basic flow the FirstChanceHandler will get called before any code gets the chance to catch it and also in case the exception will go unhandled.

PS: Please don’t throw an exception in the FirstChance handler :)

Comments

  • Anonymous
    December 06, 2013
    You know someone was going to ask, what on earth happens when an exception gets thrown out of a FirstChance handler? And what happens if your thread crosses multiple app domains and say only one of them has a firstChance handler registered?

  • Anonymous
    December 13, 2013
    William the answer to the first question is that it will land infinite recursion. I don't know the answer to the later. I'd need to experiment to give a conclusive response.

  • Anonymous
    January 12, 2014
    Hi Abhinaba, Great post. I've been recently investigating a case with production .NET application swallowing exceptions. The only way I was able to diagnose it was to attach a debugger and log all the occuring "1st chances". Then I come up with a simple inject plugin for mdbg that allows injecting arbitrary code into a running .NET application - I used it to alter logging/tracing settings in already running applications (including AppDomain.FirstChanceException event). Maybe someone will find it useful: lowleveldesign.wordpress.com/.../injecting-code-into-net-applications

  • Anonymous
    October 29, 2014
    Except. From your example, and my own experimentation. The stack trace will only reveal to you the method in which the exception was thrown. Which means that (for instance) public void MyBadCode() {     int.Parse("42EEEZ"); } will tell you the exception occurred in System.Number.StringToNumber Which is a private class, and a method called by int.Parse internally And hence you can't tell that MyBadCode is the cause.

  • Anonymous
    October 29, 2014
    Hah, found it: string m = ex.Message + " @ " + (new StackTrace(1)); StackTrace gives you the current methods stack trace (not the exception), and the 1 skips the top frame (the FirstChanceException handler), giving you the stack trace for the faulting method