CLR Profiling: DoStackSnapshot after a throw inside a catch block gives wrong instruction pointer

371 views Asked by At

I'm currently writing a CLR profiler, and I came across something very odd. When throwing two different exceptions, one from the try clause and one from the catch clause, the CLR notifies me of the same instruction pointer. More specifically: I'm registered to receive the ExceptionThrown callback

virtual HRESULT STDMETHODCALLTYPE ExceptionThrown(ObjectID thrownObjectId);

While inside that callback, I start a DoStackSnapshot on the current thread (https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo2-dostacksnapshot-method). The CLR calls my method for every 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 a try clause and the corresponding catch clause (code examples are below), I receive the SAME ip for both. I'll also mention that this is not a case of a rethrow, when this is expected, but a brand new exception which can even occur deep in the catch's call stack.

Upon researching this more and digging deep in the CoreCLR code, I could not find a reason for this to happen, which is why I'm asking this here. I'll also mention that this is VERY easily reproduce able in plain C# debugger, which I find quite shocking. I have 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 might understand why the CLR is doing this aswell.

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 will also mention that the exception objects thrown do have the correct stack traces. So, for example, if I initiate the StackTrace objects like this:

catch (Exception e)
{
    StackTrace st = new StackTrace(e);

I do receive the expected result. The code above is also acting odd during profiling: both exceptions throw share the same instruction pointer.

Below is the IL code that matches the C# code (just to validate that this isn't a case of rethrow. Removed the prints 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 will be extremely appreciated. Thanks!

1

There are 1 answers

0
Egozy On BEST ANSWER

A little late, this seems to be a bug in the CLR: https://github.com/dotnet/coreclr/issues/15559