Job:
runtime-coreclr gcstress0x3-gcstress0xc:20200315.1
Error message:
Assert failure(PID 2292 [0x000008f4], Thread: 2312 [0x0908]): !g_pConfig->fAssertOnFailFast() || (m_Next == FRAME_TOP) || (*m_Next->GetGSCookiePtr() == GetProcessGSCookie())
CORECLR! Frame::Push + 0x1A9 (0x00007ff956303ad9)
CORECLR! Frame::Push + 0xDE (0x00007ff956303c0a)
CORECLR! Thread::RedirectedHandledJITCase + 0x1AD (0x00007ff9561b7a05)
CORECLR! RedirectedHandledJITCaseForGCStress_Stub + 0x26 (0x00007ff956615a06)
<no module>! <no symbol> + 0x0 (0x00007ff8f72c00f5)
<no module>! <no symbol> + 0x0 (0x00007ff8f6e0f518)
<no module>! <no symbol> + 0x0 (0x0000000000003039)
<no module>! <no symbol> + 0x0 (0x000000000000018c)
<no module>! <no symbol> + 0x0 (0xffffffffffffffff)
<no module>! <no symbol> + 0x0 (0x9abcdef012345678)
File: F:\workspace\_work\1\s\src\coreclr\src\vm\frames.cpp Line: 417
Image: C:\h\w\B366092E\p\CoreRun.exe
Return code: 1\r\nRaw output file: C:\h\w\B366092E\w\AA26098B\e\Interop\NativeCallable\Reports\Interop.NativeCallable\NativeCallableTest\NativeCallableTest.output.txt
Raw output:
BEGIN EXECUTION
"C:\h\w\B366092E\p\corerun.exe" NativeCallableTest.dll
Running TestNativeCallableValid...
Running TestNativeCallableValid_OnNewNativeThread...
Running TestNativeCallableValid_PrepareMethod...
Running NegativeTest_NonStaticMethod...
Running NegativeTest_ViaDelegate...
Running NegativeTest_NonBlittable...
Running NegativeTest_NonInstantiatedGenericArguments...
Running NegativeTest_InstantiatedGenericArguments...
Running NegativeTest_FromInstantiatedGenericClass...
Running TestNativeCallableViaUnmanagedCalli...
Running TestNativeCallableValid_ThrowException...
Expected: 100
Actual: -1073740286
END EXECUTION - FAILED
FAILED
Test Harness Exitcode is : 1
To run the test:
> set CORE_ROOT=C:\h\w\B366092E\p
> C:\h\w\B366092E\w\AA26098B\e\Interop\NativeCallable\NativeCallableTest\NativeCallableTest.cmd
Expected: True
Actual: False
Stack trace:
at Interop_NativeCallable._NativeCallableTest_NativeCallableTest_._NativeCallableTest_NativeCallableTest_cmd() in F:\workspace\_work\1\s\artifacts\tests\coreclr\Windows_NT.x64.Checked\TestWrappers\Interop.NativeCallable\Interop.NativeCallable.XUnitWrapper.cs:line 140
Details:
https://dev.azure.com/dnceng/public/_build/results?buildId=560162&view=ms.vss-test-web.build-test-results-tab&runId=17645012&resultId=101118&paneView=debug
@jkotas or @janvorli Any suggestions here on how to address this for Windows would be helpful. I have a fix for #33612, but this one seems related to the exception handling side of things that I am less knowledgable about how to mitigate.
This is easy to reproduce locally. Looks like an issue with TestNativeCallableValid_ThrowException() test.
Bah. I think this is my bad. @jkotas If I recall a conversation about exceptions and the NativeCallableAttribute, the expectation here is that they don't mix at all and we shouldn't even be trying to validate them right? If so I will disable those tests since I added them just to have a set of scenarios I could validate fail. Looks like on GCStress they really don't work due to the stack walking logic in https://github.com/dotnet/runtime/blob/4ceccbba7455349dffc91745d78814c5c8dc939e/src/coreclr/src/vm/exceptionhandling.cpp#L6392
On Windows, the managed exception should get converted to SEH exception on the NativeCallable boundary.
On Unix, the managed exception should become unhanded on the NativeCallable boundary.
I think we are missing "unwind and continue" on the NativeCallable boundary. @janvorli Do you have thoughts about the best way to fix that?
I'll take a look. It seems that we need to do what the personality routine at the UMThunkStub was doing when unwinding from the managed frame of a native callable function. That is what the UMThunkUnwindFrameChainHandler was doing. It seems we could do that somewhere down the ProcessCLRException call chain, but I need to figure out the exact details.
I am actually getting a different assert when running with COMPlus_GCStress=C. It happens because the GetThread() returns NULL. So we are missing a runtime thread data structure creation when we enter the managed function on a foreign thread. I would expect the JIT_ReversePInvokeEnter to do that.
# Child-SP RetAddr Call Site
00 (Inline Function) --------`-------- KERNELBASE!DebugBreak [minkernel\kernelbase\debug.c @ 143]
01 00000050`7487da68 00007ffc`18f5edac KERNELBASE!wil::details::DebugBreak+0x2 [internal\sdk\inc\wil\opensource\wil\result_macros.h @ 1684]
02 00000050`7487da70 00007ffc`18ff6d2a CoreCLR!DbgAssertDialog+0x34c [F:\git\runtime3\src\coreclr\src\utilcode\debug.cpp @ 713]
03 00000050`7487dcb0 00007ffc`18ff235b CoreCLR!GCHolderBase::EnterInternalCoop+0x5a [F:\git\runtime3\src\coreclr\src\vm\threads.h @ 5448]
04 00000050`7487dcf0 00007ffc`191b47c9 CoreCLR!GCCoop::GCCoop+0x6b [F:\git\runtime3\src\coreclr\src\vm\threads.h @ 5580]
05 00000050`7487dd40 00007ffc`b2a4121f CoreCLR!ProcessCLRException+0x389 [F:\git\runtime3\src\coreclr\src\vm\exceptionhandling.cpp @ 925]
06 00000050`7487e800 00007ffc`b2a0a289 ntdll!RtlpExecuteHandlerForException+0xf [minkernel\ntos\rtl\amd64\xcptmisc.asm @ 132]
07 00000050`7487e830 00007ffc`b2a3fe8e ntdll!RtlDispatchException+0x219 [minkernel\ntos\rtl\amd64\exdsptch.c @ 693]
08 00000050`7487ef40 00007ffb`ba8c530b ntdll!KiUserExceptionDispatch+0x2e [minkernel\ntos\rtl\amd64\trampoln.asm @ 749]
09 00000050`7487fd10 00007ffc`1fda0beb NativeCallableTest!Program.ManagedCallback_Prepared(Int32)+0x1b*** WARNING: Unable to verify checksum for F:\git\runtime3\artifacts\tests\coreclr\Windows_NT.x64.Debug\Interop\NativeCallable\NativeCallableTest\NativeCallableTest.dll
0a 00000050`7487fd60 00007ffc`1fda0983 NativeCallableDll!CallManagedProc+0x3b [F:\git\runtime3\src\coreclr\tests\src\Interop\NativeCallable\NativeCallableDll.cpp @ 14]
0b 00000050`7487fda0 00007ffc`1fda0487 NativeCallableDll!`anonymous namespace'::ProxyCall+0x33 [F:\git\runtime3\src\coreclr\tests\src\Interop\NativeCallable\NativeCallableDll.cpp @ 27]
0c 00000050`7487fdd0 00007ffc`1fda06cc NativeCallableDll!std::_Invoker_functor::_Call<void (__cdecl*)(`anonymous namespace'::ProxyCallContext *),A0x81f7f9a3::ProxyCallContext *>+0x57 [C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\VC\Tools\MSVC\14.26.28619\include\type_traits @ 1610]
0d 00000050`7487fe10 00007ffc`1fda0526 NativeCallableDll!std::invoke<void (__cdecl*)(`anonymous namespace'::ProxyCallContext *),A0x81f7f9a3::ProxyCallContext *>+0x4c [C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\VC\Tools\MSVC\14.26.28619\include\type_traits @ 1610]
0e 00000050`7487fe50 00007ffc`1fe4950c NativeCallableDll!std::thread::_Invoke<std::tuple<void (__cdecl*)(`anonymous namespace'::ProxyCallContext *),A0x81f7f9a3::ProxyCallContext *>,0,1>+0x76 [C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\VC\Tools\MSVC\14.26.28619\include\thread @ 44]
0f 00000050`7487feb0 00007ffc`b28b7bd4 NativeCallableDll!thread_start<unsigned int (__cdecl*)(void *),1>+0x9c
10 00000050`7487ff10 00007ffc`b2a0ced1 KERNEL32!BaseThreadInitThunk+0x14
11 00000050`7487ff40 00000000`00000000 ntdll!RtlUserThreadStart+0x21
And looking into it more, I can see the cause. The Program.ManagedCallback_Prepared(Int32) doesn't have a call to the JIT_ReversePInvokeEnter:
0:009> !u 00007ffb`ba8d530b
Normal JIT generated code
Program.ManagedCallback_Prepared(Int32)
ilAddr is 000001C6E110234C pImport is 00000150FE19AF90
Begin 00007FFBBA8D52F0, size 67
F:\git\runtime3\src\coreclr\tests\src\Interop\NativeCallable\NativeCallableTest.cs @ 159:
00007ffb`ba8d52f0 55 push rbp
00007ffb`ba8d52f1 4883ec40 sub rsp,40h
00007ffb`ba8d52f5 488d6c2440 lea rbp,[rsp+40h]
00007ffb`ba8d52fa c5d857e4 vxorps xmm4,xmm4,xmm4
00007ffb`ba8d52fe c5f97f65e0 vmovdqa xmmword ptr [rbp-20h],xmm4
00007ffb`ba8d5303 c5f97f65f0 vmovdqa xmmword ptr [rbp-10h],xmm4
00007ffb`ba8d5308 894d10 mov dword ptr [rbp+10h],ecx
>>> 00007ffb`ba8d530b 488d4de0 lea rcx,[rbp-20h]
00007ffb`ba8d530f e8cca49a5e call CoreCLR!ProfToEEInterfaceImpl::GetCodeInfo4+0x43c (00007ffc`196601ec) (gcstress) (JitHelp: CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER)
00007ffb`ba8d5314 48b8603fafbafb7f0000 mov rax,7FFBBAAF3F60h (gcstress)
00007ffb`ba8d531e 833800 cmp dword ptr [rax],0 (gcstress)
00007ffb`ba8d5321 7405 je 00007ffb`bacb5d34 (gcstress)
00007ffb`ba8d5323 e8d8879a5e call CoreCLR!ProfToEEInterfaceImpl::GetClassIDInfo+0x53c (00007ffc`1965e50c) (gcstress) (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
00007ffb`ba8d5328 90 nop (gcstress)
F:\git\runtime3\src\coreclr\tests\src\Interop\NativeCallable\NativeCallableTest.cs @ 160:
00007ffb`ba8d5329 8b4d10 mov ecx,dword ptr [rbp+10h] (gcstress)
00007ffb`ba8d532c e857bafeff call 00007ffb`baca1794 (gcstress) (Program.DoubleImpl(Int32), mdToken: 0000000006000003)
00007ffb`ba8d5331 8945f8 mov dword ptr [rbp-8],eax (gcstress)
00007ffb`ba8d5334 8b4df8 mov ecx,dword ptr [rbp-8] (gcstress)
00007ffb`ba8d5337 894dfc mov dword ptr [rbp-4],ecx (gcstress)
00007ffb`ba8d533a 90 nop (gcstress)
00007ffb`ba8d533b eb00 jmp 00007ffb`bacb5d49 (gcstress)
F:\git\runtime3\src\coreclr\tests\src\Interop\NativeCallable\NativeCallableTest.cs @ 161:
00007ffb`ba8d533d 8b4dfc mov ecx,dword ptr [rbp-4] (gcstress)
00007ffb`ba8d5340 894df4 mov dword ptr [rbp-0Ch],ecx (gcstress)
00007ffb`ba8d5343 eb00 jmp 00007ffb`bacb5d51 (gcstress)
00007ffb`ba8d5345 488d4de0 lea rcx,[rbp-20h] (gcstress)
00007ffb`ba8d5349 e862a89a5e call CoreCLR!ProfToEEInterfaceImpl::GetCodeInfo+0x1ec (00007ffc`196605bc) (gcstress) (JitHelp: CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT)
00007ffb`ba8d534e 8b45f4 mov eax,dword ptr [rbp-0Ch] (gcstress)
00007ffb`ba8d5351 488d6500 lea rsp,[rbp]
00007ffb`ba8d5355 5d pop rbp
00007ffb`ba8d5356 c3 ret
@janvorli That is issue #33612 and the fix is https://github.com/dotnet/runtime/pull/33615. I would merge in that change or comment out all other tests except for the exception ones.
@AaronRobinsonMSFT this is different. I have already applied your fix locally before. The disass of the managed method shows that it is not calling the JIT_ReversePInvokeEnter, so it has no place to create the native Thread object.
Let me try that locally. I don't recall observing any issue with that test.
I am not seeing that locally. Using COMPlus_jitdump=ManagedCallback_Prepared I get the following below. I am also able to step through the Enter/Exit and run COMPlus_GCStress=0xC and COMPlus_GCStress=0x3 without issue. Is there something I could be missing locally? I am using a Debug version of the runtime. Is it possible I need to try a Checked version?
------------ BB01 [???..???), preds={} succs={BB02}
N002 ( 3, 3) [000010] ------------ t10 = LCL_VAR_ADDR long V03 tmp2 $101
/--* t10 long
[000019] ------------ t19 = * PUTARG_REG long REG rcx
/--* t19 long arg0 in rcx
N003 ( 17, 9) [000011] --C-G------- * CALL help void HELPER.CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER $VN.Void
------------ BB02 [000..007), preds={BB01} succs={BB03}
N001 ( 1, 1) [000000] ------------ t0 = LCL_VAR int V00 arg0 u:1 (last use) $80
N002 ( 1, 1) [000004] -c---------- t4 = CNS_INT int 1 $42
/--* t0 int
+--* t4 int
N003 ( 3, 3) [000005] ------------ t5 = * LSH int $140
/--* t5 int
N005 ( 3, 3) [000017] DA-----N---- * STORE_LCL_VAR int V02 tmp1 d:1
------------ BB03 [???..???) (return), preds={BB02} succs={}
N002 ( 3, 3) [000013] ------------ t13 = LCL_VAR_ADDR long V03 tmp2 $103
/--* t13 long
[000020] ------------ t20 = * PUTARG_REG long REG rcx
/--* t20 long arg0 in rcx
N003 ( 17, 9) [000014] --C-G------- * CALL help void HELPER.CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT $VN.Void
N001 ( 1, 1) [000007] -------N---- t7 = LCL_VAR int V02 tmp1 u:1 (last use) $140
/--* t7 int
N002 ( 2, 2) [000008] ------------ * RETURN int $181
Okay. I see the issue now. I was only using a Release build of the test binary not a Debug version. I foolishly assumed the Release would uncover more issues than the Debug version.
Okay. So the issue here is it appears the GC stress something is inserted prior to the new reverse P/Invoke helper Enter/Exit. See the diasm below. I don't know enough about this to be able to make a fix. Any help would be appreciated.
*************** After end code gen, before unwindEmit()
G_M4921_IG01: ; func=00, offs=000000H, size=001BH, bbWeight=1 PerfScore 5.08, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, nogc, isz <-- Prolog IG
IN0015: 000000 push rbp
IN0016: 000001 sub rsp, 64
IN0017: 000005 lea rbp, [rsp+40H]
IN0018: 00000A vxorps xmm4, xmm4
IN0019: 00000E vmovdqa xmmword ptr [rbp-20H], xmm4
IN001a: 000013 vmovdqa xmmword ptr [rbp-10H], xmm4
IN001b: 000018 mov dword ptr [V00 rbp+10H], ecx
G_M4921_IG02: ; offs=00001BH, size=0018H, bbWeight=1 PerfScore 4.75, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
IN0001: 00001B lea rcx, [V05 rbp-20H]
IN0002: 00001F call CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER
IN0003: 000024 mov rax, 0x7FFA10503F58
IN0004: 00002E cmp dword ptr [rax], 0
IN0005: 000031 je SHORT G_M4921_IG04
G_M4921_IG03: ; offs=000033H, size=0005H, bbWeight=0.50 PerfScore 0.50, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref
IN0006: 000033 call CORINFO_HELP_DBG_IS_JUST_MY_CODE
G_M4921_IG04: ; offs=000038H, size=0015H, bbWeight=1 PerfScore 7.50, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
IN0007: 000038 nop
IN0008: 000039 mov ecx, dword ptr [V00 rbp+10H]
IN0009: 00003C call Program:DoubleImpl(int):int
IN000a: 000041 mov dword ptr [V03 rbp-08H], eax
IN000b: 000044 mov ecx, dword ptr [V03 rbp-08H]
IN000c: 000047 mov dword ptr [V01 rbp-04H], ecx
IN000d: 00004A nop
IN000e: 00004B jmp SHORT G_M4921_IG05
G_M4921_IG05: ; offs=00004DH, size=0008H, bbWeight=1 PerfScore 4.00, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz
IN000f: 00004D mov ecx, dword ptr [V01 rbp-04H]
IN0010: 000050 mov dword ptr [V04 rbp-0CH], ecx
IN0011: 000053 jmp SHORT G_M4921_IG06
G_M4921_IG06: ; offs=000055H, size=000CH, bbWeight=1 PerfScore 2.50, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref
IN0012: 000055 lea rcx, [V05 rbp-20H]
IN0013: 000059 call CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT
IN0014: 00005E mov eax, dword ptr [V04 rbp-0CH]
G_M4921_IG07: ; offs=000061H, size=0006H, bbWeight=1 PerfScore 2.00, epilog, nogc, extend
IN001c: 000061 lea rsp, [rbp]
IN001d: 000065 pop rbp
IN001e: 000066 ret
*************** Finishing PHASE Emit code
something is inserted prior to the new reverse P/Invoke helper
What is this "something"? I do not see it in the disassembly that you have shared.
The hlt instruction. That is what I am stepping through in Visual Studio. I didn't think the hlt would be emitted by the JIT, but am assuming it is a part of the GC Stress mechanism that triggers an exception. The failure point here is in ProcessCLRException() which fails to attempt a switch to COOP mode since the reverse PInvoke hasn't called the Enter yet and a thread doesn't yet exist.
My starting premise for all of this is the reverse PInvoke Enter would be the very first thing in the prolog. Perhaps this was a bad assumption.
00007FFA102C327E 00 00 add byte ptr [rax],al
00007FFA102C3280 55 push rbp
00007FFA102C3281 48 83 EC 40 sub rsp,40h
00007FFA102C3285 48 8D 6C 24 40 lea rbp,[rsp+40h]
00007FFA102C328A C5 D8 57 E4 vxorps xmm4,xmm4,xmm4
00007FFA102C328E C5 F9 7F 65 E0 vmovdqa xmmword ptr [rbp-20h],xmm4
00007FFA102C3293 C5 F9 7F 65 F0 vmovdqa xmmword ptr [rbp-10h],xmm4
00007FFA102C3298 89 4D 10 mov dword ptr [rbp+10h],ecx
00007FFA102C329B F4 hlt
00007FFA102C329C 8D 4D E0 lea ecx,[rbp-20h]
00007FFA102C329F F4 hlt
00007FFA102C32A0 AC lods byte ptr [rsi]
Boo. Yep. This is done by GCCoverageInfo::SprinkleBreakpoints().
Therefore the question here is should I update the GCCoverageInfo to know about this type of helper and move beyond it or ignore methods attributed with the NativeCallableAttribute. Other options?
The GC coverage can only instrument at places where real GC can take place. So it is rather a matter of generating correct GC info that should not make a native callable function interruptible before the JIT_ReversePInvokeEnter.
I believe that the same problem exists for the regular PInvoke. The instructions can be marked as fully interuptible, but still run in preemptive mode.
Can we just create the Thread when we are doing GC stress and hit the GC stress exception handler?
Ah, I haven't realized that the thread runs in a preemptive mode until it reaches the JIT helper. But it is weird that GC stress doesn't handle that already. And even stranger, how come Aaron's fix doesn't help, as the OnGcCoverageInterrupt with his fix is checking for NULL Thread and if it is the case, it should just silently remove the hlt and go without ever going into the ProcessCLRException. I'm stepping through the code to see if I can find a clue.
And even stranger, how come Aaron's fix doesn't help, as the OnGcCoverageInterrupt with his fix is checking for NULL Thread and if it is the case, it should just silently remove the hlt and go without ever going into the ProcessCLRException. I'm stepping through the code to see if I can find a clue.
@janvorli That is because this is the PrepareMethod() scenario where the function is JITed from a thread that is initialized and only called on a thread that is external.
@janvorli But you just pointed out to me how I think we can fix this.
@janvorli Nope. I misunderstood why this was happening. Looks like the COOP transition is happening before the call to IsGcMarker() which would handle the case I fixed.
{
GCX_COOP();
// Update the current establisher frame
if (dwExceptionFlags & EXCEPTION_UNWINDING)
{
ExceptionTracker *pCurrentTracker = pThread->GetExceptionState()->GetCurrentExceptionTracker();
if (pCurrentTracker != NULL)
{
pCurrentTracker->SetCurrentEstablisherFrame(sf);
}
}
#ifdef _DEBUG
Thread::ObjectRefFlush(pThread);
#endif // _DEBUG
}
//
// begin Early Processing
//
{
#ifndef USE_REDIRECT_FOR_GCSTRESS
if (IsGcMarker(pContextRecord, pExceptionRecord))
{
returnDisposition = ExceptionContinueExecution;
goto lExit;
}
#endif // !USE_REDIRECT_FOR_GCSTRESS
Can we just create the Thread when we are doing GC stress and hit the GC stress exception handler?
@jkotas Can you elaborate on this? @AndyAyersMS suggested I could update bool emitter::emitNoGChelper(CorInfoHelpFunc helpFunc) to have the Enter/Exit - the CORINFO_HELP_INIT_PINVOKE_FRAME is already present. Is that what you mean here?
It seems that the call to ProcessCLRException stems from something else than the GC coverage interrupt. I can see that the CLRVectoredExceptionHandlerShim correctly detects that the Thread is null and just removes the instrumentation and returns. And then a bit later, without any other CLRVectoredExceptionHandlerShim call, the ProcessCLRException is called and crash due to the Thread being NULL. There are hundreds of CLRVectoredExceptionHandlerShim calls to handle previous GC coverage interrupts before that and none of them results in a call to ProcessCLRException. So there is something happening that causes the call to occur (with error code 0xc0000096, which represents the coverage privileged instruction). I wonder if the CLRVectoredExceptionHandlerShim returns wrong return value or something like that. I keep looking...
Yep the call to CLRVectoredExceptionHandlerShim() is returning EXCEPTION_CONTINUE_SEARCH during that hlt for GC cover. What should it be returning?
I think I've found the culprit - testing now ...
Yes, I was right. The problem is that the fix @AaronRobinsonMSFT did in OnGcCoverageInterrupt in #33615 was insufficient. We also need to make sure that the CLRVectoredExceptionHandlerShim returns EXCEPTION_CONTINUE_EXECUTION when bIsGCMarker is true and pThread is NULL. We were currently returning EXCEPTION_CONTINUE_SEARCH, which resulted in the incorrect call to ProcessCLRException.
@janvorli I just tested that very scenario. My plan was to update if (pThread || fExceptionInEE) to if (pThread || fExceptionInEE || bIsGCMarker) in excep.cpp@7982?
I would put the following at line 7967. That is sufficient. I believe we don't need anything from the body of the if you've mentioned.
else if (bIsGCMarker)
{
result = EXCEPTION_CONTINUE_EXECUTION;
}
I also have a fix for the other issue, I will create a PR soon.
I will fold the above into the previous PR so it is complete.
Fixed with #33653 and https://github.com/dotnet/runtime/pull/33615