Skip to content

Bad GC info during ReadOnlySpan<T> byref copy #128801

@hez2010

Description

@hez2010

Description

See https://dev.azure.com/dnceng-public/public/_build/results?buildId=1441945&view=ms.vss-test-web.build-test-results-tab&runId=39973646&resultId=120444&paneView=debug

I hit a GCStress failure in win-x86 CoreCLR where GC stack scanning reports an interior pointer that points into a free object. The failing method is Tier0 QuickJitted code for System.Threading.Tasks.Task+WhenAllPromise..ctor(System.ReadOnlySpan<System.Threading.Tasks.Task>) .

I'm not sure whether it's a x86-specific or tier0-only issue.

Reproduction Steps

using System;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Threading.Tasks;

public class Test
{
    static int returnVal = 100;
    static byte[][] s = new byte[1000][];

    public const int DefaultSeed = 20010415;
    public static int Seed = Environment.GetEnvironmentVariable("CORECLR_SEED") switch
    {
        string seedStr when seedStr.Equals("random", StringComparison.OrdinalIgnoreCase) => new Random().Next(),
        string seedStr when int.TryParse(seedStr, out int envSeed) => envSeed,
        _ => DefaultSeed
    };

    static void Work()
    {
        for (uint i = 0; i < 1000000; i++)
        {
            var a = s[i++ % s.Length];

            ref byte p = ref a[0];
            ref byte q = ref a[1];

            if (Unsafe.ByteOffset(ref p, ref q) != new IntPtr(1))
            {
                Console.WriteLine("ERROR: i = " + i);
                returnVal = -1;
            }
            p = 1; q = 2;
        }
    }

    public static int Main()
    {
        for(int i = 0; i < s.Length; i++) s[i] = new byte[2];

        List<Task> tasks = new List<Task>();
        for(int i = 0; i < 5; i++)
        {
            tasks.Add(Task.Run(Work));
        }

        Random r = new Random(Seed);
        for (uint i = 0; i < 10000; i++)
        {
            s[r.Next(s.Length)] = new byte[3 + r.Next(100)];
        }
        Task t = Task.WhenAll(tasks);
        t.Wait();
        return returnVal;
    }
}

on win-x86 run it with:

set DOTNET_TieredCompilation=1
set DOTNET_ReadyToRun=0
set DOTNET_JitDisasm=System.Threading.Tasks.Task+WhenAllPromise:*
set DOTNET_TC_QuickJitForLoops=1
set DOTNET_TieredPGO=1
set DOTNET_JitDelegateProfiling=1
set DOTNET_JitVTableProfiling=1
set DOTNET_JitDisasmWithGC=1
set DOTNET_GCStress=0xC
set DOTNET_ZapDisable=1
set DOTNET_TC_QuickJit=1
set DOTNET_TC_CallCounting=0
set DOTNET_JitGCDump=System.Threading.Tasks.Task+WhenAllPromise:*
set DOTNET_JitDisasmWithAddress=1
set DOTNET_JitDisasmWithCodeBytes=1

Then you will likely hit an AV after getting output like:

