I am currently writing a CLR profiler and I came across something very strange. Throwing two different exceptions, one from the try clause and one from the catch clause, the CLR notifies me of the same instruction pointer. In particular: I am registered to receive an ExceptionThrown callback
virtual HRESULT STDMETHODCALLTYPE ExceptionThrown(ObjectID thrownObjectId);
Inside this callback, I run DoStackSnapshot in the current thread ( https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo2-dostacksnapshot-method ). The CLR calls my method for each frame:
HRESULT stackSnapshotCallback(FunctionID funcId, UINT_PTR ip, COR_PRF_FRAME_INFO, ULONG32, BYTE context[], void *clientData)
However, if I have an exception thrown from the try clause and the corresponding catch clause (code examples below), I get a SAME ip for both. I also mentioned that this is not a case of rethrowing when it is expected, but a completely new exception that may even occur deep in the catch catch stack.
After learning more about this and delving into the CoreCLR code, I could not find a reason for this, so I ask about it here. I also mentioned that this is VERY easy to reproduce in a regular C # debugger, which I find pretty shocking. I used .Net Framework 4.5, but this also happened on 4.6 and 4.7.
I believe that if I understand why the following C # code behaves this way, I can understand why the CLR does this as well.
This code:
try { try { throw new Exception("A"); } catch (Exception) { StackTrace st = new StackTrace(true); StackFrame sf = st.GetFrame(0); Console.WriteLine("Inside catch, instruction: " + sf.GetILOffset() + ". line: " + sf.GetFileLineNumber()); throw new Exception("B"); } } catch (Exception) { StackTrace st = new StackTrace(true); StackFrame sf = st.GetFrame(0); Console.WriteLine("Outer catch, instruction: " + sf.GetILOffset() + ". line: " + sf.GetFileLineNumber()); }
Produces this result: Inside catch, instruction: 13. line: 54 Outer catch, instruction: 13. line: 54
I also mentioned that the generated exception objects have the correct stack traces. So, for example, if I initiate StackTrace objects as follows:
catch (Exception e) { StackTrace st = new StackTrace(e);
I get the expected result. The above code also acts odd during profiling: both exceptions throw a common instruction pointer.
Below is the IL code that corresponds to the C # code (only to verify that this is not a case of a repeated throw. Fingerprints were removed for clarity):
private static void Main(string [] args) { /* 00005284 00 */ nop try { /* 00005285 00 */ nop try { /* 00005286 00 */ nop /* 00005287 72 CF 0F 00 70 */ ldstr "A" /* 0000528C 73 8E 00 00 0A */ newobj System.Exception::.ctor(string) // returns void /* 00005291 7A */ throw } catch (System.Exception) { /* 00005292 26 */ pop /* 00005293 00 */ nop /* 00005294 72 D3 0F 00 70 */ ldstr "B" /* 00005299 73 8E 00 00 0A */ newobj System.Exception::.ctor(string) // returns void /* 0000529E 7A */ throw } } catch (System.Exception) { /* 0000529F 26 */ pop /* 000052A0 00 */ nop /* 000052A1 00 */ nop /* 000052A2 DE 00 */ leave_s loc_32 } loc_32: /* 000052A4 28 13 01 00 0A */ call System.Console::Read() // returns int /* 000052A9 26 */ pop /* 000052AA 2A */ ret }
Any help would be greatly appreciated. Thanks!