Exception Handling Differences Between 32/64 Bit
        Posted  
        
            by Alois Kraus
        on Geeks with Blogs
        
        See other posts from Geeks with Blogs
        
            or by Alois Kraus
        
        
        
        Published on Tue, 25 May 2010 21:42:28 GMT
        Indexed on 
            2010/05/25
            23:02 UTC
        
        
        Read the original article
        Hit count: 479
        
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.
© Geeks with Blogs or respective owner
