How to use RtlUnwindEx

Disclaimer:  I’ve never actually written an unwind personality routine, so take what’s here with a grain of salt.

A few days back, I spent 30 minutes defending the C++ runtime’s exception handling personality routine to the guy that has the less than enviable job of supporting the .Net Framework’s exception handling personality routine.  The eventual point to be learned is that the documentation for RtlUnwindEx is abysmal, and can cause the VC 8.0 C++ runtime to leak objects left and right.

The problem revolves around the C++ team’s decision (which I fully support) to cause code compiled with /EHs or /EHsc flags to only invoke destructors when a C++ exception occurs.  This means that if you take a hardware exception, call RaiseException yourself, or call longjmp none of your C++ destructors between the point of exception and the handler will be invoked. I’ve talked about why this is a good idea, but I’ll sum it up with this:  If that scenario seems bad to you, then you should be compiling your code with /EHa.

Anyway, back to the issue:  Say I’m writing a ‘personality routine’ which will be inovked by the OS when an exception occurs.  If you’re writing the kind that requires that your handler initiates the second phase of unwind, you need to call RtlUnwindEx.  The documentation, which is arguably bad (it’s little more that a function declaration), indicates that the EXCEPTION_RECORD structure to pass in is optional.  And that’s true:  the functions on the stack will still be unwound.  But the personality routines for those functions might want to actually know that they’re being invoked due to an exception, and not for something like a longjmp.  So, if you have an exception record that could be passed to the RtlUnwindEx function, you should.  If you don’t, another personality routine might assume that there’s not an exception to examine, and might decide to do something like NOT invoke the destructors for the object on the stack.

BTW – this bug exists only on x86, and only when you’re crossing a managed code -> native code boundary, in particularly weird scenarios.  And I hope it’s getting fixed 🙂