[cDAC] WebAssembly support: stack walking and managed metadata resolution#130988
Draft
lewing wants to merge 19 commits into
Draft
[cDAC] WebAssembly support: stack walking and managed metadata resolution#130988lewing wants to merge 19 commits into
lewing wants to merge 19 commits into
Conversation
WebAssembly has no native register context: DT_CONTEXT is an empty struct and REGDISPLAY is zeroed. Managed execution on wasm is fully interpreted, so a managed stack walk is a pointer-chase over the interpreter frame chain rather than a register-based unwind. Add a minimal WasmContext (IPlatformContext) so IPlatformAgnosticContext.GetContextForPlatform resolves for wasm targets instead of throwing. The IP/SP/FP slots are synthetic (populated by the interpreter frame-chain walker, not read from a native context blob, hence Size == 0), and register-unwind operations are intentionally unsupported. This unblocks the stack-walk entry point for wasm. The context-free contracts (RuntimeInfo, Object, RuntimeTypeSystem, Loader, Thread, GC) do not depend on this and are reachable via memory reads alone. Contributes to dotnet#120646. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
The stub ContractDescriptor computed its flags with a bitwise-AND where
an OR was intended:
.flags = 0x1u & (sizeof(void*) == 4 ? 0x02u : 0x00u)
This evaluates to 0x00 in every case (0x1 & 0x02 == 0 on 32-bit,
0x1 & 0x00 == 0 on 64-bit), so the stub never set the mandatory low bit
(bit 0, always 1 per the contract-descriptor spec) and always masked
away the pointer-size bit (bit 1).
Use OR to match the real descriptor's computation in datadescriptor.cpp:
.flags = 0x1u | (sizeof(void*) == 4 ? 0x02u : 0x00u)
The stub is only linked when CDAC_BUILD_TOOL_BINARY_PATH is unset (builds
without a .NET SDK) and carries an empty descriptor, so impact is limited,
but a consumer inspecting the flags would previously see an invalid
descriptor and the wrong pointer size.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
CoreCLR on WebAssembly has no native register context (empty DT_CONTEXT, zeroed REGDISPLAY), so the register-driven CreateStackWalk path cannot run. But managed execution on wasm is fully interpreted, and the interpreter maintains an explicit in-memory frame chain (InterpreterFrame.TopInterpMethodContextFrame -> InterpMethodContextFrame .pParent), so a managed stack walk is a pointer-chase rather than an unwind. Add IStackWalk.GetInterpretedFrames(threadPointer): it walks the Thread's explicit Frame chain and, for each InterpreterFrame, expands the InterpMethodContextFrame chain (reusing the existing WalkInterpreterFrameChain head-resolution), resolving each frame's MethodDesc via StartIp -> InterpByteCodeStart.Method -> InterpMethod.MethodDesc. It is context-free (pure memory reads) and makes no GetContextForPlatform / Unwind calls, so it works on interpreter-only targets such as wasm. The existing GetFrames (explicit Frame-chain enumeration, consumed by DacDbi internal-frame reporting) is intentionally left unchanged. Also adds the InterpretedFrameData record, a FrameHelpers helper for the StartIp -> MethodDesc resolution, mock infrastructure for the interpreter frame chain, and unit tests. Fixes an incorrect <paramref> name on MockFrameBuilder.LinkChain. Contributes to dotnet#120646. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
Rubber-duck review noted WasmContext.Size returned 0 while ContextHolder.GetBytes() serializes the full 12-byte synthetic struct, so a consumer that allocates Size bytes and one that reads GetBytes() would disagree. Return the actual synthetic-context size (three 32-bit IP/SP/FP slots) so Size and GetBytes() are consistent. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
CoreCLR on WASM will use ReadyToRun for acceptable perf, so a real stack is always a mix of R2R and interpreter frames. R2R frames are walked over the managed linear stack ($sp) via a frameSize-based virtual unwind with synthetic "virtual IPs" (there is no register context and code is not in linear memory). Add WasmUnwinder, the cDAC mirror of the native WASM R2R stack walk in src/coreclr/vm/wasm/helpers.cpp (and the ABI in clr-abi.md): - TryGetFramePointer: NormalizeFrameBase, incl. localloc indirection (STACK_WALK_INDIRECT_TO_FRAMEPOINTER) and the TERMINATE_R2R_STACK_WALK boundary where the walk hands off to the Frame / interpreter chain. - GetVirtualIP: base virtual IP + function-local virtual IP (stored /2). - GetEstablishingFramePointerFromTerminator: recovers the establishing frame pointer stored beside the terminator by CallFunclet* (funclet EH). - TryUnwindOneFrame: advance $sp by the ULEB128 fixed frame size and produce the caller's virtual IP (mirrors WasmUnwindStackFrameCore). The ExecutionManager-backed lookups (virtual-IP base, funclet check, per-function unwind data) are abstracted behind IWasmR2RInfo; they are implemented against the cDAC ExecutionManager once the WASM R2R virtual-IP data descriptors are emitted. Two-pass EH / funclet reporting is then inherited from the existing architecture-agnostic EH stackwalk (IsFunclet / FindParentStackFrame operate on CodeBlockHandle, not registers) once virtual IPs resolve to code blocks. Adds unit tests covering frame-base normalization (floor, localloc indirection, terminator), establishing-FP recovery, virtual IP resolution, and single/multi-byte ULEB128 frame-size unwinding. Contributes to dotnet#120646. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
…dFrames)" This reverts commit e01be9e. On WASM, ReadyToRun is required for acceptable perf even in developer builds, so a real managed stack is always a mix of R2R and interpreter frames. A standalone, public "interpreter frames only" API therefore surfaces a partial stack that cannot position R2R frames in call order, and risks consumers building on a half-stack. The correct shape is a single interleaved walk (CreateStackWalk-style, driven by $sp), with the interpreter frame chain as one arm and the R2R virtual-IP unwind (see WasmUnwinder) as the other. The reusable interpreter primitives (FrameHelpers.WalkInterpreterFrameChain / ResolveTopInterpMethodContextFrame) remain available for that unified walk; the StartIp->MethodDesc helper and the public IStackWalk.GetInterpretedFrames surface are withdrawn until the interleaved walk lands so the contract is not committed prematurely. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
Adds the descriptor data and reader needed to resolve a WASM R2R function
table index to its base virtual IP, funclet status, and unwind data -
backing the IWasmR2RInfo seam consumed by WasmUnwinder.
Descriptor emission (TARGET_WASM only):
- New FunctionTableIndexRangeSection type (MinFunctionTableIndex,
NumRuntimeFunctions, R2RModule, Next) + cdac_data in codeman.h.
- FunctionTableIndexRangeList global (ExecutionManager::
s_pFunctionTableIndexRangeList).
- MinVirtualIP field on ReadyToRunInfo (m_minVirtualIP).
Reader:
- WasmR2RInfo implements IWasmR2RInfo, mirroring ExecutionManager::
{FindFunctionTableIndexRangeSection, IsFuncletFunctionIndex,
GetWasmVirtualIPFromFunctionTableIndex} in codeman.cpp - including the
funclet backward-index-to-controlling-function walk and the
RUNTIME_FUNCTION funclet high-bit (0x80000000) - reusing the existing
RuntimeFunctionLookup / ReadyToRunInfo / Module infra.
Verified by a CoreCLR browser-wasm build: datadescriptor.cpp compiles
under TARGET_WASM and cdac-build-tool emits the type
(fields at offsets 0/4/8/12), the FunctionTableIndexRangeList pointer
global, and ReadyToRunInfo.MinVirtualIP into the contract descriptor -
matching the managed reader's field names and shapes.
Contributes to dotnet#120646.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
WasmContext.Unwind previously threw. Now it advances the context by one ReadyToRun frame over the managed linear stack using WasmUnwinder + WasmR2RInfo: it reads StackPointer ($sp), unwinds one frame (nextSp + caller virtual IP), and updates StackPointer/InstructionPointer. When the R2R walk terminates (an interpreter transition or the stack top), StackPointer is set to null so the stack walker can fall back to the explicit Frame chain / interpreter frame chain. This makes WasmContext a real $sp-driven context (mirroring how X86Context.Unwind drives X86Unwinder) rather than a degenerate null-object. The full interleaved CreateStackWalk integration (per-thread $sp seed, virtual-IP -> method reverse mapping, interleaving with InterpreterVirtualUnwind) is the remaining step. Contributes to dotnet#120646. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 272b3faa-2ba5-4287-bc0a-753b59d2d3d2
WebAssembly has no native register context (DT_CONTEXT is empty and REGDISPLAY is zeroed), so the initial managed stack walk context is seeded from the explicit Frame chain rather than a captured thread context. The shared StackWalk_1 driver already walks the FrameChain to the innermost transition frame in GetContextFromFrames; wire the degenerate WasmContext into that path so seeding works on WASM: - Add WasmFrameHandler, routing ContextHolder<WasmContext> through the base frame handler. The base HandleInlinedCallFrame already reads InlinedCallFrame.CallSiteSP / CallerReturnAddress / CalleeSavedFP into the synthetic IP/SP/FP slots -- the common P/Invoke-boundary seeding path. Hijack frames (a debugger / GC-suspension concept) throw NotSupported. - Dispatch ContextHolder<WasmContext> to WasmFrameHandler in FrameHelpers.GetFrameHandler (previously threw for WASM). - Test the seam end-to-end through FrameHelpers.UpdateContextFromFrame, plus the mock-infra additions (InlinedCallFrame CallSiteSP / CalleeSavedFP) it needs. Everything flows through the shared StackWalk_1 driver and the existing IsInterpreterCode -> InterpreterVirtualUnwind path; no WASM-specific contract surface is added. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
…on stash
The WASM WasmContext previously held only three synthetic IP/SP/FP slots,
which did not match the runtime's wasm T_CONTEXT (src/coreclr/pal/inc/pal.h,
HOST_WASM): { ContextFlags, InterpreterWalkFramePointer, InterpreterSP,
InterpreterFP, InterpreterIP } -- five 32-bit fields, 20 bytes. Because the
cDAC serializes this context to/from target memory (thread context and
*ExceptionFrame TargetContext blobs) and returns it to SOS, the managed
layout must match the native struct byte-for-byte.
- Rework WasmContext to mirror the native field order and size (Size is now
5 * sizeof(uint)), map StackPointer/InstructionPointer/FramePointer to the
Interpreter{SP,FP,IP} slots, and back RawContextFlags with ContextFlags.
- Expose the InterpreterWalkFramePointer slot as the synthetic first-argument
register the interpreter virtual unwind uses: WasmContext supports it in
Try{Set,Read}Register, GetFirstArgRegisterName returns it for WASM, and
WasmFrameHandler.HandleInlinedCallFrame stashes the InterpreterFrame address
there when a P/Invoke transition is directly below an InterpreterFrame --
matching native SetFirstArgReg (src/coreclr/vm/wasm/cgencpu.h) and the
per-architecture handlers.
- Unit-test the native layout / register round-trip and the
InlinedCallFrame-over-InterpreterFrame stash.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
Add mock-target coverage for the interpreter side of a mixed R2R/interpreter WASM stack walk, exercising the real InterpreterVirtualUnwind path with a WasmContext: - Stepping a multi-node InterpMethodContextFrame chain: each unwind follows pParent and takes the parent frame's IP/SP/FP (mirrors native VirtualUnwindInterpreterCallFrame). - Graceful termination when the chain is exhausted and no owning InterpreterFrame is stashed in the synthetic first-argument register. This also guards the WASM first-argument-register wiring: the exhaustion path routes through GetFirstArgRegisterName, which returned NotSupported for WASM before it was mapped to InterpreterWalkFramePointer. Re-adds the MockInterpMethodContextFrame test descriptor and lets the shared CreateTarget register an IRuntimeInfo architecture for paths that consult it. The full IStackWalk.CreateStackWalk driver loop is validated end to end via dump/live targets (no architecture mock-tests it); these unit tests cover the WASM-specific interpreter virtual unwind that feeds it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
On WASM a "code address" is a synthetic virtual IP (ExecutionManager::GetWasmVirtualIPFromStackPointer = base + function-local offset). R2R modules are registered in the RangeSectionMap by their virtual-IP range, so resolving a virtual IP to its MethodDesc uses the same generic RangeSection.Find -> ReadyToRunJitManager path as every other architecture -- there is no WASM-specific IP->MethodDesc code path. (MinVirtualIP and FunctionTableIndexRangeSection are consumed only by the unwinder's function-table-index -> base-virtual-IP mapping in WasmR2RInfo.) Add a test that resolves a wasm32 virtual IP to its R2R MethodDesc and confirms the ReadyToRun classification, mirroring GetMethodDesc_R2R_OneRuntimeFunction with explicit virtual-IP framing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
On builds without code versioning (FEATURE_CODE_VERSIONING off, e.g. WASM), the runtime omits Module::MethodDefToILCodeVersioningStateMap from the emitted Module data descriptor (src/coreclr/vm/datadescriptor/datadescriptor.inc, guarded by FEATURE_CODE_VERSIONING). The DataContractReader Loader contract read that field unconditionally, so any managed-object -> type-name resolution on WASM threw "Field not found in any layout" the moment it needed the module lookup tables. Make the field optional, matching the existing EnCClassList pattern (also feature-gated): - Data.Module: declare MethodDefToILCodeVersioningStateMap as TargetPointer? so an absent field reads as null instead of throwing. - Loader.GetLookupTables: coalesce the absent map to TargetPointer.Null, which GetModuleLookupMapElement already treats as an empty table (no code-versioning lookups, the correct behavior when the feature is off). - Update Loader.md GetLookupTables pseudo-code to reflect the optional read. - Test: a Module layout without the field resolves the map to null rather than faulting; the mock Module layout gains an includeCodeVersioning toggle. Discovered via live browser-wasm cDAC validation: type resolution on a real frozen String.Empty failed here before this fix. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
EcmaMetadata resolution calls Loader.TryGetLoadedImageContents to locate a module's metadata. It only consulted PEImage.LoadedImageLayout (m_pLayouts[IMAGE_LOADED]), which is null for images that are never mapped/ loaded -- notably webcil ReadyToRun images on WASM, whose metadata lives in the flat layout (m_pLayouts[IMAGE_FLAT]). As a result any managed-object -> type name resolution on WASM failed with "Module is not loaded" once it reached the metadata step. - peimage.h / datadescriptor.inc: expose cdac_data<PEImage>::FlatImageLayout (m_pLayouts[IMAGE_FLAT]) as a new PEImage.FlatImageLayout descriptor field, alongside the existing LoadedImageLayout. - Data.PEImage: add FlatImageLayout (nullable; older descriptors without it read as null). - Loader.TryGetLoadedImageContents: when LoadedImageLayout is null, fall back to the flat layout. Its flags lack FLAG_MAPPED, so the metadata reader treats it as a flat (non-mapped) image, which is correct for webcil. - Update Loader.md and add a MockTarget test for the flat fallback. Surfaced by live browser-wasm cDAC validation: type resolution on a frozen String reached "Module is not loaded" before this fix. NOTE: the managed changes are unit-tested; the native datadescriptor change (peimage.h / datadescriptor.inc) requires a runtime build to validate. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
Once the flat-layout fallback lets EcmaMetadata reach a WASM webcil ReadyToRun
image, it fed the flat bytes to System.Reflection.Metadata's PEReader, which
can't parse the stripped/rewrapped webcil header -> "BadImageFormatException:
Unknown file format." Make the read-only metadata path webcil-aware:
- EcmaMetadata.GetReadOnlyMetadataAddress: when the image begins with the webcil
magic ('WbIL'), locate the ECMA-335 metadata via the webcil header's
PeCliHeaderRva -> CLI (COR20) header -> metadata directory, resolving RVAs with
the loader's webcil-aware GetILAddr instead of PEReader. Non-webcil images keep
the existing PEReader path.
- Data.WebcilHeader: expose PeCliHeaderRva (RawOffset, no descriptor change).
- Loader: GetRvaData / GetILAddr had the same loaded-layout assumption as
TryGetLoadedImageContents and threw for webcil-on-WASM (LoadedImageLayout null).
Factor the loaded-or-flat layout selection into a shared helper so both fall
back to the flat layout.
- Update Loader.md / EcmaMetadata.md and add a GetILAddr flat-webcil-layout test
(the webcil header + RVA resolution the metadata path relies on).
Purely managed (WebcilHeader uses RawOffset; depends on the PEImage.FlatImageLayout
descriptor field from the previous change). Surfaced by live browser-wasm cDAC
validation, which advanced to "Unknown file format" after the flat-layout fix.
The EcmaMetadata webcil branch itself is validated end to end on a live wasm
target; the loader flat-fallback it relies on is unit-tested here.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
|
Azure Pipelines: Successfully started running 5 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
Member
Author
|
cc @dotnet/wasm-contrib |
Contributor
There was a problem hiding this comment.
Pull request overview
Enables cDAC to operate against CoreCLR/WebAssembly targets by adding WASM-aware stack-walking support (context + R2R unwinding + interpreter transition seeding) and by fixing managed metadata resolution for WASM’s webcil/flat-image layout.
Changes:
- Add WASM stack-walk context and ReadyToRun linear-stack unwinder plumbing, including function-table-index range lookup.
- Make loader/metadata paths resilient to WASM specifics (optional code-versioning table, flat PEImage layout fallback, webcil-aware metadata location).
- Add/extend unit tests and update relevant contract design docs for Loader/EcmaMetadata.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/tests/UnitTests/WasmUnwinderTests.cs | New unit tests for WASM R2R unwinder behaviors (frame pointer, VIP, ULEB128 frame sizes). |
| src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs | Adds WASM-specific stack-walk tests (frame-chain seeding, interpreter virtual unwind, context layout/register behavior). |
| src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs | Allows mocking Module layouts with/without code versioning field. |
| src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Frame.cs | Extends frame mocks for WASM seeding and adds InterpMethodContextFrame mock layout/builder support. |
| src/native/managed/cdac/tests/UnitTests/LoaderTests.cs | Tests for missing code-versioning map and flat-layout/webcil RVA resolution + image contents fallback. |
| src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs | Adds WASM “virtual IP” ReadyToRun MethodDesc resolution coverage. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs | Adds FunctionTableIndexRangeSection data type id. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/WebcilHeader.cs | Adds PeCliHeaderRva field to support webcil metadata location. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs | Adds nullable MinVirtualIP field for WASM virtual-IP base computation. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PEImage.cs | Exposes nullable FlatImageLayout to support non-mapped (flat) images. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs | Makes MethodDefToILCodeVersioningStateMap nullable for FEATURE_CODE_VERSIONING-off builds. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FunctionTableIndexRangeSection.cs | New data model for WASM function-table-index range list nodes. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/WasmFrameHandler.cs | New WASM frame handler for seeding/stashing interpreter walk frame pointer. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs | Wires WasmFrameHandler and maps WASM “first arg register” name. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/WasmContext.cs | New WASM context struct mirroring native T_CONTEXT layout and providing unwind entrypoint. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmUnwinder.cs | New R2R linear-stack unwinder (frame pointer, virtual IP, ULEB128 frame size decode). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs | Implements function-table-index → R2R module/runtime function lookup via new descriptors. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs | Adds WASM context selection based on RuntimeInfoArchitecture.Wasm. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs | Adds flat-layout fallback for image access and treats missing code-versioning map as empty. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs | Adds webcil-aware metadata resolution path; keeps PEReader path for normal PEs. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs | Adds FunctionTableIndexRangeList global name. |
| src/coreclr/vm/readytoruninfo.h | Exposes MinVirtualIP in cdac_data under TARGET_WASM. |
| src/coreclr/vm/peimage.h | Adds FlatImageLayout descriptor field (m_pLayouts[IMAGE_FLAT]). |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Emits new PEImage/ReadyToRunInfo fields and WASM-only type/global for function table index ranges. |
| src/coreclr/vm/codeman.h | Adds cdac_data for FunctionTableIndexRangeSection and exposes list head address under TARGET_WASM. |
| src/coreclr/debug/datadescriptor-shared/contractdescriptorstub.c | Fixes stub ContractDescriptor flags initialization. |
| docs/design/datacontracts/Loader.md | Documents FlatImageLayout fallback and optional code-versioning table behavior. |
| docs/design/datacontracts/EcmaMetadata.md | Documents webcil magic/header-based metadata location approach. |
- WasmContext.UnsetSingleStepFlag: no-op instead of throwing. WASM has no hardware single-step flag (like ARM/LoongArch64/RISC-V), and callers such as Debugger_1.PrepareExceptionHijack invoke it unconditionally. - WasmUnwinder.TryGetFramePointer: re-apply the linear-stack floor after following the localloc frame-pointer indirection, so a null/invalid saved pointer returns false instead of reading at a bad address. - WasmUnwinder.TryUnwindOneFrame: terminate the R2R walk when the decoded frame size is 0 (no progress) or the caller's virtual IP is null (interpreter transition / stack top), rather than reporting a bogus advanced frame. - WasmUnwinder.DecodeULEB128: bound the decode to 5 bytes (uint32 max) and fail on a malformed, unterminated encoding. - Document the WASM ExecutionManager descriptors (ReadyToRunInfo.MinVirtualIP, FunctionTableIndexRangeSection, FunctionTableIndexRangeList) in ExecutionManager.md. - Fix an accidental single-line brace/declaration in a LoaderTests method. - Add unit tests for the new unwinder guards (below-floor localloc, zero frame size, malformed ULEB128). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
- WasmR2RInfo.FindSection: dereference the FunctionTableIndexRangeList global.
CDAC_GLOBAL_POINTER globals hold the address of the pointer slot
(s_pFunctionTableIndexRangeList), so the value must be read through once to
obtain the actual list head (matching the ThreadStore/FinalizerThread pattern)
rather than walking from the slot address itself.
- WasmFrameHandler.HandleHijackFrame: throw PlatformNotSupportedException, the
standard exception for platform limitations in this codebase.
- WasmContext.Try{Set,Read}Register: compare register names with
StringComparison.OrdinalIgnoreCase instead of name.ToLowerInvariant(), which
allocated on every call on the stack-walk hot path (matching the other
platform contexts).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
Add a MockTarget harness for WasmR2RInfo that resolves a function-table index through the FunctionTableIndexRangeList global -> range section -> module -> ReadyToRunInfo -> RuntimeFunction chain. The global is modeled as a CDAC_GLOBAL_POINTER (address of the list-head slot), so the tests fail if FindSection walks from the slot address instead of dereferencing it. Covers TryGetVirtualIPBase (base virtual IP), TryGetUnwindData (LoadedImageBase + unwind RVA), the non-funclet flag, and an index outside any section. Extends MockReadyToRunInfo with the WASM MinVirtualIP field and LoadedImageBase/MinVirtualIP accessors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
…egister - Make WasmContext, IWasmR2RInfo, WasmUnwinder, WasmR2RInfo `internal` to match their sibling context/unwinder types (AMD64Context/AMD64Unwinder are internal; only IPlatformContext is public). Tests reach them via InternalsVisibleTo. - Remove IWasmR2RInfo.IsFuncletFunctionIndex and its WasmR2RInfo implementation: unused in production (funclet detection is deferred with EH unwind). Drop the corresponding fake/test members and now-unused constants. - WasmFrameHandler.HandleInlinedCallFrame: check the TrySetRegister return value and throw on failure, consistent with the other frame handlers. Full cDAC suite: 2735 passed / 0 failed / 16 skipped. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: afa9efc6-f596-41bd-9070-dc651db8b7b4
Member
Author
|
@copilot review |
Comment on lines
98
to
104
| ThisPtrRetBufPrecodeData, | ||
| InterpreterPrecodeData, | ||
| InterpByteCodeStart, | ||
| InterpMethod, | ||
| InterpMethodContextFrame, | ||
| FunctionTableIndexRangeSection, | ||
| Array, |
Open
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the managed data contract reader (cDAC) up on CoreCLR/WebAssembly, so diagnostic tools can inspect live managed state of a browser/WASI
corerun.wasmtarget the same way they do on other platforms. This covers two areas: the WASM stack walk, and the module/metadata resolution path that type-name lookup depends on.Background
WASM CoreCLR differs from other targets in ways the cDAC contracts did not yet account for:
T_CONTEXThas no real register file; execution is a mix of ReadyToRun frames unwound over the linear stack ($sp) with synthetic virtual IPs, and interpreter frames walked via the explicitInterpreterFrame/InterpMethodContextFramechain.System.Reflection.Metadata.PEReadercannot parse.Stack walking
Builds on the WASM
WasmContext/WasmUnwinder/WasmR2RInfofoundation and wires it into the sharedStackWalk_1driver — no WASM-specific contract surface is added (an earlier context-freeIStackWalk.GetInterpretedFramesexperiment is reverted); everything flows through the existing driver and theIsInterpreterCode → InterpreterVirtualUnwindpath.WasmContextnow mirrors the nativeT_CONTEXT(src/coreclr/pal/inc/pal.h,HOST_WASM):ContextFlags,InterpreterWalkFramePointer,InterpreterSP,InterpreterFP,InterpreterIP— five 32-bit fields, 20 bytes. The context is serialized to/from target memory and handed to SOS, so the managed layout must match byte-for-byte (it previously held only three IP/SP/FP slots).WasmFrameHandlerseeds the initial stack-walk context from the Frame chain (WASM has no capturedDT_CONTEXT): the innermostInlinedCallFrame'sCallSiteSPprovides$sp. On the P/Invoke-into-interpreter transition it stashes the owningInterpreterFrameaddress inInterpreterWalkFramePointer, matching nativeSetFirstArgReg(src/coreclr/vm/wasm/cgencpu.h);GetFirstArgRegisterNamereturns that slot for WASM.MethodDescresolution reuses the genericRangeSectionMappath (a virtual IP is just an address in a registered range); no WASM-specific IP→MethodDesc path is required.Managed metadata resolution (contract fixes)
Three feature/format gaps blocked managed-object → type-name resolution on WASM. Each is fixed following existing patterns:
Module.MethodDefToILCodeVersioningStateMapoptional — omitted from the descriptor whenFEATURE_CODE_VERSIONINGis off (WASM). Made nullable (matching theEnCClassListpattern) soLoader.GetLookupTablesreads it as an empty table instead of throwing "Field not found in any layout".PEImage.FlatImageLayout—cdac_data<PEImage>only exposed the loaded layout (m_pLayouts[IMAGE_LOADED]), which is null for images that are never mapped. AddFlatImageLayout(m_pLayouts[IMAGE_FLAT]) and fall back to it inLoader.TryGetLoadedImageContents(and, factored into a shared helper, inGetRvaData/GetILAddr), so webcil-on-WASM metadata is reachable instead of "Module is not loaded".EcmaMetadata.GetReadOnlyMetadataAddressfed the flat webcil bytes toPEReader(→ "BadImageFormatException: Unknown file format"). It now detects the webcil magic (WbIL) and locates the ECMA-335 metadata via the webcil header'sPeCliHeaderRva→ CLI (COR20) header → metadata directory, resolving RVAs with the loader's webcil-awareGetILAddr. Non-webcil images keep thePEReaderpath.The only native change is the
PEImage.FlatImageLayoutdescriptor field (peimage.h+datadescriptor.inc); the rest is managed.Testing
WasmContextlayout +InterpreterWalkFramePointerregister round-trip, the InlinedCallFrame-over-InterpreterFrame stash, the interpreter virtual unwind chain-step/exhaustion, virtual-IP → R2RMethodDescresolution, theModulecode-versioning-absent path, thePEImageflat-layout fallback, andGetILAddrresolving through a flat webcil layout. Full cDAC suite green.docs/design/datacontracts/{Loader,EcmaMetadata}.mdupdated to match the contract changes.clr.nativebuilds clean with the descriptor change.End-to-end validation
Validated against a live browser-
wasmCoreCLR target over CDP (rebuiltcorerun.wasmfor theFlatImageLayoutdescriptor field; managed reader hot-swapped for the rest). With all three fixes:managed_object(String.Empty)→full_type_name: "System.String",module: "System.Private.CoreLib",type_def_token: 0x0200007E,type_name_complete: true— the ECMA metadata is read from the webcil ReadyToRun image via theWbIL-header path and the TypeDef resolves.managed_threadsenumerates the managed thread (the trap thread'sLastThrownObjecttype resolves through the same metadata path).The full
read → walk → enumerate → type-namepath is green on live WASM managed state.Out of scope / follow-ups
Deferred (each with its own required trap/repro shape or larger effort): live
InlinedCallFrameCallSiteSPread on the P/Invoke seam, exception-handling/funclet unwind, GC stack-ref reporting during the walk,locallocindirect frames, wasm64, and DacDbi/debugger paths (disabled on WASM).Note
This pull request was authored with the assistance of GitHub Copilot.