; Assembly listing for method System.Threading.Tasks.Task+WhenAllPromise:.ctor(System.ReadOnlySpan`1[System.Threading.Tasks.Task]):this (Tier0)
; Emitting BLENDED_CODE for x86 + VEX on Windows
; Tier0 code
; ebp based frame
; fully interruptible
; compiling with minopt
; Final local variable assignments
;
;  V00 this         [V00    ] (  1,  1   )     ref  ->  [ebp-0x04]  do-not-enreg[] this class-hnd <System.Threading.Tasks.Task+WhenAllPromise>
;  V01 arg1         [V01    ] (  1,  1   )  struct ( 8) [ebp+0x08]  do-not-enreg[SF] ld-addr-op <System.ReadOnlySpan`1[System.Threading.Tasks.Task]>
;  V02 loc0         [V02    ] (  1,  1   )  struct ( 8) [ebp-0x0C]  do-not-enreg[SF] must-init ld-addr-op <System.ReadOnlySpan`1[System.Threading.Tasks.Task]>
;  V03 loc1         [V03    ] (  1,  1   )     int  ->  [ebp-0x10]  do-not-enreg[]
;  V04 loc2         [V04    ] (  1,  1   )  struct ( 8) [ebp-0x18]  do-not-enreg[SF] must-init ld-addr-op <System.ReadOnlySpan`1[System.Threading.Tasks.Task]>
;  V05 loc3         [V05    ] (  1,  1   )     ref  ->  [ebp-0x1C]  do-not-enreg[] must-init class-hnd <System.Threading.Tasks.Task>
;  V06 tmp0         [V06    ] (  1,  1   )     ref  ->  [ebp-0x20]  do-not-enreg[] must-init class-hnd exact "non-inline candidate call" <System.Threading.Tasks.TplEventSource>
;  V07 tmp1         [V07    ] (  1,  1   )     int  ->  [ebp-0x24]  do-not-enreg[] "argument with side effect"
;
; Lcl frame size = 36

G_M20174_IG01:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, prolog, nogc
 0c0230b0 000000 55           push     ebp
 0c0230b1 000001 8BEC         mov      ebp, esp
 0c0230b3 000003 83EC24       sub      esp, 36
 0c0230b6 000006 C5D857E4     vxorps   xmm4, xmm4, xmm4
 0c0230ba 00000A C5FA7F65E0   vmovdqu  xmmword ptr [ebp-0x20], xmm4
 0c0230bf 00000F 33C0         xor      eax, eax
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (xor: 1) 32B boundary ...............................
 0c0230c1 000011 8945F0       mov      dword ptr [ebp-0x10], eax
 0c0230c4 000014 8945F4       mov      dword ptr [ebp-0x0C], eax
 0c0230c7 000017 8945F8       mov      dword ptr [ebp-0x08], eax
 0c0230ca 00001A 894DFC       mov      gword ptr [ebp-0x04], ecx
                                                ;; size=29 bbWeight=1 PerfScore 7.08
G_M20174_IG02:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c0230cd 00001D 8B4DFC       mov      ecx, gword ptr [ebp-0x04]
                    ; gcrRegs +[ecx]
 0c0230d0 000020 FF157858D80B call     [System.Threading.Tasks.Task:.ctor():this]
                    ; gcrRegs -[ecx]
 0c0230d6 000026 837D0C00     cmp      dword ptr [ebp+0x0C], 0
 0c0230da 00002A 0F95C1       setne    cl
 0c0230dd 00002D 0FB6C9       movzx    ecx, cl
; ............................... 32B boundary ...............................
 0c0230e0 000030 BA74DEA308   mov      edx, 0x8A3DE74      ; 'Expected a non-zero length task array'
                    ; gcrRegs +[edx]
 0c0230e5 000035 FF15A0756F05 call     [System.Diagnostics.Debug:Assert(bool,System.String)]
                    ; gcrRegs -[edx]
 0c0230eb 00003B 8B45FC       mov      eax, gword ptr [ebp-0x04]
                    ; gcrRegs +[eax]
 0c0230ee 00003E 83780800     cmp      gword ptr [eax+0x08], 0
 0c0230f2 000042 0F94C1       sete     cl
 0c0230f5 000045 0FB6C9       movzx    ecx, cl
 0c0230f8 000048 BACCDEA308   mov      edx, 0x8A3DECC      ; 'Expected to be able to use the state object field for faulted/c'
                    ; gcrRegs +[edx]
 0c0230fd 00004D FF15A0756F05 call     [System.Diagnostics.Debug:Assert(bool,System.String)]
                    ; gcrRegs -[eax edx]
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (call: 3 ; jcc erratum) 32B boundary ...............................
 0c023103 000053 8B45FC       mov      eax, gword ptr [ebp-0x04]
                    ; gcrRegs +[eax]
 0c023106 000056 81481C00080000 or       dword ptr [eax+0x1C], 0x800
 0c02310d 00005D 8B4508       mov      eax, dword ptr [ebp+0x08]
                    ; gcrRegs -[eax]
 0c023110 000060 8945F4       mov      dword ptr [ebp-0x0C], eax
 0c023113 000063 8B450C       mov      eax, dword ptr [ebp+0x0C]
 0c023116 000066 8945F8       mov      dword ptr [ebp-0x08], eax
 0c023119 000069 33C0         xor      eax, eax
 0c02311b 00006B 8945F0       mov      dword ptr [ebp-0x10], eax
 0c02311e 00006E EB2F         jmp      SHORT G_M20174_IG05
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (jmp: 0 ; jcc erratum) 32B boundary ...............................
                                                ;; size=83 bbWeight=1 PerfScore 27.25
G_M20174_IG03:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c023120 000070 8B45F8       mov      eax, dword ptr [ebp-0x08]
 0c023123 000073 3945F0       cmp      dword ptr [ebp-0x10], eax
 0c023126 000076 0F83F4000000 jae      G_M20174_IG14
 0c02312c 00007C 8B45F4       mov      eax, bword ptr [ebp-0x0C]
                    ; byrRegs +[eax]
 0c02312f 00007F 8B55F0       mov      edx, dword ptr [ebp-0x10]
 0c023132 000082 833C9000     cmp      gword ptr [eax+4*edx], 0
 0c023136 000086 7510         jne      SHORT G_M20174_IG04
 0c023138 000088 B91D000000   mov      ecx, 29
 0c02313d 00008D BA33000000   mov      edx, 51
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (mov: 2) 32B boundary ...............................
 0c023142 000092 FF1528517109 call     [System.ThrowHelper:ThrowArgumentException(int,int)]
                    ; byrRegs -[eax]
                                                ;; size=40 bbWeight=1 PerfScore 11.50
G_M20174_IG04:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref
 0c023148 000098 8B45F0       mov      eax, dword ptr [ebp-0x10]
 0c02314b 00009B 40           inc      eax
 0c02314c 00009C 8945F0       mov      dword ptr [ebp-0x10], eax
                                                ;; size=7 bbWeight=1 PerfScore 2.25
G_M20174_IG05:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c02314f 00009F 8B45F0       mov      eax, dword ptr [ebp-0x10]
 0c023152 0000A2 3B45F8       cmp      eax, dword ptr [ebp-0x08]
 0c023155 0000A5 7CC9         jl       SHORT G_M20174_IG03
 0c023157 0000A7 8B0DC8168D07 mov      ecx, gword ptr [0x078D16C8]      ; static handle
                    ; gcrRegs +[ecx]
 0c02315d 0000AD 3909         cmp      dword ptr [ecx], ecx
 0c02315f 0000AF FF15A8F0BA09 call     [System.Diagnostics.Tracing.EventSource:IsEnabled():bool:this]
                    ; gcrRegs -[ecx]
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (call: 5 ; jcc erratum) 32B boundary ...............................
 0c023165 0000B5 85C0         test     eax, eax
 0c023167 0000B7 742B         je       SHORT G_M20174_IG06
 0c023169 0000B9 A1C8168D07   mov      eax, gword ptr [0x078D16C8]      ; static handle
                    ; gcrRegs +[eax]
 0c02316e 0000BE 8945E0       mov      gword ptr [ebp-0x20], eax
 0c023171 0000C1 8B4DFC       mov      ecx, gword ptr [ebp-0x04]
                    ; gcrRegs +[ecx]
 0c023174 0000C4 FF15D8F0BA09 call     [System.Threading.Tasks.Task:get_Id():int:this]
                    ; gcrRegs -[eax ecx]
 0c02317a 0000CA 8945DC       mov      dword ptr [ebp-0x24], eax
 0c02317d 0000CD 6874DFA308   push     0x8A3DF74
                    ; gcr arg push 0
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (push: 2) 32B boundary ...............................
 0c023182 0000D2 6A00         push     0
                    ; npt arg push 1
 0c023184 0000D4 6A00         push     0
                    ; npt arg push 2
 0c023186 0000D6 8B55DC       mov      edx, dword ptr [ebp-0x24]
 0c023189 0000D9 8B4DE0       mov      ecx, gword ptr [ebp-0x20]
                    ; gcrRegs +[ecx]
 0c02318c 0000DC 3909         cmp      dword ptr [ecx], ecx
 0c02318e 0000DE FF1508F1BA09 call     [System.Threading.Tasks.TplEventSource:TraceOperationBegin(int,System.String,long):this]
                    ; gcrRegs -[ecx]
                    ; gcr arg pop 3
                                                ;; size=69 bbWeight=1 PerfScore 29.25
G_M20174_IG06:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c023194 0000E4 0FB60588B29303 movzx    eax, byte  ptr [0x0393B288]      ; static handle
 0c02319b 0000EB 85C0         test     eax, eax
 0c02319d 0000ED 7409         je       SHORT G_M20174_IG07
 0c02319f 0000EF 8B4DFC       mov      ecx, gword ptr [ebp-0x04]
                    ; gcrRegs +[ecx]
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (mov: 2) 32B boundary ...............................
 0c0231a2 0000F2 FF1590F0BA09 call     [System.Threading.Tasks.Task:AddToActiveTasks(System.Threading.Tasks.Task):bool]
                    ; gcrRegs -[ecx]
                                                ;; size=20 bbWeight=1 PerfScore 7.25
G_M20174_IG07:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c0231a8 0000F8 8B45FC       mov      eax, gword ptr [ebp-0x04]
                    ; gcrRegs +[eax]
 0c0231ab 0000FB 8B550C       mov      edx, dword ptr [ebp+0x0C]
 0c0231ae 0000FE 895020       mov      dword ptr [eax+0x20], edx
 0c0231b1 000101 8B4508       mov      eax, dword ptr [ebp+0x08]
                    ; gcrRegs -[eax]
 0c0231b4 000104 8945E8       mov      dword ptr [ebp-0x18], eax
 0c0231b7 000107 8B450C       mov      eax, dword ptr [ebp+0x0C]
 0c0231ba 00010A 8945EC       mov      dword ptr [ebp-0x14], eax
 0c0231bd 00010D 33C0         xor      eax, eax
 0c0231bf 00010F 8945F0       mov      dword ptr [ebp-0x10], eax
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (mov: 2) 32B boundary ...............................
 0c0231c2 000112 EB4E         jmp      SHORT G_M20174_IG12
                                                ;; size=28 bbWeight=1 PerfScore 10.25
G_M20174_IG08:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c0231c4 000114 8B45EC       mov      eax, dword ptr [ebp-0x14]
 0c0231c7 000117 3945F0       cmp      dword ptr [ebp-0x10], eax
 0c0231ca 00011A 7354         jae      SHORT G_M20174_IG14
 0c0231cc 00011C 8B45E8       mov      eax, bword ptr [ebp-0x18]
                    ; byrRegs +[eax]
 0c0231cf 00011F 8B55F0       mov      edx, dword ptr [ebp-0x10]
 0c0231d2 000122 8B0490       mov      eax, gword ptr [eax+4*edx]
                    ; gcrRegs +[eax]
                    ; byrRegs -[eax]
 0c0231d5 000125 8945E4       mov      gword ptr [ebp-0x1C], eax
 0c0231d8 000128 837DE400     cmp      gword ptr [ebp-0x1C], 0
 0c0231dc 00012C 740F         je       SHORT G_M20174_IG09
 0c0231de 00012E 8B4DE4       mov      ecx, gword ptr [ebp-0x1C]
                    ; gcrRegs +[ecx]
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (mov: 1) 32B boundary ...............................
 0c0231e1 000131 3909         cmp      dword ptr [ecx], ecx
 0c0231e3 000133 FF15B07DCF0B call     [System.Threading.Tasks.Task:get_IsCompleted():bool:this]
                    ; gcrRegs -[eax ecx]
 0c0231e9 000139 85C0         test     eax, eax
 0c0231eb 00013B 740E         je       SHORT G_M20174_IG10
                                                ;; size=41 bbWeight=1 PerfScore 17.25
G_M20174_IG09:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c0231ed 00013D 8B4DFC       mov      ecx, gword ptr [ebp-0x04]
                    ; gcrRegs +[ecx]
 0c0231f0 000140 8B55E4       mov      edx, gword ptr [ebp-0x1C]
                    ; gcrRegs +[edx]
 0c0231f3 000143 FF15CC05040C call     [System.Threading.Tasks.Task+WhenAllPromise:Invoke(System.Threading.Tasks.Task):this]
                    ; gcrRegs -[ecx edx]
 0c0231f9 000149 EB10         jmp      SHORT G_M20174_IG11
                                                ;; size=14 bbWeight=1 PerfScore 7.00
G_M20174_IG10:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref
 0c0231fb 00014B 6A00         push     0
                    ; npt arg push 0
 0c0231fd 00014D 8B4DE4       mov      ecx, gword ptr [ebp-0x1C]
                    ; gcrRegs +[ecx]
; ............................... 32B boundary ...............................
 0c023200 000150 8B55FC       mov      edx, gword ptr [ebp-0x04]
                    ; gcrRegs +[edx]
 0c023203 000153 3909         cmp      dword ptr [ecx], ecx
 0c023205 000155 FF15C058D80B call     [System.Threading.Tasks.Task:AddCompletionAction(System.Threading.Tasks.ITaskCompletionAction,bool):this]
                    ; gcrRegs -[ecx edx]
                    ; gcr arg pop 1
                                                ;; size=16 bbWeight=1 PerfScore 8.00
G_M20174_IG11:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref
 0c02320b 00015B 8B45F0       mov      eax, dword ptr [ebp-0x10]
 0c02320e 00015E 40           inc      eax
 0c02320f 00015F 8945F0       mov      dword ptr [ebp-0x10], eax
                                                ;; size=7 bbWeight=1 PerfScore 2.25
G_M20174_IG12:        ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
 0c023212 000162 8B45F0       mov      eax, dword ptr [ebp-0x10]
 0c023215 000165 3B45EC       cmp      eax, dword ptr [ebp-0x14]
 0c023218 000168 7CAA         jl       SHORT G_M20174_IG08
                                                ;; size=8 bbWeight=1 PerfScore 3.00
G_M20174_IG13:        ; bbWeight=1, epilog, nogc, extend
 0c02321a 00016A 8BE5         mov      esp, ebp
 0c02321c 00016C 5D           pop      ebp
 0c02321d 00016D C20800       ret      8
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (ret: 0 ; jcc erratum) 32B boundary ...............................
                                                ;; size=6 bbWeight=1 PerfScore 2.75
G_M20174_IG14:        ; bbWeight=0, gcVars=00000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref
 0c023220 000170 E8AB006DF9   call     CORINFO_HELP_RNGCHKFAIL
 0c023225 000175 CC           int3
                                                ;; size=6 bbWeight=0 PerfScore 0.00

; Total bytes of code 374, prolog size 29, PerfScore 135.08, instruction count 114, allocated bytes for code 374 (MethodHash=eb9ab131) for method System.Threading.Tasks.Task+WhenAllPromise:.ctor(System.ReadOnlySpan`1[System.Threading.Tasks.Task]):this (Tier0)
; ============================================================

GC Info for method System.Threading.Tasks.Task+WhenAllPromise:.ctor(System.ReadOnlySpan`1[System.Threading.Tasks.Task]):this
GC info size =  86
Method info block:
    method      size   = 0176
    prolog      size   = 29
    epilog      size   =  6
    epilog     count   =  1
    epilog      end    = no
    callee-saved regs  = EBP
    ebp frame          = yes
    fully interruptible= yes
    double align       = no
    arguments size     =  2 DWORDs
    stack frame size   =  9 DWORDs
    untracked count    =  6
    var ptr tab count  =  0
    epilog # 0    at   016A
    argTabOffset = 6
82 76 80 8A D9 |
C7 A8 94 F5 BE |
BF 40 06 82 6A |
06    4D 14 ...|

Pointer table:
04    14 0C ...|             [EBP-04H] an untracked  local
4D    0C 05 ...|             [EBP+08H] an untracked byref local
14    05 04 ...|             [EBP-0CH] an untracked byref local
0C    04 F3 ...|             [EBP-18H] an untracked byref local
05    F3 48 ...|             [EBP-1CH] an untracked  local
04    48 0E ...|             [EBP-20H] an untracked  local
F3 48    F0 ...| 0020        reg ECX becoming live
0E    57 16 ...| 0026        reg ECX becoming dead
F0 57    43 ...| 0035        reg EDX becoming live
16    F0 57 ...| 003B        reg EDX becoming dead
43    57 06 ...| 003E        reg EAX becoming live
F0 57    10 ...| 004D        reg EDX becoming live
06    43 F0 ...| 0053        reg EAX becoming dead
10    F0 02 ...| 0053        reg EDX becoming dead
43    02 F2 ...| 0056        reg EAX becoming live
F0 02    BF ...| 0060        reg EAX becoming dead
F2 BF 47    ...| 007F        reg EAX becoming live (iptr)
F2 01    4D ...| 0098        reg EAX becoming dead
F1 4D    08 ...| 00AD        reg ECX becoming live
F0 08    41 ...| 00B5        reg ECX becoming dead
F0 41    06 ...| 00BE        reg EAX becoming live
4E    08 F0 ...| 00C4        reg ECX becoming live
06    F0 80 ...| 00CA        reg EAX becoming dead
08    80 B2 ...| 00CA        reg ECX becoming dead
F0 80    B2 ...| 00D2        push ptr  0
B2    4E F0 ...| 00D4        push non-ptr (1)
B2    F0 08 ...| 00D6        push non-ptr (2)
4E    08 D8 ...| 00DC        reg ECX becoming live
F0 08    F0 ...| 00E4        reg ECX becoming dead
D8    4E 0E ...| 00E4        pop  3 ptrs
F0 4E    43 ...| 00F2        reg ECX becoming live
0E    F0 01 ...| 00F8        reg ECX becoming dead
43    01 F2 ...| 00FB        reg EAX becoming live

Expected behavior

No crash.

Actual behavior

Crashed with AV.

Stacktrace:

[0x0]   coreclr!FailFastOnAssert+0x22() [D:\a\_work\1\s\src\coreclr\utilcode\debug.cpp @ 68]   0x9bcb524   0x72c56caf   
[0x1]   coreclr!_DbgBreakCheck+0x52f(char * szFile = 0x7362cdcc : "D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp", int iLine = 8659, char * szExpr = 0x73786a08 : "SanityCheck()..CORECLR! Object::ValidateInner + 0x137 (0x72fc0747).CORECLR! Object::Validate + 0xA9 (0x72fc05b9).CORECLR! WKS::GCHeap::Promote + 0x94 (0x7342ca44).CORECLR! PromoteCarefully + 0x81 (0x730165f1).CORECLR! GcEnumObject + 0x143 (0x73164383).CORECLR! EnumGcRefsX86 + 0xD19 (0x72e824e9).CORECLR! EECodeManager::EnumGcRefs + 0x1D2 (0x72e81772).CORECLR! GcStackCrawlCallBack + 0x2B9 (0x73164849).CORECLR! Thread::StackWalkFramesEx + 0x223 (0x7301b633).CORECLR! Thread::StackWalkFrames + 0x13B (0x7301b37b", int fConstrained = 0) [D:\a\_work\1\s\src\coreclr\utilcode\debug.cpp @ 265]   0x9bcb534   0x72c55ff9   
[0x2]   coreclr!_DbgBreakCheckNoThrow+0x79(char * szFile = 0x7362cdcc : "D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp", int iLine = 8659, char * szExpr = 0x73786a08 : "SanityCheck()..CORECLR! Object::ValidateInner + 0x137 (0x72fc0747).CORECLR! Object::Validate + 0xA9 (0x72fc05b9).CORECLR! WKS::GCHeap::Promote + 0x94 (0x7342ca44).CORECLR! PromoteCarefully + 0x81 (0x730165f1).CORECLR! GcEnumObject + 0x143 (0x73164383).CORECLR! EnumGcRefsX86 + 0xD19 (0x72e824e9).CORECLR! EECodeManager::EnumGcRefs + 0x1D2 (0x72e81772).CORECLR! GcStackCrawlCallBack + 0x2B9 (0x73164849).CORECLR! Thread::StackWalkFramesEx + 0x223 (0x7301b633).CORECLR! Thread::StackWalkFrames + 0x13B (0x7301b37b", int fConstrained = 0) [D:\a\_work\1\s\src\coreclr\utilcode\debug.cpp @ 320]   0x9bcc604   0x72c5641c   
[0x3]   coreclr!DbgAssertDialog+0x27c(char * szFile = 0x7362cdcc : "D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp", int iLine = 8659, char * szExpr = 0x7362fae4 : "SanityCheck()") [D:\a\_work\1\s\src\coreclr\utilcode\debug.cpp @ 439]   0x9bcc674   0x72fb0c01   
[0x4]   coreclr!MethodTable::Validate+0x31() [D:\a\_work\1\s\src\coreclr\vm\methodtable.cpp @ 8659]   0x9bcc718   0x72fc0747   
[0x5]   coreclr!Object::ValidateInner+0x137(int bDeep = 1, int bVerifyNextHeader = 1, int bVerifySyncBlock = 1) [D:\a\_work\1\s\src\coreclr\vm\object.cpp @ 548]   0x9bcc73c   0x72fc05b9   
[0x6]   coreclr!Object::Validate+0xa9(int bDeep = 1, int bVerifyNextHeader = 1, int bVerifySyncBlock = 1) [D:\a\_work\1\s\src\coreclr\vm\object.cpp @ 525]   0x9bcc7c8   0x7342ca44   
[0x7]   coreclr!WKS::GCHeap::Promote+0x94(Object * * ppObject = 0x356edc8, ScanContext * sc = 0x9bcdd2c, unsigned int flags = 0x1) [D:\a\_work\1\s\src\coreclr\gc\interface.cpp @ 1113]   0x9bcc7fc   0x730165f1   
[0x8]   coreclr!PromoteCarefully+0x81(void (*)(Object * *,ScanContext *,unsigned int) fn = 0x7342c9b0 : coreclr!WKS::GCHeap::Promote+0x0, Object * * ppObj = 0x356edc8, ScanContext * sc = 0x9bcdd2c, unsigned int flags = 0x1) [D:\a\_work\1\s\src\coreclr\vm\siginfo.cpp @ 5218]   0x9bcc820   0x73164383   
[0x9]   coreclr!GcEnumObject+0x143(void * pData = 0x9bcdbb0, OBJECTREF * pObj = 0x356edc8, unsigned int flags = 0x1) [D:\a\_work\1\s\src\coreclr\vm\gcenv.ee.common.cpp @ 219]   0x9bcc840   0x72e824e9   
[0xa]   coreclr!EnumGcRefsX86+0xd19(REGDISPLAY * pContext = 0x9bcd2a8, unsigned char * methodStart = 0x9f7cfd0 : 0x55, unsigned long curOffs = 0x98, GCInfoToken gcInfoToken, unsigned char * funcletStart = 0x97adbb0 : 0x55, bool isFunclet = false, bool isFilterFunclet = false, unsigned int flags = 0x0, void (*)(void *,OBJECTREF *,unsigned int) pCallBack = 0x73164240 : coreclr!GcEnumObject+0x0, void * hCallBack = 0x9bcdbb0) [D:\a\_work\1\s\src\coreclr\vm\gc_unwind_x86.inl @ 3516]   0x9bcc878   0x72e81772   
[0xb]   coreclr!EECodeManager::EnumGcRefs+0x1d2(REGDISPLAY * pContext = 0x9bcd2a8, EECodeInfo * pCodeInfo = 0x9bcceb8, unsigned int flags = 0x0, void (*)(void *,OBJECTREF *,unsigned int) pCallBack = 0x73164240 : coreclr!GcEnumObject+0x0, void * hCallBack = 0x9bcdbb0, unsigned long relOffsetOverride = 0xffffffff) [D:\a\_work\1\s\src\coreclr\vm\eetwain.cpp @ 1077]   0x9bccbd4   0x73164849   
[0xc]   coreclr!GcStackCrawlCallBack+0x2b9(CrawlFrame * pCF = 0x9bccea0, void * pData = 0x9bcdbb0) [D:\a\_work\1\s\src\coreclr\vm\gcenv.ee.common.cpp @ 325]   0x9bccc98   0x7301b633   
[0xd]   coreclr!Thread::MakeStackwalkerCallback+0x73() [D:\a\_work\1\s\src\coreclr\vm\stackwalk.cpp @ 763]   (Inline Function)   (Inline Function)   
[0xe]   coreclr!Thread::StackWalkFramesEx+0x223(REGDISPLAY * pRD = 0x9bcd2a8, StackWalkAction (*)(CrawlFrame *,void *) pCallback = 0x73164590 : coreclr!GcStackCrawlCallBack+0x0, void * pData = 0x9bcdbb0, unsigned int flags = 0x8500, Frame * pStartFrame = 0x0) [D:\a\_work\1\s\src\coreclr\vm\stackwalk.cpp @ 826]   0x9bcce78   0x7301b37b   
[0xf]   coreclr!Thread::StackWalkFrames+0x13b(StackWalkAction (*)(CrawlFrame *,void *) pCallback = 0x73164590 : coreclr!GcStackCrawlCallBack+0x0, void * pData = 0x9bcdbb0, unsigned int flags = 0x8500, Frame * pStartFrame = 0x0) [D:\a\_work\1\s\src\coreclr\vm\stackwalk.cpp @ 901]   0x9bcd27c   0x73161a07   
[0x10]   coreclr!ScanStackRoots+0x307(Thread * pThread = 0x37bd8c8, void (*)(Object * *,ScanContext *,unsigned int) fn = 0x7342c9b0 : coreclr!WKS::GCHeap::Promote+0x0, ScanContext * sc = 0x9bcdd2c) [D:\a\_work\1\s\src\coreclr\vm\gcenv.ee.cpp @ 199]   0x9bcdb7c   0x7315ef6e   
[0x11]   coreclr!GCToEEInterface::GcScanRoots+0x16e(void (*)(Object * *,ScanContext *,unsigned int) fn = 0x7342c9b0 : coreclr!WKS::GCHeap::Promote+0x0, int condemned = 2, int max_gen = 2, ScanContext * sc = 0x9bcdd2c) [D:\a\_work\1\s\src\coreclr\vm\gcenv.ee.cpp @ 314]   0x9bcdca4   0x73445df1   
[0x12]   coreclr!WKS::gc_heap::mark_phase+0x231(int condemned_gen_number = 2) [D:\a\_work\1\s\src\coreclr\gc\mark_phase.cpp @ 3163]   0x9bcdcc8   0x7343e416   
[0x13]   coreclr!WKS::gc_heap::gc1+0x1b6() [D:\a\_work\1\s\src\coreclr\gc\collect.cpp @ 160]   0x9bcdd5c   0x7343e225   
[0x14]   coreclr!WKS::gc_heap::garbage_collect+0x815(int n = 2) [D:\a\_work\1\s\src\coreclr\gc\collect.cpp @ 1300]   0x9bcddc4   0x7342b1f4   
[0x15]   coreclr!WKS::GCHeap::GarbageCollectGeneration+0x3e4(unsigned int gen = 0x2, gc_reason reason = reason_gcstress (8)) [D:\a\_work\1\s\src\coreclr\gc\interface.cpp @ 1932]   0x9bcdde8   0x7342b3de   
[0x16]   coreclr!WKS::GCHeap::GarbageCollectTry+0x9e(int generation = 2, int low_memory_p = 0, int mode = -2147483648) [D:\a\_work\1\s\src\coreclr\gc\interface.cpp @ 1818]   0x9bcde18   0x7342db9e   
[0x17]   coreclr!WKS::GCHeap::StressHeap+0x44e(gc_alloc_context * context = 0x92c2158) [D:\a\_work\1\s\src\coreclr\gc\interface.cpp @ 1397]   0x9bcde2c   0x7315b847   
[0x18]   coreclr!DoGcStress+0x727(_CONTEXT * regs = 0x9bced0c, NativeCodeVersion nativeCodeVersion) [D:\a\_work\1\s\src\coreclr\vm\gccover.cpp @ 901]   0x9bcde74   0x7315bc93   
[0x19]   coreclr!OnGcCoverageInterrupt+0x213(_CONTEXT * regs = 0x9bced0c) [D:\a\_work\1\s\src\coreclr\vm\gccover.cpp @ 1381]   0x9bce96c   0x72e93989   
[0x1a]   coreclr!IsGcMarker+0x9a(_CONTEXT * pContext = 0x9bced0c, pExceptionRecord = <unavailable>) [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 5174]   (Inline Function)   (Inline Function)   
[0x1b]   coreclr!CLRVectoredExceptionHandlerShim+0x159(_EXCEPTION_POINTERS * pExceptionInfo = 0x9bcebdc) [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 6461]   0x9bceb50   0x76fdf441   
[0x1c]   ntdll!RtlpCallVectoredHandlers+0x39460()   0x9bcebcc   0x76f9ec6a   
[0x1d]   ntdll!RtlDispatchException+0x7c()   0x9bcec14   0x76fb134f   
[0x1e]   ntdll!KiUserExceptionDispatcher+0xf   0x9bcecac   0xa173407   
[0x1f]   System_Private_CoreLib!System.Threading.WaitHandle.WaitForMultipleObjectsIgnoringSyncContext(IntPtr*, Int32, Boolean, Int32, Boolean)+0x77 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs @ 31]   0x9bcf7ac   0xa173274   
[0x20]   System_Private_CoreLib!System.Threading.WaitHandle.WaitOneCore(IntPtr, Int32, Boolean)+0x24 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs @ 130]   0x9bcf868   0xa172449   
[0x21]   System_Private_CoreLib!System.Threading.WaitHandle.WaitOneNoCheck(Int32, Boolean, System.Object, WaitHandleWaitSourceMap)+0x1f1 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @ 181]   0x9bcf87c   0xa17220b   
[0x22]   System_Private_CoreLib!System.Threading.WaitHandle.WaitOne()+0x1b [/_/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @ 498]   0x9bcf8e0   0x97a9c42   
[0x23]   System_Private_CoreLib!System.Threading.PortableThreadPool+GateThread.GateThreadStart()+0x142 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs @ 65]   0x9bcf8ec   0x97a915b   
[0x24]   System_Private_CoreLib!System.Threading.Thread.StartCallback(System.Threading.Thread*)+0x93 [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 109]   0x9bcf994   0x730d882e   
[0x25]   coreclr!UnmanagedCallersOnlyCaller::InvokeDirect<OBJECTREF *>+0x18e(OBJECTREF * <args_0> = 0x9bcfb94) [D:\a\_work\1\s\src\coreclr\vm\callhelpers.h @ 675]   0x9bcf9c0   0x730d9219   
[0x26]   coreclr!KickOffThread_Worker+0x229(void * ptr = 0x0) [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 146]   0x9bcfac4   0x7303818d   
[0x27]   coreclr!ManagedThreadBase_DispatchInner+0xcd(ManagedThreadCallState * pCallState = 0x9bcfe40) [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5733]   0x9bcfbc4   0x7303829e   
[0x28]   coreclr!ManagedThreadBase_DispatchMiddle+0xbe(ManagedThreadCallState * pCallState = 0x9bcfe40) [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5777]   0x9bcfc38   0x7303a43d   
[0x29]   coreclr!``ManagedThreadBase_DispatchOuter'::`8'::__Body::Run'::`5'::__Body::Run+0x8d(Param * pParam = 0x9bcfdac) [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5930]   0x9bcfd00   0x7303a55e   
[0x2a]   coreclr!`ManagedThreadBase_DispatchOuter'::`8'::__Body::Run+0x9e(ManagedThreadBase_DispatchOuter::__l2::TryArgs * pArgs = 0x9bcfd9c) [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5930]   0x9bcfd40   0x730386fb   
[0x2b]   coreclr!ManagedThreadBase_DispatchOuter+0xdb(ManagedThreadCallState * pCallState = 0x9bcfe40) [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5948]   0x9bcfd88   0x73037c8f   
[0x2c]   coreclr!ManagedThreadBase::KickOff+0xcf(void (*)(void *) pTarget = 0x730d8ff0 : coreclr!KickOffThread_Worker+0x0, void * args = 0x0) [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 5967]   0x9bcfde8   0x730d8f75   
[0x2d]   coreclr!KickOffThread+0x1c5(void * pass = 0x92c2380) [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 189]   0x9bcfe60   0x73b962c4   
[0x2e]   kernel32!BaseThreadInitThunk+0x24   0x9bcff24   0x76fa0aa9   
[0x2f]   ntdll!__RtlUserThreadStart+0x2f()   0x9bcff38   0x76fa0a74   
[0x30]   ntdll!_RtlUserThreadStart+0x1b   0x9bcff80   0x0   

