diff --git a/docs/design/datacontracts/PlatformMetadata.md b/docs/design/datacontracts/PlatformMetadata.md new file mode 100644 index 0000000000000..299231f6b8ef7 --- /dev/null +++ b/docs/design/datacontracts/PlatformMetadata.md @@ -0,0 +1,52 @@ +# Contract PlatformMetadata + +This contract exposes properties that describe the target platform + +## APIs of contract + +```csharp + internal enum CodePointerFlags : byte + { + // Set if the target process is executing on arm32 + HasArm32ThumbBit = 0x1, + // Set if arm64e pointer authentication is used in the target process + HasArm64PtrAuth = 0x2, + } + // Returns a pointer to a structure describing platform-specific precode stubs properties + TargetPointer GetPrecodeMachineDescriptor(); + + // Returns flags describing the behavior of code pointers + CodePointerFlags GetCodePointerFlags(); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| PlatformMetadata | PrecodeMachineDescriptor | precode stub-related platform specific properties | +| PlatformMetadata | CodePointerFlags | fields describing the behavior of target code pointers | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| PlatformMetadata | pointer | address of the `PlatformMetadata` data | + +Contracts used: +| Contract Name | +| --- | +| *none* | + +```csharp +TargetPointer GetPrecodeMachineDescriptor() +{ + TargetPointer metadataAddress = _target.ReadGlobalPointer("PlatformMetadata"); + return metadataAddress + /* PlatformMetadata::PrecodeMachineDescriptor */ +} + +CodePointerFlags GetCodePointerFlags() +{ + TargetPointer metadataAddress = _target.ReadGlobalPointer("PlatformMetadata"); + return (CodePointerFlags)_target.Read(metadataAddress + /*PlatformMetadata::CodePointerFlags*/); +} +``` diff --git a/docs/design/datacontracts/PrecodeStubs.md b/docs/design/datacontracts/PrecodeStubs.md new file mode 100644 index 0000000000000..b9448ed0507f7 --- /dev/null +++ b/docs/design/datacontracts/PrecodeStubs.md @@ -0,0 +1,213 @@ +# 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 | +| --- | +| `PlatformMetadata` | + +### 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 instruction 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) + { + // We get the precode type in two phases: + // 1. Read the precode type from the intruction address. + // 2. If it's "stub", look at the stub data and get the actual precode type - it could be stub, + // but it could also be a pinvoke precode + // precode.h Precode::GetType() + byte approxPrecodeType = ReadPrecodeType(instrAddress); + byte exactPrecodeType; + if (approxPrecodeType == MachineDescriptor.StubPrecodeType) + { + // get the actual type from the StubPrecodeData + Data.StubPrecodeData stubPrecodeData = GetStubPrecodeData(instrAddress); + exactPrecodeType = stubPrecodeData.Type; + } + else + { + exactPrecodeType = approxPrecodeType; + } + + if (exactPrecodeType == MachineDescriptor.StubPrecodeType) + { + return KnownPrecodeType.Stub; + } + else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && exactPrecodeType == ndType) + { + return KnownPrecodeType.PInvokeImport; + } + else if (MachineDescriptor.FixupPrecodeType is byte fixupType && exactPrecodeType == fixupType) + { + return KnownPrecodeType.Fixup; + } + else if (MachineDescriptor.ThisPointerRetBufPrecodeType is byte thisPtrRetBufType && exactPrecodeType == thisPtrRetBufType) + { + return KnownPrecodeType.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) + } + } + + 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/contractpointerdata.cpp b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp index cf7d914e728d7..1dba7454b1bb2 100644 --- a/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp +++ b/src/coreclr/debug/runtimeinfo/contractpointerdata.cpp @@ -6,6 +6,7 @@ #include #include +#include "cdacplatformmetadata.hpp" #include "threads.h" #include "vars.hpp" diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 5f3194ed3f9cf..1c520c22005d7 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -16,6 +16,8 @@ "ExecutionManager": 1, "Loader": 1, "Object": 1, + "PlatformMetadata": 1, + "PrecodeStubs": 1, "RuntimeTypeSystem": 1, "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index bea29213783eb..a6443a730985e 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -9,6 +9,7 @@ #include "static_assert.h" #include +#include "cdacplatformmetadata.hpp" #include "methodtable.h" #include "threads.h" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 3cc3b5efe6bc4..e00ee41c4759f 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -353,6 +353,42 @@ CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, c CDAC_TYPE_FIELD(MethodDescVersioningState, /*uint8*/, Flags, cdac_data::Flags) CDAC_TYPE_END(MethodDescVersioningState) +CDAC_TYPE_BEGIN(PrecodeMachineDescriptor) +CDAC_TYPE_INDETERMINATE(PrecodeMachineDescriptor) +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)) +#ifdef HAS_NDIRECT_IMPORT_PRECODE +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, PInvokeImportPrecodeType, offsetof(PrecodeMachineDescriptor, PInvokeImportPrecodeType)) +#endif +#ifdef HAS_FIXUP_PRECODE +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, FixupPrecodeType, offsetof(PrecodeMachineDescriptor, FixupPrecodeType)) +#endif +#ifdef HAS_THISPTR_RETBUF_PRECODE +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, ThisPointerRetBufPrecodeType, offsetof(PrecodeMachineDescriptor, ThisPointerRetBufPrecodeType)) +#endif +CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint32*/, StubCodePageSize, offsetof(PrecodeMachineDescriptor, StubCodePageSize)) +CDAC_TYPE_END(PrecodeMachineDescriptor) + +CDAC_TYPE_BEGIN(PlatformMetadata) +CDAC_TYPE_INDETERMINATE(PlatformMetadata) +CDAC_TYPE_FIELD(PlatformMetadata, /*PrecodeMachineDescriptor*/, PrecodeMachineDescriptor, offsetof(CDacPlatformMetadata, precode)) +CDAC_TYPE_FIELD(PlatformMetadata, /*uint8*/, CodePointerFlags, offsetof(CDacPlatformMetadata, codePointerFlags)) +CDAC_TYPE_END(PlatformMetadata) + +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) @@ -444,6 +480,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(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 334faa6f16764..3033d20588221 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -50,6 +50,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON bundle.cpp castcache.cpp callcounting.cpp + cdacplatformmetadata.cpp ceeload.cpp class.cpp classhash.cpp diff --git a/src/coreclr/vm/cdacdata.h b/src/coreclr/vm/cdacdata.h index a16796d09d270..a0ef5deca55f3 100644 --- a/src/coreclr/vm/cdacdata.h +++ b/src/coreclr/vm/cdacdata.h @@ -8,7 +8,7 @@ // // This struct enables exposing information that is private to a class to the cDAC. For example, // if class C has private information that must be provided, declare cdac_data as a friend of C -// where D is the specialization of cdac_data that will expose the information. Then provide a +// where D is the specialization of cdac_data that will expose the information. Then provide a // specialization cdac_data with constexpr members exposing the information. // // Note: in the common case, type D will be type C. diff --git a/src/coreclr/vm/cdacplatformmetadata.cpp b/src/coreclr/vm/cdacplatformmetadata.cpp new file mode 100644 index 0000000000000..b89c90fae7ed8 --- /dev/null +++ b/src/coreclr/vm/cdacplatformmetadata.cpp @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "cdacplatformmetadata.hpp" + +#ifndef DACCESS_COMPILE +CDacPlatformMetadata g_cdacPlatformMetadata; + +void CDacPlatformMetadata::Init() +{ + PrecodeMachineDescriptor::Init(&g_cdacPlatformMetadata.precode); +#if defined(TARGET_ARM) + g_cdacPlatformMetadata.codePointerFlags = CDacCodePointerFlags::HasArm32ThumbBit; +#elif defined(TARGET_ARM64) && defined(TARGET_OSX) + // TODO set HasArm64PtrAuth if arm64e + g_cdacPlatformMetadata.codePointerFlags = CDacCodePointerFlags::None; +#else + g_cdacPlatformMetadata.codePointerFlags = CDacCodePointerFlags::None; +#endif +} + +#endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/cdacplatformmetadata.hpp b/src/coreclr/vm/cdacplatformmetadata.hpp new file mode 100644 index 0000000000000..0ee94acab1a1e --- /dev/null +++ b/src/coreclr/vm/cdacplatformmetadata.hpp @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef CDACPLATFORMMETADATA_HPP__ +#define CDACPLATFORMMETADATA_HPP__ + +#include "precode.h" + +// Cross-cutting metadata for cDAC +#ifndef DACCESS_COMPILE +enum class CDacCodePointerFlags : uint8_t +{ + None = 0, + HasArm32ThumbBit = 0x1, + HasArm64PtrAuth = 0x2, +}; + + +struct CDacPlatformMetadata +{ + PrecodeMachineDescriptor precode; + CDacCodePointerFlags codePointerFlags; + CDacPlatformMetadata() = default; + CDacPlatformMetadata(const CDacPlatformMetadata&) = delete; + CDacPlatformMetadata& operator=(const CDacPlatformMetadata&) = delete; + static void Init(); +}; + +extern CDacPlatformMetadata g_cdacPlatformMetadata; +#endif // DACCESS_COMPILE + + +#endif// CDACPLATFORMMETADATA_HPP__ diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 79cca2d6c1851..3e38d60a538fe 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -163,6 +163,7 @@ #include "jithost.h" #include "pgo.h" #include "pendingload.h" +#include "cdacplatformmetadata.hpp" #ifndef TARGET_UNIX #include "dwreport.h" @@ -624,6 +625,7 @@ void EEStartupHelper() // We cache the SystemInfo for anyone to use throughout the life of the EE. GetSystemInfo(&g_SystemInfo); + CDacPlatformMetadata::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..74111c297fc25 100644 --- a/src/coreclr/vm/precode.cpp +++ b/src/coreclr/vm/precode.cpp @@ -662,4 +662,37 @@ BOOL DoesSlotCallPrestub(PCODE pCode) return FALSE; } +void PrecodeMachineDescriptor::Init(PrecodeMachineDescriptor *dest) +{ + dest->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 + dest->ReadWidthOfPrecodeType = 2; +#else + dest->ReadWidthOfPrecodeType = 1; +#endif +#if defined(SHIFTOF_PRECODE_TYPE) + dest->ShiftOfPrecodeType = SHIFTOF_PRECODE_TYPE; +#else + dest->ShiftOfPrecodeType = 0; +#endif + + dest->InvalidPrecodeType = InvalidPrecode::Type; + dest->StubPrecodeType = StubPrecode::Type; +#ifdef HAS_NDIRECT_IMPORT_PRECODE + dest->PInvokeImportPrecodeType = NDirectImportPrecode::Type; +#endif // HAS_NDIRECT_IMPORT_PRECODE +#ifdef HAS_FIXUP_PRECODE + dest->FixupPrecodeType = FixupPrecode::Type; +#endif +#ifdef HAS_THISPTR_RETBUF_PRECODE + dest->ThisPointerRetBufPrecodeType = ThisPtrRetBufPrecode::Type; +#endif + dest->StubCodePageSize = GetStubCodePageSize(); +} + #endif // !DACCESS_COMPILE + diff --git a/src/coreclr/vm/precode.h b/src/coreclr/vm/precode.h index 22ae9b1adaf18..aca583676a20f 100644 --- a/src/coreclr/vm/precode.h +++ b/src/coreclr/vm/precode.h @@ -43,6 +43,7 @@ EXTERN_C VOID STDCALL PrecodeRemotingThunk(); #define SIZEOF_PRECODE_BASE CODE_SIZE_ALIGN #define OFFSETOF_PRECODE_TYPE 0 +#define SHIFTOF_PRECODE_TYPE 5 #elif defined(TARGET_RISCV64) @@ -438,12 +439,16 @@ class Precode { #if defined(TARGET_LOONGARCH64) assert(0 == OFFSETOF_PRECODE_TYPE); + static_assert(5 == SHIFTOF_PRECODE_TYPE, "expected shift of 5"); short type = *((short*)m_data); - type >>= 5; + type >>= SHIFTOF_PRECODE_TYPE; #elif defined(TARGET_RISCV64) assert(0 == OFFSETOF_PRECODE_TYPE); BYTE type = *((BYTE*)m_data + OFFSETOF_PRECODE_TYPE); #else +#if defined(SHIFTOF_PRECODE_TYPE) +#error "did not expect SHIFTOF_PRECODE_TYPE to be defined" +#endif BYTE type = m_data[OFFSETOF_PRECODE_TYPE]; #endif @@ -596,4 +601,41 @@ 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 +{ + uint32_t StubCodePageSize; + + 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; +#ifdef HAS_NDIRECT_IMPORT_PRECODE + uint8_t PInvokeImportPrecodeType; +#endif + +#ifdef HAS_FIXUP_PRECODE + uint8_t FixupPrecodeType; +#endif + +#ifdef HAS_THISPTR_RETBUF_PRECODE + uint8_t ThisPointerRetBufPrecodeType; +#endif + +public: + PrecodeMachineDescriptor() = default; + PrecodeMachineDescriptor(const PrecodeMachineDescriptor&) = delete; + PrecodeMachineDescriptor& operator=(const PrecodeMachineDescriptor&) = delete; + static void Init(PrecodeMachineDescriptor* dest); +}; +#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 1075c99c5c7e7..be0935515b941 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -47,4 +47,12 @@ internal abstract class ContractRegistry /// Gets an instance of the CodeVersions contract for the target. /// public abstract ICodeVersions CodeVersions { get; } + /// + /// Gets an instance of the PlatformMetadata contract for the target. + /// + public abstract IPlatformMetadata PlatformMetadata { 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/IPlatformMetadata.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPlatformMetadata.cs new file mode 100644 index 0000000000000..1e384a0642459 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IPlatformMetadata.cs @@ -0,0 +1,24 @@ +// 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 enum CodePointerFlags : byte +{ + HasArm32ThumbBit = 0x1, + HasArm64PtrAuth = 0x2, +} + +internal interface IPlatformMetadata : IContract +{ + static string IContract.Name { get; } = nameof(PlatformMetadata); + TargetPointer GetPrecodeMachineDescriptor() => throw new NotImplementedException(); + CodePointerFlags GetCodePointerFlags() => throw new NotImplementedException(); +} + +internal readonly struct PlatformMetadata : IPlatformMetadata +{ + +} 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 5b184a44ad023..2f1176152c21e 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,10 @@ public enum DataType String, MethodDesc, MethodDescChunk, + PlatformMetadata, + 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..6632082b222d4 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 PlatformMetadata = nameof(PlatformMetadata); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadataFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadataFactory.cs new file mode 100644 index 0000000000000..4c1a1b7b6bc67 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadataFactory.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 PlatformMetadataFactory : IContractFactory +{ + IPlatformMetadata IContractFactory.CreateContract(Target target, int version) + { + TargetPointer cdacMetadataAddress = target.ReadGlobalPointer(Constants.Globals.PlatformMetadata); + Data.PlatformMetadata cdacMetadata = target.ProcessedData.GetOrAdd(cdacMetadataAddress); + return version switch + { + 1 => new PlatformMetadata_1(target, cdacMetadata), + _ => default(PlatformMetadata), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs new file mode 100644 index 0000000000000..0e317fa202347 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs @@ -0,0 +1,28 @@ +// 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 partial struct PlatformMetadata_1 : IPlatformMetadata +{ + internal readonly Target _target; + private readonly Data.PlatformMetadata _cdacMetadata; + + public PlatformMetadata_1(Target target, Data.PlatformMetadata cdacMetadata) + { + _target = target; + _cdacMetadata = cdacMetadata; + } + + TargetPointer IPlatformMetadata.GetPrecodeMachineDescriptor() + { + return _cdacMetadata.PrecodeMachineDescriptor; + } + + CodePointerFlags IPlatformMetadata.GetCodePointerFlags() + { + return (CodePointerFlags)_cdacMetadata.CodePointerFlags; + } +} 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..0361f57efedfe --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubsFactory.cs @@ -0,0 +1,22 @@ +// 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) + { + IPlatformMetadata cDacMetadata = target.Contracts.PlatformMetadata; + TargetPointer precodeMachineDescriptorAddress = cDacMetadata.GetPrecodeMachineDescriptor(); + Data.PrecodeMachineDescriptor precodeMachineDescriptor = target.ProcessedData.GetOrAdd(precodeMachineDescriptorAddress); + CodePointerFlags codePointerFlags= cDacMetadata.GetCodePointerFlags(); + return version switch + { + 1 => new PrecodeStubs_1(target, precodeMachineDescriptor, codePointerFlags), + _ => 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..1357dcac66d29 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs @@ -0,0 +1,195 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct PrecodeStubs_1 : IPrecodeStubs +{ + private readonly Target _target; + private readonly CodePointerFlags _codePointerFlags; + 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) + { + // We get the precode type in two phases: + // 1. Read the precode type from the intruction address. + // 2. If it's "stub", look at the stub data and get the actual precode type - it could be stub, + // but it could also be a pinvoke precode + // precode.h Precode::GetType() + byte approxPrecodeType = ReadPrecodeType(instrAddress); + byte exactPrecodeType; + if (approxPrecodeType == MachineDescriptor.StubPrecodeType) + { + // get the actual type from the StubPrecodeData + Data.StubPrecodeData stubPrecodeData = GetStubPrecodeData(instrAddress); + exactPrecodeType = stubPrecodeData.Type; + } + else + { + exactPrecodeType = approxPrecodeType; + } + + if (exactPrecodeType == MachineDescriptor.StubPrecodeType) + { + return KnownPrecodeType.Stub; + } + else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && exactPrecodeType == ndType) + { + return KnownPrecodeType.PInvokeImport; + } + else if (MachineDescriptor.FixupPrecodeType is byte fixupType && exactPrecodeType == fixupType) + { + return KnownPrecodeType.Fixup; + } + else if (MachineDescriptor.ThisPointerRetBufPrecodeType is byte thisPtrRetBufType && exactPrecodeType == thisPtrRetBufType) + { + return KnownPrecodeType.ThisPtrRetBuf; + } + else + { + return null; + } + } + + internal TargetPointer CodePointerReadableInstrPointer(TargetCodePointer codePointer) + { + if (_codePointerFlags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) + { + return codePointer.AsTargetPointer & ~1ul; + } + if (_codePointerFlags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) + { + throw new NotImplementedException("CodePointerReadableInstrPointer for ARM64 with pointer authentication"); + } + Debug.Assert(_codePointerFlags == 0); + return codePointer.AsTargetPointer; + } + + + 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, CodePointerFlags codePointerFlags) + { + _target = target; + MachineDescriptor = precodeMachineDescriptor; + _codePointerFlags = codePointerFlags; + } + + 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/PlatformMetadata.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PlatformMetadata.cs new file mode 100644 index 0000000000000..b58055f4486e3 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PlatformMetadata.cs @@ -0,0 +1,21 @@ +// 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 PlatformMetadata : IData +{ + static PlatformMetadata IData.Create(Target target, TargetPointer address) + => new PlatformMetadata(target, address); + + public PlatformMetadata(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.PlatformMetadata); + PrecodeMachineDescriptor = address + (ulong)type.Fields[nameof(PrecodeMachineDescriptor)].Offset; + CodePointerFlags = target.Read(address + (ulong)type.Fields[nameof(CodePointerFlags)].Offset); + } + + /* Address of */ + public TargetPointer PrecodeMachineDescriptor { get; init; } + public byte CodePointerFlags { 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..ebecc22395505 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/PrecodeMachineDescriptor.cs @@ -0,0 +1,56 @@ +// 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); + 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 (type.Fields.ContainsKey(nameof(PInvokeImportPrecodeType))) + { + PInvokeImportPrecodeType = target.Read(address + (ulong)type.Fields[nameof(PInvokeImportPrecodeType)].Offset); + } + else + { + PInvokeImportPrecodeType = null; + } + if (type.Fields.ContainsKey(nameof(FixupPrecodeType))) + { + FixupPrecodeType = target.Read(address + (ulong)type.Fields[nameof(FixupPrecodeType)].Offset); + } + else + { + FixupPrecodeType = null; + } + if (type.Fields.ContainsKey(nameof(ThisPointerRetBufPrecodeType))) + { + ThisPointerRetBufPrecodeType = target.Read(address + (ulong)type.Fields[nameof(ThisPointerRetBufPrecodeType)].Offset); + } + else + { + ThisPointerRetBufPrecodeType = null; + } + StubCodePageSize = target.Read(address + (ulong)type.Fields[nameof(StubCodePageSize)].Offset); + } + + 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 byte? ThisPointerRetBufPrecodeType { get; init; } + + public uint StubCodePageSize { get; init; } +} 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 9540b31b43590..1f38f8e9d20c5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -34,6 +34,8 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IDacStreams)] = new DacStreamsFactory(), [typeof(IExecutionManager)] = new ExecutionManagerFactory(), [typeof(ICodeVersions)] = new CodeVersionsFactory(), + [typeof(IPlatformMetadata)] = new PlatformMetadataFactory(), + [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), }; configureFactories?.Invoke(_factories); } @@ -47,6 +49,8 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IDacStreams DacStreams => GetContract(); public override IExecutionManager ExecutionManager => GetContract(); public override ICodeVersions CodeVersions => GetContract(); + public override IPlatformMetadata PlatformMetadata => GetContract(); + public override IPrecodeStubs PrecodeStubs => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs new file mode 100644 index 0000000000000..1b0abf72fd5f0 --- /dev/null +++ b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs @@ -0,0 +1,345 @@ +// 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; +using System.Reflection; +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class PrecodeStubsTests +{ + // high level outline of a precode machine descriptor + public class PrecodeTestDescriptor { + public string Name { get; } + public required MockTarget.Architecture Arch { get; init; } + public bool IsThumb { get; init; } + public required int ReadWidthOfPrecodeType { get; init; } + public required int OffsetOfPrecodeType { get; init; } + public required int ShiftOfPrecodeType { get; init; } + // #if defined(TARGET_ARM64) && defined(TARGET_UNIX) + // return max(16*1024u, GetOsPageSize()); + // #elif defined(TARGET_ARM) + // return 4096; // ARM is special as the 32bit instruction set does not easily permit a 16KB offset + // #else + // return 16*1024; + // #endif + public required uint StubCodePageSize { get; init; } + + // #if defined(TARGET_AMD64) + // static const BYTE Type = 0x4C; + // static const SIZE_T CodeSize = 24; + // #elif defined(TARGET_X86) + // static const BYTE Type = 0xA1; + // static const SIZE_T CodeSize = 24; + // #elif defined(TARGET_ARM64) + // static const int Type = 0x4A; + // static const SIZE_T CodeSize = 24; + // #elif defined(TARGET_ARM) + // static const int Type = 0xFF; + // static const SIZE_T CodeSize = 12; + // #elif defined(TARGET_LOONGARCH64) + // static const int Type = 0x4; + // static const SIZE_T CodeSize = 24; + // #elif defined(TARGET_RISCV64) + // static const int Type = 0x17; + // static const SIZE_T CodeSize = 24; + // #endif // TARGET_AMD64 + public required byte StubPrecode { get; init; } + public required int StubPrecodeSize { get; init; } + public PrecodeTestDescriptor(string name) { + Name = name; + } + + internal void WritePrecodeType(int precodeType,TargetTestHelpers targetTestHelpers, Span dest) + { + if (ReadWidthOfPrecodeType == 1) + { + byte value = (byte)(((byte)precodeType & 0xff) << ShiftOfPrecodeType); + // TODO: fill in the other bits with something + targetTestHelpers.Write(dest.Slice(OffsetOfPrecodeType, 1), value); + } + else if (ReadWidthOfPrecodeType == 2) + { + ushort value = (ushort)(((ushort)precodeType & 0xff) << ShiftOfPrecodeType); + // TODO: fill in the other bits with something + targetTestHelpers.Write(dest.Slice(OffsetOfPrecodeType, 2), value); + } + else + { + throw new InvalidOperationException("Don't know how to write a precode type of width {ReadWidthOfPrecodeType}"); + } + } + } + + internal static PrecodeTestDescriptor X64TestDescriptor = new PrecodeTestDescriptor("X64") { + Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, + ReadWidthOfPrecodeType = 1, + ShiftOfPrecodeType = 0, + OffsetOfPrecodeType = 0, + StubCodePageSize = 0x4000u, // 16KiB + StubPrecode = 0x4c, + StubPrecodeSize = 24, + }; + internal static PrecodeTestDescriptor Arm64TestDescriptor = new PrecodeTestDescriptor("Arm64") { + Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, + ReadWidthOfPrecodeType = 1, + ShiftOfPrecodeType = 0, + OffsetOfPrecodeType = 0, + StubCodePageSize = 0x4000u, // 16KiB + StubPrecode = 0x4a, + StubPrecodeSize = 24, + + }; + internal static PrecodeTestDescriptor LoongArch64TestDescriptor = new PrecodeTestDescriptor("LoongArch64") { + Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = true }, + ReadWidthOfPrecodeType = 2, + ShiftOfPrecodeType = 5, + OffsetOfPrecodeType = 0, + StubCodePageSize = 0x4000u, // 16KiB + StubPrecode = 0x4, + StubPrecodeSize = 24, + }; + + internal static PrecodeTestDescriptor Arm32Thumb = new PrecodeTestDescriptor("Arm32Thumb") { + Arch = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = false }, + IsThumb = true, + ReadWidthOfPrecodeType = 1, + ShiftOfPrecodeType = 0, + OffsetOfPrecodeType = 7, + StubCodePageSize = 0x1000u, // 4KiB + StubPrecode = 0xff, + StubPrecodeSize = 12, + }; + + public static IEnumerable PrecodeTestDescriptorData() + { + var arch32le = new MockTarget.Architecture { IsLittleEndian = true, Is64Bit = false }; + var arch32be = new MockTarget.Architecture { IsLittleEndian = false, Is64Bit = false }; + var arch64be = new MockTarget.Architecture { IsLittleEndian = false, Is64Bit = true }; + + yield return new object[] { X64TestDescriptor }; + yield return new object[] { Arm64TestDescriptor }; + yield return new object[] { LoongArch64TestDescriptor }; + yield return new object[] { Arm32Thumb }; + // FIXME: maybe make these a little more exotic + yield return new object[] { new PrecodeTestDescriptor("Fake 32-bit LE") { + Arch = arch32le, + ReadWidthOfPrecodeType = 1, + ShiftOfPrecodeType = 0, + OffsetOfPrecodeType = 0, + StubCodePageSize = 0x4000u, // 16KiB + StubPrecode = 0xa1, + StubPrecodeSize = 24, + }}; + yield return new object[] { new PrecodeTestDescriptor("Fake 32-bit BE") { + Arch = arch32be, + ReadWidthOfPrecodeType = 1, + ShiftOfPrecodeType = 0, + OffsetOfPrecodeType = 0, + StubCodePageSize = 0x4000u, // 16KiB + StubPrecode = 0xa1, + StubPrecodeSize = 24, + }}; + yield return new object[] { new PrecodeTestDescriptor("Fake 64-bit BE") { + Arch = arch64be, + ReadWidthOfPrecodeType = 1, + ShiftOfPrecodeType = 0, + OffsetOfPrecodeType = 0, + StubCodePageSize = 0x4000u, // 16KiB + StubPrecode = 0xa1, + StubPrecodeSize = 24, + }}; + } + + internal struct AllocationRange + { + public ulong PrecodeDescriptorStart; + public ulong PrecodeDescriptorEnd; + // This address range will behave a little unusually. + // For testing, we will use a bump allocator to allocate the stub data, and then + // subtract the code page size to get the code address for the stub and explicitly allocate + // the code fragment + public ulong StubDataPageStart; + public ulong StubDataPageEnd; + } + + internal readonly static AllocationRange DefaultAllocationRange = new AllocationRange { + PrecodeDescriptorStart = 0x3333_1000u, + PrecodeDescriptorEnd = 0x3333_2000u, + StubDataPageStart = 0x11ee_0000u, + StubDataPageEnd = 0x11ee_4000u, + }; + + internal class PrecodeBuilder { + public readonly MockMemorySpace.Builder Builder; + public readonly MockMemorySpace.BumpAllocator PrecodeAllocator; + public readonly MockMemorySpace.BumpAllocator StubDataPageAllocator; + public readonly Dictionary? TypeInfoCache; + + public TargetPointer MachineDescriptorAddress; + public CodePointerFlags CodePointerFlags {get; private set;} + public PrecodeBuilder(MockTarget.Architecture arch) : this(DefaultAllocationRange, new MockMemorySpace.Builder(new TargetTestHelpers(arch))) { + } + public PrecodeBuilder(AllocationRange allocationRange, MockMemorySpace.Builder builder, Dictionary? typeInfoCache = null) { + Builder = builder; + PrecodeAllocator = new MockMemorySpace.BumpAllocator(allocationRange.PrecodeDescriptorStart, allocationRange.PrecodeDescriptorEnd); + StubDataPageAllocator = new MockMemorySpace.BumpAllocator(allocationRange.StubDataPageStart, allocationRange.StubDataPageEnd); + TypeInfoCache = typeInfoCache ?? CreateTypeInfoCache(Builder.TargetTestHelpers); + } + + public Dictionary CreateTypeInfoCache(TargetTestHelpers targetTestHelpers) { + var typeInfo = new Dictionary(); + AddToTypeInfoCache(typeInfo, targetTestHelpers); + return typeInfo; + } + + public void AddToTypeInfoCache(Dictionary typeInfoCache, TargetTestHelpers targetTestHelpers) { + var layout = targetTestHelpers.LayoutFields([ + (nameof(Data.PrecodeMachineDescriptor.StubCodePageSize), DataType.uint32), + (nameof(Data.PrecodeMachineDescriptor.OffsetOfPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.ReadWidthOfPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.ShiftOfPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.InvalidPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.StubPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.PInvokeImportPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.FixupPrecodeType), DataType.uint8), + (nameof(Data.PrecodeMachineDescriptor.ThisPointerRetBufPrecodeType), DataType.uint8), + ]); + typeInfoCache[DataType.PrecodeMachineDescriptor] = new Target.TypeInfo() { + Fields = layout.Fields, + Size = layout.Stride, + }; + layout = targetTestHelpers.LayoutFields([ + (nameof(Data.StubPrecodeData.Type), DataType.uint8), + (nameof(Data.StubPrecodeData.MethodDesc), DataType.pointer), + ]); + typeInfoCache[DataType.StubPrecodeData] = new Target.TypeInfo() { + Fields = layout.Fields, + Size = layout.Stride, + }; + } + + private void SetCodePointerFlags(PrecodeTestDescriptor test) + { + CodePointerFlags = default; + if (test.IsThumb) { + CodePointerFlags |= CodePointerFlags.HasArm32ThumbBit; + } + } + + public void AddCDacMetadata(PrecodeTestDescriptor descriptor) { + SetCodePointerFlags(descriptor); + var typeInfo = TypeInfoCache[DataType.PrecodeMachineDescriptor]; + var fragment = PrecodeAllocator.Allocate((ulong)typeInfo.Size, $"{descriptor.Name} Precode Machine Descriptor"); + Builder.AddHeapFragment(fragment); + MachineDescriptorAddress = fragment.Address; + Span desc = Builder.BorrowAddressRange(fragment.Address, (int)typeInfo.Size); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ReadWidthOfPrecodeType)].Offset, sizeof(byte)), (byte)descriptor.ReadWidthOfPrecodeType); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.OffsetOfPrecodeType)].Offset, sizeof(byte)), (byte)descriptor.OffsetOfPrecodeType); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.ShiftOfPrecodeType)].Offset, sizeof(byte)), (byte)descriptor.ShiftOfPrecodeType); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubCodePageSize)].Offset, sizeof(uint)), descriptor.StubCodePageSize); + Builder.TargetTestHelpers.Write(desc.Slice(typeInfo.Fields[nameof(Data.PrecodeMachineDescriptor.StubPrecodeType)].Offset, sizeof(byte)), descriptor.StubPrecode); + // FIXME: set the other fields + } + + public TargetCodePointer AddStubPrecodeEntry(string name, PrecodeTestDescriptor test, TargetPointer methodDesc) { + // TODO[cdac]: allow writing other kinds of stub precode subtypes + ulong stubCodeSize = (ulong)test.StubPrecodeSize; + var stubDataTypeInfo = TypeInfoCache[DataType.StubPrecodeData]; + MockMemorySpace.HeapFragment stubDataFragment = StubDataPageAllocator.Allocate((ulong)stubDataTypeInfo.Size, $"Stub data for {name} on {test.Name}"); + Builder.AddHeapFragment(stubDataFragment); + // allocate the code one page before the stub data + ulong stubCodeStart = stubDataFragment.Address - test.StubCodePageSize; + MockMemorySpace.HeapFragment stubCodeFragment = new MockMemorySpace.HeapFragment { + Address = stubCodeStart, + Data = new byte[stubCodeSize], + Name = $"Stub code for {name} on {test.Name} with data at 0x{stubDataFragment.Address:x}", + }; + test.WritePrecodeType(test.StubPrecode, Builder.TargetTestHelpers, stubCodeFragment.Data); + Builder.AddHeapFragment(stubCodeFragment); + + Span stubData = Builder.BorrowAddressRange(stubDataFragment.Address, (int)stubDataTypeInfo.Size); + Builder.TargetTestHelpers.Write(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData.Type)].Offset, sizeof(byte)), test.StubPrecode); + Builder.TargetTestHelpers.WritePointer(stubData.Slice(stubDataTypeInfo.Fields[nameof(Data.StubPrecodeData.MethodDesc)].Offset, Builder.TargetTestHelpers.PointerSize), methodDesc); + TargetCodePointer address = stubCodeFragment.Address; + if (test.IsThumb) { + address = new TargetCodePointer(address.Value | 1); + } + return address; + } + + public void MarkCreated() => Builder.MarkCreated(); + } + + internal class PrecodeTestTarget : TestPlaceholderTarget + { + private class TestPlatformMetadata : IPlatformMetadata + { + private readonly CodePointerFlags _codePointerFlags; + private readonly TargetPointer _precodeMachineDescriptorAddress; + public TestPlatformMetadata(CodePointerFlags codePointerFlags, TargetPointer precodeMachineDescriptorAddress) { + _codePointerFlags = codePointerFlags; + _precodeMachineDescriptorAddress = precodeMachineDescriptorAddress; + } + TargetPointer IPlatformMetadata.GetPrecodeMachineDescriptor() => _precodeMachineDescriptorAddress; + CodePointerFlags IPlatformMetadata.GetCodePointerFlags() => _codePointerFlags; + } + internal readonly TargetPointer PrecodeMachineDescriptorAddress; + // hack for this test put the precode machine descriptor at the same address as the PlatformMetadata + internal TargetPointer PlatformMetadataAddress => PrecodeMachineDescriptorAddress; + public static PrecodeTestTarget FromBuilder(PrecodeBuilder precodeBuilder) + { + precodeBuilder.MarkCreated(); + var arch = precodeBuilder.Builder.TargetTestHelpers.Arch; + ReadFromTargetDelegate reader = precodeBuilder.Builder.GetReadContext().ReadFromTarget; + var typeInfo = precodeBuilder.TypeInfoCache; + return new PrecodeTestTarget(arch, reader, precodeBuilder.CodePointerFlags, precodeBuilder.MachineDescriptorAddress, typeInfo); + } + public PrecodeTestTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, CodePointerFlags codePointerFlags, TargetPointer platformMetadataAddress, Dictionary typeInfoCache) : base(arch) { + PrecodeMachineDescriptorAddress = platformMetadataAddress; + SetTypeInfoCache(typeInfoCache); + SetDataCache(new DefaultDataCache(this)); + SetDataReader(reader); + IContractFactory precodeFactory = new PrecodeStubsFactory(); + SetContracts(new TestRegistry() { + CDacMetadataContract = new (() => new TestPlatformMetadata(codePointerFlags, PrecodeMachineDescriptorAddress)), + PrecodeStubsContract = new (() => precodeFactory.CreateContract(this, 1)), + + }); + } + + public override TargetPointer ReadGlobalPointer (string name) + { + if (name == Constants.Globals.PlatformMetadata) { + return PlatformMetadataAddress; + } + return base.ReadGlobalPointer(name); + } + } + + [Theory] + [MemberData(nameof(PrecodeTestDescriptorData))] + public void TestPrecodeStubPrecodeExpectedMethodDesc(PrecodeTestDescriptor test) + { + var builder = new PrecodeBuilder(test.Arch); + builder.AddCDacMetadata(test); + + TargetPointer expectedMethodDesc = new TargetPointer(0xeeee_eee0u); // arbitrary + TargetCodePointer stub1 = builder.AddStubPrecodeEntry("Stub 1", test, expectedMethodDesc); + + var target = PrecodeTestTarget.FromBuilder(builder); + Assert.NotNull(target); + + var precodeContract = target.Contracts.PrecodeStubs; + + Assert.NotNull(precodeContract); + + var actualMethodDesc = precodeContract.GetMethodDescFromStubAddress(stub1); + Assert.Equal(expectedMethodDesc, actualMethodDesc); + + + } +} diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 1c4304904b8fd..eba171463ef17 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -251,6 +251,8 @@ internal void WritePointer(Span dest, ulong value) } } + internal void WriteNUInt(Span dest, TargetNUInt targetNUInt) => WritePointer(dest, targetNUInt.Value); + internal TargetPointer ReadPointer(ReadOnlySpan src) { if (Arch.Is64Bit) diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 0bc04c7da53b2..743882ad04229 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -211,6 +211,8 @@ public TestRegistry() { } internal Lazy? DacStreamsContract { get; set; } internal Lazy ExecutionManagerContract { get; set; } internal Lazy? CodeVersionsContract { get; set; } + internal Lazy? CDacMetadataContract { 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(); @@ -221,6 +223,8 @@ public TestRegistry() { } public override Contracts.IDacStreams DacStreams => DacStreamsContract.Value ?? throw new NotImplementedException(); public override Contracts.IExecutionManager ExecutionManager => ExecutionManagerContract.Value ?? throw new NotImplementedException(); public override Contracts.ICodeVersions CodeVersions => CodeVersionsContract.Value ?? throw new NotImplementedException(); + public override Contracts.IPlatformMetadata PlatformMetadata => CDacMetadataContract.Value ?? throw new NotImplementedException(); + public override Contracts.IPrecodeStubs PrecodeStubs => PrecodeStubsContract.Value ?? throw new NotImplementedException(); } // a data cache that throws NotImplementedException for all methods,