diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index fa614820343781..bda78e52a0efd1 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -270,7 +270,7 @@ virtual-IP ranges bypass native thunk classification. | `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; 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` | `LoadedImageBase` | `pointer` | Base address of the loaded R2R image; unset for component assemblies of a composite image, so it is read from the `CompositeInfo` | | `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` | @@ -398,7 +398,7 @@ 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 codeBase = /* RangeSection.RangeBegin, or ReadyToRunInfo.MinVirtualIP on WebAssembly */; - TargetPointer loadedImageBase = /* ReadyToRunInfo.LoadedImageBase on WebAssembly, otherwise codeBase */; + TargetPointer loadedImageBase = /* LoadedImageBase of r2rInfo.CompositeInfo on WebAssembly, otherwise codeBase */; TargetPointer relativeAddr = addr - codeBase; TargetPointer runtimeFunctions = Target.ReadPointer(r2rInfo + /* ReadyToRunInfo::RuntimeFunctions offset */); diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index f31c38a1d6010a..c05770adf5628a 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -207,8 +207,9 @@ On ARM64, the `pac` unwind opcode strips pointer authentication bits from `Lr` u | `Object` | `m_pMethTab` | `pointer` | Method table for the object | | `PInvokeCalliFrame` | `VASigCookiePtr` | `pointer` | Pointer to the varargs signature cookie for the unmanaged call | | `PortableEntryPoint` | `ActualCode` | `pointer` | Native code for the entrypoint; on WebAssembly R2R code this is a function-table index (only defined if `FeaturePortableEntrypoints` is enabled) | +| `ReadyToRunInfo` | `CompositeInfo` | `pointer` | Pointer to composite R2R info - or itself for non-composite | | `ReadyToRunInfo` | `ImportSections` | `pointer` | Pointer to the array of ReadyToRun import sections | -| `ReadyToRunInfo` | `LoadedImageBase` | `pointer` | Base address of the loaded R2R image | +| `ReadyToRunInfo` | `LoadedImageBase` | `pointer` | Base address of the loaded R2R image; unset for component assemblies of a composite image, so it is read from the `CompositeInfo` | | `ReadyToRunInfo` | `MinVirtualIP` | `pointer` | Base virtual IP assigned to the ReadyToRun module on WebAssembly | | `ReadyToRunInfo` | `NumImportSections` | `uint32` | Number of ReadyToRun import sections | | `ReadyToRunInfo` | `NumRuntimeFunctions` | `uint32` | Number of `RuntimeFunctions` | @@ -873,7 +874,7 @@ GCRefMap resolution from a frame's `Indirection` pointer proceeds as follows: 1. Call `FindReadyToRunModule(indirection)` (see [ExecutionManager contract](./ExecutionManager.md)) to find the ReadyToRun module containing the import slot. 2. Load the module's `ReadyToRunInfo` to access the import section array. -3. Compute the RVA of the indirection address: `rva = indirection - imageBase`. +3. Compute the RVA of the indirection address: `rva = indirection - imageBase`, where `imageBase` is the `LoadedImageBase` of the `ReadyToRunInfo`'s `CompositeInfo`. 4. Search through `READYTORUN_IMPORT_SECTION` entries to find the section containing the RVA. 5. Compute the slot index within the section: `index = (rva - sectionVA) / entrySize`. 6. If the section has the `GCRefMap` flag (`0x0008`), read the DWORD at `imageBase + Signatures + index * 4`. This RVA points directly to the GCRefMap prefix of the import signature. diff --git a/docs/design/datacontracts/data-descriptor-meanings.json b/docs/design/datacontracts/data-descriptor-meanings.json index cfadd9c2d1b0e8..ee23b38a31ccd9 100644 --- a/docs/design/datacontracts/data-descriptor-meanings.json +++ b/docs/design/datacontracts/data-descriptor-meanings.json @@ -534,7 +534,7 @@ "ReadyToRunInfo.EntryPointToMethodDescMap": "`HashMap` of entry point addresses to `MethodDesc` pointers", "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.LoadedImageBase": "Base address of the loaded R2R image; unset for component assemblies of a composite image, so it is read from the `CompositeInfo`", "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", 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 511065f743fe44..ed5669434a9554 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 @@ -17,7 +17,6 @@ internal sealed partial class ReadyToRunInfo : IData [Field] public partial TargetPointer? DelayLoadMethodCallThunks { get; } [Field] public partial TargetPointer DebugInfoSection { get; } [Field] public partial TargetPointer ExceptionInfoSection { get; } - [Field] public partial TargetPointer LoadedImageBase { get; } [Field] public partial TargetPointer Composite { get; } [Field] public partial uint NumImportSections { get; } @@ -27,6 +26,7 @@ internal sealed partial class ReadyToRunInfo : IData [CustomInit(nameof(InitHotColdMap))] public partial TargetPointer HotColdMap { get; } [CustomInit(nameof(InitImportSections))] public partial TargetPointer ImportSections { get; } [CustomInit(nameof(InitEntryPointToMethodDescMap))] public partial TargetPointer EntryPointToMethodDescMap { get; } + [CustomInit(nameof(InitLoadedImageBase))] public partial TargetPointer LoadedImageBase { get; } [DataDescriptorDependency(nameof(NumRuntimeFunctions), "uint32")] [DataDescriptorDependency(nameof(RuntimeFunctions), "pointer")] @@ -68,4 +68,14 @@ private partial TargetPointer InitEntryPointToMethodDescMap(Target target, Targe // Map is from the composite info pointer (set to itself for non-multi-assembly composite images) return CompositeInfo + (ulong)type.Fields[nameof(EntryPointToMethodDescMap)].Offset; } + + [DataDescriptorDependency(nameof(CompositeInfo), "pointer")] + [DataDescriptorDependency(nameof(LoadedImageBase), "pointer")] + private partial TargetPointer InitLoadedImageBase(Target target, TargetPointer address) + { + // Component assemblies of a multi-assembly composite image do not record the image base; + // read it from the composite info (set to itself for non-component images). + Target.TypeInfo type = target.GetTypeInfo(DataType.ReadyToRunInfo); + return target.ReadPointerField(CompositeInfo, type, nameof(LoadedImageBase)); + } } diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs index 25ae3b178f2e86..f02bc29965930b 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs @@ -441,8 +441,10 @@ int ReadWithReadableZeroPage(ulong address, Span buffer) } } - [Fact] - public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape(bool compositeComponent) { MockTarget.Architecture wasmArch = new() { IsLittleEndian = true, Is64Bit = false }; @@ -466,7 +468,16 @@ public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape() emBuilder.AllocateJittedCodeRange(VirtualIPRangeStart, VirtualIPRangeSize); MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([0x80, FunctionBeginAddress, 0x200], []); r2rInfo.MinVirtualIP = VirtualIPRangeStart; - r2rInfo.LoadedImageBase = LoadedImageBase; + + // A component assembly of a composite image leaves LoadedImageBase unset; the image base + // and entry point map belong to the composite image's ReadyToRunInfo. + MockReadyToRunInfo compositeInfo = r2rInfo; + if (compositeComponent) + { + compositeInfo = emBuilder.AddReadyToRunInfo([0x80, FunctionBeginAddress, 0x200], []); + r2rInfo.CompositeInfo = compositeInfo.Address; + } + compositeInfo.LoadedImageBase = LoadedImageBase; runtimeFunctionsAddress = r2rInfo.RuntimeFunctions; runtimeFunctionSize = emBuilder.RuntimeFunctionLayout.Size; Assert.Equal(8, runtimeFunctionSize); @@ -476,7 +487,7 @@ public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape() MockHashMapBuilder hashMapBuilder = new(emBuilder.Builder); hashMapBuilder.PopulatePtrMap( - r2rInfo.EntryPointToMethodDescMapAddress, + compositeInfo.EntryPointToMethodDescMapAddress, [(CapturedVirtualIP, ExpectedMethodDescAddress)]); MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); diff --git a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs index de49bf32c3cb27..b5b852d038fcaf 100644 --- a/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/StackWalkTests.cs @@ -24,13 +24,14 @@ public unsafe class StackWalkTests private const uint X86TransitionBlockSize = 7 * sizeof(uint); [Theory] - [InlineData(false)] - [InlineData(true)] - public void FindGCRefMap_ResolvesImportRecord(bool inlineGCRefMap) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FindGCRefMap_ResolvesImportRecord(bool inlineGCRefMap, bool compositeComponent) { (TestPlaceholderTarget target, _) = CreateX86TransitionFrameTarget( nameof(Data.ExternalMethodFrame), gcRefMap: [0x83, 0x05], hasMethodDesc: false, - inlineGCRefMap: inlineGCRefMap); + inlineGCRefMap: inlineGCRefMap, compositeComponent: compositeComponent); TargetPointer map = new FrameHelpers(target).FindGCRefMap(new TargetPointer(0x100108)); @@ -1208,7 +1209,8 @@ private static (TestPlaceholderTarget Target, ContextHolder Context) string frameType, byte[]? gcRefMap, bool hasMethodDesc, byte[]? signatureBlob = null, uint? representativeMethodDescAddress = null, bool hasUnresolvedIndirection = false, RuntimeInfoArchitecture architecture = RuntimeInfoArchitecture.X86, bool hasCodeData = true, - bool hasGCRefMapField = true, bool? valueType = null, bool unboxing = false, bool inlineGCRefMap = true) + bool hasGCRefMapField = true, bool? valueType = null, bool unboxing = false, bool inlineGCRefMap = true, + bool compositeComponent = false) { MockTarget.Architecture arch = new() { IsLittleEndian = true, Is64Bit = false }; TestPlaceholderTarget.Builder builder = new(arch); @@ -1260,11 +1262,20 @@ private static (TestPlaceholderTarget Target, ContextHolder Context) byte[] module = new byte[4]; helpers.Write(module, 0x4000u); builder.MemoryBuilder.AddHeapFragment(new() { Address = 0x3000, Data = module, Name = "Module" }); - byte[] r2rInfo = new byte[12]; - helpers.Write(r2rInfo.AsSpan(0), ImageBase); + byte[] r2rInfo = new byte[16]; + // A component assembly of a composite image leaves LoadedImageBase unset; the composite's info holds it. + helpers.Write(r2rInfo.AsSpan(0), compositeComponent ? 0u : ImageBase); helpers.Write(r2rInfo.AsSpan(4), 1u); helpers.Write(r2rInfo.AsSpan(8), 0x5000u); + helpers.Write(r2rInfo.AsSpan(12), compositeComponent ? 0x4100u : 0x4000u); builder.MemoryBuilder.AddHeapFragment(new() { Address = 0x4000, Data = r2rInfo, Name = "ReadyToRun info" }); + if (compositeComponent) + { + byte[] compositeInfo = new byte[16]; + helpers.Write(compositeInfo.AsSpan(0), ImageBase); + helpers.Write(compositeInfo.AsSpan(12), 0x4100u); + builder.MemoryBuilder.AddHeapFragment(new() { Address = 0x4100, Data = compositeInfo, Name = "Composite ReadyToRun info" }); + } byte[] importSection = new byte[20]; helpers.Write(importSection.AsSpan(0), ImportSectionRva); helpers.Write(importSection.AsSpan(4), 3u * sizeof(uint)); @@ -1340,7 +1351,8 @@ private static (TestPlaceholderTarget Target, ContextHolder Context) (nameof(X86Context.Ebp), 0), (nameof(X86Context.Ebx), 4), (nameof(X86Context.Edi), 8), (nameof(X86Context.Esi), 12)), [DataType.Module] = CreateTypeInfo((nameof(Data.Module.ReadyToRunInfo), 0)), [DataType.ReadyToRunInfo] = CreateTypeInfo( - (nameof(Data.ReadyToRunInfo.LoadedImageBase), 0), (nameof(Data.ReadyToRunInfo.NumImportSections), 4), (nameof(Data.ReadyToRunInfo.ImportSections), 8)), + (nameof(Data.ReadyToRunInfo.LoadedImageBase), 0), (nameof(Data.ReadyToRunInfo.NumImportSections), 4), (nameof(Data.ReadyToRunInfo.ImportSections), 8), + (nameof(Data.ReadyToRunInfo.CompositeInfo), 12)), }; if (methodDescMap) {