Regression?

Yes

Known Workarounds

No response

Configuration

Can reproduce with the main branch.

Other information

Analysis from copilot (Click to expand the details)

Analysis

The failure appears to be caused by bad GC info during a copy of the byref field of a ReadOnlySpan<Task> from an incoming stack argument to a local stack home.

The generated code copies the span byref like this:

00005D 8B4508       mov      eax, dword ptr [ebp+0x08]
                    ; gcrRegs -[eax]
000060 8945F4       mov      dword ptr [ebp-0x0C], eax

At native offset 0x5D, eax is loaded with the byref/interior pointer field from the incoming ReadOnlySpan<Task> argument. However, the GC info does not appear to mark eax as a byref/interior pointer during the interval before the store at offset 0x60.

Under GCStress, a GC can occur after the load into eax and before the store to [ebp-0x0C]. If the object backing the span moves during that GC, the stack argument [ebp+0x08] is updated, but eax is not updated because it was not reported as a byref. Execution then resumes and stores the stale byref into [ebp-0x0C].

Later, [ebp-0x0C] is correctly reported as an untracked byref local, but by then it contains a stale pointer into the old object location, which is now a free object.

Failures

The GC root being promoted was:

ppObject = 0356edc8

The stack frame for the managed method had:

EBP = 0356edd4
ppObject = 0356edc8 = EBP - 0x0C

So the reported root is the byref field of the local ReadOnlySpan<Task> stored at [ebp-0x0C].

At the time of failure:

[ebp-0x0C] = 05b73610

PromoteCarefully interpreted this as an interior pointer and found the containing object:

05b7359c Free Object
Size: 372 (0x174) bytes

Local variable assignments:

V00 this         ref                 -> [ebp-0x04]
V01 arg1         ReadOnlySpan<Task>  -> [ebp+0x08]
V02 loc0         ReadOnlySpan<Task>  -> [ebp-0x0C]
V03 loc1         int                 -> [ebp-0x10]
V04 loc2         ReadOnlySpan<Task>  -> [ebp-0x18]
V05 loc3         Task ref            -> [ebp-0x1C]
V06 tmp0         TplEventSource ref  -> [ebp-0x20]
V07 tmp1         int                 -> [ebp-0x24]

The first span copy:

00005D 8B4508       mov      eax, dword ptr [ebp+0x08]
                    ; gcrRegs -[eax]
000060 8945F4       mov      dword ptr [ebp-0x0C], eax
000063 8B450C       mov      eax, dword ptr [ebp+0x0C]
000066 8945F8       mov      dword ptr [ebp-0x08], eax
000069 33C0         xor      eax, eax
00006B 8945F0       mov      dword ptr [ebp-0x10], eax

The local span is later used here:

00007C 8B45F4       mov      eax, bword ptr [ebp-0x0C]
                    ; byrRegs +[eax]
00007F 8B55F0       mov      edx, dword ptr [ebp-0x10]
000082 833C9000     cmp      gword ptr [eax+4*edx], 0
000086 7510         jne      SHORT G_M20174_IG04

The crash occurred at a GCStress interrupt around:

G_M20174_IG04:
000098 8B45F0       mov      eax, dword ptr [ebp-0x10]
00009B 40           inc      eax
00009C 8945F0       mov      dword ptr [ebp-0x10], eax

At that point [ebp-0x0C] is correctly reported as an untracked byref local, but it already contains the stale byref value.

The second span copy has the same shape:

000101 8B4508       mov      eax, dword ptr [ebp+0x08]
                    ; gcrRegs -[eax]
000104 8945E8       mov      dword ptr [ebp-0x18], eax

GC info

The GC info marks the stack homes as untracked byrefs:

[EBP+08H] an untracked byref local
[EBP-0CH] an untracked byref local
[EBP-18H] an untracked byref local

Relevant register transitions:

0056        reg EAX becoming live
0060        reg EAX becoming dead
007F        reg EAX becoming live (iptr)
0098        reg EAX becoming dead

The 007F transition corresponds to the later load from [ebp-0x0C]:

00007C mov eax, bword ptr [ebp-0x0C]

However, the earlier load from [ebp+0x08] at 00005D appears not to make eax live as an interior pointer before the store at 000060.

Expected behavior

At the instruction interval:

00005D mov eax, dword ptr [ebp+0x08]
000060 mov dword ptr [ebp-0x0C], eax

eax contains the byref/interior pointer field of ReadOnlySpan<Task>.

Since the method is fully interruptible and GCStress can interrupt between these two instructions, eax should either:

  1. be reported as an interior pointer for this interval, or
  2. the interval should not be GC-interruptible.

Otherwise, a moving GC can update [ebp+0x08] while leaving eax stale, and the stale value is then stored into [ebp-0x0C].

Actual behavior

eax does not appear to be reported as a byref/interior pointer between the load from [ebp+0x08] and the store to [ebp-0x0C].

Under GCStress, this can leave the local span copy with a stale interior pointer:

[ebp+0x08] = updated byref
[ebp-0x0C] = stale byref into old object location

A later GC reports [ebp-0x0C] as an interior pointer. The pointer maps into a free object, and object validation asserts.

Suspected root cause

This looks like bad x86 Tier0 fully-interruptible GC info for a byref register during a stack-to-stack copy of a struct field containing a byref, specifically ReadOnlySpan<T>._reference.

The stack homes themselves are reported as untracked byrefs, which seems correct. The missing piece appears to be the temporary register used during the copy:

mov eax, [ebp+0x08]      ; eax should become byref/iptr
mov [ebp-0x0C], eax

The same pattern also occurs for the second local span copy:

mov eax, [ebp+0x08]
mov [ebp-0x18], eax

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions