Skip to content

Stack trace line numbers wrong after tier-1. Possible related to PGO/GDV #134523

Description

@anderspedersen

Description

We have noticed than since switching from .NET Framework to .NET Core many of our stack traces will always point to the first line of a method, even if the exception happens several lines later.

While worse stack traces might be a side-effect of compiler optimizations, investigating the method with WinDbg/SOS indicates that the runtime is indeed aware of the correct line number.

Reproduction Steps


public class Repro
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public bool Method(Func<IHolder> holderFactory, bool flag)
    {
        var holder = holderFactory();
        
        return flag && holder.Data.ToString() != null; // NullReferenceException, since Data is null
    }
}

public interface IHolder
{
    object Data { get; }
}

class HolderImpl : IHolder
{
    public object Data { get; set; }
}

internal class Program
{
    public static void Main(string[] args)
    {
        var r = new Repro();

        // Before tier-1
        try
        {
            r.Method(() => new HolderImpl(), true);
        }
        catch (NullReferenceException e)
        {
            Console.WriteLine(e);
        }

        // Warm-up
        for (int i = 0; i < 1000000; i++)
        {
            try
            {
                r.Method(() => new HolderImpl(), true);
            }
            catch (NullReferenceException) { }
        }
        
        // After tier-1  
        try
        {
            r.Method(() => new HolderImpl(), true);
        }
        catch (NullReferenceException e)
        {
            Console.WriteLine(e);
        }
    }
}

Expected behavior

Both exceptions should show NRE happened on line 10

System.NullReferenceException: Object reference not set to an instance of an object.
at Repro.Method(Func1 holderFactory, Boolean flag) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 10 at Program.Main(String[] args) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 33 System.NullReferenceException: Object reference not set to an instance of an object. at Repro.Method(Func1 holderFactory, Boolean flag) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 10
at Program.Main(String[] args) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 53

Actual behavior

Exception after tier-1 shows NRE happened in first line of method (line 8)

System.NullReferenceException: Object reference not set to an instance of an object.
at Repro.Method(Func1 holderFactory, Boolean flag) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 10 at Program.Main(String[] args) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 33 System.NullReferenceException: Object reference not set to an instance of an object. at Repro.Method(Func1 holderFactory, Boolean flag) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 8
at Program.Main(String[] args) in C:\projects\WrongStackRepro\WrongStackRepro\Program.cs:line 53

Regression?

Does not happen in .NET 7, happens in .NET 8 and later

Known Workarounds

Adding [MethodImpl(MethodImplOptions.AggressiveOptimization)] (thus disabling PGO) will make the issue disappear, hence why I suspect the issue might be related to PGO/GDV.

Configuration

Windows, x64, .NET10

Other information

When viewing method in WinDbg it correctly maps the assembly instruction that is causing the NRE to the correct source line number:

0:009> !U /d 00007ffd9b6ebc00
Normal JIT generated code
Repro.Method(System.Func`1<IHolder>, Boolean)
ilAddr is 000001F10EAA2050 pImport is 000002AB23B46490
Begin 00007FFD9B6EBC00, size 7d

C:\projects\WrongStackRepro\WrongStackRepro\Program.cs @ 8:
>>> 00007ffd`9b6ebc00 53              push    rbx
00007ffd`9b6ebc01 4883ec20        sub     rsp,20h
00007ffd`9b6ebc05 418bd8          mov     ebx,r8d
00007ffd`9b6ebc08 48b980228b9bfd7f0000 mov rcx,offset Program+<>c.<Main>b__0_3() (00007ffd`9b8b2280)
00007ffd`9b6ebc12 48394a18        cmp     qword ptr [rdx+18h],rcx
00007ffd`9b6ebc16 753f            jne     WrongStackRepro!Repro.Method(System.Func`1<IHolder>, Boolean)+0x6247 (00007ffd`9b6ebc57)
00007ffd`9b6ebc18 48b978208f9bfd7f0000 mov rcx,7FFD9B8F2078h (MT: HolderImpl)
00007ffd`9b6ebc22 e81996c25f      call    coreclr!coreclr_set_error_writer+0x5d20 (00007ffd`fb315240) (JitHelp: CORINFO_HELP_NEWSFAST)

C:\projects\WrongStackRepro\WrongStackRepro\Program.cs @ 10:
00007ffd`9b6ebc27 84db            test    bl,bl
00007ffd`9b6ebc29 744a            je      WrongStackRepro!Repro.Method(System.Func`1<IHolder>, Boolean)+0x6265 (00007ffd`9b6ebc75)
00007ffd`9b6ebc2b 48b978208f9bfd7f0000 mov rcx,7FFD9B8F2078h (MT: HolderImpl)
00007ffd`9b6ebc35 483908          cmp     qword ptr [rax],rcx
00007ffd`9b6ebc38 7526            jne     WrongStackRepro!Repro.Method(System.Func`1<IHolder>, Boolean)+0x6250 (00007ffd`9b6ebc60)
00007ffd`9b6ebc3a 488b4808        mov     rcx,qword ptr [rax+8]
00007ffd`9b6ebc3e 488b01          mov     rax,qword ptr [rcx]
00007ffd`9b6ebc41 488b4040        mov     rax,qword ptr [rax+40h]
00007ffd`9b6ebc45 ff5008          call    qword ptr [rax+8]
00007ffd`9b6ebc48 4885c0          test    rax,rax
00007ffd`9b6ebc4b 0f95c0          setne   al
00007ffd`9b6ebc4e 0fb6c0          movzx   eax,al
00007ffd`9b6ebc51 4883c420        add     rsp,20h
00007ffd`9b6ebc55 5b              pop     rbx
00007ffd`9b6ebc56 c3              ret

C:\projects\WrongStackRepro\WrongStackRepro\Program.cs @ 8:
00007ffd`9b6ebc57 488b4a08        mov     rcx,qword ptr [rdx+8]
00007ffd`9b6ebc5b ff5218          call    qword ptr [rdx+18h]
00007ffd`9b6ebc5e ebc7            jmp     WrongStackRepro!Repro.Method(System.Func`1<IHolder>, Boolean)+0x6217 (00007ffd`9b6ebc27)

C:\projects\WrongStackRepro\WrongStackRepro\Program.cs @ 10:
00007ffd`9b6ebc60 488bc8          mov     rcx,rax
00007ffd`9b6ebc63 49bbe8005e9bfd7f0000 mov r11,7FFD9B5E00E8h
00007ffd`9b6ebc6d 41ff13          call    qword ptr [r11] (CLRStub[VSD_LookupStub]@00007FFD9B6E04C0)
00007ffd`9b6ebc70 488bc8          mov     rcx,rax
00007ffd`9b6ebc73 ebc9            jmp     WrongStackRepro!Repro.Method(System.Func`1<IHolder>, Boolean)+0x622e (00007ffd`9b6ebc3e)
00007ffd`9b6ebc75 33c0            xor     eax,eax
00007ffd`9b6ebc77 4883c420        add     rsp,20h
00007ffd`9b6ebc7b 5b              pop     rbx
00007ffd`9b6ebc7c c3              ret

The line that is causing the NRE:

00007ffd9b6ebc3e 488b01 mov rax, qword ptr [rcx]

is shown as part of a Program.cs @ 10 block.

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 23, 2026
  2. dotnet-policy-service commented on Sep 23, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  3. added and removed
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 23, 2026
  4. dotnet-policy-service commented on Sep 23, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
    See info in area-owners.md if you want to be subscribed.

  5. EgorBo commented on Sep 23, 2026

    @EgorBo
    Member

    From a quick AI triage, it looks like VM off-by-one in stack-trace mapping, not incorrect GDV codegen

  6. added this to the 12.0.0 milestone on Oct 1, 2026
  7. removed
    untriagedNew issue has not been triaged by the area owner
    on Oct 1, 2026
  8. anderspedersen commented on Oct 2, 2026

    @anderspedersen
    Author

    @EgorBo
    I did a bit of digging, and I believe issue is partly caused by shortcomings in the GDV codegen.

    Basically there seems to be two issues at play for my repro:

    1. Stack trace mapping seems to assume that IPs are return addresses, which is true when IL_Throw is called, but false when exception is caused by CPU. So it tries to find the preceding instruction, in my repro it means it is using the mapping from the devirtualized call to get_Data, rather than the mapping from the instruction that caused the NRE.

    2. The mapping from devirtualized calls seems to be missing in some cases. Tried to build latest runtime and get following IP mapping for my repro:

    *************** In genIPmappingGen()
    IP mapping count : 12
    IL offs PROLOG : 0x00000000 ( STACK_EMPTY )
    IL offs 0x0000 : 0x00000009 ( STACK_EMPTY )
    IL offs 0x0000 : 0x00000019 ( STACK_EMPTY )
    IL offs 0x0007 : 0x00000034 ( STACK_EMPTY )
    IL offs 0x000A : 0x00000038 ( STACK_EMPTY )
    IL offs NO_MAP : 0x00000047 ( STACK_EMPTY )
    IL offs 0x000A : 0x0000004B ( STACK_EMPTY )
    IL offs EPILOG : 0x0000005E ( STACK_EMPTY )
    IL offs 0x0000 : 0x00000065 ( STACK_EMPTY )
    IL offs 0x000A : 0x00000071 ( STACK_EMPTY )
    IL offs 0x0019 : 0x00000086 ( STACK_EMPTY )
    IL offs EPILOG : 0x00000088 ( STACK_EMPTY )
    

    With codegen:

    G_M52253_IG01:        ; func=00, offs=0x000000, size=0x0009, bbWeight=1, PerfScore 2.50, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB01 [0000], byref, prolog, nogc
    
    IN001f: 000000 push     rsi
    IN0020: 000001 push     rbx
    IN0021: 000002 sub      rsp, 40
    IN0022: 000006 mov      ebx, r8d
    
    G_M52253_IG02:        ; offs=0x000009, size=0x002B, bbWeight=1, PerfScore 8.00, gcrefRegs=0004 {rdx}, byrefRegs=0000 {}, BB01 [0000], BB05 [0004], byref, isz
    
    IN0001: 000009 mov      rcx, 0x7FF8C7B43B28      ; code for Program+<>c:<Main>b__0_2():IHolder:this
    IN0002: 000013 cmp      qword ptr [rdx+0x18], rcx
    IN0003: 000017 jne      SHORT G_M52253_IG06
    IN0004: 000019 mov      rcx, 0x7FF8C7C60018      ; HolderImpl
    IN0005: 000023 call     CORINFO_HELP_NEWSFAST
    IN0006: 000028 mov      rsi, rax
    IN0007: 00002B mov      rcx, rsi
    IN0008: 00002E call     [System.Object:.ctor():this]
    
    G_M52253_IG03:        ; offs=0x000034, size=0x0017, bbWeight=1, PerfScore 6.50, gcrefRegs=0040 {rsi}, byrefRegs=0000 {}, BB04 [0003], BB02 [0001], BB08 [0007], byref, isz
    
    IN0009: 000034 test     bl, bl
    IN000a: 000036 je       SHORT G_M52253_IG08
    IN000b: 000038 mov      rcx, 0x7FF8C7C60018      ; HolderImpl
    IN000c: 000042 cmp      qword ptr [rsi], rcx
    IN000d: 000045 jne      SHORT G_M52253_IG07
    IN000e: 000047 mov      rcx, gword ptr [rsi+0x08]
    
    G_M52253_IG04:        ; offs=0x00004B, size=0x0013, bbWeight=1, PerfScore 8.00, gcrefRegs=0002 {rcx}, byrefRegs=0000 {}, BB07 [0006], byref
    
    IN000f: 00004B mov      rax, qword ptr [rcx]
    IN0010: 00004E mov      rax, qword ptr [rax+0x48]
    IN0011: 000052 call     [rax+0x08]System.Object:ToString():System.String:this
    IN0012: 000055 test     rax, rax
    IN0013: 000058 setne    al
    IN0014: 00005B movzx    rax, al
    
    G_M52253_IG05:        ; offs=0x00005E, size=0x0007, bbWeight=1, PerfScore 2.25, BB07 [0006], epilog, nogc, extend
    
    IN0023: 00005E add      rsp, 40
    IN0024: 000062 pop      rbx
    IN0025: 000063 pop      rsi
    IN0026: 000064 ret      
    
    G_M52253_IG06:        ; offs=0x000065, size=0x000C, bbWeight=0, PerfScore 0.00, gcVars=0000000000000000 {}, gcrefRegs=0004 {rdx}, byrefRegs=0000 {}, BB06 [0005], gcvars, byref, isz
    
    IN0015: 000065 mov      rcx, gword ptr [rdx+0x10]
    IN0016: 000069 call     [rdx+0x18]System.Func`1[System.__Canon]:Invoke():System.__Canon:this
    IN0017: 00006C mov      rsi, rax
    IN0018: 00006F jmp      SHORT G_M52253_IG03
    
    G_M52253_IG07:        ; offs=0x000071, size=0x0015, bbWeight=0, PerfScore 0.00, gcrefRegs=0040 {rsi}, byrefRegs=0000 {}, BB09 [0008], byref, isz
    
    IN0019: 000071 mov      rcx, rsi
    IN001a: 000074 mov      r11, 0x7FF8C70700B0      ; code for IHolder:get_Data():System.Object:this
    IN001b: 00007E call     [r11]IHolder:get_Data():System.Object:this
    IN001c: 000081 mov      rcx, rax
    IN001d: 000084 jmp      SHORT G_M52253_IG04
    
    G_M52253_IG08:        ; offs=0x000086, size=0x0002, bbWeight=0, PerfScore 0.00, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB03 [0002], byref
    
    IN001e: 000086 xor      eax, eax
    
    G_M52253_IG09:        ; offs=0x000088, size=0x0007, bbWeight=0, PerfScore 0.00, BB03 [0002], epilog, nogc, extend
    
    IN0027: 000088 add      rsp, 40
    IN0028: 00008C pop      rbx
    IN0029: 00008D pop      rsi
    IN002a: 00008E ret      
    

    So, NRE is happening at offset 0x4B, but is using mapping from offset 0x47, that is the devirtualized and inlined call to HolderImpl.get_Data and has no mapping.

    Notice that we are also seeing same issue from software exceptions, so the regression since older .NET versions seems to be the missing IP mapping, rather than the wrong stack trace mapping.

    I tried fixing the IP mapping issue, by letting the basic blocks generated by GDV get their internal status based on the basic block that is being expanded. This fixes the issue, but don't know if that is the direction you want to go. If so, let me know, and I will be happy to create a PR.
    anderspedersen@fa8c130

    I have attached JitDumps from before my change and after:
    jitdump_before_fix.txt
    jitdump_fixed.txt

  9. anderspedersen commented on Oct 4, 2026

    @anderspedersen
    Author

    I did some more debugging, and I believe my original fix was wrong, not marking the basic block as internal was just suppressing the NO_MAP mappings, but not actually inserting the correct mappings.

    As far as I can tell, the issue happens because when doing GDV of a method with an return value, the compiler inserts a statement for the call with DebugInfo and a statement for the return expression without DebugInfo. Later, more logic from the devirtualized call is merged into the return expression statement, that remains without DebugInfo.

    I tried patching the code to include DebugInfo with the return expression statement. This seems to fix the issue.
    anderspedersen@0fce089

    BB08 right after devirtualization:

    ***** BB08 [0007]
    STMT00012 ( ??? ... ??? )
                   [000044] DA---------                         *  STORE_LCL_VAR ref    V08 tmp4         
                   [000043] -----------                         \--*  LCL_VAR   ref    V03 loc0         
    
    ***** BB08 [0007]
    STMT00013 ( 0x00A[E--] ... ??? )
    -               [000045] I-CXG------                         *  CALL nullcheck ref    HolderImpl:get_Data():System.Object:this (exactContextHandle=0x00007FF9250450D9)
    +               [000045] I-CXG------                         *  CALL nullcheck ref    HolderImpl:get_Data():System.Object:this (exactContextHandle=0x00007FF92ACC4F91)
                   [000047] ----------- this                    \--*  LCL_VAR   ref    V08 tmp4         
    
    ***** BB08 [0007]
    -STMT00014 ( ??? ... ??? )
    +STMT00014 ( 0x00A[E--] ... ??? )
                   [000049] DAC--------                         *  STORE_LCL_VAR ref    V07 tmp3         
                   [000048] --C--------                         \--*  RET_EXPR  ref   (for [000045])

    And BB08 after optimizations:

    ***** BB08 [0007]
    -STMT00014 ( ??? ... ??? )
    +STMT00014 ( 0x00A[E--] ... ??? )
    N005 (  4,  4) [000049] DA--GO-----                         *  STORE_LCL_VAR ref    V07 tmp3         d:3 $VN.Void
    N004 (  4,  4) [000063] n---GO-----                         \--*  IND       ref    <l:$205, c:$86>
    N003 (  2,  2) [000069] -------N---                            \--*  ADD       byref  $181
    N001 (  1,  1) [000043] -----------                               +--*  LCL_VAR   ref    V05 tmp1         u:4 (last use) $300
    N002 (  1,  1) [000068] -----------                               \--*  CNS_INT   long   8 Fseq[<Data>k__BackingField] $141

    And the final IP mappings:

    IP mapping count : 12
    IL offs PROLOG : 0x00000000 ( STACK_EMPTY )
    IL offs 0x0000 : 0x00000008 ( STACK_EMPTY )
    -IL offs NO_MAP : 0x00000018 ( STACK_EMPTY )
    +IL offs 0x0000 : 0x00000018 ( STACK_EMPTY )
    IL offs 0x0007 : 0x00000027 ( STACK_EMPTY )
    IL offs 0x000A : 0x0000002B ( STACK_EMPTY )
    -IL offs NO_MAP : 0x0000003A ( STACK_EMPTY )
    +IL offs 0x000A : 0x0000003A ( STACK_EMPTY )
    IL offs 0x000A : 0x0000003E ( STACK_EMPTY )
    IL offs EPILOG : 0x00000051 ( STACK_EMPTY )
    IL offs 0x0000 : 0x00000057 ( STACK_EMPTY )
    IL offs 0x000A : 0x00000060 ( STACK_EMPTY )
    IL offs 0x0019 : 0x00000075 ( STACK_EMPTY )
    IL offs EPILOG : 0x00000077 ( STACK_EMPTY )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions