From be62b4f0906cb1dd9ab6816075975f241281989b Mon Sep 17 00:00:00 2001 From: Lee Culver Date: Mon, 23 Feb 2026 19:14:40 -0500 Subject: [PATCH] Don't crash when walking callstack roots in the debugger (#124402) Changeset https://github.com/dotnet/runtime/commit/1fa1745581e26069093a679626e346b675aa534f introduced a stack walking regression where we will crash trying to unwind/report registers. The real fix here is in cgenamd64.cpp/excep.cpp, which will point the context to a local copy before we try to use it. I've also added some changes in gcinfodecoder.cpp that aren't strictly necessary but would have avoided the problem to begin with. Fixes #124401. --- src/coreclr/vm/amd64/cgenamd64.cpp | 39 +++++++++++++++++----------- src/coreclr/vm/arm/stubs.cpp | 25 ++++++++++++------ src/coreclr/vm/arm64/stubs.cpp | 33 ++++++++++++++--------- src/coreclr/vm/excep.cpp | 6 ++--- src/coreclr/vm/i386/cgenx86.cpp | 13 ++++++++-- src/coreclr/vm/loongarch64/stubs.cpp | 31 ++++++++++++++-------- src/coreclr/vm/riscv64/stubs.cpp | 39 +++++++++++++++++----------- 7 files changed, 120 insertions(+), 66 deletions(-) diff --git a/src/coreclr/vm/amd64/cgenamd64.cpp b/src/coreclr/vm/amd64/cgenamd64.cpp index f674b15bd302f4..572583e22a0b3e 100644 --- a/src/coreclr/vm/amd64/cgenamd64.cpp +++ b/src/coreclr/vm/amd64/cgenamd64.cpp @@ -167,21 +167,30 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u pRD->SSP = m_SSP; #endif - pRD->pCurrentContextPointers->Rax = &m_ctx.Rax; - pRD->pCurrentContextPointers->Rcx = &m_ctx.Rcx; - pRD->pCurrentContextPointers->Rdx = &m_ctx.Rdx; - pRD->pCurrentContextPointers->Rbx = &m_ctx.Rbx; - pRD->pCurrentContextPointers->Rbp = &m_ctx.Rbp; - pRD->pCurrentContextPointers->Rsi = &m_ctx.Rsi; - pRD->pCurrentContextPointers->Rdi = &m_ctx.Rdi; - pRD->pCurrentContextPointers->R8 = &m_ctx.R8; - pRD->pCurrentContextPointers->R9 = &m_ctx.R9; - pRD->pCurrentContextPointers->R10 = &m_ctx.R10; - pRD->pCurrentContextPointers->R11 = &m_ctx.R11; - pRD->pCurrentContextPointers->R12 = &m_ctx.R12; - pRD->pCurrentContextPointers->R13 = &m_ctx.R13; - pRD->pCurrentContextPointers->R14 = &m_ctx.R14; - pRD->pCurrentContextPointers->R15 = &m_ctx.R15; +#ifdef DACCESS_COMPILE + // &m_ctx.Xxx resolves through the DAC cache and the entry can be evicted + // before context pointers are consumed. Point at the local copy in + // pCurrentContext instead (values were already copied above). + CONTEXT *pContext = pRD->pCurrentContext; +#else + CONTEXT *pContext = &m_ctx; +#endif + + pRD->pCurrentContextPointers->Rax = &pContext->Rax; + pRD->pCurrentContextPointers->Rcx = &pContext->Rcx; + pRD->pCurrentContextPointers->Rdx = &pContext->Rdx; + pRD->pCurrentContextPointers->Rbx = &pContext->Rbx; + pRD->pCurrentContextPointers->Rbp = &pContext->Rbp; + pRD->pCurrentContextPointers->Rsi = &pContext->Rsi; + pRD->pCurrentContextPointers->Rdi = &pContext->Rdi; + pRD->pCurrentContextPointers->R8 = &pContext->R8; + pRD->pCurrentContextPointers->R9 = &pContext->R9; + pRD->pCurrentContextPointers->R10 = &pContext->R10; + pRD->pCurrentContextPointers->R11 = &pContext->R11; + pRD->pCurrentContextPointers->R12 = &pContext->R12; + pRD->pCurrentContextPointers->R13 = &pContext->R13; + pRD->pCurrentContextPointers->R14 = &pContext->R14; + pRD->pCurrentContextPointers->R15 = &pContext->R15; pRD->IsCallerContextValid = FALSE; pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index b28d2dccbc09d9..43c08ddda2c18c 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -1292,14 +1292,23 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u // Update the integer registers in KNONVOLATILE_CONTEXT_POINTERS from // the exception context we have. - pRD->pCurrentContextPointers->R4 = (PDWORD)&m_ctx.R4; - pRD->pCurrentContextPointers->R5 = (PDWORD)&m_ctx.R5; - pRD->pCurrentContextPointers->R6 = (PDWORD)&m_ctx.R6; - pRD->pCurrentContextPointers->R7 = (PDWORD)&m_ctx.R7; - pRD->pCurrentContextPointers->R8 = (PDWORD)&m_ctx.R8; - pRD->pCurrentContextPointers->R9 = (PDWORD)&m_ctx.R9; - pRD->pCurrentContextPointers->R10 = (PDWORD)&m_ctx.R10; - pRD->pCurrentContextPointers->R11 = (PDWORD)&m_ctx.R11; +#ifdef DACCESS_COMPILE + // &m_ctx.Xxx resolves through the DAC cache and the entry can be evicted + // before context pointers are consumed. Point at the local copy in + // pCurrentContext instead (values were already copied above). + T_CONTEXT *pContext = pRD->pCurrentContext; +#else + T_CONTEXT *pContext = &m_ctx; +#endif + + pRD->pCurrentContextPointers->R4 = (PDWORD)&pContext->R4; + pRD->pCurrentContextPointers->R5 = (PDWORD)&pContext->R5; + pRD->pCurrentContextPointers->R6 = (PDWORD)&pContext->R6; + pRD->pCurrentContextPointers->R7 = (PDWORD)&pContext->R7; + pRD->pCurrentContextPointers->R8 = (PDWORD)&pContext->R8; + pRD->pCurrentContextPointers->R9 = (PDWORD)&pContext->R9; + pRD->pCurrentContextPointers->R10 = (PDWORD)&pContext->R10; + pRD->pCurrentContextPointers->R11 = (PDWORD)&pContext->R11; pRD->pCurrentContextPointers->Lr = NULL; pRD->IsCallerContextValid = FALSE; diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index 533a9b1cf5afcc..db7b1497ad878d 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -240,18 +240,27 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u // Update the integer registers in KNONVOLATILE_CONTEXT_POINTERS from // the exception context we have. - pRD->pCurrentContextPointers->X19 = (PDWORD64)&m_ctx.X19; - pRD->pCurrentContextPointers->X20 = (PDWORD64)&m_ctx.X20; - pRD->pCurrentContextPointers->X21 = (PDWORD64)&m_ctx.X21; - pRD->pCurrentContextPointers->X22 = (PDWORD64)&m_ctx.X22; - pRD->pCurrentContextPointers->X23 = (PDWORD64)&m_ctx.X23; - pRD->pCurrentContextPointers->X24 = (PDWORD64)&m_ctx.X24; - pRD->pCurrentContextPointers->X25 = (PDWORD64)&m_ctx.X25; - pRD->pCurrentContextPointers->X26 = (PDWORD64)&m_ctx.X26; - pRD->pCurrentContextPointers->X27 = (PDWORD64)&m_ctx.X27; - pRD->pCurrentContextPointers->X28 = (PDWORD64)&m_ctx.X28; - pRD->pCurrentContextPointers->Fp = (PDWORD64)&m_ctx.Fp; - pRD->pCurrentContextPointers->Lr = (PDWORD64)&m_ctx.Lr; +#ifdef DACCESS_COMPILE + // &m_ctx.Xxx resolves through the DAC cache and the entry can be evicted + // before context pointers are consumed. Point at the local copy in + // pCurrentContext instead (values were already copied above). + T_CONTEXT *pContext = pRD->pCurrentContext; +#else + T_CONTEXT *pContext = &m_ctx; +#endif + + pRD->pCurrentContextPointers->X19 = (PDWORD64)&pContext->X19; + pRD->pCurrentContextPointers->X20 = (PDWORD64)&pContext->X20; + pRD->pCurrentContextPointers->X21 = (PDWORD64)&pContext->X21; + pRD->pCurrentContextPointers->X22 = (PDWORD64)&pContext->X22; + pRD->pCurrentContextPointers->X23 = (PDWORD64)&pContext->X23; + pRD->pCurrentContextPointers->X24 = (PDWORD64)&pContext->X24; + pRD->pCurrentContextPointers->X25 = (PDWORD64)&pContext->X25; + pRD->pCurrentContextPointers->X26 = (PDWORD64)&pContext->X26; + pRD->pCurrentContextPointers->X27 = (PDWORD64)&pContext->X27; + pRD->pCurrentContextPointers->X28 = (PDWORD64)&pContext->X28; + pRD->pCurrentContextPointers->Fp = (PDWORD64)&pContext->Fp; + pRD->pCurrentContextPointers->Lr = (PDWORD64)&pContext->Lr; ClearRegDisplayArgumentAndScratchRegisters(pRD); diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 1829b5b16c8b2a..beb948639c9c96 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -11339,9 +11339,9 @@ void SoftwareExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u ENUM_CALLEE_SAVED_REGISTERS(); #undef CALLEE_SAVED_REGISTER -#if defined(DACCESS_COMPILE) && defined(TARGET_X86) -// X86 unwinding always works in terms of context pointers, so they need to be in the correct address space when debugging -// This may work for other architectures as well, but that isn't tested. +#if defined(DACCESS_COMPILE) +// Context pointers in m_ContextPointers reference the target process address space and cannot be used directly. +// Point them at the local context copy instead. #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = &pRD->pCurrentContext->regname; #else #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = m_ContextPointers.regname; diff --git a/src/coreclr/vm/i386/cgenx86.cpp b/src/coreclr/vm/i386/cgenx86.cpp index 98c845c00e14b7..381e47dac4b960 100644 --- a/src/coreclr/vm/i386/cgenx86.cpp +++ b/src/coreclr/vm/i386/cgenx86.cpp @@ -312,11 +312,20 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u pRD->SP = m_ctx.Esp; pRD->ControlPC = m_ctx.Eip; -#define ARGUMENT_AND_SCRATCH_REGISTER(regname) pRD->pCurrentContextPointers->regname = &m_ctx.regname; +#ifdef DACCESS_COMPILE + // &m_ctx.Xxx resolves through the DAC cache and the entry can be evicted + // before context pointers are consumed. Point at the local copy in + // pCurrentContext instead (values were already copied above). + CONTEXT *pContext = pRD->pCurrentContext; +#else + CONTEXT *pContext = &m_ctx; +#endif + +#define ARGUMENT_AND_SCRATCH_REGISTER(regname) pRD->pCurrentContextPointers->regname = &pContext->regname; ENUM_ARGUMENT_AND_SCRATCH_REGISTERS(); #undef ARGUMENT_AND_SCRATCH_REGISTER -#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = &m_ctx.regname; +#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = &pContext->regname; ENUM_CALLEE_SAVED_REGISTERS(); #undef CALLEE_SAVED_REGISTER diff --git a/src/coreclr/vm/loongarch64/stubs.cpp b/src/coreclr/vm/loongarch64/stubs.cpp index 4491cf187ab113..34f88d56b61e99 100644 --- a/src/coreclr/vm/loongarch64/stubs.cpp +++ b/src/coreclr/vm/loongarch64/stubs.cpp @@ -306,17 +306,26 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u // Update the integer registers in KNONVOLATILE_CONTEXT_POINTERS from // the exception context we have. - pRD->pCurrentContextPointers->S0 = (PDWORD64)&m_ctx.S0; - pRD->pCurrentContextPointers->S1 = (PDWORD64)&m_ctx.S1; - pRD->pCurrentContextPointers->S2 = (PDWORD64)&m_ctx.S2; - pRD->pCurrentContextPointers->S3 = (PDWORD64)&m_ctx.S3; - pRD->pCurrentContextPointers->S4 = (PDWORD64)&m_ctx.S4; - pRD->pCurrentContextPointers->S5 = (PDWORD64)&m_ctx.S5; - pRD->pCurrentContextPointers->S6 = (PDWORD64)&m_ctx.S6; - pRD->pCurrentContextPointers->S7 = (PDWORD64)&m_ctx.S7; - pRD->pCurrentContextPointers->S8 = (PDWORD64)&m_ctx.S8; - pRD->pCurrentContextPointers->Fp = (PDWORD64)&m_ctx.Fp; - pRD->pCurrentContextPointers->Ra = (PDWORD64)&m_ctx.Ra; +#ifdef DACCESS_COMPILE + // &m_ctx.Xxx resolves through the DAC cache and the entry can be evicted + // before context pointers are consumed. Point at the local copy in + // pCurrentContext instead (values were already copied above). + T_CONTEXT *pContext = pRD->pCurrentContext; +#else + T_CONTEXT *pContext = &m_ctx; +#endif + + pRD->pCurrentContextPointers->S0 = (PDWORD64)&pContext->S0; + pRD->pCurrentContextPointers->S1 = (PDWORD64)&pContext->S1; + pRD->pCurrentContextPointers->S2 = (PDWORD64)&pContext->S2; + pRD->pCurrentContextPointers->S3 = (PDWORD64)&pContext->S3; + pRD->pCurrentContextPointers->S4 = (PDWORD64)&pContext->S4; + pRD->pCurrentContextPointers->S5 = (PDWORD64)&pContext->S5; + pRD->pCurrentContextPointers->S6 = (PDWORD64)&pContext->S6; + pRD->pCurrentContextPointers->S7 = (PDWORD64)&pContext->S7; + pRD->pCurrentContextPointers->S8 = (PDWORD64)&pContext->S8; + pRD->pCurrentContextPointers->Fp = (PDWORD64)&pContext->Fp; + pRD->pCurrentContextPointers->Ra = (PDWORD64)&pContext->Ra; ClearRegDisplayArgumentAndScratchRegisters(pRD); diff --git a/src/coreclr/vm/riscv64/stubs.cpp b/src/coreclr/vm/riscv64/stubs.cpp index 0acf47f0201c80..718c81402a8763 100644 --- a/src/coreclr/vm/riscv64/stubs.cpp +++ b/src/coreclr/vm/riscv64/stubs.cpp @@ -260,21 +260,30 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u // Update the integer registers in KNONVOLATILE_CONTEXT_POINTERS from // the exception context we have. - pRD->pCurrentContextPointers->S1 = (PDWORD64)&m_ctx.S1; - pRD->pCurrentContextPointers->S2 = (PDWORD64)&m_ctx.S2; - pRD->pCurrentContextPointers->S3 = (PDWORD64)&m_ctx.S3; - pRD->pCurrentContextPointers->S4 = (PDWORD64)&m_ctx.S4; - pRD->pCurrentContextPointers->S5 = (PDWORD64)&m_ctx.S5; - pRD->pCurrentContextPointers->S6 = (PDWORD64)&m_ctx.S6; - pRD->pCurrentContextPointers->S7 = (PDWORD64)&m_ctx.S7; - pRD->pCurrentContextPointers->S8 = (PDWORD64)&m_ctx.S8; - pRD->pCurrentContextPointers->S9 = (PDWORD64)&m_ctx.S9; - pRD->pCurrentContextPointers->S10 = (PDWORD64)&m_ctx.S10; - pRD->pCurrentContextPointers->S11 = (PDWORD64)&m_ctx.S11; - pRD->pCurrentContextPointers->Fp = (PDWORD64)&m_ctx.Fp; - pRD->pCurrentContextPointers->Gp = (PDWORD64)&m_ctx.Gp; - pRD->pCurrentContextPointers->Tp = (PDWORD64)&m_ctx.Tp; - pRD->pCurrentContextPointers->Ra = (PDWORD64)&m_ctx.Ra; +#ifdef DACCESS_COMPILE + // &m_ctx.Xxx resolves through the DAC cache and the entry can be evicted + // before context pointers are consumed. Point at the local copy in + // pCurrentContext instead (values were already copied above). + T_CONTEXT *pContext = pRD->pCurrentContext; +#else + T_CONTEXT *pContext = &m_ctx; +#endif + + pRD->pCurrentContextPointers->S1 = (PDWORD64)&pContext->S1; + pRD->pCurrentContextPointers->S2 = (PDWORD64)&pContext->S2; + pRD->pCurrentContextPointers->S3 = (PDWORD64)&pContext->S3; + pRD->pCurrentContextPointers->S4 = (PDWORD64)&pContext->S4; + pRD->pCurrentContextPointers->S5 = (PDWORD64)&pContext->S5; + pRD->pCurrentContextPointers->S6 = (PDWORD64)&pContext->S6; + pRD->pCurrentContextPointers->S7 = (PDWORD64)&pContext->S7; + pRD->pCurrentContextPointers->S8 = (PDWORD64)&pContext->S8; + pRD->pCurrentContextPointers->S9 = (PDWORD64)&pContext->S9; + pRD->pCurrentContextPointers->S10 = (PDWORD64)&pContext->S10; + pRD->pCurrentContextPointers->S11 = (PDWORD64)&pContext->S11; + pRD->pCurrentContextPointers->Fp = (PDWORD64)&pContext->Fp; + pRD->pCurrentContextPointers->Gp = (PDWORD64)&pContext->Gp; + pRD->pCurrentContextPointers->Tp = (PDWORD64)&pContext->Tp; + pRD->pCurrentContextPointers->Ra = (PDWORD64)&pContext->Ra; ClearRegDisplayArgumentAndScratchRegisters(pRD);