Exception Handling Differences Between 32/64 Bit
- by Alois Kraus
I do quite a bit of debugging .NET applications but from time to time I see things that are impossible (at a first look). I may ask you dear reader what your mental exception handling model is. Exception handling is easy after all right? Lets suppose the following code: private void F1(object sender, EventArgs e)
{
try
{
F2();
}
catch (Exception ex)
{
throw new Exception("even worse Exception");
}
}
private void F2()
{
try
{
F3();
}
finally
{
throw new Exception("other exception");
}
}
private void F3()
{
throw new NotImplementedException();
}
What will the call stack look like when you break into the catch(Exception) clause in Windbg (32 and 64 bit on .NET 3.5 SP1)?
The mental model I have is that when an exception is thrown the stack frames are unwound until the catch handler can execute. An exception does propagate the call chain upwards.
So when F3 does throw an exception the control flow will resume at the finally handler in F2 which does throw another exception hiding the original one (that is nasty) and then the new Exception will be catched in F1 where the catch handler is executed. So we should see in the catch handler in F1 as call stack only the F1 stack frame right? Well lets try it out in Windbg. For this I created a simple Windows Forms application with one button which does execute the F1 method in its click handler. When you compile the application for 64 bit and the catch handler is reached you will find with the following commands in Windbg
Load sos extension from the same path where mscorwks was loaded in the current process
.loadby sos mscorwks
Beak on clr exceptions
sxe clr
Continue execution
g
Dump mixed call stack container C++ and .NET Stacks interleaved
0:000> !DumpStack
OS Thread Id: 0x1d8 (0)
Child-SP RetAddr Call Site
00000000002c88c0 000007fefa68f0bd KERNELBASE!RaiseException+0x39
00000000002c8990 000007fefac42ed0 mscorwks!RaiseTheExceptionInternalOnly+0x295
00000000002c8a60 000007ff005dd7f4 mscorwks!JIT_Throw+0x130
00000000002c8c10 000007fefa6942e1 WindowsFormsApplication1!WindowsFormsApplication1.Form1.F1(System.Object, System.EventArgs)+0xb4
00000000002c8c60 000007fefa661012 mscorwks!ExceptionTracker::CallHandler+0x145
00000000002c8d60 000007fefa711a72 mscorwks!ExceptionTracker::CallCatchHandler+0x9e
00000000002c8df0 0000000077b055cd mscorwks!ProcessCLRException+0x25e
00000000002c8e90 0000000077ae55f8 ntdll!RtlpExecuteHandlerForUnwind+0xd
00000000002c8ec0 000007fefa637c1a ntdll!RtlUnwindEx+0x539
00000000002c9560 000007fefa711a21 mscorwks!ClrUnwindEx+0x36
00000000002c9a70 0000000077b0554d mscorwks!ProcessCLRException+0x20d
00000000002c9b10 0000000077ae5d1c ntdll!RtlpExecuteHandlerForException+0xd
00000000002c9b40 0000000077b1fe48 ntdll!RtlDispatchException+0x3cb
00000000002ca220 000007fefdaeaa7d ntdll!KiUserExceptionDispatcher+0x2e
00000000002ca7e0 000007fefa68f0bd KERNELBASE!RaiseException+0x39
00000000002ca8b0 000007fefac42ed0 mscorwks!RaiseTheExceptionInternalOnly+0x295
00000000002ca980 000007ff005dd8df mscorwks!JIT_Throw+0x130
00000000002cab30 000007fefa6942e1 WindowsFormsApplication1!WindowsFormsApplication1.Form1.F2()+0x9f
00000000002cab80 000007fefa71b5b3 mscorwks!ExceptionTracker::CallHandler+0x145
00000000002cac80 000007fefa70dcd0 mscorwks!ExceptionTracker::ProcessManagedCallFrame+0x683
00000000002caed0 000007fefa7119af mscorwks!ExceptionTracker::ProcessOSExceptionNotification+0x430
00000000002cbd90 0000000077b055cd mscorwks!ProcessCLRException+0x19b
00000000002cbe30 0000000077ae55f8 ntdll!RtlpExecuteHandlerForUnwind+0xd
00000000002cbe60 000007fefa637c1a ntdll!RtlUnwindEx+0x539
00000000002cc500 000007fefa711a21 mscorwks!ClrUnwindEx+0x36
00000000002cca10 0000000077b0554d mscorwks!ProcessCLRException+0x20d
00000000002ccab0 0000000077ae5d1c ntdll!RtlpExecuteHandlerForException+0xd
00000000002ccae0 0000000077b1fe48 ntdll!RtlDispatchException+0x3cb
00000000002cd1c0 000007fefdaeaa7d ntdll!KiUserExceptionDispatcher+0x2e
00000000002cd780 000007fefa68f0bd KERNELBASE!RaiseException+0x39
00000000002cd850 000007fefac42ed0 mscorwks!RaiseTheExceptionInternalOnly+0x295
00000000002cd920 000007ff005dd968 mscorwks!JIT_Throw+0x130
00000000002cdad0 000007ff005dd875 WindowsFormsApplication1!WindowsFormsApplication1.Form1.F3()+0x48
00000000002cdb10 000007ff005dd786 WindowsFormsApplication1!WindowsFormsApplication1.Form1.F2()+0x35
00000000002cdb60 000007ff005dbe6a WindowsFormsApplication1!WindowsFormsApplication1.Form1.F1(System.Object, System.EventArgs)+0x46
00000000002cdbc0 000007ff005dd452 System_Windows_Forms!System.Windows.Forms.Control.OnClick(System.EventArgs)+0x5a
Hm okaaay. I see my method F1 two times in this call stack. Looks like we did get some recursion bug. But that can´t be given the obvious code above. Let´s try the same thing in a 32 bit process.
0:000> !DumpStack
OS Thread Id: 0x33e4 (0)
Current frame: KERNELBASE!RaiseException+0x58
ChildEBP RetAddr Caller,Callee
0028ed38 767db727 KERNELBASE!RaiseException+0x58, calling ntdll!RtlRaiseException
0028ed4c 68b9008c mscorwks!Binder::RawGetClass+0x20, calling mscorwks!Module::LookupTypeDef
0028ed5c 68b904ff mscorwks!Binder::IsClass+0x23, calling mscorwks!Binder::RawGetClass
0028ed68 68bfb96f mscorwks!Binder::IsException+0x14, calling mscorwks!Binder::IsClass
0028ed78 68bfb996 mscorwks!IsExceptionOfType+0x23, calling mscorwks!Binder::IsException
0028ed80 68bfbb1c mscorwks!RaiseTheExceptionInternalOnly+0x2a8, calling KERNEL32!RaiseExceptionStub
0028eda8 68ba0713 mscorwks!Module::ResolveStringRef+0xe0, calling mscorwks!BaseDomain::GetStringObjRefPtrFromUnicodeString
0028edc8 68b91e8d mscorwks!SetObjectReferenceUnchecked+0x19
0028ede0 68c8e910 mscorwks!JIT_Throw+0xfc, calling mscorwks!RaiseTheExceptionInternalOnly
0028ee44 68c8e734 mscorwks!JIT_StrCns+0x22, calling mscorwks!LazyMachStateCaptureState
0028ee54 68c8e865 mscorwks!JIT_Throw+0x1e, calling mscorwks!LazyMachStateCaptureState
0028eea4 02ffaecd (MethodDesc 0x7af08c +0x7d WindowsFormsApplication1.Form1.F1(System.Object, System.EventArgs)), calling mscorwks!JIT_Throw
0028eeec 02ffaf19 (MethodDesc 0x7af098 +0x29 WindowsFormsApplication1.Form1.F2()), calling 06370634
0028ef58 02ffae37 (MethodDesc 0x7a7bb0 +0x4f System.Windows.Forms.Control.OnClick(System.EventArgs))
That does look more familar. The call stack has been unwound and we do see only some frames into the history where the debugger was smart enough to find out that we have called F2 from F1. The exception handling on 64 bit systems does work quite differently which seems to have the nice property to remember the called methods not only during the first pass of exception filter clauses (during first pass all catch handler are called if they are going to catch the exception which is about to be thrown) but also when the actual stack unwind has taken place. This makes it possible to follow not only the call stack right at the moment but also to look into the “history” of the catch/finally clauses. In a 64 bit process you only need to look at the ExceptionTracker to find out if a catch or finally handler was called. The two frames ProcessManagedCallFrame/CallHandler does indicate a finally clause whereas CallCatchHandler/CallHandler indicates a catch clause.
That was a interesting one. Oh and by the way if you manage to load the Microsoft symbols you can also find out the hidden exception which. When you encounter in the call stack a line
0016eb34 75b79617 KERNELBASE!RaiseException+0x58 ====> Exception Code e0434f4d cxr@16e850 exr@16e838
Then it is a good idea to execute
.exr 16e838
!analyze –v
to find out more. In the managed world it is even easier since we can dump the objects allocated on the stack which have not yet been garbage collected to look at former method parameters. The command
!dso
which is the abbreviation for dump stack objects will give you
0:000> !dso
OS Thread Id: 0x46c (0)
ESP/REG Object Name
0016dd4c 020737f0 System.Exception
0016dd98 020737f0 System.Exception
0016dda8 01f5c6cc System.Windows.Forms.Button
0016ddac 01f5d2b8 System.EventHandler
0016ddb0 02071744 System.Windows.Forms.MouseEventArgs
0016ddc0 01f5d2b8 System.EventHandler
0016ddcc 01f5c6cc System.Windows.Forms.Button
0016dddc 020737f0 System.Exception
0016dde4 01f5d2b8 System.EventHandler
0016ddec 02071744 System.Windows.Forms.MouseEventArgs
0016de40 020737f0 System.Exception
0016de80 02071744 System.Windows.Forms.MouseEventArgs
0016de8c 01f5d2b8 System.EventHandler
0016de90 01f5c6cc System.Windows.Forms.Button
0016df10 02073784 System.SByte[]
0016df5c 02073684 System.NotImplementedException
0016e2a0 02073684 System.NotImplementedException
0016e2e8 01ed69f4 System.Resources.ResourceManager
From there it is easy to do
0:000> !pe 02073684
Exception object: 02073684
Exception type: System.NotImplementedException
Message: Die Methode oder der Vorgang sind nicht implementiert.
InnerException: <none>
StackTrace (generated):
SP IP Function
0016ECB0 006904AD WindowsFormsApplication2!WindowsFormsApplication2.Form1.F3()+0x35
0016ECC0 00690411 WindowsFormsApplication2!WindowsFormsApplication2.Form1.F2()+0x29
0016ECF0 0069038F WindowsFormsApplication2!WindowsFormsApplication2.Form1.F1(System.Object, System.EventArgs)+0x3f
StackTraceString: <none>
HResult: 80004001
to see the former exception. That´s all for today.