Skip to content

Rethrow stack trace reporting difference between NativeAOT and CoreCLR #107507

@SingleAccretion

Description

@SingleAccretion

Test app:

[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowCatchAndRethrow(bool doCatch)
{
    try
    {
        throw new Exception();
    }
    catch when (doCatch)
    {
        throw;
    }
}

try
{
    ThrowCatchAndRethrow(doCatch: true);
}
catch (Exception e)
{
    Console.WriteLine("With rethrow:\n" + e.StackTrace);
    Console.WriteLine($"Offset: 0x{new StackTrace(e).GetFrame(0).GetNativeOffset():X}");
}

try
{
    ThrowCatchAndRethrow(doCatch: false);
}
catch (Exception e)
{
    Console.WriteLine("\nNo rethrow:\n" + e.StackTrace);
    Console.WriteLine($"Offset: 0x{new StackTrace(e).GetFrame(0).GetNativeOffset():X}");
}

CoreCLR output:

> dotnet run -f net9.0
With rethrow:
   at RyuJitReproduction.Program.ThrowCatchAndRethrow(Boolean doCatch) in C:\Users\Accretion\source\dotnet\RyuJit\RyuJitReproduction\Program.cs:line 77
   at RyuJitReproduction.Program.Main() in C:\Users\Accretion\source\dotnet\RyuJit\RyuJitReproduction\Program.cs:line 94
Offset: 0x54

No rethrow:
   at RyuJitReproduction.Program.ThrowCatchAndRethrow(Boolean doCatch) in C:\Users\Accretion\source\dotnet\RyuJit\RyuJitReproduction\Program.cs:line 77
   at RyuJitReproduction.Program.Main() in C:\Users\Accretion\source\dotnet\RyuJit\RyuJitReproduction\Program.cs:line 104
Offset: 0x54

Note how the native offsets are equal, and so are the line numbers.

NativeAOT output:

> dotnet publish -f net9.0 /p:PublishAot=true && .\bin\Release\net9.0\win-x64\publish\RyuJitReproduction.exe
With rethrow:
   at RyuJitReproduction.Program.ThrowCatchAndRethrow(Boolean) + 0x50
   at RyuJitReproduction.Program.Main() + 0x55
Offset: 0x50

No rethrow:
   at RyuJitReproduction.Program.ThrowCatchAndRethrow(Boolean) + 0x2d
   at RyuJitReproduction.Program.Main() + 0x5e
Offset: 0x2D

Note how different IPs are reported (the IP of the throw is switched to the IP of the rethrow).

See also ThrowStatementDoesNotResetExceptionStackLineOtherMethod, which suggests that this difference is a NativeAOT bug.

This is the reason for the difference:

else if (isFirstRethrowFrame)
{
// For the first frame after rethrow, we replace the last entry in the stack trace with the IP
// of the rethrow. This is overwriting the IP of where control left the corresponding try
// region for the catch that is rethrowing.
_corDbgStackTrace[_idxFirstFreeStackTraceEntry - 1] = IP;
return;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions