From 8ba56d9d3111d793940d5c71eab2aa97507e12bb Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 15:38:15 -0400 Subject: [PATCH 1/2] [cdac] PrecodeStubs contract * move PrecodeStubs to a separate contract * remove instance field initialization from PrecodeMachineDescriptor * PrecodeMachineDescriptor::Init() will initialize g_PrecodeMachineDescriptor * fix thumb flag logic * add FixupPrecodeData * checkpoint: StubPrecodeData, StubCodePageSize * checkpoint: PrecodeMachineDescriptor and KnownPrecodeType --- docs/design/datacontracts/PrecodeStubs.md | 230 ++++++++++++++++++ src/coreclr/debug/runtimeinfo/contracts.jsonc | 1 + .../debug/runtimeinfo/datadescriptor.h | 27 ++ src/coreclr/vm/ceemain.cpp | 1 + src/coreclr/vm/precode.cpp | 50 ++++ src/coreclr/vm/precode.h | 36 +++ .../ContractRegistry.cs | 4 + .../Contracts/IPrecodeStubs.cs | 17 ++ .../DataType.cs | 3 + .../Constants.cs | 1 + .../Contracts/PrecodeStubsFactory.cs | 20 ++ .../Contracts/PrecodeStubs_1.cs | 173 +++++++++++++ .../Data/FixupPrecodeData.cs | 18 ++ .../Data/PrecodeMachineDescriptor.cs | 43 ++++ .../Data/StubPrecodeData.cs | 20 ++ .../CachingContractRegistry.cs | 2 + .../cdacreader/tests/TestPlaceholderTarget.cs | 2 + 17 files changed, 648 insertions(+) create mode 100644 docs/design/datacontracts/PrecodeStubs.md create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubsFactory.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FixupPrecodeData.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs diff --git a/docs/design/datacontracts/PrecodeStubs.md b/docs/design/datacontracts/PrecodeStubs.md new file mode 100644 index 0000000000000..9f1de75dbd6de --- /dev/null +++ b/docs/design/datacontracts/PrecodeStubs.md @@ -0,0 +1,230 @@ +# Contract PrecodeStubs + +This contract provides support for examining [precode](../coreclr/botr/method-descriptor.md#precode): small fragments of code used to implement temporary entry points and an efficient wrapper for stubs. + +## APIs of contract + +```csharp + // Gets a pointer to the MethodDesc for a given stub entrypoint + TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| PrecodeMachineDescriptor | OffsetOfPrecodeType | See `ReadPrecodeType` | +| PrecodeMachineDescriptor | ShiftOfPrecodeType | See `ReadPrecodeType` | +| PrecodeMachineDescriptor | ReadWidthOfPrecodeType | See `ReadPrecodeType` | +| PrecodeMachineDescriptor | StubCodePageSize | Size of a precode code page (in bytes) | +| PrecodeMachineDescriptor | CodePointerToInstrPointerMask | mask to apply to code pointers to get an address (see arm32 note) +| PrecodeMachineDescriptor | StubPrecodeType | precode sort byte for stub precodes | +| PrecodeMachineDescriptor | HasPInvokeImportPrecode | 1 if platform supports PInvoke precode stubs | +| PrecodeMachineDescriptor | PInvokeImportPrecodeType| precode sort byte for PInvoke precode stubs, if supported | +| PrecodeMachineDescriptor | HasFixupPrecode | 1 if platform supports fixup precode stubs | +| PrecodeMachineDescriptor | FixupPrecodeType| precode sort byte for fixup precode stubs, if supported | +| StubPrecodeData | MethodDesc | pointer to the MethodDesc associated with this stub precode | +| StubPrecodeData | Type | precise sort of stub precode | +| FixupPrecodeData | MethodDesc | pointer to the MethodDesc associated with this fixup precode | + +arm32 note: the `CodePointerToInstrPointerMask` is used to convert IP values that may include an arm Thumb bit (for example extracted from disassembling a call instruction or from a snapshot of the registers) into an address. On other architectures applying the mask is a no-op. + + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| PrecodeMachineDescriptor | pointer | address of the `PrecodeMachineDescriptor` data | + +Contracts used: +| Contract Name | +| --- | +| *none* | + +### Determining the precode type + +An initial approximation of the precode type relies on a particular pattern at a known offset from the precode entrypoint. +The precode type is expected to be encoded as an immediate. On some platforms the value is spread over multiple instructon bytes and may need to be right-shifted. + +``` + private byte ReadPrecodeType(TargetPointer instrPointer) + { + if (MachineDescriptor.ReadWidthOfPrecodeType == 1) + { + byte precodeType = _target.Read(instrPointer + MachineDescriptor.OffsetOfPrecodeType); + return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType); + } + else if (MachineDescriptor.ReadWidthOfPrecodeType == 2) + { + ushort precodeType = _target.Read(instrPointer + MachineDescriptor.OffsetOfPrecodeType); + return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType); + } + else + { + throw new InvalidOperationException($"Invalid precode type width {MachineDescriptor.ReadWidthOfPrecodeType}"); + } + } +``` + +After the initial precode type is determined, for stub precodes a refined precode type is extracted from the stub precode data. + +```csharp + private KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrAddress) + { + // precode.h Precode::GetType() + byte precodeType = ReadPrecodeType(instrAddress); + if (precodeType == MachineDescriptor.StubPrecodeType) + { + // get the actual type from the StubPrecodeData + Data.StubPrecodeData stubPrecodeData = GetStubPrecodeData(instrAddress); + precodeType = stubPrecodeData.Type; + } + + if (precodeType == MachineDescriptor.StubPrecodeType) + { + return KnownPrecodeType.Stub; + } + else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && precodeType == ndType) + { + return KnownPrecodeType.PInvokeImport; + } + else if (MachineDescriptor.FixupPrecodeType is byte fixupType && precodeType == fixupType) + { + return KnownPrecodeType.Fixup; + } + // TODO: ThisPtrRetBuf + else + { + return null; + } + } +``` + +### `MethodDescFromStubAddress` + +```csharp + internal enum KnownPrecodeType + { + Stub = 1, + PInvokeImport, // also known as NDirectImport in the runtime + Fixup, + ThisPtrRetBuf, + } + + internal abstract class ValidPrecode + { + public TargetPointer InstrPointer { get; } + public KnownPrecodeType PrecodeType { get; } + + protected ValidPrecode(TargetPointer instrPointer, KnownPrecodeType precodeType) + { + InstrPointer = instrPointer; + PrecodeType = precodeType; + } + + internal abstract TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); + + } + + internal class StubPrecode : ValidPrecode + { + internal StubPrecode(TargetPointer instrPointer, KnownPrecodeType type = KnownPrecodeType.Stub) : base(instrPointer, type) { } + + internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + TargetPointer stubPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize; + return target.ReadPointer (stubPrecodeDataAddress + /* offset of StubPrecodeData.MethodDesc */ ); + } + } + + internal sealed class PInvokeImportPrecode : StubPrecode + { + internal PInvokeImportPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.PInvokeImport) { } + } + + internal sealed class FixupPrecode : ValidPrecode + { + internal FixupPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.Fixup) { } + internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + TargetPointer fixupPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize; + return target.ReadPointer (fixupPrecodeDataAddress + /* offset of FixupPrecodeData.MethodDesc */); + } + } + + internal sealed class ThisPtrRetBufPrecode : ValidPrecode + { + internal ThisPtrRetBufPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.ThisPtrRetBuf) { } + + internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + throw new NotImplementedException(); // TODO(cdac) + } + } + + private KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrAddress) + { + // precode.h Precode::GetType() + byte precodeType = ReadPrecodeType(instrAddress); + if (precodeType == MachineDescriptor.StubPrecodeType) + { + // get the actual type from the StubPrecodeData + precodeType = target.Read(instrAddress + MachineDescriptor.CodePageSize + /* offset of StubPrecodeData.Type */); + } + + if (precodeType == MachineDescriptor.StubPrecodeType) + { + return KnownPrecodeType.Stub; + } + else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && precodeType == ndType) + { + return KnownPrecodeType.PInvokeImport; + } + else if (MachineDescriptor.FixupPrecodeType is byte fixupType && precodeType == fixupType) + { + return KnownPrecodeType.Fixup; + } + // TODO: ThisPtrRetBuf + else + { + return null; + } + } + + internal TargetPointer CodePointerReadableInstrPointer(TargetCodePointer codePointer) + { + // Mask off the thumb bit, if we're on arm32, to get the actual instruction pointer + ulong instrPointer = (ulong)codePointer.AsTargetPointer & MachineDescriptor.CodePointerToInstrPointerMask.Value; + return new TargetPointer(instrPointer); + } + + + internal ValidPrecode GetPrecodeFromEntryPoint(TargetCodePointer entryPoint) + { + TargetPointer instrPointer = CodePointerReadableInstrPointer(entryPoint); + if (IsAlignedInstrPointer(instrPointer) && TryGetKnownPrecodeType(instrPointer) is KnownPrecodeType precodeType) + { + switch (precodeType) + { + case KnownPrecodeType.Stub: + return new StubPrecode(instrPointer); + case KnownPrecodeType.Fixup: + return new FixupPrecode(instrPointer); + case KnownPrecodeType.PInvokeImport: + return new PInvokeImportPrecode(instrPointer); + case KnownPrecodeType.ThisPtrRetBuf: + return new ThisPtrRetBufPrecode(instrPointer); + default: + break; + } + } + throw new InvalidOperationException($"Invalid precode type 0x{instrPointer:x16}"); + } + + TargetPointer IPrecodeStubs.GetMethodDescFromStubAddress(TargetCodePointer entryPoint) + { + ValidPrecode precode = GetPrecodeFromEntryPoint(entryPoint); + + return precode.GetMethodDesc(_target, MachineDescriptor); + } +``` diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 15a1aece96cee..6c410628f6213 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -15,6 +15,7 @@ "ExecutionManager": 1, "Loader": 1, "Object": 1, + "PrecodeStubs": 1, "RuntimeTypeSystem": 1, "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 5165f148263af..825da65b4db64 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -344,6 +344,32 @@ CDAC_TYPE_BEGIN(CodePointer) CDAC_TYPE_SIZE(sizeof(PCODE)) CDAC_TYPE_END(CodePointer) +CDAC_TYPE_BEGIN(PrecodeMachineDescriptor) +CDAC_TYPE_INDETERMINATE(PrecodeMachineDescriptor) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uintptr*/, CodePointerToInstrPointerMask, offsetof(PrecodeMachineDescriptor, CodePointerToInstrPointerMask)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, ReadWidthOfPrecodeType, offsetof(PrecodeMachineDescriptor, ReadWidthOfPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, ShiftOfPrecodeType, offsetof(PrecodeMachineDescriptor, ShiftOfPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, OffsetOfPrecodeType, offsetof(PrecodeMachineDescriptor, OffsetOfPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, InvalidPrecodeType, offsetof(PrecodeMachineDescriptor, InvalidPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, StubPrecodeType, offsetof(PrecodeMachineDescriptor, StubPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, HasPInvokeImportPrecode, offsetof(PrecodeMachineDescriptor, HasPInvokeImportPrecode)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, PInvokeImportPrecodeType, offsetof(PrecodeMachineDescriptor, PInvokeImportPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, HasFixupPrecode, offsetof(PrecodeMachineDescriptor, HasFixupPrecode)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, FixupPrecodeType, offsetof(PrecodeMachineDescriptor, FixupPrecodeType)) +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint32*/, StubCodePageSize, offsetof(PrecodeMachineDescriptor, StubCodePageSize)) +CDAC_TYPE_END(PrecodeMachineDescriptor) + +CDAC_TYPE_BEGIN(StubPrecodeData) +CDAC_TYPE_INDETERMINATE(StubPrecodeData) +CDAC_TYPE_FIELD(StubPrecodeData, /*pointer*/, MethodDesc, offsetof(StubPrecodeData, MethodDesc)) +CDAC_TYPE_FIELD(StubPrecodeData, /*uint8*/, Type, offsetof(StubPrecodeData, Type)) +CDAC_TYPE_END(StubPrecodeData) + +CDAC_TYPE_BEGIN(FixupPrecodeData) +CDAC_TYPE_INDETERMINATE(FixupPrecodeData) +CDAC_TYPE_FIELD(FixupPrecodeData, /*pointer*/, MethodDesc, offsetof(FixupPrecodeData, MethodDesc)) +CDAC_TYPE_END(FixupPrecodeData) + CDAC_TYPE_BEGIN(RangeSectionMap) CDAC_TYPE_INDETERMINATE(RangeSectionMap) CDAC_TYPE_FIELD(RangeSectionMap, /*pointer*/, TopLevelData, cdac_data::TopLevelData) @@ -421,6 +447,7 @@ CDAC_GLOBAL_POINTER(SyncTableEntries, &::g_pSyncTable) CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) +CDAC_GLOBAL_POINTER(PrecodeMachineDescriptor, &::g_PrecodeMachineDescriptor) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 79cca2d6c1851..06fa54b896e4d 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -624,6 +624,7 @@ void EEStartupHelper() // We cache the SystemInfo for anyone to use throughout the life of the EE. GetSystemInfo(&g_SystemInfo); + PrecodeMachineDescriptor::Init(); // Set callbacks so that LoadStringRC knows which language our // threads are in so that it can return the proper localized string. diff --git a/src/coreclr/vm/precode.cpp b/src/coreclr/vm/precode.cpp index 4dbc3e4394834..d8c69a03cb9f7 100644 --- a/src/coreclr/vm/precode.cpp +++ b/src/coreclr/vm/precode.cpp @@ -662,4 +662,54 @@ BOOL DoesSlotCallPrestub(PCODE pCode) return FALSE; } +PrecodeMachineDescriptor g_PrecodeMachineDescriptor; + +void PrecodeMachineDescriptor::Init() +{ +#ifndef TARGET_ARM + g_PrecodeMachineDescriptor.CodePointerToInstrPointerMask = ~0; +#else + // mask off the thumb bit + g_PrecodeMachineDescriptor.CodePointerToInstrPointerMask = ~1; +#endif + g_PrecodeMachineDescriptor.OffsetOfPrecodeType = OFFSETOF_PRECODE_TYPE; + // cDAC will do (where N = 8*ReadWidthOfPrecodeType): + // uintN_t PrecodeType = *(uintN_t*)(pPrecode + OffsetOfPrecodeType); + // PrecodeType >>= ShiftOfPrecodeType; + // return (byte)PrecodeType; +#ifdef TARGET_LOONGARCH64 + g_PrecodeMachineDescriptor.ReadWidthOfPrecodeType = 2; + g_PrecodeMachineDescriptor.ShiftOfPrecodeType = 5; +#else + g_PrecodeMachineDescriptor.ReadWidthOfPrecodeType = 1; + g_PrecodeMachineDescriptor.ShiftOfPrecodeType = 0; +#endif + + g_PrecodeMachineDescriptor.InvalidPrecodeType = InvalidPrecode::Type; + g_PrecodeMachineDescriptor.StubPrecodeType = StubPrecode::Type; +#ifdef HAS_NDIRECT_IMPORT_PRECODE + g_PrecodeMachineDescriptor.HasPInvokeImportPrecode = 1; + g_PrecodeMachineDescriptor.PInvokeImportPrecodeType = NDirectImportPrecode::Type; +#else + g_PrecodeMachineDescriptor.HasPInvokeImportPrecode = 0; + g_PrecodeMachineDescriptor.PInvokeImportPrecodeType = 0; +#endif // HAS_NDIRECT_IMPORT_PRECODE +#ifdef HAS_FIXUP_PRECODE + g_PrecodeMachineDescriptor.HasFixupPrecode = 1; + g_PrecodeMachineDescriptor.FixupPrecodeType = FixupPrecode::Type; +#else + g_PrecodeMachineDescriptor.HasFixupPrecode = 0; + g_PrecodeMachineDescriptor.FixupPrecodeType = 0; +#endif // HAS_FIXUP_PRECODE +#ifdef HAS_THISPTR_RETBUF_PRECODE + g_PrecodeMachineDescriptor.HasThisPtrRetBufPrecode = 1; + g_PrecodeMachineDescriptor.HasThisPointerRetBufPrecodeType = ThisPtrRetBufPrecode::Type; +#else + g_PrecodeMachineDescriptor.HasThisPtrRetBufPrecode = 0; + g_PrecodeMachineDescriptor.HasThisPointerRetBufPrecodeType = 0; +#endif // HAS_THISPTR_RETBUF_PRECODE + g_PrecodeMachineDescriptor.StubCodePageSize = GetStubCodePageSize(); +} + #endif // !DACCESS_COMPILE + diff --git a/src/coreclr/vm/precode.h b/src/coreclr/vm/precode.h index 22ae9b1adaf18..b1f7ff9e118b6 100644 --- a/src/coreclr/vm/precode.h +++ b/src/coreclr/vm/precode.h @@ -596,4 +596,40 @@ static_assert_no_msg(NDirectImportPrecode::Type != ThisPtrRetBufPrecode::Type); static_assert_no_msg(sizeof(Precode) <= sizeof(NDirectImportPrecode)); static_assert_no_msg(sizeof(Precode) <= sizeof(FixupPrecode)); static_assert_no_msg(sizeof(Precode) <= sizeof(ThisPtrRetBufPrecode)); + +#ifndef DACCESS_COMPILE +// A summary of the precode layout for diagnostic purposes +struct PrecodeMachineDescriptor +{ + uintptr_t CodePointerToInstrPointerMask; + uint8_t OffsetOfPrecodeType; + // cDAC will do (where N = 8*ReadWidthOfPrecodeType): + // uintN_t PrecodeType = *(uintN_t*)(pPrecode + OffsetOfPrecodeType); + // PrecodeType >>= ShiftOfPrecodeType; + // return (byte)PrecodeType; + uint8_t ReadWidthOfPrecodeType; + uint8_t ShiftOfPrecodeType; + + uint8_t InvalidPrecodeType; + uint8_t StubPrecodeType; + uint8_t HasPInvokeImportPrecode; + uint8_t PInvokeImportPrecodeType; + + uint8_t HasFixupPrecode; + uint8_t FixupPrecodeType; + + uint8_t HasThisPtrRetBufPrecode; + uint8_t HasThisPointerRetBufPrecodeType; + + uint32_t StubCodePageSize; +public: + PrecodeMachineDescriptor() = default; + PrecodeMachineDescriptor(const PrecodeMachineDescriptor&) = delete; + PrecodeMachineDescriptor& operator=(const PrecodeMachineDescriptor&) = delete; + static void Init(); +}; + +extern PrecodeMachineDescriptor g_PrecodeMachineDescriptor; +#endif //DACCESS_COMPILE + #endif // __PRECODE_H__ diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 3d641f9ab0ef7..8e0aa0eb501b5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -43,4 +43,8 @@ internal abstract class ContractRegistry /// Gets an instance of the ExecutionManager contract for the target. /// public abstract IExecutionManager ExecutionManager { get; } + /// + /// Gets an instance of the PrecodeStubs contract for the target. + /// + public abstract IPrecodeStubs PrecodeStubs { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs new file mode 100644 index 0000000000000..e693d65afd2a8 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPrecodeStubs.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal interface IPrecodeStubs : IContract +{ + static string IContract.Name { get; } = nameof(PrecodeStubs); + TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint) => throw new NotImplementedException(); +} + +internal readonly struct PrecodeStubs : IPrecodeStubs +{ + +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 37dae1a357bef..c3536b6b1959a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -44,6 +44,9 @@ public enum DataType String, MethodDesc, MethodDescChunk, + PrecodeMachineDescriptor, + StubPrecodeData, + FixupPrecodeData, Array, SyncBlock, SyncTableEntry, diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index b5862cf785b9c..cf3827e4d27bb 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -41,5 +41,6 @@ internal static class Globals internal const string ExecutionManagerCodeRangeMapAddress = nameof(ExecutionManagerCodeRangeMapAddress); internal const string StubCodeBlockLast = nameof(StubCodeBlockLast); + internal const string PrecodeMachineDescriptor = nameof(PrecodeMachineDescriptor); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubsFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubsFactory.cs new file mode 100644 index 0000000000000..638601afea60e --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubsFactory.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class PrecodeStubsFactory : IContractFactory +{ + IPrecodeStubs IContractFactory.CreateContract(Target target, int version) + { + TargetPointer precodeMachineDescriptorAddress = target.ReadGlobalPointer(Constants.Globals.PrecodeMachineDescriptor); + Data.PrecodeMachineDescriptor precodeMachineDescriptor = target.ProcessedData.GetOrAdd(precodeMachineDescriptorAddress); + return version switch + { + 1 => new PrecodeStubs_1(target, precodeMachineDescriptor), + _ => default(PrecodeStubs), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs new file mode 100644 index 0000000000000..d2ae91a1f27a9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs @@ -0,0 +1,173 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct PrecodeStubs_1 : IPrecodeStubs +{ + private readonly Target _target; + internal readonly Data.PrecodeMachineDescriptor MachineDescriptor; + + internal enum KnownPrecodeType + { + Stub = 1, + PInvokeImport, // also known as NDirectImport in the runtime + Fixup, + ThisPtrRetBuf, + } + + internal abstract class ValidPrecode + { + public TargetPointer InstrPointer { get; } + public KnownPrecodeType PrecodeType { get; } + + protected ValidPrecode(TargetPointer instrPointer, KnownPrecodeType precodeType) + { + InstrPointer = instrPointer; + PrecodeType = precodeType; + } + + internal abstract TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor); + + } + + internal class StubPrecode : ValidPrecode + { + internal StubPrecode(TargetPointer instrPointer, KnownPrecodeType type = KnownPrecodeType.Stub) : base(instrPointer, type) { } + + internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + TargetPointer stubPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize; + Data.StubPrecodeData stubPrecodeData = target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); + return stubPrecodeData.MethodDesc; + } + } + + internal sealed class PInvokeImportPrecode : StubPrecode + { + internal PInvokeImportPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.PInvokeImport) { } + } + + internal sealed class FixupPrecode : ValidPrecode + { + internal FixupPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.Fixup) { } + internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + TargetPointer fixupPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize; + Data.FixupPrecodeData fixupPrecodeData = target.ProcessedData.GetOrAdd(fixupPrecodeDataAddress); + return fixupPrecodeData.MethodDesc; + + } + } + + internal sealed class ThisPtrRetBufPrecode : ValidPrecode // FIXME: is this a StubPrecode? + { + internal ThisPtrRetBufPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.ThisPtrRetBuf) { } + + internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + throw new NotImplementedException(); // TODO(cdac) + } + } + + private bool IsAlignedInstrPointer(TargetPointer instrPointer) => _target.IsAlignedToPointerSize(instrPointer); + + private byte ReadPrecodeType(TargetPointer instrPointer) + { + if (MachineDescriptor.ReadWidthOfPrecodeType == 1) + { + byte precodeType = _target.Read(instrPointer + MachineDescriptor.OffsetOfPrecodeType); + return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType); + } + else if (MachineDescriptor.ReadWidthOfPrecodeType == 2) + { + ushort precodeType = _target.Read(instrPointer + MachineDescriptor.OffsetOfPrecodeType); + return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType); + } + else + { + throw new InvalidOperationException($"Invalid precode type width {MachineDescriptor.ReadWidthOfPrecodeType}"); + } + } + + private Data.StubPrecodeData GetStubPrecodeData(TargetPointer stubInstrPointer) + { + TargetPointer stubPrecodeDataAddress = stubInstrPointer + MachineDescriptor.StubCodePageSize; + return _target.ProcessedData.GetOrAdd(stubPrecodeDataAddress); + } + + private KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrAddress) + { + // precode.h Precode::GetType() + byte precodeType = ReadPrecodeType(instrAddress); + if (precodeType == MachineDescriptor.StubPrecodeType) + { + // get the actual type from the StubPrecodeData + Data.StubPrecodeData stubPrecodeData = GetStubPrecodeData(instrAddress); + precodeType = stubPrecodeData.Type; + } + + if (precodeType == MachineDescriptor.StubPrecodeType) + { + return KnownPrecodeType.Stub; + } + else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && precodeType == ndType) + { + return KnownPrecodeType.PInvokeImport; + } + else if (MachineDescriptor.FixupPrecodeType is byte fixupType && precodeType == fixupType) + { + return KnownPrecodeType.Fixup; + } + // TODO: ThisPtrRetBuf + else + { + return null; + } + } + + internal TargetPointer CodePointerReadableInstrPointer(TargetCodePointer codePointer) + { + // Mask off the thumb bit, if we're on arm32, to get the actual instruction pointer + ulong instrPointer = (ulong)codePointer.AsTargetPointer & MachineDescriptor.CodePointerToInstrPointerMask.Value; + return new TargetPointer(instrPointer); + } + + + internal ValidPrecode GetPrecodeFromEntryPoint(TargetCodePointer entryPoint) + { + TargetPointer instrPointer = CodePointerReadableInstrPointer(entryPoint); + if (IsAlignedInstrPointer(instrPointer) && TryGetKnownPrecodeType(instrPointer) is KnownPrecodeType precodeType) + { + switch (precodeType) + { + case KnownPrecodeType.Stub: + return new StubPrecode(instrPointer); + case KnownPrecodeType.Fixup: + return new FixupPrecode(instrPointer); + case KnownPrecodeType.PInvokeImport: + return new PInvokeImportPrecode(instrPointer); + case KnownPrecodeType.ThisPtrRetBuf: + return new ThisPtrRetBufPrecode(instrPointer); + default: + break; + } + } + throw new InvalidOperationException($"Invalid precode type 0x{instrPointer:x16}"); + } + public PrecodeStubs_1(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor) + { + _target = target; + MachineDescriptor = precodeMachineDescriptor; + } + + TargetPointer IPrecodeStubs.GetMethodDescFromStubAddress(TargetCodePointer entryPoint) + { + ValidPrecode precode = GetPrecodeFromEntryPoint(entryPoint); + + return precode.GetMethodDesc(_target, MachineDescriptor); + } + +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FixupPrecodeData.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FixupPrecodeData.cs new file mode 100644 index 0000000000000..811508cc4354a --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/FixupPrecodeData.cs @@ -0,0 +1,18 @@ +// 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; + +internal sealed class FixupPrecodeData : IData +{ + static FixupPrecodeData IData.Create(Target target, TargetPointer address) + => new FixupPrecodeData(target, address); + + public FixupPrecodeData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.FixupPrecodeData); + MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + } + + public TargetPointer MethodDesc { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs new file mode 100644 index 0000000000000..6861e21e8ac31 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs @@ -0,0 +1,43 @@ +// 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; + +internal sealed class PrecodeMachineDescriptor : IData +{ + static PrecodeMachineDescriptor IData.Create(Target target, TargetPointer address) + => new PrecodeMachineDescriptor(target, address); + + public PrecodeMachineDescriptor(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.PrecodeMachineDescriptor); + CodePointerToInstrPointerMask = target.ReadNUInt(address + (ulong)type.Fields[nameof(CodePointerToInstrPointerMask)].Offset); + OffsetOfPrecodeType = target.Read(address + (ulong)type.Fields[nameof(OffsetOfPrecodeType)].Offset); + ReadWidthOfPrecodeType = target.Read(address + (ulong)type.Fields[nameof(ReadWidthOfPrecodeType)].Offset); + ShiftOfPrecodeType = target.Read(address + (ulong)type.Fields[nameof(ShiftOfPrecodeType)].Offset); + InvalidPrecodeType = target.Read(address + (ulong)type.Fields[nameof(InvalidPrecodeType)].Offset); + StubPrecodeType = target.Read(address + (ulong)type.Fields[nameof(StubPrecodeType)].Offset); + if (target.Read(address + (ulong)type.Fields[nameof(HasPInvokeImportPrecode)].Offset) == 1) + { + PInvokeImportPrecodeType = target.Read(address + (ulong)type.Fields[nameof(PInvokeImportPrecodeType)].Offset); + } + if (target.Read(address + (ulong)type.Fields[nameof(HasFixupPrecode)].Offset) == 1) + { + FixupPrecodeType = target.Read(address + (ulong)type.Fields[nameof(FixupPrecodeType)].Offset); + } + StubCodePageSize = target.Read(address + (ulong)type.Fields[nameof(StubCodePageSize)].Offset); + } + + public TargetNUInt CodePointerToInstrPointerMask { get; init; } + public byte OffsetOfPrecodeType { get; init; } + public byte ReadWidthOfPrecodeType { get; init; } + public byte ShiftOfPrecodeType { get; init; } + public byte InvalidPrecodeType { get; init; } + public byte StubPrecodeType { get; init; } + public byte? PInvokeImportPrecodeType { get; init; } + public byte? FixupPrecodeType { get; init; } + + public uint StubCodePageSize { get; init; } + private const string HasPInvokeImportPrecode = nameof(HasPInvokeImportPrecode); + private const string HasFixupPrecode = nameof(HasFixupPrecode); +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs new file mode 100644 index 0000000000000..320795b41d542 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/StubPrecodeData.cs @@ -0,0 +1,20 @@ +// 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; + +internal sealed class StubPrecodeData : IData +{ + static StubPrecodeData IData.Create(Target target, TargetPointer address) + => new StubPrecodeData(target, address); + + public StubPrecodeData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.StubPrecodeData); + MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + Type = target.Read(address + (ulong)type.Fields[nameof(Type)].Offset); + } + + public TargetPointer MethodDesc { get; init; } + public byte Type { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 61ba6a2850221..7fa2a00045539 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -33,6 +33,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IRuntimeTypeSystem)] = new RuntimeTypeSystemFactory(), [typeof(IDacStreams)] = new DacStreamsFactory(), [typeof(IExecutionManager)] = new ExecutionManagerFactory(), + [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), }; configureFactories?.Invoke(_factories); } @@ -45,6 +46,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); public override IDacStreams DacStreams => GetContract(); public override IExecutionManager ExecutionManager => GetContract(); + public override IPrecodeStubs PrecodeStubs => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index afabf8a914b59..2314a1106426a 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -205,6 +205,7 @@ public TestRegistry() { } internal Lazy? RuntimeTypeSystemContract { get; set; } internal Lazy? DacStreamsContract { get; set; } internal Lazy ExecutionManagerContract { get; set; } + internal Lazy? PrecodeStubsContract { get; set; } public override Contracts.IException Exception => ExceptionContract.Value ?? throw new NotImplementedException(); public override Contracts.ILoader Loader => LoaderContract.Value ?? throw new NotImplementedException(); @@ -214,6 +215,7 @@ public TestRegistry() { } public override Contracts.IRuntimeTypeSystem RuntimeTypeSystem => RuntimeTypeSystemContract.Value ?? throw new NotImplementedException(); public override Contracts.IDacStreams DacStreams => DacStreamsContract.Value ?? throw new NotImplementedException(); public override Contracts.IExecutionManager ExecutionManager => ExecutionManagerContract.Value ?? throw new NotImplementedException(); + public override Contracts.IPrecodeStubs PrecodeStubs => PrecodeStubsContract.Value ?? throw new NotImplementedException(); } // a data cache that throws NotImplementedException for all methods, From cecb380bc02d9e2d9d593dca71466b7588ca3e06 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 16:33:03 -0400 Subject: [PATCH 2/2] WIP: start adding PrecodeStubs test infrastructure --- .../cdacreader/tests/PrecodeStubsTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/native/managed/cdacreader/tests/PrecodeStubsTests.cs diff --git a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs new file mode 100644 index 0000000000000..35d4f63340ab3 --- /dev/null +++ b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using System.Collections.Generic; +using System; +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class PrecodeStubsTests +{ + // high level outline of a precode machine descriptor + public class PrecodeTestDescriptor { + public string Name { get; } + public PrecodeTestDescriptor(string name) { + Name = name; + } + } + + internal static PrecodeTestDescriptor X64TestDescriptor = new PrecodeTestDescriptor("X64"); + internal static PrecodeTestDescriptor Arm64TestDescriptor = new PrecodeTestDescriptor("Arm64"); + internal static PrecodeTestDescriptor LoongArch64TestDescriptor = new PrecodeTestDescriptor("LoongArch64"); + internal static PrecodeTestDescriptor GenericTestDescriptor = new PrecodeTestDescriptor("Generic"); + + public static IEnumerable PrecodeTestDescriptorData() + { + foreach (object[] inp in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)inp[0]; + if (arch.Is64Bit && arch.IsLittleEndian) + { + yield return new object[] { arch, X64TestDescriptor }; + yield return new object[] { arch, Arm64TestDescriptor }; + yield return new object[] { arch, LoongArch64TestDescriptor }; + } + yield return new object[] { arch, GenericTestDescriptor}; + } + } + + [Theory] + [MemberData(nameof(PrecodeTestDescriptorData))] + public void TestPrecodeStubs(MockTarget.Architecture arch, PrecodeTestDescriptor precodeTestDescriptor) + { + // TODO: make a PrecodeMachineDescriptor based on the precodeTestDescriptor and then make some stubs + // and ask them for their MethodDesc + // TODO: finish me + var target = new TestPlaceholderTarget(arch); + Assert.NotNull(target); + Assert.NotNull(precodeTestDescriptor); + } +}