diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index bfa5b823e7730a..d0de293a21b763 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -162,6 +162,28 @@ The [range section map](#rangesectionmap) is used to partition the address space Within a range section fragment, a [nibble map](#nibblemap) structure is used to map arbitrary IP addresses back to the start of the method (and to the code header which immediately preceeeds the entrypoint to the code). +WebAssembly ReadyToRun code uses encoded virtual IPs rather than linear-memory code addresses. +These ranges are registered in `VirtualIPRangeList`, not the range section map. A virtual-IP +lookup walks the intrusive list with cycle detection and a per-lookup budget of 65,536 nodes. +The budget bounds traversal work and visited-set growth for corrupt chains of distinct nodes; +it is a reader resource policy, not a native registration limit or proof of corruption. +It provides 64 times the headroom of the original 1,024-node budget while remaining finite. +Exactly 65,536 nodes may be traversed successfully. If the last permitted node has a non-null +`Next`, lookup fails closed before dereferencing that next node, even if a matching range has +already been found. This intentionally rejects longer lists, including otherwise valid ones. +Traversal continues after a match to detect ambiguity within the budget. Invalid +range structures, cycles, or multiple ranges containing the requested IP fail closed, and an +encoded virtual IP that is absent from the list does not fall through to the real-address map. +Module and ReadyToRun metadata are validated only for a range containing the requested IP. +In particular, registration publishes a range before assigning its module's `MinVirtualIP`; +an unrelated range in that state must not prevent lookup of already initialized modules. + +ReadyToRun hot/cold mapping is optional: targets built without `FEATURE_COLD_R2R_CODE`, +including WebAssembly, omit `NumHotColdMap` and `HotColdMap`. An absent count is treated as +zero for method-index adjustment, relative-offset calculation, and method-region queries, +without reading the absent map. WebAssembly also omits `DelayLoadMethodCallThunks`; +virtual-IP ranges bypass native thunk classification. + ### Data descriptors used @@ -219,6 +241,7 @@ Within a range section fragment, a [nibble map](#nibblemap) structure is used to | `RangeSection` | `NextForDelete` | `pointer` | Pointer to next range section for deletion | | `RangeSection` | `R2RModule` | `pointer` | ReadyToRun module | | `RangeSection` | `RangeBegin` | `pointer` | Begin address of the range section | +| `RangeSection` | `RangeEndOpen` | `pointer` | Exclusive end address of the range section | | `RangeSection` | `RangeList` | `pointer` | Pointer to the `CodeRangeMapRangeList` associated with this range section | | `RangeSectionFragment` | `Next` | `pointer` | Tagged pointer to the next fragment (bit 0 is the collectible flag; must be stripped to obtain the address) | | `RangeSectionFragment` | `RangeBegin` | `pointer` | Begin address of the fragment | @@ -232,11 +255,12 @@ Within a range section fragment, a [nibble map](#nibblemap) structure is used to | `ReadyToRunInfo` | `Composite` | `pointer` | Pointer to the `ReadyToRunCoreInfo` used for section lookup | | `ReadyToRunInfo` | `CompositeInfo` | `pointer` | Pointer to composite R2R info - or itself for non-composite | | `ReadyToRunInfo` | `DebugInfoSection` | `pointer` | Pointer to an `ImageDataDirectory` for the debug info | -| `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | `pointer` | Pointer to an `ImageDataDirectory` for the delay load method call thunks | +| `ReadyToRunInfo` | `DelayLoadMethodCallThunks` | `pointer` | Pointer to an `ImageDataDirectory` for the delay load method call thunks; absent on WebAssembly | | `ReadyToRunInfo` | `EntryPointToMethodDescMap` | `HashMap` | `HashMap` of entry point addresses to `MethodDesc` pointers | -| `ReadyToRunInfo` | `HotColdMap` | `pointer` | Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) | +| `ReadyToRunInfo` | `HotColdMap` | `pointer` | Pointer to an array of 32-bit integers; present only with `FEATURE_COLD_R2R_CODE` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80) | | `ReadyToRunInfo` | `LoadedImageBase` | `pointer` | Base address of the loaded R2R image | -| `ReadyToRunInfo` | `NumHotColdMap` | `uint32` | Number of entries in the `HotColdMap` | +| `ReadyToRunInfo` | `MinVirtualIP` | `pointer` | Base virtual IP assigned to the ReadyToRun module on WebAssembly | +| `ReadyToRunInfo` | `NumHotColdMap` | `uint32` | Number of entries in the `HotColdMap`; absent without `FEATURE_COLD_R2R_CODE`, meaning no hot/cold mapping | | `ReadyToRunInfo` | `NumRuntimeFunctions` | `uint32` | Number of `RuntimeFunctions` | | `ReadyToRunInfo` | `ReadyToRunHeader` | `pointer` | Pointer to the ReadyToRunHeader | | `ReadyToRunInfo` | `RuntimeFunctions` | `pointer` | Pointer to an array of `RuntimeFunctions` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontyperuntimefunctions) | @@ -250,10 +274,12 @@ Within a range section fragment, a [nibble map](#nibblemap) structure is used to | `RealCodeHeader` | `NumUnwindInfos` | `uint32` | Number of Unwind Infos | | `RealCodeHeader` | `UnwindInfos` | `pointer` | Start address of Unwind Infos | | `RuntimeFunction` | *(type size)* | `uint32` | Size of a runtime function entry in bytes | -| `RuntimeFunction` | `BeginAddress` | `uint32` | Begin address of the function. On ARM32, bit 0 (the Thumb bit) is set. | +| `RuntimeFunction` | `BeginAddress` | `uint32` | Begin address of the function. On ARM32, bit 0 is the Thumb bit; on WebAssembly, bit 31 marks a funclet and is excluded from address arithmetic. | | `RuntimeFunction` | `EndAddress` | `uint32` | End address of the function. Only exists on some platforms | | `RuntimeFunction` | `UnwindData` | `uint32` | Pointer to the unwind info for the function | | `UnwindInfo` | `FunctionLength` | `uint32` | Length of the associated function in bytes. Only exists on some platforms | +| `VirtualIPRangeSection` | `Next` | `pointer` | Pointer to the next registered WebAssembly ReadyToRun virtual-IP range | +| `VirtualIPRangeSection` | `RangeSection` | `pointer` | Address of the embedded synthetic `RangeSection` describing the virtual-IP range | ### Global variables used @@ -268,6 +294,7 @@ Within a range section fragment, a [nibble map](#nibblemap) structure is used to | `ObjectMethodTable` | `pointer` | Address of the global variable holding the System.Object MethodTable pointer | | `StubCodeBlockLast` | `uint8` | Maximum sentinel code header value indentifying a stub code block | | `ThePreStub` | `pointer` | Address of the global containing the prestub entrypoint | +| `VirtualIPRangeList` | `pointer` | Address of the global pointer to the WebAssembly ReadyToRun virtual-IP range list | ### Contracts used @@ -357,8 +384,9 @@ bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddre // Find the relative address that we are looking for TargetCodePointer addr = /* code pointer from jittedCodeAddress using PlatformMetadata.GetCodePointerFlags */ - TargetPointer imageBase = Target.ReadPointer(/* range section address + RangeSection::RangeBegin offset */); - TargetPointer relativeAddr = addr - imageBase; + TargetPointer codeBase = /* RangeSection.RangeBegin, or ReadyToRunInfo.MinVirtualIP on WebAssembly */; + TargetPointer loadedImageBase = /* ReadyToRunInfo.LoadedImageBase on WebAssembly, otherwise codeBase */; + TargetPointer relativeAddr = addr - codeBase; TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */); int index = // Iterate through runtimeFunctions and find index of function with relativeAddress @@ -373,7 +401,8 @@ bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddre TargetPointer function = runtimeFunctions + (ulong)(index * /* size of RuntimeFunction */); - TargetPointer startAddress = imageBase + Target.Read(function + /* RuntimeFunction::BeginAddress offset */); + uint beginAddress = /* RuntimeFunction.BeginAddress with platform flags removed */; + TargetPointer startAddress = codeBase + beginAddress; TargetPointer entryPoint = /* code pointer from startAddress using PlatformMetadata.GetCodePointerFlags */ TargetPointer mapAddress = r2rInfo + /* ReadyToRunInfo::EntryPointToMethodDescMap offset */; @@ -389,7 +418,7 @@ bool GetMethodInfo(TargetPointer rangeSection, TargetCodePointer jittedCodeAddre { uint coldIndex = // look up cold part in hot/cold map TargetPointer coldFunction = runtimeFunctions + (ulong)(coldIndex * /* size of RuntimeFunction */); - TargetPointer coldStart = imageBase + Target.Read(function + /* RuntimeFunction::BeginAddress offset */); + TargetPointer coldStart = codeBase + /* masked cold RuntimeFunction.BeginAddress */; relativeOffset = /* function length of hot part */ + addr - coldStart; } @@ -431,7 +460,7 @@ public override void GetMethodRegionInfo(RangeSection rangeSection, TargetCodePo if (/* found in hot/cold map */) { // Compute cold region bounds from cold runtime function start/end indices - coldStart = imageBase + coldStartFunc.BeginAddress; + coldStart = codeBase + /* masked cold RuntimeFunction.BeginAddress */; coldSize = coldEndOffset - coldBeginOffset; hotSize -= coldSize; } @@ -514,7 +543,7 @@ The `GetMethodDesc`, `GetStartAddress`, and `GetRelativeOffset` APIs extract fie * For interpreted code (`InterpreterJitManager`), there is no native unwind info. `GetUnwindInfo` returns null. -Unwind info (`RUNTIME_FUNCTION`) use relative addressing. For managed code, these values are relative to the start of the code's containing range in the RangeSectionMap (described below). This could be the beginning of a `CodeHeap` for jitted code or the base address of the loaded image for ReadyToRun code. +Unwind info (`RUNTIME_FUNCTION`) uses relative addressing. For managed code, these values are relative to the beginning of a `CodeHeap` for jitted code or the loaded-image base for ReadyToRun code. On WebAssembly, ReadyToRun entrypoint identity remains relative to `MinVirtualIP`, while unwind, debug, GC, and exception data RVAs are resolved from `LoadedImageBase`. `GetUnwindInfoBaseAddress` finds this base address for a given `CodeBlockHandle`. `IExecutionManager.GetDebugInfo` gets a pointer to the relevant DebugInfo for a `CodeBlockHandle`. The ExecutionManager delegates to the JitManager implementations as the DebugInfo is stored in different ways on jitted and R2R code. @@ -551,7 +580,7 @@ There are two distinct clause data types. JIT-compiled code uses `EEExceptionCla After obtaining the clause array bounds, the common iteration logic classifies each clause by its flags. The native `COR_ILEXCEPTION_CLAUSE` flags are bit flags: `Filter` (0x1), `Finally` (0x2), `Fault` (0x4). If none are set, the clause is `Typed`. For typed clauses, if the `CachedClass` flag (0x10000000) is set (JIT-only, used for dynamic methods), the union field contains a resolved `TypeHandle` pointer; the clause is a catch-all if this pointer equals the `ObjectMethodTable` global. Otherwise, the union field is a metadata `ClassToken`. To determine whether a typed clause is a catch-all handler, the `ClassToken` (which may be a `TypeDef` or `TypeRef`) is resolved to a `MethodTable` via the `Loader` contract's module lookup maps (`TypeDefToMethodTable` or `TypeRefToMethodTable`) and compared against the `ObjectMethodTable` global. For typed clauses without a cached type handle, the module address is resolved by walking `CodeBlockHandle` -> `MethodDesc` -> `MethodTable` -> `TypeHandle` -> `Module` via the `RuntimeTypeSystem` contract. -`IsFilterFunclet` first checks `IsFunclet`. If the code block is a funclet, it retrieves the EH clauses for the method and checks whether any filter clause's handler offset matches the funclet's relative offset. If a match is found, the funclet is a filter funclet. +`IsFilterFunclet` first checks `IsFunclet`. If the code block is a funclet, it retrieves the EH clauses for the method. On WebAssembly, `FilterOffset` identifies the executable filter entry, which can follow a synthetic funclet prolog. The reader resolves the code address at `methodStart + FilterOffset` and checks whether its containing runtime function has the same funclet start as the queried code block. On other targets, the filter offset is compared directly with the funclet's relative offset. `IExecutionManager.GetStackParameterSize` returns the size (in bytes) of stack-passed parameters at the call to the method described by the code block handle. It mirrors the native `EECodeManager::GetStackParameterSize`: it returns 0 for funclets and for non-x86 targets. On x86, it returns 0 for methods using the varargs calling convention (which are caller-popped), otherwise it returns the argument size encoded in the GC info header. @@ -560,15 +589,14 @@ After obtaining the clause array bounds, the common iteration logic classifies e `GetCodeKind` classifies a code address by finding its owning range section and determining the code kind. It distinguishes between jitted code, stub code blocks (jump stubs, precode stubs, VSD stubs, etc.), ReadyToRun code, interpreter code, and the global prestub entrypoint. If no range section owns the address, it compares the address against the exposed prestub entrypoint. Returns `Unknown` if the address cannot be classified. We depend on the values of the StubCodeBlockKind enum defined in codeman.h; for non-R2R code, we compare either the RangeList type or the code header against the values of this enum. ### FindReadyToRunModule -`FindReadyToRunModule` locates the ReadyToRun module whose PE image contains the given address. Unlike `GetCodeBlockHandle` (which only matches code regions), this API matches against the full PE image range - including data sections such as import tables. This is used in GCRefMap resolution as it requires finding the module that owns an import section indirection address, which is in the data section rather than the code section. +`FindReadyToRunModule` locates the ReadyToRun module that owns an address. Real addresses use the range section map, whose ReadyToRun ranges cover the full PE image including import tables. Encoded WebAssembly virtual IPs use `VirtualIPRangeList` and never fall through to the real-address map. ```csharp TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address) { - // Use the RangeSectionMap to find the RangeSection containing the address. - // ReadyToRun range sections cover the entire PE image (code + data), - // so this works for import section addresses used by GCRefMap lookup. - RangeSection range = RangeSection.Find(target, topRangeSectionMap, address); + // Encoded WebAssembly virtual IPs use VirtualIPRangeList first. + // All other addresses use the RangeSectionMap. + RangeSection range = RangeSection.Find(target, topRangeSectionMap, virtualIPRangeList, address); if (range.Data is null) return TargetPointer.Null; diff --git a/docs/design/datacontracts/data-descriptor-meanings.json b/docs/design/datacontracts/data-descriptor-meanings.json index 69c08076843c3b..ca3300efc44b64 100644 --- a/docs/design/datacontracts/data-descriptor-meanings.json +++ b/docs/design/datacontracts/data-descriptor-meanings.json @@ -488,6 +488,7 @@ "RangeSection.NextForDelete": "Pointer to next range section for deletion", "RangeSection.R2RModule": "ReadyToRun module", "RangeSection.RangeBegin": "Begin address of the range section", + "RangeSection.RangeEndOpen": "Exclusive end address of the range section", "RangeSection.RangeList": "Pointer to the `CodeRangeMapRangeList` associated with this range section", "RangeSectionFragment.Next": "Tagged pointer to the next fragment (bit 0 is the collectible flag; must be stripped to obtain the address)", "RangeSectionFragment.RangeBegin": "Begin address of the fragment", @@ -514,12 +515,13 @@ "ReadyToRunInfo.Composite": "Pointer to the `ReadyToRunCoreInfo` used for section lookup", "ReadyToRunInfo.CompositeInfo": "Pointer to composite R2R info - or itself for non-composite", "ReadyToRunInfo.DebugInfoSection": "Pointer to an `ImageDataDirectory` for the debug info", - "ReadyToRunInfo.DelayLoadMethodCallThunks": "Pointer to an `ImageDataDirectory` for the delay load method call thunks", + "ReadyToRunInfo.DelayLoadMethodCallThunks": "Pointer to an `ImageDataDirectory` for the delay load method call thunks; absent on WebAssembly", "ReadyToRunInfo.EntryPointToMethodDescMap": "`HashMap` of entry point addresses to `MethodDesc` pointers", - "ReadyToRunInfo.HotColdMap": "Pointer to an array of 32-bit integers - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80)", + "ReadyToRunInfo.HotColdMap": "Pointer to an array of 32-bit integers; present only with `FEATURE_COLD_R2R_CODE` - [see R2R format](../coreclr/botr/readytorun-format.md#readytorunsectiontypehotcoldmap-v80)", "ReadyToRunInfo.ImportSections": "Pointer to the array of ReadyToRun import sections", "ReadyToRunInfo.LoadedImageBase": "Base address of the loaded R2R image", - "ReadyToRunInfo.NumHotColdMap": "Number of entries in the `HotColdMap`", + "ReadyToRunInfo.MinVirtualIP": "Base virtual IP assigned to the ReadyToRun module on WebAssembly", + "ReadyToRunInfo.NumHotColdMap": "Number of entries in the `HotColdMap`; absent without `FEATURE_COLD_R2R_CODE`, meaning no hot/cold mapping", "ReadyToRunInfo.NumImportSections": "Number of ReadyToRun import sections", "ReadyToRunInfo.NumRuntimeFunctions": "Number of `RuntimeFunctions`", "ReadyToRunInfo.ReadyToRunHeader": "Pointer to the ReadyToRunHeader", @@ -536,7 +538,7 @@ "RegionFreeList.HeadFreeRegion": "Head of the free region segment list", "RegionFreeList.Size": "Size in bytes of each region free-list structure", "ResumableFrame.TargetContextPtr": "Pointer to the Frame's Target Context", - "RuntimeFunction.BeginAddress": "Begin address of the function. On ARM32, bit 0 (the Thumb bit) is set.", + "RuntimeFunction.BeginAddress": "Begin address of the function. On ARM32, bit 0 is the Thumb bit; on WebAssembly, bit 31 marks a funclet and is excluded from address arithmetic.", "RuntimeFunction.EndAddress": "End address of the function. Only exists on some platforms", "RuntimeFunction.Size": "Size of a runtime function entry in bytes", "RuntimeFunction.UnwindData": "Pointer to the unwind info for the function", @@ -695,6 +697,8 @@ "UnorderedArrayBase.Count": "Number of valid entries currently stored in the array.", "UnorderedArrayBase.Table": "Pointer to the backing storage holding the array's entries.", "UnwindInfo.FunctionLength": "Length of the associated function in bytes. Only exists on some platforms", + "VirtualIPRangeSection.Next": "Pointer to the next registered WebAssembly ReadyToRun virtual-IP range", + "VirtualIPRangeSection.RangeSection": "Address of the embedded synthetic `RangeSection` describing the virtual-IP range", "VASigCookie.Signature": "The raw vararg signature (see `Signature`).", "VASigCookie.SignatureLength": "Length in bytes of the raw vararg signature blob.", "VASigCookie.SignaturePointer": "Target address of the raw vararg signature blob.", @@ -857,6 +861,7 @@ "TearOffAddRefSimple": "Address of Unknown_AddRefSpecial; identifies SimpleComCallWrapper interface pointers", "TearOffAddRefSimpleInner": "Address of Unknown_AddRefInner; identifies inner SimpleComCallWrapper interface pointers", "ThePreStub": "Address of the global containing the prestub entrypoint", + "VirtualIPRangeList": "Address of the global pointer to the WebAssembly ReadyToRun virtual-IP range list", "ThinlockThreadIdDispenser": "Pointer to the dispenser of thin-lock thread IDs", "ThreadStore": "Pointer to the runtime thread store", "TotalCpuCount": "Number of available processors", diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index e96e5bbbe6aa15..21ab76810e1676 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -780,6 +780,7 @@ class Range struct RangeSection { friend class RangeSectionMap; + // [cDAC] [ExecutionManager]: Contract depends on these values. enum RangeSectionFlags { RANGE_SECTION_NONE = 0x0, @@ -2817,11 +2818,19 @@ struct cdac_data static constexpr PTR_InterpreterJitManager* InterpreterJitManagerAddress = &ExecutionManager::m_pInterpreterJitManager; #endif // FEATURE_INTERPRETER #ifdef TARGET_WASM + static constexpr VirtualIPRangeSection** VirtualIPRangeListAddress = &ExecutionManager::s_pVirtualIPRangeList; static constexpr FunctionTableIndexRangeSection** FunctionTableIndexRangeListAddress = &ExecutionManager::s_pFunctionTableIndexRangeList; #endif // TARGET_WASM }; #ifdef TARGET_WASM +template<> +struct cdac_data +{ + static constexpr size_t RangeSection = offsetof(VirtualIPRangeSection, rangeSection); + static constexpr size_t Next = offsetof(VirtualIPRangeSection, pNext); +}; + template<> struct cdac_data { diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index c5021886cc9a86..0285901c45c0ab 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -973,6 +973,12 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, MinVirtualIP, cdac_data::RangeSection) +CDAC_TYPE_FIELD(VirtualIPRangeSection, T_POINTER, Next, cdac_data::Next) +CDAC_TYPE_END(VirtualIPRangeSection) + CDAC_TYPE_BEGIN(FunctionTableIndexRangeSection) CDAC_TYPE_INDETERMINATE(FunctionTableIndexRangeSection) CDAC_TYPE_FIELD(FunctionTableIndexRangeSection, T_UINT32, MinFunctionTableIndex, cdac_data::MinFunctionTableIndex) @@ -1831,6 +1837,7 @@ CDAC_GLOBAL_POINTER(EEJitManagerAddress, cdac_data::EEJitManag CDAC_GLOBAL_POINTER(InterpreterJitManagerAddress, cdac_data::InterpreterJitManagerAddress) #endif // FEATURE_INTERPRETER #ifdef TARGET_WASM +CDAC_GLOBAL_POINTER(VirtualIPRangeList, cdac_data::VirtualIPRangeListAddress) CDAC_GLOBAL_POINTER(FunctionTableIndexRangeList, cdac_data::FunctionTableIndexRangeListAddress) #endif // TARGET_WASM CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index a6e0c7b843be23..2093c34cbd699d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -12,6 +12,7 @@ public static class Globals public const string SystemDomain = nameof(SystemDomain); public const string ThreadStore = nameof(ThreadStore); public const string FinalizerThread = nameof(FinalizerThread); + public const string VirtualIPRangeList = nameof(VirtualIPRangeList); public const string FunctionTableIndexRangeList = nameof(FunctionTableIndexRangeList); public const string GCThread = nameof(GCThread); public const string Debugger = nameof(Debugger); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index 8168c1c0929666..be7c32c517bce9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -21,7 +21,9 @@ public ReadyToRunJitManager(Target target) : base(target) { _hashMap = PtrHashMapLookup.Create(target); _hotCold = HotColdLookup.Create(target); - _runtimeFunctions = RuntimeFunctionLookup.Create(target); + _runtimeFunctions = RuntimeFunctionLookup.Create( + target, + target.Contracts.RuntimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.Wasm); } private enum ReadyToRunSectionType @@ -35,34 +37,36 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer info = default; Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); - if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index)) return false; index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); - index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out TargetPointer methodDesc); + if (!TryAdjustRuntimeFunctionToMethodStart(r2rInfo, codeBase, index, out index, out TargetPointer methodDesc)) + return false; Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index); + uint functionBeginAddress = _runtimeFunctions.GetBeginAddress(function); TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); // The R2R RUNTIME_FUNCTION.BeginAddress encodes thumb code with the thumb bit set on // ARM32. Native RUNTIME_FUNCTION__BeginAddress uses ThumbCodeToDataPointer to strip it // (clrnt.h, ARM32 branch). Mirror that so we store a raw TADDR-style start address. TargetPointer startAddress = CodePointerUtils.AddressFromCodePointer( - new TargetCodePointer(imageBase.Value + function.BeginAddress), Target); + new TargetCodePointer(codeBase.Value + functionBeginAddress), Target); TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress); // Take hot/cold splitting into account for the relative offset - if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint coldFunctionIndex)) + if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap ?? 0, r2rInfo.HotColdMap, index, out uint coldFunctionIndex)) { Debug.Assert(coldFunctionIndex < r2rInfo.NumRuntimeFunctions); Data.RuntimeFunction coldFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldFunctionIndex); TargetPointer coldStart = CodePointerUtils.AddressFromCodePointer( - new TargetCodePointer(imageBase.Value + coldFunction.BeginAddress), Target); + new TargetCodePointer(codeBase.Value + _runtimeFunctions.GetBeginAddress(coldFunction)), Target); if (addr >= coldStart) { // If the address is in the cold part, the relative offset is the size of the // hot part plus the offset from the address to the start of the cold part - uint hotSize = _runtimeFunctions.GetFunctionLength(imageBase, function); + uint hotSize = _runtimeFunctions.GetFunctionLength(GetLoadedImageBase(rangeSection, r2rInfo), function); relativeOffset = new TargetNUInt(hotSize + addr - coldStart); } } @@ -87,17 +91,18 @@ public override void GetMethodRegionInfo( hotSize = gcInfo.GetCodeLength(gcInfoHandle); Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); - if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index)) return; - if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, r2rInfo.NumRuntimeFunctions, out uint coldStartIdx, out uint coldEndIdx)) + if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap ?? 0, r2rInfo.HotColdMap, index, r2rInfo.NumRuntimeFunctions, out uint coldStartIdx, out uint coldEndIdx)) { Data.RuntimeFunction coldStartFunc = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldStartIdx); Data.RuntimeFunction coldEndFunc = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldEndIdx); - uint coldBeginOffset = coldStartFunc.BeginAddress; - uint coldEndOffset = coldEndFunc.BeginAddress + _runtimeFunctions.GetFunctionLength(imageBase, coldEndFunc); + uint coldBeginOffset = _runtimeFunctions.GetBeginAddress(coldStartFunc); + uint coldEndOffset = _runtimeFunctions.GetBeginAddress(coldEndFunc) + + _runtimeFunctions.GetFunctionLength(GetLoadedImageBase(rangeSection, r2rInfo), coldEndFunc); coldSize = coldEndOffset - coldBeginOffset; - coldStart = imageBase + coldBeginOffset; + coldStart = codeBase + coldBeginOffset; hotSize -= coldSize; } @@ -120,16 +125,18 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode // ReadyToRunJitManager::GetDebugInfo Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); - if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index)) return TargetPointer.Null; index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); - index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _); + if (!TryAdjustRuntimeFunctionToMethodStart(r2rInfo, codeBase, index, out index, out _)) + return TargetPointer.Null; Data.ImageDataDirectory debugInfoData = Target.ProcessedData.GetOrAdd(r2rInfo.DebugInfoSection); + TargetPointer loadedImageBase = GetLoadedImageBase(rangeSection, r2rInfo); ILCompiler.Reflection.ReadyToRun.NativeReader imageReader = new( - new TargetStream(Target, imageBase, debugInfoData.VirtualAddress + debugInfoData.Size), + new TargetStream(Target, loadedImageBase, debugInfoData.VirtualAddress + debugInfoData.Size), Target.IsLittleEndian ); ILCompiler.Reflection.ReadyToRun.NativeArray debugInfoArray = new(imageReader, debugInfoData.VirtualAddress); @@ -144,14 +151,14 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode if (lookBack != 0) debugInfoOffset = (uint)offset - lookBack; - return imageBase + debugInfoOffset; + return loadedImageBase + debugInfoOffset; } public override CodeKind GetCodeKind(RangeSection rangeSection, TargetCodePointer codeAddress) { if (rangeSection.Data == null) return CodeKind.Unknown; - return IsStubCodeBlockThunk(rangeSection.Data, GetReadyToRunInfo(rangeSection), codeAddress) ? CodeKind.MethodCallThunk : CodeKind.ReadyToRun; + return IsStubCodeBlockThunk(rangeSection, GetReadyToRunInfo(rangeSection), codeAddress) ? CodeKind.MethodCallThunk : CodeKind.ReadyToRun; } public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion) @@ -161,15 +168,16 @@ public override void GetGCInfo(RangeSection rangeSection, TargetCodePointer jitt // ReadyToRunJitManager::GetGCInfoToken Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); - if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer imageBase, out uint index)) + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index)) return; index = AdjustRuntimeFunctionIndexForHotCold(r2rInfo, index); - index = AdjustRuntimeFunctionToMethodStart(r2rInfo, imageBase, index, out _); + if (!TryAdjustRuntimeFunctionToMethodStart(r2rInfo, codeBase, index, out index, out _)) + return; Data.RuntimeFunction runtimeFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index); - TargetPointer unwindInfo = runtimeFunction.UnwindData + imageBase; + TargetPointer unwindInfo = runtimeFunction.UnwindData + GetLoadedImageBase(rangeSection, r2rInfo); uint unwindDataSize = UnwindDataSize.GetUnwindDataSize(Target, unwindInfo, Target.Contracts.RuntimeInfo.GetTargetArchitecture()); gcInfo = unwindInfo + unwindDataSize; gcVersion = GetR2RGCInfoVersion(r2rInfo); @@ -210,23 +218,26 @@ private bool GetRuntimeFunction( RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress, - out TargetPointer imageBase, + out TargetPointer codeBase, out uint runtimeFunctionIndex) { - imageBase = TargetPointer.Null; + codeBase = TargetPointer.Null; runtimeFunctionIndex = 0; if (rangeSection.Data == null) throw new ArgumentException(nameof(rangeSection)); // Check if address is in a thunk - if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + if (IsStubCodeBlockThunk(rangeSection, r2rInfo, jittedCodeAddress)) return false; // Find the relative address that we are looking for TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); - imageBase = rangeSection.Data.RangeBegin; - TargetPointer relativeAddr = addr - imageBase; + codeBase = GetCodeBase(rangeSection, r2rInfo); + if (codeBase == TargetPointer.Null) + return false; + + TargetPointer relativeAddr = addr - codeBase; return _runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out runtimeFunctionIndex); } @@ -234,44 +245,56 @@ private bool GetRuntimeFunction( private uint AdjustRuntimeFunctionIndexForHotCold(Data.ReadyToRunInfo r2rInfo, uint index) { // Look up index in hot/cold map - if the function is in the cold part, get the index of the hot part. - index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index); + index = _hotCold.GetHotFunctionIndex(r2rInfo.NumHotColdMap ?? 0, r2rInfo.HotColdMap, index); Debug.Assert(index < r2rInfo.NumRuntimeFunctions); return index; } - private uint AdjustRuntimeFunctionToMethodStart(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint index, out TargetPointer methodDesc) + private bool TryAdjustRuntimeFunctionToMethodStart( + Data.ReadyToRunInfo r2rInfo, + TargetPointer codeBase, + uint index, + out uint methodIndex, + out TargetPointer methodDesc) { - methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); + methodIndex = index; + methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, codeBase, methodIndex); while (methodDesc == TargetPointer.Null) { + if (methodIndex == 0) + return false; + // Funclets won't have a direct entry in the map of runtime function entry point to method desc. // The funclet's address (and index) will be greater than that of the corresponding function, so // we decrement the index to find the actual function / method desc for the funclet. - index--; - methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, imageBase, index); + methodIndex--; + methodDesc = GetMethodDescForRuntimeFunction(r2rInfo, codeBase, methodIndex); } Debug.Assert(methodDesc != TargetPointer.Null); - return index; + return true; } - private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) + private bool IsStubCodeBlockThunk(RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) { - if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null) + if (rangeSection.IsVirtualIP) + return false; + + if (r2rInfo.DelayLoadMethodCallThunks is not { } thunks || thunks == TargetPointer.Null) return false; // Check if the address is in the region containing thunks for READYTORUN_HELPER_DelayLoad_MethodCall - Data.ImageDataDirectory thunksData = Target.ProcessedData.GetOrAdd(r2rInfo.DelayLoadMethodCallThunks); - ulong rva = jittedCodeAddress - rangeSection.RangeBegin; + Data.ImageDataDirectory thunksData = Target.ProcessedData.GetOrAdd(thunks); + ulong rva = jittedCodeAddress - GetLoadedImageBase(rangeSection, r2rInfo); return thunksData.VirtualAddress <= rva && rva < thunksData.VirtualAddress + thunksData.Size; } - private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInfo, TargetPointer imageBase, uint runtimeFunctionIndex) + private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInfo, TargetPointer codeBase, uint runtimeFunctionIndex) { Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, runtimeFunctionIndex); // ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage - TargetCodePointer startAddress = imageBase + function.BeginAddress; + TargetCodePointer startAddress = codeBase + _runtimeFunctions.GetBeginAddress(function); TargetPointer entryPoint = CodePointerUtils.AddressFromCodePointer(startAddress, Target); TargetPointer methodDesc = _hashMap.GetValue(r2rInfo.EntryPointToMethodDescMap, entryPoint); @@ -280,6 +303,12 @@ private TargetPointer GetMethodDescForRuntimeFunction(Data.ReadyToRunInfo r2rInf return methodDesc; } + + private static TargetPointer GetCodeBase(RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo) + => rangeSection.IsVirtualIP ? r2rInfo.MinVirtualIP ?? TargetPointer.Null : rangeSection.Data!.RangeBegin; + + private static TargetPointer GetLoadedImageBase(RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo) + => rangeSection.IsVirtualIP ? r2rInfo.LoadedImageBase : rangeSection.Data!.RangeBegin; #endregion private void GetExceptionClauses(TargetPointer exceptionLookupTableAddr, uint count, TargetPointer rangeStart, uint methodRVA, out TargetPointer startExInfoRVA, out TargetPointer endExInfoRVA) @@ -320,12 +349,12 @@ public override void GetExceptionClauses(RangeSection range, CodeBlockHandle cbh } uint count = section.Size / Data.ExceptionLookupTableEntry.GetSize(Target); - ulong exceptionLookupTableAddr = section.VirtualAddress + r2rInfo.LoadedImageBase; + TargetPointer loadedImageBase = GetLoadedImageBase(range, r2rInfo); + ulong exceptionLookupTableAddr = section.VirtualAddress + loadedImageBase; - GetMethodRVAAndRangeStart(cbh, out TargetPointer methodStart, out TargetPointer rangeStart); - uint methodRVA = (uint)(methodStart - rangeStart); + GetMethodRVAAndImageBase(range, r2rInfo, cbh, out uint methodRVA, out TargetPointer imageBase); - GetExceptionClauses(exceptionLookupTableAddr, count, rangeStart, methodRVA, out startAddr, out endAddr); + GetExceptionClauses(exceptionLookupTableAddr, count, imageBase, methodRVA, out startAddr, out endAddr); } private ImageDataDirectory? FindSection(Data.ReadyToRunInfo r2rInfo, uint sectionType) @@ -339,11 +368,18 @@ public override void GetExceptionClauses(RangeSection range, CodeBlockHandle cbh return null; } - private void GetMethodRVAAndRangeStart(CodeBlockHandle cbh, out TargetPointer methodStart, out TargetPointer rangeStart) + private void GetMethodRVAAndImageBase( + RangeSection range, + Data.ReadyToRunInfo r2rInfo, + CodeBlockHandle cbh, + out uint methodRVA, + out TargetPointer imageBase) { IExecutionManager executionManager = Target.Contracts.ExecutionManager; - methodStart = executionManager.GetStartAddress(cbh); - rangeStart = executionManager.GetUnwindInfoBaseAddress(cbh); + TargetPointer methodStart = executionManager.GetStartAddress(cbh); + TargetPointer codeBase = GetCodeBase(range, r2rInfo); + methodRVA = checked((uint)(methodStart - codeBase)); + imageBase = GetLoadedImageBase(range, r2rInfo); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index e6c7e9afffa529..ac4f7600e4df6c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -23,9 +23,7 @@ internal sealed partial class ExecutionManagerCore : IExecutionManager private readonly ReadyToRunJitManager _r2rJitManager; private readonly InterpreterJitManager _interpreterJitManager; private readonly CachedValue _thePreStub; - - private Data.RangeSectionMap _topRangeSectionMap - => _target.ProcessedData.GetOrAdd(_topRangeSectionMapAddress); + private readonly TargetPointer _virtualIPRangeListAddress; public ExecutionManagerCore(Target target, TargetPointer topRangeSectionMapAddress) { @@ -39,6 +37,9 @@ public ExecutionManagerCore(Target target, TargetPointer topRangeSectionMapAddre _thePreStub = new(() => target.TryReadGlobalPointer(Constants.Globals.ThePreStub, out TargetPointer? thePreStubPtr) ? target.ReadPointer(thePreStubPtr.Value) : TargetPointer.Null); + _virtualIPRangeListAddress = _target.TryReadGlobalPointer(Constants.Globals.VirtualIPRangeList, out TargetPointer? virtualIPRangeListAddress) + ? virtualIPRangeListAddress.Value + : TargetPointer.Null; } public void Flush(FlushScope scope) @@ -71,6 +72,7 @@ private enum RangeSectionFlags : int CodeHeap = 0x02, RangeList = 0x04, Interpreter = 0x08, + VirtualIP = 0x10, } // Mirrors the native CodeHeap::CodeHeapType enum in codeman.h. @@ -147,6 +149,7 @@ public RangeSection(Data.RangeSection rangeSection) internal bool IsRangeList => HasFlags(RangeSectionFlags.RangeList); internal bool IsCodeHeap => HasFlags(RangeSectionFlags.CodeHeap); internal bool IsInterpreter => HasFlags(RangeSectionFlags.Interpreter); + internal bool IsVirtualIP => HasFlags(RangeSectionFlags.VirtualIP); internal bool HasR2RModule => Data!.R2RModule != TargetPointer.Null; @@ -156,8 +159,17 @@ internal static bool IsStubCodeBlock(Target target, TargetPointer codeHeaderIndi return codeHeaderIndirect.Value <= stubCodeBlockLast; } - internal static RangeSection Find(Target target, Data.RangeSectionMap topRangeSectionMap, ExecutionManagerHelpers.RangeSectionMap rangeSectionLookup, TargetCodePointer jittedCodeAddress) + internal static RangeSection Find( + Target target, + TargetPointer topRangeSectionMapAddress, + ExecutionManagerHelpers.RangeSectionMap rangeSectionLookup, + TargetPointer virtualIPRangeListAddress, + TargetCodePointer jittedCodeAddress) { + if (IsVirtualIPAddress(target, jittedCodeAddress)) + return FindVirtualIPRangeSection(target, virtualIPRangeListAddress, jittedCodeAddress); + + Data.RangeSectionMap topRangeSectionMap = target.ProcessedData.GetOrAdd(topRangeSectionMapAddress); TargetPointer rangeSectionFragmentPtr = rangeSectionLookup.FindFragment(target, topRangeSectionMap, jittedCodeAddress); // The lowest level of the range section map covers a large address space which may contain multiple small fragments. // Iterate over them to find the one that contains the jitted code address. @@ -182,6 +194,82 @@ internal static RangeSection Find(Target target, Data.RangeSectionMap topRangeSe } return new RangeSection(rangeSection); } + + private static RangeSection FindVirtualIPRangeSection( + Target target, + TargetPointer virtualIPRangeListAddress, + TargetCodePointer virtualIP) + { + // Reader resource budget, not a limit imposed by native range registration. + const int MaxVirtualIPRangeNodes = 65_536; + + if (virtualIPRangeListAddress == TargetPointer.Null) + return new RangeSection(); + + try + { + TargetPointer current = target.ReadPointer(virtualIPRangeListAddress); + HashSet visited = new(); + Data.RangeSection? matchingRange = null; + while (current != TargetPointer.Null) + { + if (visited.Count == MaxVirtualIPRangeNodes || !visited.Add(current)) + return new RangeSection(); + + Data.VirtualIPRangeSection node = target.ProcessedData.GetOrAdd(current); + Data.RangeSection range = target.ProcessedData.GetOrAdd(node.RangeSection); + if (range.RangeBegin >= range.RangeEndOpen + || range.R2RModule == TargetPointer.Null + || range.JitManager == TargetPointer.Null + || range.NextForDelete != TargetPointer.Null + || (range.Flags & (int)RangeSectionFlags.VirtualIP) == 0) + { + return new RangeSection(); + } + + if (range.RangeBegin <= virtualIP.Value && virtualIP.Value < range.RangeEndOpen) + { + if (matchingRange is not null) + return new RangeSection(); + + // Registration publishes the node before setting MinVirtualIP. Only + // the candidate module must be initialized for this lookup to succeed. + Data.Module module = target.ProcessedData.GetOrAdd(range.R2RModule); + if (module.ReadyToRunInfo == TargetPointer.Null) + return new RangeSection(); + + Data.ReadyToRunInfo r2rInfo = target.ProcessedData.GetOrAdd(module.ReadyToRunInfo); + if (r2rInfo.MinVirtualIP is not TargetPointer minVirtualIP + || minVirtualIP != range.RangeBegin + || r2rInfo.LoadedImageBase == TargetPointer.Null) + { + return new RangeSection(); + } + + matchingRange = range; + } + + current = node.Next; + } + + return matchingRange is null ? new RangeSection() : new RangeSection(matchingRange); + } + catch (VirtualReadException) + { + return new RangeSection(); + } + } + + private static bool IsVirtualIPAddress(Target target, TargetCodePointer codeAddress) + { + if (target.Contracts.RuntimeInfo.GetTargetArchitecture() != RuntimeInfoArchitecture.Wasm) + return false; + + ulong mask = target.PointerSize == sizeof(uint) + ? 0x80000001u + : 0x8000000000000001ul; + return (codeAddress.Value & mask) == mask; + } } private JitManager? GetJitManager(RangeSection rangeSection) @@ -206,7 +294,7 @@ internal static RangeSection Find(Target target, Data.RangeSectionMap topRangeSe private CodeBlock? GetCodeBlock(TargetCodePointer jittedCodeAddress) { - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, jittedCodeAddress); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMapAddress, _rangeSectionMapLookup, _virtualIPRangeListAddress, jittedCodeAddress); if (range.Data == null) { return null; @@ -270,8 +358,11 @@ TargetPointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeInfoH // TODO(cdac): EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS, implement iterating over fragments until finding // non-fragment RuntimeFunction + uint beginAddress = range.IsVirtualIP + ? runtimeFunction.BeginAddress & 0x7fff_ffff + : runtimeFunction.BeginAddress; return CodePointerUtils.AddressFromCodePointer( - new TargetCodePointer(range.Data.RangeBegin + runtimeFunction.BeginAddress), _target); + new TargetCodePointer(range.Data.RangeBegin + beginAddress), _target); } void IExecutionManager.GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) @@ -296,7 +387,7 @@ TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer ent Data.PortableEntryPoint portableEntryPoint = _target.ProcessedData.GetOrAdd(entrypoint.AsTargetPointer); return portableEntryPoint.MethodDesc; } - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, entrypoint); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMapAddress, _rangeSectionMapLookup, _virtualIPRangeListAddress, entrypoint); if (range.Data == null) return TargetPointer.Null; if (range.IsRangeList) @@ -353,8 +444,25 @@ bool IExecutionManager.IsFilterFunclet(CodeBlockHandle codeInfoHandle) List clauses = eman.GetExceptionClauses(codeInfoHandle); foreach (ExceptionClauseInfo clause in clauses) { - if (clause.ClauseType == ExceptionClauseInfo.ExceptionClauseFlags.Filter && clause.FilterOffset == funcletStartOffset) + if (clause.ClauseType != ExceptionClauseInfo.ExceptionClauseFlags.Filter + || clause.FilterOffset is not uint filterOffset) + { + continue; + } + + if (_target.Contracts.RuntimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.Wasm) + { + TargetCodePointer filterAddress = new(info.StartAddress.Value + filterOffset); + if (eman.GetCodeBlockHandle(filterAddress) is CodeBlockHandle filterCodeInfoHandle + && eman.GetFuncletStartAddress(filterCodeInfoHandle) == funcletStartAddress) + { + return true; + } + } + else if (filterOffset == funcletStartOffset) + { return true; + } } return false; @@ -377,7 +485,12 @@ TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInf if (range.Data == null) throw new InvalidOperationException($"{nameof(RangeSection)} not found for {codeInfoHandle.Address}"); - return range.Data.RangeBegin; + if (!range.IsVirtualIP) + return range.Data.RangeBegin; + + Data.Module module = _target.ProcessedData.GetOrAdd(range.Data.R2RModule); + Data.ReadyToRunInfo r2rInfo = _target.ProcessedData.GetOrAdd(module.ReadyToRunInfo); + return r2rInfo.LoadedImageBase; } TargetPointer IExecutionManager.GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte) @@ -451,7 +564,7 @@ TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address) // The R2R range section covers the entire PE image (code + data), so this // works for import section addresses used by FindGCRefMap. TargetCodePointer codeAddr = CodePointerUtils.CodePointerFromAddress(address, _target); - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeAddr); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMapAddress, _rangeSectionMapLookup, _virtualIPRangeListAddress, codeAddr); if (range.Data is null) return TargetPointer.Null; @@ -574,7 +687,7 @@ private RangeSection RangeSectionFromCodeBlockHandle(CodeBlockHandle codeInfoHan if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMapAddress, _rangeSectionMapLookup, _virtualIPRangeListAddress, codeInfoHandle.Address.Value); return range; } @@ -701,7 +814,7 @@ private static CodeKind GetStubKind(StubKind stubKind) public CodeKind GetCodeKind(TargetCodePointer codeAddress) { - RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeAddress); + RangeSection range = RangeSection.Find(_target, _topRangeSectionMapAddress, _rangeSectionMapLookup, _virtualIPRangeListAddress, codeAddress); if (range.Data == null) { TargetPointer address = new(codeAddress.Value); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs index 8b2d2a00747880..6ef30f830c6877 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -7,22 +7,32 @@ namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; internal sealed class RuntimeFunctionLookup { - public static RuntimeFunctionLookup Create(Target target) - => new RuntimeFunctionLookup(target); + private const uint WasmFuncletFlag = 0x80000000; + public static RuntimeFunctionLookup Create(Target target, bool isWasm = false) + => new RuntimeFunctionLookup(target, isWasm); + + private readonly bool _isWasm; private readonly uint _runtimeFunctionSize; private readonly Target _target; - private RuntimeFunctionLookup(Target target) + private RuntimeFunctionLookup(Target target, bool isWasm) { _target = target; + _isWasm = isWasm; _runtimeFunctionSize = Data.RuntimeFunction.GetSize(target); } + public uint GetBeginAddress(Data.RuntimeFunction function) + => _isWasm ? function.BeginAddress & ~WasmFuncletFlag : function.BeginAddress; + + public bool IsFunclet(Data.RuntimeFunction function) + => _isWasm && (function.BeginAddress & WasmFuncletFlag) != 0; + public uint GetFunctionLength(TargetPointer imageBase, Data.RuntimeFunction function) { if (function.EndAddress.HasValue) - return function.EndAddress.Value - function.BeginAddress; + return function.EndAddress.Value - GetBeginAddress(function); Data.UnwindInfo unwindInfo = _target.ProcessedData.GetOrAdd(imageBase + function.UnwindData); if (unwindInfo.FunctionLength.HasValue) @@ -38,6 +48,10 @@ public uint GetFunctionLength(TargetPointer imageBase, Data.RuntimeFunction func public bool TryGetRuntimeFunctionIndexForAddress(TargetPointer runtimeFunctions, uint numRuntimeFunctions, TargetPointer relativeAddress, out uint index) { + index = 0; + if (numRuntimeFunctions == 0) + return false; + // NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod uint start = 0; uint end = numRuntimeFunctions - 1; @@ -49,7 +63,7 @@ public bool TryGetRuntimeFunctionIndexForAddress(TargetPointer runtimeFunctions, bool Compare(uint index) { Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); - return relativeAddress < func.BeginAddress; + return relativeAddress < GetBeginAddress(func); }; bool Match(uint index) @@ -58,12 +72,12 @@ bool Match(uint index) if (index < numRuntimeFunctions - 1) { Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, index + 1); - if (relativeAddress >= nextFunc.BeginAddress) + if (relativeAddress >= GetBeginAddress(nextFunc)) return false; } Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); - return relativeAddress >= func.BeginAddress; + return relativeAddress >= GetBeginAddress(func); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs index 952087ab9be95d..426f72377f7cd5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/UnwindDataSize.cs @@ -98,11 +98,30 @@ public static uint GetUnwindDataSize(Target target, TargetPointer unwindInfo, Ru size += 4; // exception handler RVA return size; } + case RuntimeInfoArchitecture.Wasm: + { + uint size = GetULEB128Size(target, unwindInfo); + size += GetULEB128Size(target, unwindInfo + size); + return size; + } default: throw new NotSupportedException($"GetUnwindDataSize not supported for architecture: {arch}"); } } + private static uint GetULEB128Size(Target target, TargetPointer address) + { + const int MaxBytes = 5; + + for (uint offset = 0; offset < MaxBytes; offset++) + { + if ((target.Read(address + offset) & 0x80) == 0) + return offset + 1; + } + + throw new InvalidOperationException("Malformed ULEB128 value in WASM unwind data."); + } + private static uint AlignUp(int offset, int align) => (uint)((offset + align - 1) & ~(align - 1)); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs index 0fc20af0914447..f18d6c4bdf7fb6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Wasm/WasmR2RInfo.cs @@ -15,16 +15,13 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.Wa /// internal sealed class WasmR2RInfo : IWasmR2RInfo { - // RUNTIME_FUNCTION__IsFunclet: the funclet flag is the high bit of BeginAddress (clrnt.h). - private const uint FuncletFlag = 0x80000000; - private readonly Target _target; private readonly RuntimeFunctionLookup _runtimeFunctions; public WasmR2RInfo(Target target) { _target = target; - _runtimeFunctions = RuntimeFunctionLookup.Create(target); + _runtimeFunctions = RuntimeFunctionLookup.Create(target, isWasm: true); } // Mirrors ExecutionManager::FindFunctionTableIndexRangeSection. @@ -76,7 +73,7 @@ public bool TryGetVirtualIPBase(uint functionTableIndex, out ulong baseVirtualIP while (true) { Data.RuntimeFunction runtimeFunction = GetRuntimeFunction(r2rInfo, localIndex); - if ((runtimeFunction.BeginAddress & FuncletFlag) != 0) + if (_runtimeFunctions.IsFunclet(runtimeFunction)) { if (localIndex == 0) return false; @@ -84,7 +81,7 @@ public bool TryGetVirtualIPBase(uint functionTableIndex, out ulong baseVirtualIP continue; } - baseVirtualIP = minVirtualIP.Value + runtimeFunction.BeginAddress; + baseVirtualIP = minVirtualIP.Value + _runtimeFunctions.GetBeginAddress(runtimeFunction); return true; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs index 01a0427b92fc6f..511065f743fe44 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ReadyToRunInfo.cs @@ -11,8 +11,10 @@ internal sealed partial class ReadyToRunInfo : IData [Field] public partial TargetPointer CompositeInfo { get; } [Field] public partial TargetPointer ReadyToRunHeader { get; } [Field] public partial uint NumRuntimeFunctions { get; } - [Field] public partial uint NumHotColdMap { get; } - [Field] public partial TargetPointer DelayLoadMethodCallThunks { get; } + // Absent when the runtime is built without FEATURE_COLD_R2R_CODE. + [Field] public partial uint? NumHotColdMap { get; } + // Absent on WASM. + [Field] public partial TargetPointer? DelayLoadMethodCallThunks { get; } [Field] public partial TargetPointer DebugInfoSection { get; } [Field] public partial TargetPointer ExceptionInfoSection { get; } [Field] public partial TargetPointer LoadedImageBase { get; } @@ -41,8 +43,9 @@ private partial TargetPointer InitRuntimeFunctions(Target target, TargetPointer private partial TargetPointer InitHotColdMap(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.ReadyToRunInfo); - Debug.Assert(NumHotColdMap % 2 == 0, "Hot/cold map should have an even number of entries (pairs of hot/cold runtime function indexes)"); - return NumHotColdMap > 0 + uint count = NumHotColdMap ?? 0; + Debug.Assert(count % 2 == 0, "Hot/cold map should have an even number of entries (pairs of hot/cold runtime function indexes)"); + return count > 0 ? target.ReadPointerField(address, type, nameof(HotColdMap)) : TargetPointer.Null; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualIPRangeSection.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualIPRangeSection.cs new file mode 100644 index 00000000000000..5d8fe80b9f3e83 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VirtualIPRangeSection.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType(nameof(DataType.VirtualIPRangeSection))] +internal sealed partial class VirtualIPRangeSection : IData +{ + [FieldAddress] public partial TargetPointer RangeSection { get; } + [Field] public partial TargetPointer Next { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index ab30d4293917d9..07742cd8fafba5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -109,6 +109,7 @@ public enum DataType InterpByteCodeStart, InterpMethod, InterpMethodContextFrame, + VirtualIPRangeSection, FunctionTableIndexRangeSection, Array, Delegate, diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs index fc5a2e8ce4c05d..6cb2a4f8d91034 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs @@ -21,6 +21,7 @@ public class ExecutionManagerTests [DataType.RangeSectionMap] = TargetTestHelpers.CreateTypeInfo(emBuilder.RangeSectionMapLayout), [DataType.RangeSectionFragment] = TargetTestHelpers.CreateTypeInfo(emBuilder.RangeSectionFragmentLayout), [DataType.RangeSection] = TargetTestHelpers.CreateTypeInfo(emBuilder.RangeSectionLayout), + [DataType.VirtualIPRangeSection] = TargetTestHelpers.CreateTypeInfo(emBuilder.VirtualIPRangeSectionLayout), [DataType.CodeHeapListNode] = TargetTestHelpers.CreateTypeInfo(emBuilder.CodeHeapListNodeLayout), [DataType.CodeHeap] = TargetTestHelpers.CreateTypeInfo(emBuilder.CodeHeapLayout), [DataType.LoaderCodeHeap] = TargetTestHelpers.CreateTypeInfo(emBuilder.LoaderCodeHeapLayout), @@ -28,6 +29,7 @@ public class ExecutionManagerTests [DataType.RealCodeHeader] = TargetTestHelpers.CreateTypeInfo(emBuilder.RealCodeHeaderLayout), [DataType.InterpreterRealCodeHeader] = TargetTestHelpers.CreateTypeInfo(emBuilder.InterpreterRealCodeHeaderLayout), [DataType.ReadyToRunInfo] = TargetTestHelpers.CreateTypeInfo(emBuilder.ReadyToRunInfoLayout), + [DataType.ReadyToRunHeader] = TargetTestHelpers.CreateTypeInfo(emBuilder.ReadyToRunHeaderLayout), [DataType.EEJitManager] = TargetTestHelpers.CreateTypeInfo(emBuilder.EEJitManagerLayout), [DataType.DynamicFunctionTable] = TargetTestHelpers.CreateTypeInfo(emBuilder.DynamicFunctionTableLayout), [DataType.Module] = TargetTestHelpers.CreateTypeInfo(emBuilder.ModuleLayout), @@ -55,18 +57,21 @@ internal static Target CreateTarget( internal static Target CreateTarget( MockExecutionManagerBuilder emBuilder, RuntimeInfoOperatingSystem operatingSystem = RuntimeInfoOperatingSystem.Windows, - RuntimeInfoArchitecture? targetArchitecture = null) + RuntimeInfoArchitecture? targetArchitecture = null, + Action? configureTarget = null) => CreateTarget( emBuilder, operatingSystem, targetArchitecture, - []); + [], + configureTarget); private static Target CreateTarget( MockExecutionManagerBuilder emBuilder, RuntimeInfoOperatingSystem operatingSystem, RuntimeInfoArchitecture? targetArchitecture, - (string Name, ulong Value)[] additionalGlobals) + (string Name, ulong Value)[] additionalGlobals, + Action? configureTarget = null) { var arch = emBuilder.Builder.TargetTestHelpers.Arch; RuntimeInfoArchitecture architecture = targetArchitecture ?? (arch.Is64Bit @@ -76,26 +81,29 @@ private static Target CreateTarget( runtimeInfo.Setup(r => r.GetTargetOperatingSystem()).Returns(operatingSystem); runtimeInfo.Setup(r => r.GetTargetArchitecture()).Returns(architecture); - return new TestPlaceholderTarget.Builder(arch) + var targetBuilder = new TestPlaceholderTarget.Builder(arch) .UseReader(emBuilder.Builder.GetMemoryContext().ReadFromTarget) .AddTypes(CreateContractTypes(emBuilder)) .AddGlobals(emBuilder.Globals) .AddGlobals(additionalGlobals) .AddContract(version: emBuilder.Version) .AddMockContract(Mock.Of()) - .AddMockContract(runtimeInfo) - .Build(); + .AddMockContract(runtimeInfo); + configureTarget?.Invoke(targetBuilder); + return targetBuilder.Build(); } private static IExecutionManager CreateExecutionManagerContract( string version, MockTarget.Architecture arch, Action? configure = null, - ulong allCodeHeaps = 0) + ulong allCodeHeaps = 0, + RuntimeInfoArchitecture? targetArchitecture = null) { - MockExecutionManagerBuilder emBuilder = new(version, arch, MockExecutionManagerBuilder.DefaultAllocationRange, allCodeHeaps); + MockExecutionManagerBuilder emBuilder = new(version, arch, MockExecutionManagerBuilder.DefaultAllocationRange, allCodeHeaps, + isWasm: targetArchitecture == RuntimeInfoArchitecture.Wasm); configure?.Invoke(emBuilder); - Target target = CreateTarget(emBuilder); + Target target = CreateTarget(emBuilder, RuntimeInfoOperatingSystem.Windows, targetArchitecture); return target.Contracts.ExecutionManager; } @@ -324,53 +332,607 @@ public void GetMethodDesc_R2R_OneRuntimeFunction(string version, MockTarget.Arch } } - // On WASM there are no native code pointers: 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 any other architecture -- there is no WASM-specific IP->MethodDesc - // code path (MinVirtualIP / FunctionTableIndexRangeSection are only consumed by the unwinder's - // function-table-index -> base-virtual-IP mapping). This verifies that resolution on a wasm32 - // (32-bit little-endian) target, treating the code address as a virtual IP, and confirms the - // R2R classification. - [Theory] - [InlineData("c1")] - public void GetMethodDesc_R2R_WasmVirtualIP(string version) + [Fact] + public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape() { MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; - const ulong virtualIPBase = 0x0050_0000u; // R2R module base virtual IP - const uint virtualIPRangeSize = 0xc000u; - const ulong jitManagerAddress = 0x000b_ff00; - const ulong expectedMethodDescAddress = 0x0101_aaa0; - - uint functionLocalVirtualIP = 0x100; // offset of the R2R function within the module + const ulong VirtualIPRangeStart = 0x8001_0001; + const uint VirtualIPRangeSize = 0x400; + const uint FunctionBeginAddress = 0x108; + const ulong CapturedVirtualIP = 0x8001_0109; + const ulong LoadedImageBase = 0x0090_0000; + const ulong JitManagerAddress = 0x000b_ff00; + const ulong ExpectedMethodDescAddress = 0x0101_aaa0; + ulong moduleAddress = 0; + ulong runtimeFunctionsAddress = 0; + int runtimeFunctionSize = 0; IExecutionManager em = CreateExecutionManagerContract( - version, + "c1", wasmArch, emBuilder => { - var jittedCode = emBuilder.AllocateJittedCodeRange(virtualIPBase, virtualIPRangeSize); - MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([functionLocalVirtualIP], []); + MockExecutionManagerBuilder.JittedCodeRange virtualIPRange = + emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, VirtualIPRangeSize); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([0x80, FunctionBeginAddress, 0x200], []); + r2rInfo.MinVirtualIP = VirtualIPRangeStart; + r2rInfo.LoadedImageBase = LoadedImageBase; + runtimeFunctionsAddress = r2rInfo.RuntimeFunctions; + runtimeFunctionSize = emBuilder.RuntimeFunctionLayout.Size; + Assert.Equal(8, runtimeFunctionSize); + Assert.DoesNotContain(emBuilder.RuntimeFunctionLayout.Fields, field => field.Name == "EndAddress"); + Assert.DoesNotContain(emBuilder.ReadyToRunInfoLayout.Fields, + field => field.Name is "NumHotColdMap" or "HotColdMap" or "DelayLoadMethodCallThunks"); + MockHashMapBuilder hashMapBuilder = new(emBuilder.Builder); hashMapBuilder.PopulatePtrMap( r2rInfo.EntryPointToMethodDescMapAddress, - [(jittedCode.RangeStart + functionLocalVirtualIP, expectedMethodDescAddress)]); + [(CapturedVirtualIP, ExpectedMethodDescAddress)]); MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); - MockRangeSection rangeSection = emBuilder.AddReadyToRunRangeSection(jittedCode, jitManagerAddress, r2rModule.Address); - _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); - }); + moduleAddress = r2rModule.Address; + _ = emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address); + }, + targetArchitecture: RuntimeInfoArchitecture.Wasm); - TargetCodePointer virtualIP = new(virtualIPBase + functionLocalVirtualIP); + TargetCodePointer virtualIP = new(CapturedVirtualIP); + CodeBlockHandle? handle = em.GetCodeBlockHandle(virtualIP); - var handle = em.GetCodeBlockHandle(virtualIP); Assert.NotNull(handle); - Assert.Equal(new TargetPointer(expectedMethodDescAddress), em.GetMethodDesc(handle.Value)); + Assert.Equal(new TargetPointer(ExpectedMethodDescAddress), em.GetMethodDesc(handle.Value)); + Assert.Equal(new TargetPointer(CapturedVirtualIP), em.GetStartAddress(handle.Value)); + Assert.Equal(new TargetPointer(LoadedImageBase), em.GetUnwindInfoBaseAddress(handle.Value)); + Assert.Equal(new TargetPointer(runtimeFunctionsAddress + (ulong)runtimeFunctionSize), em.GetUnwindInfo(handle.Value)); + Assert.Equal(new TargetPointer(moduleAddress), em.FindReadyToRunModule(new TargetPointer(CapturedVirtualIP))); Assert.Equal(CodeKind.ReadyToRun, em.GetCodeKind(virtualIP)); } + [Fact] + public void ReadyToRunInfo_WasmMissingOptionalFields_EnsureAllFieldsRead() + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + MockExecutionManagerBuilder emBuilder = new( + "c1", + wasmArch, + MockExecutionManagerBuilder.DefaultAllocationRange, + isWasm: true); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([0x100], []); + r2rInfo.MinVirtualIP = 0x8001_0001; + r2rInfo.LoadedImageBase = 0x0090_0000; + Target target = CreateTarget( + emBuilder, + RuntimeInfoOperatingSystem.Windows, + RuntimeInfoArchitecture.Wasm); + + Data.ReadyToRunInfo data = + target.ProcessedData.GetOrAdd(r2rInfo.Address); + ((Data.IReadableData)data).EnsureAllFieldsRead(); + + Assert.Null(data.NumHotColdMap); + Assert.Null(data.DelayLoadMethodCallThunks); + } + + [Fact] + public void GetGCInfo_R2R_WasmVirtualIPUsesLoadedImageBase() + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + + const ulong VirtualIPRangeStart = 0x8001_0001; + const uint FunctionBeginAddress = 0x108; + const ulong LoadedImageBase = 0x0090_0000; + const uint UnwindDataRva = 0x80; + const ulong MethodDescAddress = 0x0101_aaa0; + const ulong JitManagerAddress = 0x000b_ff00; + const byte ExpectedGCInfoByte = 0xa5; + + MockExecutionManagerBuilder emBuilder = new( + "c1", + wasmArch, + MockExecutionManagerBuilder.DefaultAllocationRange, + isWasm: true); + MockExecutionManagerBuilder.JittedCodeRange virtualIPRange = + emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, 0x400); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([FunctionBeginAddress], []); + r2rInfo.MinVirtualIP = VirtualIPRangeStart; + r2rInfo.LoadedImageBase = LoadedImageBase; + r2rInfo.ReadyToRunHeader = emBuilder.AddReadyToRunHeader(majorVersion: 21).Address; + emBuilder.SetRuntimeFunctionUnwindData(r2rInfo, index: 0, UnwindDataRva); + emBuilder.Builder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = LoadedImageBase + UnwindDataRva, + // Frame size 128, function length 32, followed by the GC info. + Data = [0x80, 0x01, 0x20, ExpectedGCInfoByte], + Name = "WASM unwind and GC info", + }); + + MockHashMapBuilder hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo.EntryPointToMethodDescMapAddress, + [(VirtualIPRangeStart + FunctionBeginAddress, MethodDescAddress)]); + MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); + _ = emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address); + + IGCInfoHandle gcInfoHandle = Mock.Of(); + Mock gcInfoContract = new(MockBehavior.Strict); + gcInfoContract.Setup(c => c.DecodePlatformSpecificGCInfo(new TargetPointer(LoadedImageBase + UnwindDataRva + 3), 5)) + .Returns(gcInfoHandle); + gcInfoContract.Setup(c => c.GetCodeLength(gcInfoHandle)).Returns(32u); + Target target = CreateTarget( + emBuilder, + RuntimeInfoOperatingSystem.Windows, + RuntimeInfoArchitecture.Wasm, + targetBuilder => targetBuilder.AddMockContract(gcInfoContract)); + IExecutionManager em = target.Contracts.ExecutionManager; + CodeBlockHandle? handle = + em.GetCodeBlockHandle(new TargetCodePointer(VirtualIPRangeStart + FunctionBeginAddress)); + + Assert.NotNull(handle); + em.GetGCInfo(handle.Value, out TargetPointer gcInfo, out uint gcVersion); + Assert.Equal(new TargetPointer(LoadedImageBase + UnwindDataRva + 3), gcInfo); + Assert.Equal(5u, gcVersion); + Assert.Equal(ExpectedGCInfoByte, target.Read(gcInfo)); + em.GetMethodRegionInfo(handle.Value, out uint hotSize, out TargetPointer coldStart, out uint coldSize); + Assert.Equal(32u, hotSize); + Assert.Equal(TargetPointer.Null, coldStart); + Assert.Equal(0u, coldSize); + } + + [Fact] + public void GetCodeKind_R2R_WasmVirtualIPRangeList_RejectsBoundariesAndKeepsAdjacentRangesSeparate() + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + + const ulong FirstRangeStart = 0x8001_0001; + const ulong SecondRangeStart = 0x8001_0201; + const uint RangeSize = 0x200; + const ulong JitManagerAddress = 0x000b_ff00; + ulong firstModuleAddress = 0; + ulong secondModuleAddress = 0; + + IExecutionManager em = CreateExecutionManagerContract( + "c1", + wasmArch, + emBuilder => + { + MockExecutionManagerBuilder.JittedCodeRange firstRange = + emBuilder.AllocateJittedCodeRange(FirstRangeStart, RangeSize); + MockReadyToRunInfo firstInfo = emBuilder.AddReadyToRunInfo([0], []); + firstInfo.MinVirtualIP = FirstRangeStart; + firstInfo.LoadedImageBase = 0x0090_0000; + MockLoaderModule firstModule = emBuilder.AddReadyToRunModule(firstInfo.Address); + firstModuleAddress = firstModule.Address; + MockVirtualIPRangeSection firstNode = + emBuilder.AddVirtualIPRangeSection(firstRange, JitManagerAddress, firstModule.Address, registerAsHead: false); + + MockExecutionManagerBuilder.JittedCodeRange secondRange = + emBuilder.AllocateJittedCodeRange(SecondRangeStart, RangeSize); + MockReadyToRunInfo secondInfo = emBuilder.AddReadyToRunInfo([0], []); + secondInfo.MinVirtualIP = SecondRangeStart; + secondInfo.LoadedImageBase = 0x00a0_0000; + MockLoaderModule secondModule = emBuilder.AddReadyToRunModule(secondInfo.Address); + secondModuleAddress = secondModule.Address; + _ = emBuilder.AddVirtualIPRangeSection(secondRange, JitManagerAddress, secondModule.Address, firstNode.Address); + }, + targetArchitecture: RuntimeInfoArchitecture.Wasm); + + Assert.Equal(CodeKind.Unknown, em.GetCodeKind(new TargetCodePointer(FirstRangeStart - 2))); + Assert.Equal(new TargetPointer(firstModuleAddress), em.FindReadyToRunModule(new TargetPointer(FirstRangeStart))); + Assert.Equal(new TargetPointer(secondModuleAddress), em.FindReadyToRunModule(new TargetPointer(SecondRangeStart))); + Assert.Equal(CodeKind.Unknown, em.GetCodeKind(new TargetCodePointer(SecondRangeStart + RangeSize))); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void GetDebugInfoAndExceptionClauses_R2R_WasmUsesLoadedImageBase(bool funclet, bool filter) + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + const ulong VirtualIPRangeStart = 0x8001_0001; + const uint RootBeginAddress = 0x100; + const uint FuncletBeginAddress = 0x120; + const uint LoadedImageBase = 0x0090_0000; + const uint DebugInfoRva = 0x80; + const ulong MethodDescAddress = 0x0101_aaa0; + const ulong JitManagerAddress = 0x000b_ff00; + const byte ExpectedDebugInfoByte = 0xa5; + + MockExecutionManagerBuilder emBuilder = new( + "c1", wasmArch, MockExecutionManagerBuilder.DefaultAllocationRange, isWasm: true); + var range = emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, 0x400); + MockReadyToRunInfo info = emBuilder.AddReadyToRunInfo([0x80, RootBeginAddress, 0x8000_0000 | FuncletBeginAddress], []); + info.MinVirtualIP = VirtualIPRangeStart; + info.LoadedImageBase = LoadedImageBase; + new MockHashMapBuilder(emBuilder.Builder).PopulatePtrMap( + info.EntryPointToMethodDescMapAddress, [(VirtualIPRangeStart + RootBeginAddress, MethodDescAddress)]); + MockLoaderModule module = emBuilder.AddReadyToRunModule(info.Address); + emBuilder.AddVirtualIPRangeSection(range, JitManagerAddress, module.Address); + emBuilder.SetDebugInfoSection(info, DebugInfoRva, 5); + emBuilder.Builder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = LoadedImageBase + DebugInfoRva, + // NativeArray: three entries, one-byte block offset, leaf for index 1, no lookback. + Data = [0x18, 0x01, 0x08, 0x00, ExpectedDebugInfoByte], + Name = "WASM debug info", + }); + Dictionary exceptionTypes = + AddWasmExceptionInfo( + emBuilder, + info, + LoadedImageBase, + RootBeginAddress, + filter, + FuncletBeginAddress - RootBeginAddress); + + MethodDescHandle methodHandle = new(new TargetPointer(MethodDescAddress)); + TargetPointer methodTable = new(0x0102_0000); + ITypeHandle typeHandle = new TargetTypeHandle(methodTable); + Mock rts = new(MockBehavior.Strict); + rts.Setup(r => r.GetMethodDescHandle(new TargetPointer(MethodDescAddress))).Returns(methodHandle); + rts.Setup(r => r.GetMethodTable(methodHandle)).Returns(methodTable); + rts.Setup(r => r.GetTypeHandle(methodTable)).Returns(typeHandle); + rts.Setup(r => r.GetModule(typeHandle)).Returns(new TargetPointer(module.Address)); + Target target = CreateTarget(emBuilder, RuntimeInfoOperatingSystem.Windows, RuntimeInfoArchitecture.Wasm, + targetBuilder => targetBuilder.AddTypes(exceptionTypes).AddMockContract(rts)); + IExecutionManager em = target.Contracts.ExecutionManager; + + CodeBlockHandle? handle = em.GetCodeBlockHandle( + new TargetCodePointer(VirtualIPRangeStart + (funclet ? FuncletBeginAddress : RootBeginAddress))); + Assert.NotNull(handle); + TargetPointer debugInfo = em.GetDebugInfo(handle.Value, out bool hasFlagByte); + Assert.False(hasFlagByte); + Assert.Equal(new TargetPointer(LoadedImageBase + DebugInfoRva + 4), debugInfo); + Assert.Equal(ExpectedDebugInfoByte, target.Read(debugInfo)); + ExceptionClauseInfo clause = Assert.Single(em.GetExceptionClauses(handle.Value)); + Assert.Equal( + filter ? ExceptionClauseInfo.ExceptionClauseFlags.Filter : ExceptionClauseInfo.ExceptionClauseFlags.Finally, + clause.ClauseType); + Assert.Equal(2u, clause.TryStartPC); + Assert.Equal(16u, clause.TryEndPC); + Assert.Equal(filter ? 48u : FuncletBeginAddress - RootBeginAddress, clause.HandlerStartPC); + Assert.Equal(filter ? 64u : 48u, clause.HandlerEndPC); + Assert.Null(clause.ClassToken); + Assert.Equal(filter ? FuncletBeginAddress - RootBeginAddress + 2 : null, clause.FilterOffset); + Assert.Equal(filter, em.IsFilterFunclet(handle.Value)); + } + + private static Dictionary AddWasmExceptionInfo( + MockExecutionManagerBuilder emBuilder, + MockReadyToRunInfo info, + uint imageBase, + uint methodRva, + bool filter, + uint funcletOffset) + { + const uint CoreInfoRva = 0x200; + const uint CoreHeaderRva = 0x210; + const uint ExceptionTableRva = 0x300; + const uint ClauseRva = 0x340; + const uint ClauseSize = 24; + Dictionary types = []; + info.Composite = imageBase + CoreInfoRva; + AddWords(imageBase + CoreInfoRva, DataType.ReadyToRunCoreInfo, + [new("Header", DataType.pointer)], [imageBase + CoreHeaderRva]); + AddWords(imageBase + CoreHeaderRva, DataType.ReadyToRunCoreHeader, + [new("NumberOfSections", DataType.uint32)], [1]); + AddWords(imageBase + CoreHeaderRva + 4, DataType.ReadyToRunSection, + [new("Type", DataType.uint32), new("Section", DataType.ImageDataDirectory, 8)], + [104, ExceptionTableRva, 16]); + AddWords(imageBase + ExceptionTableRva, DataType.ExceptionLookupTableEntry, + [new("MethodStartRVA", DataType.uint32), new("ExceptionInfoRVA", DataType.uint32)], + [methodRva, ClauseRva, uint.MaxValue, ClauseRva + ClauseSize]); + AddWords(imageBase + ClauseRva, DataType.R2RExceptionClause, + [new("Flags", DataType.uint32), new("TryStartPC", DataType.uint32), new("TryEndPC", DataType.uint32), + new("HandlerStartPC", DataType.uint32), new("HandlerEndPC", DataType.uint32), new("ClassToken", DataType.uint32)], + filter + ? [1, 2, 16, 48, 64, funcletOffset + 2] + : [2, 2, 16, funcletOffset, 48, 0]); + return types; + + void AddWords(uint address, DataType type, TargetTestHelpers.Field[] fields, uint[] words) + { + TargetTestHelpers helpers = emBuilder.Builder.TargetTestHelpers; + var layout = helpers.LayoutFields(TargetTestHelpers.FieldLayout.Packed, fields); + types[type] = new Target.TypeInfo { Fields = layout.Fields, Size = layout.Stride }; + byte[] data = new byte[words.Length * sizeof(uint)]; + for (int i = 0; i < words.Length; i++) + helpers.Write(data.AsSpan(i * sizeof(uint), sizeof(uint)), words[i]); + emBuilder.Builder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = address, + Data = data, + Name = type.ToString(), + }); + } + } + + [Fact] + public void GetCodeKind_R2R_WasmVirtualIPMissingFromList_DoesNotUseRangeSectionMap() + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + + const ulong VirtualIPRangeStart = 0x8001_0001; + const uint VirtualIPRangeSize = 0x400; + const ulong JitManagerAddress = 0x000b_ff00; + + IExecutionManager em = CreateExecutionManagerContract( + "c1", + wasmArch, + emBuilder => + { + MockExecutionManagerBuilder.JittedCodeRange falseMappedRange = + emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, VirtualIPRangeSize); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([0x100], []); + r2rInfo.MinVirtualIP = VirtualIPRangeStart; + r2rInfo.LoadedImageBase = 0x0090_0000; + MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); + MockRangeSection rangeSection = + emBuilder.AddReadyToRunRangeSection(falseMappedRange, JitManagerAddress, r2rModule.Address); + _ = emBuilder.AddRangeSectionFragment(falseMappedRange, rangeSection.Address); + }, + targetArchitecture: RuntimeInfoArchitecture.Wasm); + + TargetCodePointer virtualIP = new(VirtualIPRangeStart + 0x100); + Assert.Null(em.GetCodeBlockHandle(virtualIP)); + Assert.Equal(CodeKind.Unknown, em.GetCodeKind(virtualIP)); + Assert.Equal(TargetPointer.Null, em.FindReadyToRunModule(virtualIP.AsTargetPointer)); + } + + [Theory] + [InlineData("self-cycle")] + [InlineData("two-node-cycle")] + [InlineData("inverted-range")] + [InlineData("null-module")] + [InlineData("overlap")] + public void GetCodeKind_R2R_WasmVirtualIPCorruptList_FailsClosed(string corruption) + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + + const ulong VirtualIPRangeStart = 0x8001_0001; + const uint VirtualIPRangeSize = 0x200; + const ulong ProbeVirtualIP = VirtualIPRangeStart + 0x10; + const ulong JitManagerAddress = 0x000b_ff00; + + IExecutionManager em = CreateExecutionManagerContract( + "c1", + wasmArch, + emBuilder => + { + MockExecutionManagerBuilder.JittedCodeRange virtualIPRange = + emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, VirtualIPRangeSize); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([0], []); + r2rInfo.MinVirtualIP = VirtualIPRangeStart; + r2rInfo.LoadedImageBase = 0x0090_0000; + MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); + + switch (corruption) + { + case "self-cycle": + { + MockVirtualIPRangeSection node = + emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address); + node.Next = node.Address; + break; + } + case "two-node-cycle": + { + MockVirtualIPRangeSection first = + emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address, registerAsHead: false); + MockVirtualIPRangeSection second = + emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address, first.Address, registerAsHead: false); + first.Next = second.Address; + emBuilder.SetVirtualIPRangeListHead(first.Address); + break; + } + case "inverted-range": + { + MockVirtualIPRangeSection node = + emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address); + MockRangeSection range = emBuilder.GetRangeSection(node); + range.RangeBegin = range.RangeEndOpen; + break; + } + case "null-module": + _ = emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModuleAddress: 0); + break; + case "overlap": + { + MockVirtualIPRangeSection first = + emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address, registerAsHead: false); + _ = emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address, first.Address); + break; + } + default: + throw new InvalidOperationException(corruption); + } + }, + targetArchitecture: RuntimeInfoArchitecture.Wasm); + + Assert.Null(em.GetCodeBlockHandle(new TargetCodePointer(ProbeVirtualIP))); + Assert.Equal(CodeKind.Unknown, em.GetCodeKind(new TargetCodePointer(ProbeVirtualIP))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetMethodDesc_R2R_WasmVirtualIPUnrelatedRegistration(bool registrationComplete) + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + const ulong InitializedRangeStart = 0x8001_0001; + const ulong PendingRangeStart = 0x8002_0001; + const ulong MethodDescAddress = 0x0101_aaa0; + const ulong JitManagerAddress = 0x000b_ff00; + ulong initializedModule = 0; + + IExecutionManager em = CreateExecutionManagerContract("c1", wasmArch, emBuilder => + { + var initializedRange = emBuilder.AllocateJittedCodeRange(InitializedRangeStart, 0x200); + MockReadyToRunInfo initializedInfo = emBuilder.AddReadyToRunInfo([0], []); + initializedInfo.MinVirtualIP = InitializedRangeStart; + initializedInfo.LoadedImageBase = 0x0090_0000; + new MockHashMapBuilder(emBuilder.Builder).PopulatePtrMap( + initializedInfo.EntryPointToMethodDescMapAddress, [(InitializedRangeStart, MethodDescAddress)]); + initializedModule = emBuilder.AddReadyToRunModule(initializedInfo.Address).Address; + MockVirtualIPRangeSection tail = emBuilder.AddVirtualIPRangeSection( + initializedRange, JitManagerAddress, initializedModule, registerAsHead: false); + + var pendingRange = emBuilder.AllocateJittedCodeRange(PendingRangeStart, 0x200); + MockReadyToRunInfo pendingInfo = emBuilder.AddReadyToRunInfo([0], []); + pendingInfo.MinVirtualIP = registrationComplete ? PendingRangeStart : 0; + pendingInfo.LoadedImageBase = 0x00a0_0000; + MockLoaderModule pendingModule = emBuilder.AddReadyToRunModule(pendingInfo.Address); + emBuilder.AddVirtualIPRangeSection(pendingRange, JitManagerAddress, pendingModule.Address, tail.Address); + }, targetArchitecture: RuntimeInfoArchitecture.Wasm); + + CodeBlockHandle? handle = em.GetCodeBlockHandle(new TargetCodePointer(InitializedRangeStart)); + Assert.NotNull(handle); + Assert.Equal(new TargetPointer(MethodDescAddress), em.GetMethodDesc(handle.Value)); + Assert.Equal(new TargetPointer(initializedModule), em.FindReadyToRunModule(new TargetPointer(InitializedRangeStart))); + Assert.Equal(registrationComplete ? CodeKind.ReadyToRun : CodeKind.Unknown, + em.GetCodeKind(new TargetCodePointer(PendingRangeStart))); + } + + [Theory] + [InlineData(1024, false)] + [InlineData(1025, false)] + [InlineData(2, true)] + [InlineData(1025, true)] + public void FindReadyToRunModule_WasmVirtualIPLongList(int count, bool cycle) + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + const ulong FirstRangeStart = 0x8001_0001; + const uint RangeSize = 0x200; + const ulong JitManagerAddress = 0x000b_ff00; + ulong firstModule = 0; + ulong lastModule = 0; + + IExecutionManager em = CreateExecutionManagerContract("c1", wasmArch, emBuilder => + { + ulong head = 0; + MockVirtualIPRangeSection? tail = null; + for (int i = 0; i < count; i++) + { + ulong rangeStart = FirstRangeStart + (ulong)i * RangeSize; + var range = emBuilder.AllocateJittedCodeRange(rangeStart, RangeSize); + MockReadyToRunInfo info = emBuilder.AddReadyToRunInfo([0], []); + info.MinVirtualIP = rangeStart; + info.LoadedImageBase = 0x0090_0000 + (ulong)i * RangeSize; + MockLoaderModule module = emBuilder.AddReadyToRunModule(info.Address); + MockVirtualIPRangeSection node = emBuilder.AddVirtualIPRangeSection( + range, JitManagerAddress, module.Address, head, registerAsHead: false); + head = node.Address; + if (i == 0) + { + tail = node; + firstModule = module.Address; + } + lastModule = module.Address; + } + if (cycle) + tail!.Next = head; + emBuilder.SetVirtualIPRangeListHead(head); + }, targetArchitecture: RuntimeInfoArchitecture.Wasm); + + Assert.Equal(new TargetPointer(cycle ? 0 : lastModule), + em.FindReadyToRunModule(new TargetPointer(FirstRangeStart + (ulong)(count - 1) * RangeSize))); + Assert.Equal(new TargetPointer(cycle ? 0 : firstModule), + em.FindReadyToRunModule(new TargetPointer(FirstRangeStart))); + Assert.Equal(TargetPointer.Null, + em.FindReadyToRunModule(new TargetPointer(FirstRangeStart + (ulong)count * RangeSize))); + } + + [Fact] + public void GetMethodDesc_R2R_WasmFuncletUsesMaskedBeginAddressAndControllingMethodDesc() + { + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + + const ulong VirtualIPRangeStart = 0x8001_0001; + const uint RootBeginAddress = 0x100; + const uint FuncletBeginAddress = 0x120; + const uint FlaggedFuncletBeginAddress = 0x8000_0120; + const ulong MethodDescAddress = 0x0101_aaa0; + const ulong JitManagerAddress = 0x000b_ff00; + ulong runtimeFunctionsAddress = 0; + int runtimeFunctionSize = 0; + + IExecutionManager em = CreateExecutionManagerContract( + "c1", + wasmArch, + emBuilder => + { + MockExecutionManagerBuilder.JittedCodeRange virtualIPRange = + emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, 0x400); + MockReadyToRunInfo r2rInfo = + emBuilder.AddReadyToRunInfo([RootBeginAddress, FlaggedFuncletBeginAddress, 0x180], []); + r2rInfo.MinVirtualIP = VirtualIPRangeStart; + r2rInfo.LoadedImageBase = 0x0090_0000; + runtimeFunctionsAddress = r2rInfo.RuntimeFunctions; + runtimeFunctionSize = emBuilder.RuntimeFunctionLayout.Size; + MockHashMapBuilder hashMapBuilder = new(emBuilder.Builder); + hashMapBuilder.PopulatePtrMap( + r2rInfo.EntryPointToMethodDescMapAddress, + [(VirtualIPRangeStart + RootBeginAddress, MethodDescAddress)]); + + MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); + _ = emBuilder.AddVirtualIPRangeSection(virtualIPRange, JitManagerAddress, r2rModule.Address); + }, + targetArchitecture: RuntimeInfoArchitecture.Wasm); + + CodeBlockHandle? root = em.GetCodeBlockHandle(new TargetCodePointer(VirtualIPRangeStart + RootBeginAddress)); + CodeBlockHandle? funclet = em.GetCodeBlockHandle(new TargetCodePointer(VirtualIPRangeStart + FuncletBeginAddress + 2)); + + Assert.NotNull(root); + Assert.NotNull(funclet); + Assert.Equal(new TargetPointer(MethodDescAddress), em.GetMethodDesc(root.Value)); + Assert.Equal(new TargetPointer(MethodDescAddress), em.GetMethodDesc(funclet.Value)); + Assert.False(em.IsFunclet(root.Value)); + Assert.True(em.IsFunclet(funclet.Value)); + Assert.Equal(em.GetStartAddress(root.Value), em.GetStartAddress(funclet.Value)); + Assert.Equal(new TargetPointer(VirtualIPRangeStart + FuncletBeginAddress), em.GetFuncletStartAddress(funclet.Value)); + Assert.Equal(new TargetPointer(runtimeFunctionsAddress + (ulong)runtimeFunctionSize), em.GetUnwindInfo(funclet.Value)); + } + + [Theory] + [InlineData(65_536, true)] + [InlineData(65_537, false)] + public void FindReadyToRunModule_WasmVirtualIPTraversalBudget(int count, bool withinBudget) + { + const int NodeBudget = 65_536; + const ulong FirstRangeStart = 0x8001_0001; + const uint RangeSize = 0x200; + MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; + var allocationRange = MockExecutionManagerBuilder.DefaultAllocationRange with + { + ExecutionManagerStart = 0x0100_0000, + ExecutionManagerEnd = 0x0400_0000, + }; + MockExecutionManagerBuilder emBuilder = new("c1", wasmArch, allocationRange, isWasm: true); + (ulong nodes, ulong firstModule, ulong lastModule) = + emBuilder.AddVirtualIPRangeSectionArray(count, FirstRangeStart, RangeSize, 0x000b_ff00); + int nodeSize = emBuilder.VirtualIPRangeSectionLayout.Size; + int highestNodeRead = -1; + MockMemorySpace.MemoryContext memory = emBuilder.Builder.GetMemoryContext(); + Target target = CreateTarget(emBuilder, RuntimeInfoOperatingSystem.Windows, RuntimeInfoArchitecture.Wasm, + targetBuilder => targetBuilder.UseReader((address, buffer) => + { + if (address >= nodes && address < nodes + (ulong)(count * nodeSize)) + highestNodeRead = Math.Max(highestNodeRead, (int)((address - nodes) / (ulong)nodeSize)); + return memory.ReadFromTarget(address, buffer); + })); + IExecutionManager em = target.Contracts.ExecutionManager; + + TargetPointer firstResult = em.FindReadyToRunModule(new TargetPointer(FirstRangeStart)); + Assert.Equal(NodeBudget - 1, highestNodeRead); + Assert.Equal(new TargetPointer(withinBudget ? firstModule : 0), firstResult); + Assert.Equal(new TargetPointer(withinBudget ? lastModule : 0), + em.FindReadyToRunModule(new TargetPointer(FirstRangeStart + (ulong)(count - 1) * RangeSize))); + Assert.Equal(NodeBudget - 1, highestNodeRead); + Assert.Equal(withinBudget ? CodeKind.ReadyToRun : CodeKind.Unknown, + em.GetCodeKind(new TargetCodePointer(FirstRangeStart))); + Assert.Equal(NodeBudget - 1, highestNodeRead); + } + [Theory] [MemberData(nameof(StdArchAllVersions))] public void GetMethodDesc_R2R_MultipleRuntimeFunctions(string version, MockTarget.Architecture arch) diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs index c0009d5e9a1ef1..b75167b17662b1 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.ExecutionManager.cs @@ -157,6 +157,28 @@ public ulong RangeList } } +internal sealed class MockVirtualIPRangeSection : TypedView +{ + private const string RangeSectionFieldName = "RangeSection"; + private const string NextFieldName = "Next"; + + public static Layout CreateLayout( + MockTarget.Architecture architecture, + int rangeSectionSize) + => new SequentialLayoutBuilder("VirtualIPRangeSection", architecture) + .AddField(RangeSectionFieldName, rangeSectionSize) + .AddPointerField(NextFieldName) + .Build(); + + public ulong RangeSectionAddress => GetFieldAddress(RangeSectionFieldName); + + public ulong Next + { + get => ReadPointerField(NextFieldName); + set => WritePointerField(NextFieldName, value); + } +} + internal sealed class MockCodeRangeMapRangeList : TypedView { private const string RangeListTypeFieldName = "RangeListType"; @@ -419,25 +441,35 @@ internal sealed class MockReadyToRunInfo : TypedView private const string NumImportSectionsFieldName = "NumImportSections"; private const string MinVirtualIPFieldName = "MinVirtualIP"; - public static Layout CreateLayout(MockTarget.Architecture architecture, int hashMapStride) - => new SequentialLayoutBuilder("ReadyToRunInfo", architecture) + public static Layout CreateLayout(MockTarget.Architecture architecture, int hashMapStride, bool isWasm = false) + { + SequentialLayoutBuilder builder = new SequentialLayoutBuilder("ReadyToRunInfo", architecture) .AddPointerField(ReadyToRunHeaderFieldName) .AddPointerField(CompositeInfoFieldName) .AddUInt32Field(NumRuntimeFunctionsFieldName) - .AddPointerField(RuntimeFunctionsFieldName) - .AddUInt32Field(NumHotColdMapFieldName) - .AddPointerField(HotColdMapFieldName) - .AddPointerField(DelayLoadMethodCallThunksFieldName) + .AddPointerField(RuntimeFunctionsFieldName); + if (!isWasm) + { + builder.AddUInt32Field(NumHotColdMapFieldName) + .AddPointerField(HotColdMapFieldName) + .AddPointerField(DelayLoadMethodCallThunksFieldName); + } + builder .AddPointerField(DebugInfoSectionFieldName) .AddPointerField(ExceptionInfoSectionFieldName) .AddPointerField(ImportSectionsFieldName) .AddUInt32Field(NumImportSectionsFieldName) .AddField(EntryPointToMethodDescMapFieldName, hashMapStride) .AddPointerField(LoadedImageBaseFieldName) - .AddPointerField(CompositeFieldName) - // WASM-only: base virtual IP for the module's ReadyToRun functions (nullable field). - .AddPointerField(MinVirtualIPFieldName) - .Build(); + .AddPointerField(CompositeFieldName); + if (isWasm) + { + builder.AddPointerField(MinVirtualIPFieldName); + } + return builder.Build(); + } + + public bool HasHotColdMap => Array.Exists(Layout.Fields, static f => f.Name == NumHotColdMapFieldName); public ulong CompositeInfo { @@ -445,6 +477,12 @@ public ulong CompositeInfo set => WritePointerField(CompositeInfoFieldName, value); } + public ulong ReadyToRunHeader + { + get => ReadPointerField(ReadyToRunHeaderFieldName); + set => WritePointerField(ReadyToRunHeaderFieldName, value); + } + public uint NumRuntimeFunctions { get => ReadUInt32Field(NumRuntimeFunctionsFieldName); @@ -475,6 +513,18 @@ public ulong ExceptionInfoSection set => WritePointerField(ExceptionInfoSectionFieldName, value); } + public ulong DebugInfoSection + { + get => ReadPointerField(DebugInfoSectionFieldName); + set => WritePointerField(DebugInfoSectionFieldName, value); + } + + public ulong Composite + { + get => ReadPointerField(CompositeFieldName); + set => WritePointerField(CompositeFieldName, value); + } + public ulong DelayLoadMethodCallThunks { get => ReadPointerField(DelayLoadMethodCallThunksFieldName); @@ -496,6 +546,30 @@ public ulong MinVirtualIP } } +internal sealed class MockReadyToRunHeader : TypedView +{ + private const string MajorVersionFieldName = "MajorVersion"; + private const string MinorVersionFieldName = "MinorVersion"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("ReadyToRunHeader", architecture) + .AddUInt16Field(MajorVersionFieldName) + .AddUInt16Field(MinorVersionFieldName) + .Build(); + + public ushort MajorVersion + { + get => ReadUInt16Field(MajorVersionFieldName); + set => WriteUInt16Field(MajorVersionFieldName, value); + } + + public ushort MinorVersion + { + get => ReadUInt16Field(MinorVersionFieldName); + set => WriteUInt16Field(MinorVersionFieldName, value); + } +} + internal sealed class MockImageDataDirectory : TypedView { private const string VirtualAddressFieldName = "VirtualAddress"; @@ -602,6 +676,7 @@ internal sealed class MockExecutionManagerBuilder private const uint CodeHeapRangeSectionFlag = 0x02; private const uint RangeListRangeSectionFlag = 0x04; private const uint InterpreterRangeSectionFlag = 0x0A; // CodeHeap | Interpreter + private const uint VirtualIPRangeSectionFlag = 0x10; private const string EEJitManagerGlobalName = "EEJitManagerGlobalPointer"; private const int RangeSectionMapBitsPerLevel = 8; @@ -622,7 +697,7 @@ public readonly struct AllocationRange NibbleMapStart = 0x00ee_0000, NibbleMapEnd = 0x00ef_0000, ExecutionManagerStart = 0x0033_4000, - ExecutionManagerEnd = 0x0033_5000, + ExecutionManagerEnd = 0x0037_4000, }; private readonly struct RangeSectionMapCursor @@ -653,6 +728,7 @@ internal readonly struct JittedCodeRange internal Layout RangeSectionMapLayout { get; } internal Layout RangeSectionFragmentLayout { get; } internal Layout RangeSectionLayout { get; } + internal Layout VirtualIPRangeSectionLayout { get; } internal Layout CodeHeapListNodeLayout { get; } internal Layout CodeHeapLayout { get; } internal Layout LoaderCodeHeapLayout { get; } @@ -660,6 +736,7 @@ internal readonly struct JittedCodeRange internal Layout RealCodeHeaderLayout { get; } internal Layout InterpreterRealCodeHeaderLayout { get; } internal Layout ReadyToRunInfoLayout { get; } + internal Layout ReadyToRunHeaderLayout { get; } internal Layout EEJitManagerLayout { get; } internal Layout DynamicFunctionTableLayout { get; } internal Layout ModuleLayout { get; } @@ -682,19 +759,20 @@ internal readonly struct JittedCodeRange private readonly ulong _rangeSectionMapTopLevelAddress; private readonly int _rangeSectionMapLevelsCount; private readonly int _rangeSectionMapMaxSetBit; + private ulong _virtualIPRangeListGlobalAddress; - internal MockExecutionManagerBuilder(string version, MockTarget.Architecture arch, AllocationRange allocationRange, ulong allCodeHeaps = 0) - : this(version, new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange, allCodeHeaps) + internal MockExecutionManagerBuilder(string version, MockTarget.Architecture arch, AllocationRange allocationRange, ulong allCodeHeaps = 0, bool isWasm = false) + : this(version, new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange, allCodeHeaps, isWasm) { } - internal MockExecutionManagerBuilder(string version, MockMemorySpace.Builder builder, AllocationRange allocationRange, ulong allCodeHeaps = 0) + internal MockExecutionManagerBuilder(string version, MockMemorySpace.Builder builder, AllocationRange allocationRange, ulong allCodeHeaps = 0, bool isWasm = false) { ArgumentNullException.ThrowIfNull(builder); Version = version; Builder = builder; - _runtimeFunctions = new MockRuntimeFunctionsBuilder(builder); + _runtimeFunctions = new MockRuntimeFunctionsBuilder(builder, includeEndAddress: !isWasm); _rangeSectionMapAllocator = Builder.CreateAllocator(allocationRange.RangeSectionMapStart, allocationRange.RangeSectionMapEnd); _nibbleMapAllocator = Builder.CreateAllocator(allocationRange.NibbleMapStart, allocationRange.NibbleMapEnd); _allocator = Builder.CreateAllocator(allocationRange.ExecutionManagerStart, allocationRange.ExecutionManagerEnd); @@ -712,13 +790,15 @@ internal MockExecutionManagerBuilder(string version, MockMemorySpace.Builder bui RangeSectionMapLayout = MockRangeSectionMap.CreateLayout(architecture); RangeSectionFragmentLayout = MockRangeSectionFragment.CreateLayout(architecture); RangeSectionLayout = MockRangeSection.CreateLayout(architecture); + VirtualIPRangeSectionLayout = MockVirtualIPRangeSection.CreateLayout(architecture, RangeSectionLayout.Size); CodeHeapListNodeLayout = MockCodeHeapListNode.CreateLayout(architecture); CodeHeapLayout = MockCodeHeap.CreateLayout(architecture); LoaderCodeHeapLayout = MockLoaderCodeHeap.CreateLayout(architecture); HostCodeHeapLayout = MockHostCodeHeap.CreateLayout(architecture); RealCodeHeaderLayout = MockRealCodeHeader.CreateLayout(architecture); InterpreterRealCodeHeaderLayout = MockInterpreterRealCodeHeader.CreateLayout(architecture); - ReadyToRunInfoLayout = MockReadyToRunInfo.CreateLayout(architecture, hashMapStride); + ReadyToRunInfoLayout = MockReadyToRunInfo.CreateLayout(architecture, hashMapStride, isWasm); + ReadyToRunHeaderLayout = MockReadyToRunHeader.CreateLayout(architecture); EEJitManagerLayout = MockEEJitManager.CreateLayout(architecture); DynamicFunctionTableLayout = MockDynamicFunctionTable.CreateLayout(architecture); ModuleLayout = MockLoaderModule.CreateLayout(architecture); @@ -798,6 +878,54 @@ public MockRangeSection AddReadyToRunRangeSection(JittedCodeRange jittedCodeRang return rangeSection; } + public MockVirtualIPRangeSection AddVirtualIPRangeSection( + JittedCodeRange virtualIPRange, + ulong jitManagerAddress, + ulong r2rModuleAddress, + ulong next = 0, + bool registerAsHead = true) + { + MockMemorySpace.HeapFragment fragment = _allocator.Allocate((ulong)VirtualIPRangeSectionLayout.Size, "VirtualIPRangeSection"); + MockVirtualIPRangeSection node = VirtualIPRangeSectionLayout.Create(fragment); + MockRangeSection rangeSection = RangeSectionLayout.Create( + fragment.Data.AsMemory(0, RangeSectionLayout.Size), + node.RangeSectionAddress); + rangeSection.RangeBegin = virtualIPRange.RangeStart; + rangeSection.RangeEndOpen = virtualIPRange.RangeEnd; + rangeSection.Flags = VirtualIPRangeSectionFlag; + rangeSection.R2RModule = r2rModuleAddress; + rangeSection.JitManager = jitManagerAddress; + node.Next = next; + + if (registerAsHead) + SetVirtualIPRangeListHead(node.Address); + + return node; + } + + public void SetVirtualIPRangeListHead(ulong head) + { + if (_virtualIPRangeListGlobalAddress == 0) + { + _virtualIPRangeListGlobalAddress = AddPointerGlobal(head, "VirtualIPRangeListGlobalPointer"); + Globals = + [ + .. Globals, + (nameof(Constants.Globals.VirtualIPRangeList), _virtualIPRangeListGlobalAddress), + ]; + return; + } + + Builder.TargetTestHelpers.WritePointer( + Builder.BorrowAddressRange(_virtualIPRangeListGlobalAddress, Builder.TargetTestHelpers.PointerSize), + head); + } + + public MockRangeSection GetRangeSection(MockVirtualIPRangeSection node) + => RangeSectionLayout.Create( + node.Memory.Slice(0, RangeSectionLayout.Size), + node.RangeSectionAddress); + public MockRangeSection AddRangeListRangeSection(JittedCodeRange jittedCodeRange, ulong jitManagerAddress, int rangeListType) { MockCodeRangeMapRangeList rangeList = AllocateAndCreate(CodeRangeMapRangeListLayout, "CodeRangeMapRangeList"); @@ -998,11 +1126,36 @@ public MockReadyToRunInfo AddReadyToRunInfo(uint[] runtimeFunctions, uint[] hotC readyToRunInfo.CompositeInfo = readyToRunInfo.Address; readyToRunInfo.NumRuntimeFunctions = checked((uint)runtimeFunctions.Length); readyToRunInfo.RuntimeFunctions = runtimeFunctionsAddress; - readyToRunInfo.NumHotColdMap = checked((uint)hotColdMap.Length); - readyToRunInfo.HotColdMap = hotColdMapAddress; + if (readyToRunInfo.HasHotColdMap) + { + readyToRunInfo.NumHotColdMap = checked((uint)hotColdMap.Length); + readyToRunInfo.HotColdMap = hotColdMapAddress; + } + else if (hotColdMap.Length != 0) + { + throw new InvalidOperationException("Hot/cold code is not supported by this target."); + } return readyToRunInfo; } + public MockReadyToRunHeader AddReadyToRunHeader(ushort majorVersion, ushort minorVersion = 0) + { + MockReadyToRunHeader header = AllocateAndCreate(ReadyToRunHeaderLayout, "ReadyToRunHeader"); + header.MajorVersion = majorVersion; + header.MinorVersion = minorVersion; + return header; + } + + public void SetRuntimeFunctionUnwindData(MockReadyToRunInfo readyToRunInfo, uint index, uint unwindData) + { + ulong address = readyToRunInfo.RuntimeFunctions + (index * (uint)RuntimeFunctionLayout.Size); + Span runtimeFunction = Builder.BorrowAddressRange(address, RuntimeFunctionLayout.Size); + LayoutField unwindDataField = RuntimeFunctionLayout.GetField("UnwindData"); + Builder.TargetTestHelpers.Write( + runtimeFunction.Slice(unwindDataField.Offset, sizeof(uint)), + unwindData); + } + public void SetDelayLoadMethodCallThunks(MockReadyToRunInfo readyToRunInfo, uint thunkRva, uint thunkSize) { MockImageDataDirectory imageDataDir = AllocateAndCreate(ImageDataDirectoryLayout, "DelayLoadMethodCallThunks"); @@ -1011,6 +1164,47 @@ public void SetDelayLoadMethodCallThunks(MockReadyToRunInfo readyToRunInfo, uint readyToRunInfo.DelayLoadMethodCallThunks = imageDataDir.Address; } + public void SetDebugInfoSection(MockReadyToRunInfo readyToRunInfo, uint rva, uint size) + { + MockImageDataDirectory directory = AllocateAndCreate(ImageDataDirectoryLayout, "DebugInfoSection"); + directory.VirtualAddress = rva; + directory.Size = size; + readyToRunInfo.DebugInfoSection = directory.Address; + } + + public (ulong NodesAddress, ulong FirstModule, ulong LastModule) AddVirtualIPRangeSectionArray( + int count, ulong rangeStart, uint rangeSize, ulong jitManager) + { + ArgumentOutOfRangeException.ThrowIfLessThan(count, 2); + MockMemorySpace.HeapFragment nodes = _allocator.Allocate( + checked((ulong)count * (ulong)VirtualIPRangeSectionLayout.Size), "VirtualIPRangeSections"); + MockReadyToRunInfo firstInfo = AddReadyToRunInfo([0], []); + firstInfo.MinVirtualIP = rangeStart; + firstInfo.LoadedImageBase = 0x1000_0000; + MockLoaderModule firstModule = AddReadyToRunModule(firstInfo.Address); + MockReadyToRunInfo lastInfo = AddReadyToRunInfo([0], []); + lastInfo.MinVirtualIP = rangeStart + (ulong)(count - 1) * rangeSize; + lastInfo.LoadedImageBase = 0x2000_0000; + MockLoaderModule lastModule = AddReadyToRunModule(lastInfo.Address); + for (int i = 0; i < count; i++) + { + ulong nodeAddress = nodes.Address + (ulong)(i * VirtualIPRangeSectionLayout.Size); + Memory nodeMemory = nodes.Data.AsMemory(i * VirtualIPRangeSectionLayout.Size, VirtualIPRangeSectionLayout.Size); + MockVirtualIPRangeSection node = VirtualIPRangeSectionLayout.Create(nodeMemory, nodeAddress); + MockRangeSection range = RangeSectionLayout.Create( + nodeMemory.Slice(0, RangeSectionLayout.Size), node.RangeSectionAddress); + range.RangeBegin = rangeStart + (ulong)i * rangeSize; + range.RangeEndOpen = range.RangeBegin + rangeSize; + range.JitManager = jitManager; + // Only the head and tail are queried; unrelated ranges need no distinct module metadata. + range.R2RModule = i + 1 == count ? lastModule.Address : firstModule.Address; + range.Flags = VirtualIPRangeSectionFlag; + node.Next = i + 1 == count ? 0 : nodeAddress + (ulong)VirtualIPRangeSectionLayout.Size; + } + SetVirtualIPRangeListHead(nodes.Address); + return (nodes.Address, firstModule.Address, lastModule.Address); + } + public MockLoaderModule AddReadyToRunModule(ulong readyToRunInfoAddress) { MockLoaderModule module = AllocateAndCreate(ModuleLayout, "R2R Module"); diff --git a/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs b/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs index 9e4ff20497ca4b..4554ee3d5fa69e 100644 --- a/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/WasmR2RInfoTests.cs @@ -33,7 +33,7 @@ private static TestPlaceholderTarget CreateTarget(bool emptyList = false) int hashMapStride = MockHashMap.CreateLayout(WasmArch).Size; var moduleLayout = MockLoaderModule.CreateLayout(WasmArch); - var r2rInfoLayout = MockReadyToRunInfo.CreateLayout(WasmArch, hashMapStride); + var r2rInfoLayout = MockReadyToRunInfo.CreateLayout(WasmArch, hashMapStride, isWasm: true); var runtimeFunctionLayout = helpers.LayoutFields([ new("BeginAddress", DataType.uint32), new("UnwindData", DataType.uint32), @@ -146,7 +146,7 @@ private static (TestPlaceholderTarget Target, ulong FrameAddress) CreateDispatch int hashMapStride = MockHashMap.CreateLayout(WasmArch).Size; var moduleLayout = MockLoaderModule.CreateLayout(WasmArch); - var r2rInfoLayout = MockReadyToRunInfo.CreateLayout(WasmArch, hashMapStride); + var r2rInfoLayout = MockReadyToRunInfo.CreateLayout(WasmArch, hashMapStride, isWasm: true); var runtimeFunctionLayout = helpers.LayoutFields([ new("BeginAddress", DataType.uint32), new("UnwindData", DataType.uint32),