[cDAC] Remove DT_CONTEXT from DBI layer#130367
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
This PR refactors the DBI / cDAC boundary so the DBI layer no longer depends on a DT_CONTEXT struct, moving context-layout knowledge behind new IDacDbiInterface APIs and using opaque target-sized byte buffers where host/target layouts may differ.
Changes:
- Extends
IDacDbiInterface(IDL + managed projection) with new context/query/register APIs and switches multiple stackwalk/context entrypoints fromDT_CONTEXT*toBYTE*. - Updates CoreCLR debugger right-side stackwalking and register-set plumbing to work over opaque context buffers and DAC-provided helpers (e.g.,
CopyContext,Read/WriteRegistersFromContext). - Updates managed contract context models to describe register copy sets/spans and adds an “Extended” register category.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs | Updates STRData ctx to BYTE* and adds new DacDbi APIs + related enums/structs for context/register operations. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs | Adds scalar/wide register copy metadata; adjusts register classifications and extended-reg handling. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs | Adds scalar/wide register copy metadata; adjusts control/general register typing. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RegisterAttribute.cs | Adds RegisterType.Extended. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs | Updates context sizing and floating-point layout; adds scalar/wide register copy metadata and register typing changes. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs | Adds default properties for extended-register sizing and new register copy metadata accessors. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs | Adds abstract extended-register properties and register copy metadata accessors used for flag-gated copies. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs | Forwards new extended-register properties and register copy metadata accessors. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs | Adds scalar/wide register copy metadata; adjusts register typing. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs | Adds scalar/wide register copy metadata; adjusts register typing. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs | Adds scalar/wide register copy metadata and explicit XMM span; adjusts register typing and FP/debug tagging. |
| src/coreclr/inc/dacdbi.idl | Removes DT_CONTEXT typedef; switches context parameters to BYTE*; adds new DacDbi context/register APIs and TargetInfo. |
| src/coreclr/debug/shared/riscv64/primitives.cpp | Switches CORDbgCopyThreadContext to T_CONTEXT and uses CONTEXT_* flags. |
| src/coreclr/debug/shared/loongarch64/primitives.cpp | Switches CORDbgCopyThreadContext to T_CONTEXT and uses CONTEXT_* flags; updates FP span sizing. |
| src/coreclr/debug/shared/i386/primitives.cpp | Switches CORDbgCopyThreadContext to T_CONTEXT and uses CONTEXT_* flags. |
| src/coreclr/debug/shared/arm64/primitives.cpp | Switches CORDbgCopyThreadContext to T_CONTEXT and uses CONTEXT_* flags. |
| src/coreclr/debug/shared/arm/primitives.cpp | Switches CORDbgCopyThreadContext to T_CONTEXT and uses CONTEXT_* flags; updates debug span constant. |
| src/coreclr/debug/shared/amd64/primitives.cpp | Switches CORDbgCopyThreadContext to T_CONTEXT. |
| src/coreclr/debug/inc/riscv64/primitives.h | Switches helpers to T_CONTEXT. |
| src/coreclr/debug/inc/loongarch64/primitives.h | Switches helpers to T_CONTEXT. |
| src/coreclr/debug/inc/i386/primitives.h | Switches helpers to T_CONTEXT. |
| src/coreclr/debug/inc/dbgipcevents.h | Removes arch-specific float count and SP/FP address helpers; introduces CORDB_MAX_FLOAT_REGISTERS. |
| src/coreclr/debug/inc/dacdbistructures.h | Changes Debugger_STRData::ctx to BYTE* and documents as opaque target context bytes. |
| src/coreclr/debug/inc/dacdbiinterface.h | Switches various context params to BYTE*; adds new DacDbi context/register APIs and target info enums/struct. |
| src/coreclr/debug/inc/common.h | Updates CORDbgCopyThreadContext signature to T_CONTEXT. |
| src/coreclr/debug/inc/arm64/primitives.h | Switches helpers to T_CONTEXT; removes unused typedef. |
| src/coreclr/debug/inc/arm/primitives.h | Switches helpers to T_CONTEXT; removes unused typedef. |
| src/coreclr/debug/inc/arm_primitives.h | Switches helpers to T_CONTEXT and adjusts breakpoint-PC logic preprocessor condition. |
| src/coreclr/debug/inc/amd64/primitives.h | Switches helpers to T_CONTEXT; removes unused typedef. |
| src/coreclr/debug/ee/debugger.cpp | Removes DT_CONTEXT casts when calling breakpoint-PC and SS-flag helpers. |
| src/coreclr/debug/ee/controller.cpp | Removes DT_CONTEXT casts when calling SS-flag and IP helpers. |
| src/coreclr/debug/di/valuehome.cpp | Replaces architecture-specific context/register mutations with DAC register read/write and target-sized buffers. |
| src/coreclr/debug/di/stdafx.h | Replaces per-arch thread-context macros with unified DbiGet/SetThreadContext(T_CONTEXT*) declarations. |
| src/coreclr/debug/di/shimstackwalk.cpp | Uses DAC register reads for SP and converts shim contexts to opaque target-sized buffers. |
| src/coreclr/debug/di/shimpriv.h | Updates shim stackwalk state to own target-sized context buffers and passes sizes through. |
| src/coreclr/debug/di/shimlocaldatatarget.cpp | Switches local datatarget thread-context casts from DT_CONTEXT to T_CONTEXT. |
| src/coreclr/debug/di/rsstackwalk.cpp | Converts stackwalk internal state from DT_CONTEXT to opaque buffers and uses DAC CopyContext. |
| src/coreclr/debug/di/rsregsetcommon.cpp | Converts register-set state from cached DT_CONTEXT to opaque buffers and uses DAC CopyContext. |
| src/coreclr/debug/di/rspriv.h | Updates many signatures/fields from DT_CONTEXT to BYTE*/T_CONTEXT; adds context-size caching and DAC register helpers. |
| src/coreclr/debug/di/process.cpp | Adds target-context-size caching via DAC; switches SafeRead/WriteThreadContext to operate on opaque buffers and target info queries. |
| src/coreclr/debug/di/platformspecific.cpp | Stops including per-arch cordbregisterset.cpp implementations; keeps per-arch primitives. |
| src/coreclr/debug/di/module.cpp | Routes JIT-reg-num → CorDebugRegister mapping through DAC-backed helper on CordbProcess. |
| src/coreclr/debug/di/cordb.cpp | Unifies DbiGet/SetThreadContext implementation around T_CONTEXT and updated alignment logic. |
| src/coreclr/debug/di/CMakeLists.txt | Adds a shared cordbregisterset.cpp (new common register-set impl) and removes floatconversion ASM wiring. |
| src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp | Switches DacDbi stackwalk context entrypoints to BYTE* but reinterprets to DT_CONTEXT internally. |
| src/coreclr/debug/daccess/dacdbiimpl.h | Updates interface signatures for BYTE* contexts and declares new DacDbi context/register APIs. |
| src/coreclr/debug/di/riscv64/floatconversion.S | Removes unused float conversion assembly. |
| src/coreclr/debug/di/riscv64/cordbregisterset.cpp | Removes per-arch register-set implementation. |
| src/coreclr/debug/di/loongarch64/floatconversion.S | Removes unused float conversion assembly. |
| src/coreclr/debug/di/loongarch64/cordbregisterset.cpp | Removes per-arch register-set implementation. |
| src/coreclr/debug/di/i386/cordbregisterset.cpp | Removes per-arch register-set implementation. |
| src/coreclr/debug/di/arm64/floatconversion.S | Removes unused float conversion assembly. |
| src/coreclr/debug/di/arm64/floatconversion.asm | Removes unused float conversion assembly. |
| src/coreclr/debug/di/arm64/cordbregisterset.cpp | Removes per-arch register-set implementation. |
| src/coreclr/debug/di/arm/floatconversion.S | Removes unused float conversion assembly. |
| src/coreclr/debug/di/arm/cordbregisterset.cpp | Removes per-arch register-set implementation. |
| src/coreclr/debug/di/amd64/floatconversion.S | Removes unused float conversion assembly. |
| src/coreclr/debug/di/amd64/FloatConversion.asm | Removes unused float conversion assembly. |
| src/coreclr/debug/di/amd64/cordbregisterset.cpp | Removes per-arch register-set implementation. |
Copilot's findings
- Files reviewed: 62/62 changed files
- Comments generated: 5
| ULONG32 cbCtx = GetProcess()->GetTargetContextSize(); | ||
| NewArrayHolder<BYTE> tmpCtx(new BYTE[cbCtx]); | ||
| memcpy(tmpCtx, m_pContextBuffer, cbCtx); | ||
| // flags == 0: tmpCtx already carries the desired ContextFlags (copied above). | ||
| IfFailThrow(pDAC->CopyContext(tmpCtx, cbCtx, context, contextSize, 0)); | ||
| IfFailThrow(pDAC->CheckContext(m_pCordbThread->m_vmThreadToken, tmpCtx)); | ||
|
|
||
| memcpy(m_pContextBuffer, tmpCtx, cbCtx); |
| // allocate a new CordbRegisterSet object | ||
| RSInitHolder<CordbRegisterSet> pRegisterSet(new CordbRegisterSet(m_pThread, | ||
| &m_context, | ||
| RSInitHolder<CordbRegisterSet> pRegisterSet(new CordbRegisterSet(m_pContextBuffer, | ||
| GetProcess()->GetTargetContextSize(), | ||
| m_pThread, | ||
| IsLeafFrame(), | ||
| false)); | ||
|
|
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/coreclr/debug/inc/common.h:64
- The comment claims the buffers "only need to be at least sizeof(DT_CONTEXT)", but the helper APIs below (ContextSizeForFlags/CheckContextSizeForFlags) and the x86 implementation allow smaller buffers when CONTEXT_EXTENDED_REGISTERS isn’t requested. This comment should describe the actual contract (size is flag-dependent) so future callers don’t over-allocate or assume a hard minimum of sizeof(DT_CONTEXT).
- Files reviewed: 74/74 changed files
- Comments generated: 1
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (2)
src/coreclr/debug/di/rsstackwalk.cpp:357
- The destination ContextFlags are not being merged with the incoming context's ContextFlags before calling CopyContext. With flags==0, CopyContext preserves the destination's existing flags and the chunk-wise copy only updates chunks where (dstFlags & srcFlags) contains the chunk flag. This differs from the previous logic that OR'ed the flags (tmpCtx.ContextFlags |= pSrcContext->ContextFlags), and can silently skip applying updates from the incoming context when the cached context lacks those flags.
memcpy(tmpCtx, m_pContextBuffer, cbCtx);
// flags == 0: tmpCtx already carries the desired ContextFlags (copied above).
ContextBuffer temporaryContext = { tmpCtx, cbCtx };
ContextBuffer sourceContext = { context, contextSize };
IfFailThrow(pDAC->CopyContext(temporaryContext, sourceContext, 0));
src/coreclr/debug/inc/common.h:65
- This comment says the buffers must be at least sizeof(DT_CONTEXT), but the implementation (e.g. i386 CopyThreadContext) explicitly allows smaller buffers when ContextFlags do not include extended register state. The doc here should match the actual size contract (ContextFlags + the regions selected by those flags).
- Files reviewed: 74/74 changed files
- Comments generated: 1
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (3)
src/coreclr/debug/di/rsstackwalk.cpp:357
- SetContextWorker no longer ORs the destination ContextFlags with the incoming context's flags before copying, so CopyContext(..., flags: 0) may skip copying chunks that are present in the incoming context but not already enabled in the cached context flags. The old code explicitly did
tmpCtx.ContextFlags |= pSrcContext->ContextFlagsbefore copying. Consider stamping the destination flags when calling CopyContext so the copy doesn't depend on the cached ContextFlags value.
ULONG32 cbCtx = GetProcess()->GetTargetContextSize();
NewArrayHolder<BYTE> tmpCtx(new BYTE[cbCtx]);
memcpy(tmpCtx, m_pContextBuffer, cbCtx);
// flags == 0: tmpCtx already carries the desired ContextFlags (copied above).
ContextBuffer temporaryContext = { tmpCtx, cbCtx };
ContextBuffer sourceContext = { context, contextSize };
IfFailThrow(pDAC->CopyContext(temporaryContext, sourceContext, 0));
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:220
- TryWriteFloatingPointRegister assumes any value length other than 4 bytes is an 8-byte double and calls ReadInt64LittleEndian(value). If a caller passes an unexpected size (e.g. 0, 2, 10), this will throw instead of returning false. Add an explicit length check for 4/8 bytes before decoding.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:231 - TryGetX87SlotOffset reads StatusWord via context.Slice(statusWordOffset) without checking context length first. If the caller passes a truncated context buffer, this will throw. Other platform implementations return false on undersized spans; this helper should do the same.
- Files reviewed: 74/74 changed files
- Comments generated: 1
| ULONG32 size = 0; | ||
| IfFailThrow(GetDAC()->GetTargetContextSize(0x00000020L, &size)); // CONTEXT_EXTENDED_REGISTERS | ||
| m_ctxSize = size; |
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (2)
src/coreclr/debug/inc/common.h:64
- The comment says the buffers only need to be at least sizeof(DT_CONTEXT), but the codebase also supports smaller CONTEXT buffers when ContextFlags omits trailing regions (e.g., x86 without CONTEXT_EXTENDED_REGISTERS via ContextSizeForFlags / CheckContextSizeForBuffer). Please update the comment to reflect the actual minimum size requirement so callers don’t assume they must always allocate sizeof(DT_CONTEXT).
src/coreclr/debug/di/rsstackwalk.cpp:357 - SetContextWorker no longer merges the incoming ContextFlags into the temporary context before calling CopyContext with flags==0 (which preserves the destination flags). If the current cached context has fewer flags than the incoming context, CopyContext will skip copying those requested chunks, changing behavior vs the previous tmpCtx.ContextFlags |= pSrcContext->ContextFlags logic.
ULONG32 cbCtx = GetProcess()->GetTargetContextSize();
NewArrayHolder<BYTE> tmpCtx(new BYTE[cbCtx]);
memcpy(tmpCtx, m_pContextBuffer, cbCtx);
// flags == 0: tmpCtx already carries the desired ContextFlags (copied above).
ContextBuffer temporaryContext = { tmpCtx, cbCtx };
ContextBuffer sourceContext = { context, contextSize };
IfFailThrow(pDAC->CopyContext(temporaryContext, sourceContext, 0));
- Files reviewed: 74/74 changed files
- Comments generated: 0 new
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (2)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:221
- TryWriteFloatingPointRegister assumes the incoming byte payload is either 4 or 8 bytes, but it doesn't validate this. Any other length will currently fall into the 8-byte path and can throw (e.g. ReadInt64LittleEndian on a short span) or encode garbage, which is risky given this helper sits underneath the DacDbi register-write path. Consider explicitly rejecting non-(4|8) sized inputs and returning false.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/SimdRegisterAccess.cs:44 - SimdRegisterAccess.TryWriteRegister advertises writing a scalar value that is "(4 or 8 bytes)", but it currently accepts any length and will blindly copy it into the register slot. Since this helper feeds the DacDbi float/SIMD scalar register write path, it's safer to enforce the documented 4/8-byte contract and reject other sizes to avoid corrupting the CONTEXT image.
- Files reviewed: 74/74 changed files
- Comments generated: 1
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (2)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:220
- TryWriteFloatingPointRegister assumes the value span is either 4 or 8 bytes; any other length will throw from BinaryPrimitives.ReadInt64LittleEndian instead of returning false. Since this is exposed via IPlatformContext/IPlatformAgnosticContext, it should validate input length and fail gracefully.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:238 - TryGetX87SlotOffset reads StatusWord via context.Slice(...) without checking the span length first. If an undersized context buffer is ever passed in, this will throw instead of returning false. Add a bounds check before reading the status word so the helper fails gracefully.
- Files reviewed: 74/74 changed files
- Comments generated: 1
| // A variable sized host memory context buffer in target architecture specific layout. | ||
| // The currently supported layout matches the CONTEXT structure for the | ||
| // target architecture and OS. | ||
| // Not (yet) supported: | ||
| // - Optional trailing XState feature data in the buffer | ||
| // - Add an architecture enum field to make the struct self-describing | ||
| struct MSLAYOUT ContextBuffer |
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (4)
src/coreclr/debug/di/rsthread.cpp:5531
- CordbNativeFrame::GetRegisterSet creates a CordbRegisterSet that holds a raw pointer to the frame’s context buffer (GetContext()) but does not take ownership / make a copy. If the caller releases the ICorDebugFrame but keeps the ICorDebugRegisterSet alive (legal COM usage), the frame’s context buffer can be freed while the register set still uses it, leading to use-after-free when reading registers.
// allocate a new CordbRegisterSet object
RSInitHolder<CordbRegisterSet> pRegisterSet(new CordbRegisterSet(GetContext(),
m_pThread,
IsLeafFrame(),
false));
src/coreclr/debug/inc/common.h:64
- The comment claims the context buffers must be at least sizeof(DT_CONTEXT), but the helpers below (ContextSizeForFlags / CheckContextSizeForBuffer) and the x86 copy logic allow smaller buffers when trailing regions (e.g., extended registers) are not requested via ContextFlags. The documentation should match the actual size contract.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:220 - TryWriteFloatingPointRegister treats any value length other than 4 as an 8-byte double and then reads with ReadInt64LittleEndian, which will throw for unexpected lengths. Since the API contract is 4 or 8 bytes, this should validate the input length and return false for anything else.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs:232 - TryGetX87SlotOffset reads the StatusWord using context.Slice(offset) without validating that the buffer is large enough. If an undersized context span is passed, this will throw instead of returning false.
- Files reviewed: 74/74 changed files
- Comments generated: 0 new
In order to encapsulate the target-specific knowledge within the DacDbi and therefore within the cDAC, it is necessary to remove the DT_CONTEXT struct, which represents the target context, from the DBI layer. This PR accomplishes this by: