Catch, Rethrow, and Debuggability


When you catch and rethrow an exception, the stack is unwound by the time the catch clause is executed, and thus you may lose valuable information in the debugger.


Consider the following code snippet:

    static void Main(string[] args)
{
Func1a();
}

static void Func1a()
{
try
{
Func2();
}
catch
{
throw; // rethrown, this goes unhandled
}
}

static void Func2()
{
int x = 5;
throw new Exception(“Test”); // original first chance exception
}


If you stop in the debugger when the exception is first thrown, you get the full callstack right at the throw point :

  random.exe!Program.Func2() C#
random.exe!Program.Func1b() C#
random.exe!Program.Main(string[] args = {Dimensions:[0]}) C#

You can then use the full power of the debugger, such as inspecting parameters, locals,  interceptable exceptions, mixed-mode callstacks (if interop-debugging), native assembly windows, etc.



However, if you stop at the catch/rethrow point, the stacktrace in the debugger is:

  random.exe!Program.Func1b() C#
random.exe!Program.Main(string[] args = {Dimensions:[0]}) C#

Now Func2(), the original throw site, is no longer on the stack in the debugger. 


Other trivia:



  1. What about the Exception.StackTrace property? Exceptions have a StackTrace property which is a string representation of the stack trace. It is not nearly as rich as the stack trace under the debugger as mentioned above.

  2. ‘throw;’ vs. ‘throw e;’?  These affect the exception object that’s thrown, but don’t affect the callstack from which it’s thrown. Imagine this scenario:
            catch (Exception e)
    {
    if (…)
    throw;
    else
    throw e;
    }

    The callstack would be determined by the time you entered the catch block, and so would be the same in both cases.


  3. Does it have to be this way? This is a natural implementation resulting from Structured Exception Handling (SEH) because catch handlers are executed in the 2nd-pass. See Matt Pietrek’s excellent SEH article for more gory details. You could, of course, imagine alternative implementations.

  4. What about unmanaged code? This is also true for unmanaged code (see Matt’s article). However, unmanaged C++ exceptions don’t even have stacktrace properties, so that case is even harder to debug.

  5. The example above uses catch-all (which is frowned upon); but this rethrow problem is also true for catching specific exceptions.

  6. [Update 2/12/07]:  Here’s a technique using JMC to make catch / rethrow more debuggable.

The practical side:



  1. Some library functions will automatically catch + rethrow. For eg:
    – MethodInfo.Invoke will catch and rethrow exceptions as a TargetInvocationException, with an inner exception of the original exception.
    – Exceptions crossing appdomain boundaries will get caught + marshaled across the appdomain boundary + rethrown  on the other side.

  2. People commonly get burned by this because most people (for good reason) don’t stop on first-chance exceptions. So by the time you gets the unhandled exception notification, the useful info has already been lost via catch/rethrow.  

  3. This can be especially tough for unhandled exception bug reports.


Comments (18)

  1. Peter Ritchie says:

    Nice collection of usually disparate information.

    One thing I would add is that you sometimes want to make use of the stack-hiding side effects of rethrow.  If you’re calling into security-related code, or making use of code that sends sensitive information as parameters (i.e. on the stack) a catch/rethrow will make sure that sensitive information isn’t propagated.

  2. Peter –

    That’s a good reason to use catch/rethrow for an app; ignoring  the debugger.

    I want to be clear to others thought that the debugger has complete control over a debuggee. While an app can hide things from other parts of the app (via CAS, etc), the debuggee can’t hide things from a determined debugger.

    Regardless, as cute trivia, if you *really* want to hide the stack, consider:

    Exception e = null;

    try {

      SecuritySensitiveMethod();

    }

    catch(Exception e2)

    {

      e = e2;

    }

    if (e != null) throw MyNewException(e);

  3. Is there a way to make the debugger stop for the case above even if that is not my code?

    I find that I need that fairly often, when exception redirection is done two layers too deep and I don’t get the correct information

  4. It also changes the line number in the stacktrace in the method that rethrows (since the rethrow becomes the site of throw in that method).

  5. DAL says:

    Mike,

    FWIW, some trivia…

    The stack isn’t "lost" if the original exception is rethrown as the inner exception of a new exception object, which chains the exception objects together. Using a catch-wrap-throw pattern ensures that the original stack trace is available, even across multiple catch–wrap-throws.

      try

       {

       Func();

       }

      catch(Exception ex)

       {

       throw new Exception("msg",ex);         }

       }

    The stack trace of the outermost exception will begin at the callsite of the last throw statement, but you can examine the inner exception for the original stack trace. A routine that unrolls all the exceptions and merges the stack traces can produce an output that looks like a single stack trace. (This also makes it possible to add context info as part of the message at each rethrow point)

    The debugger makes it easy to access the inner exceptions, all the way back to the callsite of the original throw statement, and this will work even if the exception is eventually unhandled.

  6. DAL – what you’re talking about is the Exception.StackTrace property that I mentioned above. That’s a useful thing, and it’s nice you have inner exceptions to chain them together.

      throw new Exception("msg",ex);

    and

      throw;

    are very similar with this respect.

    My point is that the stack has been unwound by the time the catch is executing (and thus I’d say "lost"). You have a string representation via the StackTrace property, but that falls short of being able to actually view it in the debugger’s callstack window and do the things I mentioned above.

  7. DAL says:

    You are, of course, correct. The ability of the debugger to actually walk back up the call stack is gone once the stack has been unwound, which it has been by the time the catch handler is invoked. You only get the full power of the debugger in the context of the call.

    Your original comments, as well as some of the responders, mentioned the stacktrace property, which is why I wrote about this other aspect of it. The stack trace can preserve the text representation of call stack, but that is not the same as actual call stack.

  8. I previously mentioned that catch / rethrow an exception impedes debuggability because the callstack

  9. Brian Chapman says:

    So what are the usefull differences between "throw" and "throw e"?

    in:

    catch(Exception e)

    {

     if(test)

       throw;

     else

       throw e;

    }

  10. Someone asked what the practical differences are between ‘throw;’ (no arguments) and ‘throw object;’

  11. Brian –

    Nobody should ever write code like that. I was just using it as an academic example to proove a point about callstack unwinding.  I should have been clearer.

    Here is more comparison between throw + throw e:

    http://blogs.msdn.com/jmstall/archive/2007/02/15/throw-vs-rethrow.aspx

  12. ... says:

    Very nice site! Good work.

  13. ... says:

    Nice design, good graphical content. I think I’ll come back later again;)

  14. I just noticed that my blog had birthday #3 (Sep 30th) . In tradition, some various stats… 384 posts.

  15. Nates Stuff says:

    It’s time for another episode of Good Idea / Bad Idea: Good Idea! Catching a specific exception from

  16. Anders Liu says:

    Anders Hejlsberg,C#和.NET框架背后的创造型天才,在12年前加入微软之前,他就以编译器编写者的身份驰名16年了。他的BLS Pascal、Turbo Pascal和Delphi彻底变革了软件开发方式。今天,他依然能够冒出新的想法和激进的倡议。