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.