diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md new file mode 100644 index 0000000000000..efd838181cbbb --- /dev/null +++ b/docs/design/datacontracts/CodeVersions.md @@ -0,0 +1,149 @@ +# Contract CodeVersions + +This contract encapsulates support for [code versioning](../features/code-versioning.md) in the runtime. + +## APIs of contract + +```csharp +internal struct NativeCodeVersionHandle +{ + // no public constructors + internal readonly TargetPointer MethodDescAddress; + internal readonly TargetPointer CodeVersionNodeAddress; + internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) + { + if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null) + { + throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null"); + } + MethodDescAddress = methodDescAddress; + CodeVersionNodeAddress = codeVersionNodeAddress; + } + + internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null); + public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null; +} +``` + +```csharp + // Return a handle to the version of the native code that includes the given instruction pointer + public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip); + // Return a handle to the active version of the native code for a given method descriptor + public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc); + + // returns true if the given method descriptor supports multiple code versions + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); + + // Return the instruction pointer corresponding to the start of the given native code version + public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| MethodDescVersioningState | ? | ? | +| NativeCodeVersionNode | ? | ? | +| ILCodeVersioningState | ? | ? | + + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | + +Contracts used: +| Contract Name | +| --- | +| ExecutionManager | +| Loader | +| RuntimeTypeSystem | + +### Finding the start of a specific native code version + +```csharp + NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) + { + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip); + if (!info.HasValue) + { + return NativeCodeVersionHandle.Invalid; + } + TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value); + if (methodDescAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (!rts.IsVersionable(md)) + { + return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + } + else + { + TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value); + return GetSpecificNativeCodeVersion(md, startAddress); + } + } + + private NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress) + { + TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); + if (methodDescVersioningStateAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd(methodDescVersioningStateAddress); + // CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc, PCODE startAddress) + return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) => + { + return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; + }); + } + + private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) + { + // NativeCodeVersion::Next, heavily inlined + TargetPointer currentAddress = versioningState.NativeCodeVersionNode; + while (currentAddress != TargetPointer.Null) + { + Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); + if (predicate(current)) + { + return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); + } + currentAddress = current.Next; + } + return NativeCodeVersionHandle.Invalid; + } +``` + +### Finding the active native code version of a method descriptor + +```csharp + NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) + { + // CodeVersionManager::GetActiveILCodeVersion + // then ILCodeVersion::GetActiveNativeCodeVersion + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(typeHandle); + uint methodDefToken = rts.GetMethodToken(md); + ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); + if (!methodDefActiveVersion.IsValid) + { + return NativeCodeVersionHandle.Invalid; + } + return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); + } +``` + +**FIXME** + +### Determining whether a method descriptor supports code versioning + +**TODO** diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md new file mode 100644 index 0000000000000..276dfa6cb11ae --- /dev/null +++ b/docs/design/datacontracts/ExecutionManager.md @@ -0,0 +1,57 @@ +# Contract ExecutionManager + +This contract + + +## APIs of contract + +```csharp +internal struct EECodeInfoHandle +{ + // no public constructor + public readonly TargetPointer Address; + internal EECodeInfoHandle(TargetPointer address) => Address = address; +} +``` + +```csharp + // Collect execution engine info for a code block that includes the given instruction pointer. + // Return a handle for the information, or null if an owning code block cannot be found. + EECodeInfoHandle? GetEECodeInfoHandle(TargetCodePointer ip); + // Get the method descriptor corresponding to the given code block + TargetPointer GetMethodDesc(EECodeInfoHandle codeInfoHandle) => throw new NotImplementedException(); + // Get the instruction pointer address of the start of the code block + TargetCodePointer GetStartAddress(EECodeInfoHandle codeInfoHandle) => throw new NotImplementedException(); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| RangeSectionMap | TopLevelData | pointer to the outermost RangeSection | +| RangeSectionFragment| ? | ? | +| RangeSection | ? | ? | +| RealCodeHeader | ? | ? | +| HeapList | ? | ? | + + + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| ExecutionManagerCodeRangeMapAddress | TargetPointer | Pointer to the global RangeSectionMap +| StubCodeBlockLast | uint8 | Maximum sentinel code header value indentifying a stub code block + +Contracts used: +| Contract Name | +| --- | + +```csharp +``` + +**TODO** Methods + +### NibbleMap + +**TODO** diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index d0fe390b5ad39..be9a563bb1f56 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -38,6 +38,9 @@ TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetThunkHeap(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); ModuleLookupTables GetLookupTables(ModuleHandle handle); +TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags); +bool IsCollectibleLoaderAllocator(ModuleHandle handle); + ``` ## Version 1 @@ -58,6 +61,10 @@ Data descriptors used: | `Module` | `TypeDefToMethodTableMap` | Mapping table | | `Module` | `TypeRefToMethodTableMap` | Mapping table | | `ModuleLookupMap` | `TableData` | Start of the mapping table's data | +| `ModuleLookupMap` | `SupportedFlagsMask` | Mask for flag bits on lookup map entries | +| `ModuleLookupMap` | `Count` | Number of TargetPointer sized entries in this section of the map | +| `ModuleLookupMap` | `Next` | Pointer to next ModuleLookupMap segment for this map +| `LoaderAllocator` | `IsCollectible` | Flag indicating if this is loader allocator may be collected ``` csharp ModuleHandle GetModuleHandle(TargetPointer modulePointer) @@ -110,3 +117,5 @@ ModuleLookupTables GetLookupTables(ModuleHandle handle) Module::MethodDefToILCodeVersioningState */)); } ``` + +**TODO* pseudocode for IsCollectibleLoaderAllocator and LookupTableMap element lookup 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/docs/design/datacontracts/ReJIT.md b/docs/design/datacontracts/ReJIT.md new file mode 100644 index 0000000000000..dd93a2e2e5ab1 --- /dev/null +++ b/docs/design/datacontracts/ReJIT.md @@ -0,0 +1,37 @@ +# Contract ReJIT + +This contract encapsulates support for [ReJIT](../features/code-versioning.md) in the runtime. + +## APIs of contract + +```csharp +bool IsEnabled(); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| ProfControlBlock | GlobalEventMask | an `ICorProfiler` `COR_PRF_MONITOR` value | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +|ProfilerControlBlock | TargetPointer | pointer to the `ProfControlBlock` | + +Contracts used: +| Contract Name | +| --- | + +```csharp +bool IsEnabled() +{ + TargetPointer address = target.ReadGlobalPointer("ProfilerControlBlock"); + ulong globalEventMask = target.Read(address + /* ProfControlBlock::GlobalEventMask offset*/); + bool profEnabledReJIT = (GlobalEventMask & (ulong)COR_PRF_MONITOR.COR_PRF_ENABLE_REJIT) != 0; + bool clrConfigEnabledReJit = /* host process does not have environment variable DOTNET_ProfAPI_ReJitOnAttach set to 0 */; + // See https://github.com/dotnet/runtime/issues/106148 + return profEnabledReJIT || clrConfigEnabledReJIT; +} +``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 8accd2e6dc68f..6018020f370d7 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -138,6 +138,28 @@ partial interface IRuntimeTypeSystem : IContract // Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime // A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsILStub(MethodDescHandle methodDesc); + + // Return true if a MethodDesc is in a collectible module + public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc); + + // Return true if a MethodDesc supports mulitiple code versions + public virtual bool IsVersionable(MethodDescHandle methodDesc); + + // Return a pointer to the IL versioning state of the MethodDesc + public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc); + + // Return the MethodTable slot number of the MethodDesc + public virtual ushort GetSlotNumber(MethodDescHandle methodDesc); + + // Return true if the MethodDesc has space associated with it for storing a pointer to a code block + public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc); + + // Return the address of the space that stores a pointer to a code block associated with the MethodDesc + public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc); + + // Get an instruction pointer that can be called to cause the MethodDesc to be executed + public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc); + } ``` @@ -607,6 +629,7 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t | `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. | | `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` | +**TODO** MethodDesc code pointers additions In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table will typically have multiple chunks. There are subkinds of MethodDescs at runtime of varying sizes (but the sizes must be mutliples of `MethodDescAlignment`) and each chunk contains method descriptors of the same size. @@ -631,6 +654,15 @@ We depend on the following data descriptors: | `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` | | `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc | +**TODO** MethodDesc code pointers additions + +The contract depends on the following other contracts + +| Contract | +| --- | +| Loader | +| ReJIT | +| CodeVersions | And the following enumeration definitions @@ -821,4 +853,4 @@ And the various apis are implemented with the following algorithms return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub); } ``` -**TODO(cdac)** +**TODO(cdac)** additional code pointers methods on MethodDesc diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 1b886e300471f..c867d21d40996 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1089,7 +1089,17 @@ HRESULT ClrDataAccess::GetMethodDescData( _ASSERTE(methodDescData->wSlotNumber == mdDataLocal.wSlotNumber); _ASSERTE(methodDescData->NativeCodeAddr == mdDataLocal.NativeCodeAddr); _ASSERTE(methodDescData->AddressOfNativeCodeSlot == mdDataLocal.AddressOfNativeCodeSlot); - //TODO[cdac]: assert the rest of mdDataLocal contains the same info as methodDescData + _ASSERTE(methodDescData->MethodDescPtr == mdDataLocal.MethodDescPtr); + _ASSERTE(methodDescData->MethodTablePtr == mdDataLocal.MethodTablePtr); + _ASSERTE(methodDescData->ModulePtr == mdDataLocal.ModulePtr); + _ASSERTE(methodDescData->MDToken == mdDataLocal.MDToken); + _ASSERTE(methodDescData->GCInfo == mdDataLocal.GCInfo); + _ASSERTE(methodDescData->GCStressCodeCopy == mdDataLocal.GCStressCodeCopy); + _ASSERTE(methodDescData->managedDynamicMethodObject == mdDataLocal.managedDynamicMethodObject); + _ASSERTE(methodDescData->requestedIP == mdDataLocal.requestedIP); + // TODO[cdac]: cdacreader always returns 0 currently + _ASSERTE(methodDescData->cJittedRejitVersions == 0 || methodDescData->cJittedRejitVersions == mdDataLocal.cJittedRejitVersions); + // TODO[cdac]: compare rejitDataCurrent and rejitDataRequested, too if (rgRevertedRejitData != NULL) { _ASSERTE (cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData); diff --git a/src/coreclr/debug/runtimeinfo/.editorconfig b/src/coreclr/debug/runtimeinfo/.editorconfig new file mode 100644 index 0000000000000..ce2ce7b8d1ec5 --- /dev/null +++ b/src/coreclr/debug/runtimeinfo/.editorconfig @@ -0,0 +1,2 @@ +[contracts.jsonc] +indent_size = 2 diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 499a284cbcd70..3483a6a5b733a 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -9,11 +9,15 @@ // cdac-build-tool can take multiple "-c contract_file" arguments // so to conditionally include contracts, put additional contracts in a separate file { + "CodeVersions": 1, "DacStreams": 1, "EcmaMetadata" : 1, "Exception": 1, + "ExecutionManager": 1, "Loader": 1, "Object": 1, + "PrecodeStubs": 1, + "ReJIT": 1, "RuntimeTypeSystem": 1, "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index cb7bb72627090..06eecf83a3390 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -233,8 +233,16 @@ CDAC_TYPE_END(Module) CDAC_TYPE_BEGIN(ModuleLookupMap) CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, TableData, offsetof(LookupMapBase, pTable)) +CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, Next, offsetof(LookupMapBase, pNext)) +CDAC_TYPE_FIELD(ModuleLookupMap, /*uint32*/, Count, offsetof(LookupMapBase, dwCount)) +CDAC_TYPE_FIELD(ModuleLookupMap, /*nuint*/, SupportedFlagsMask, offsetof(LookupMapBase, supportedFlags)) CDAC_TYPE_END(ModuleLookupMap) +CDAC_TYPE_BEGIN(LoaderAllocator) +CDAC_TYPE_INDETERMINATE(LoaderAllocator) +CDAC_TYPE_FIELD(LoaderAllocator, /*uint8*/, IsCollectible, cdac_data::IsCollectible) +CDAC_TYPE_END(LoaderAllocator) + // RuntimeTypeSystem CDAC_TYPE_BEGIN(MethodTable) @@ -300,11 +308,13 @@ CDAC_TYPE_FIELD(DynamicMetadata, /*inline byte array*/, Data, cdac_data::ChunkIndex) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Slot, cdac_data::Slot) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags3AndTokenRemainder, cdac_data::Flags3AndTokenRemainder) +CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, EntryPointFlags, cdac_data::EntryPointFlags) +CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, CodeData, cdac_data::CodeData) CDAC_TYPE_END(MethodDesc) CDAC_TYPE_BEGIN(MethodDescChunk) @@ -335,6 +345,105 @@ CDAC_TYPE_INDETERMINATE(DynamicMethodDesc) CDAC_TYPE_FIELD(DynamicMethodDesc, /*pointer*/, MethodName, cdac_data::MethodName) CDAC_TYPE_END(DynamicMethodDesc) +CDAC_TYPE_BEGIN(CodePointer) +CDAC_TYPE_SIZE(sizeof(PCODE)) +CDAC_TYPE_END(CodePointer) + +CDAC_TYPE_BEGIN(MethodDescCodeData) +CDAC_TYPE_INDETERMINATE(MethodDescCodeData) +CDAC_TYPE_FIELD(MethodDescCodeData, /*CodePointer*/, TemporaryEntryPoint, offsetof(MethodDescCodeData,TemporaryEntryPoint)) +CDAC_TYPE_FIELD(MethodDescCodeData, /*pointer*/, VersioningState, offsetof(MethodDescCodeData,VersioningState)) +CDAC_TYPE_END(MethodDescCodeData) + +CDAC_TYPE_BEGIN(MethodDescVersioningState) +CDAC_TYPE_INDETERMINATE(MethodDescVersioningState) +CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, cdac_data::NativeCodeVersionNode) +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, /*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) +CDAC_TYPE_END(RangeSectionMap) + +CDAC_TYPE_BEGIN(RangeSectionFragment) +CDAC_TYPE_INDETERMINATE(RangeSectionFragment) +CDAC_TYPE_FIELD(RangeSectionFragment, /*pointer*/, RangeBegin, cdac_data::RangeSectionFragment::RangeBegin) +CDAC_TYPE_FIELD(RangeSectionFragment, /*pointer*/, RangeEndOpen, cdac_data::RangeSectionFragment::RangeEndOpen) +CDAC_TYPE_FIELD(RangeSectionFragment, /*pointer*/, RangeSection, cdac_data::RangeSectionFragment::RangeSection) +CDAC_TYPE_FIELD(RangeSectionFragment, /*pointer*/, Next, cdac_data::RangeSectionFragment::Next) +CDAC_TYPE_END(RangeSectionFragment) + +CDAC_TYPE_BEGIN(RangeSection) +CDAC_TYPE_INDETERMINATE(RangeSection) +CDAC_TYPE_FIELD(RangeSection, /*pointer*/, RangeBegin, cdac_data::RangeBegin) +CDAC_TYPE_FIELD(RangeSection, /*pointer*/, RangeEndOpen, cdac_data::RangeEndOpen) +CDAC_TYPE_FIELD(RangeSection, /*pointer*/, NextForDelete, cdac_data::NextForDelete) +CDAC_TYPE_FIELD(RangeSection, /*pointer*/, JitManager, cdac_data::JitManager) +CDAC_TYPE_FIELD(RangeSection, /*int32_t*/, Flags, cdac_data::Flags) +CDAC_TYPE_FIELD(RangeSection, /*pointer*/, HeapList, cdac_data::HeapList) +CDAC_TYPE_FIELD(RangeSection, /*pointer*/, R2RModule, cdac_data::R2RModule) +CDAC_TYPE_END(RangeSection) + +CDAC_TYPE_BEGIN(RealCodeHeader) +CDAC_TYPE_INDETERMINATE(RealCodeHeader) +CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc)) +CDAC_TYPE_END(RealCodeHeader) + +CDAC_TYPE_BEGIN(HeapList) +CDAC_TYPE_FIELD(HeapList, /*pointer*/, Next, offsetof(HeapList, hpNext)) +CDAC_TYPE_FIELD(HeapList, /*pointer*/, StartAddress, offsetof(HeapList, startAddress)) +CDAC_TYPE_FIELD(HeapList, /*pointer*/, EndAddress, offsetof(HeapList, endAddress)) +CDAC_TYPE_FIELD(HeapList, /*pointer*/, MapBase, offsetof(HeapList, mapBase)) +CDAC_TYPE_FIELD(HeapList, /*pointer*/, HeaderMap, offsetof(HeapList, pHdrMap)) +CDAC_TYPE_END(HeapList) + +CDAC_TYPE_BEGIN(ILCodeVersioningState) +CDAC_TYPE_INDETERMINATE(ILCodeVersioningState) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, Node, cdac_data::Node) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionKind, cdac_data::ActiveVersionKind) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionNode, cdac_data::ActiveVersionNode) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionModule, cdac_data::ActiveVersionModule) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionMethodDef, cdac_data::ActiveVersionMethodDef) +CDAC_TYPE_END(ILCodeVersioningState) + +CDAC_TYPE_BEGIN(NativeCodeVersionNode) +CDAC_TYPE_INDETERMINATE(NativeCodeVersionNode) +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, Next, cdac_data::Next) +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data::MethodDesc) +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, NativeCode, cdac_data::NativeCode) +CDAC_TYPE_END(NativeCodeVersionNode) + +CDAC_TYPE_BEGIN(ProfControlBlock) +CDAC_TYPE_FIELD(ProfControlBlock, /*uint64*/, GlobalEventMask, offsetof(ProfControlBlock, globalEventMask)) +CDAC_TYPE_END(ProfControlBlock) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -364,6 +473,7 @@ CDAC_GLOBAL(DirectorySeparator, uint8, (uint8_t)DIRECTORY_SEPARATOR_CHAR_A) CDAC_GLOBAL(MethodDescAlignment, uint64, MethodDesc::ALIGNMENT) CDAC_GLOBAL(ObjectHeaderSize, uint64, OBJHEADER_SIZE) CDAC_GLOBAL(SyncBlockValueToObjectOffset, uint16, OBJHEADER_SIZE - cdac_data::SyncBlockValue) +CDAC_GLOBAL(StubCodeBlockLast, uint8, STUB_CODE_BLOCK_LAST) CDAC_GLOBAL_POINTER(ArrayBoundsZero, cdac_data::ArrayBoundsZero) CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass) CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) @@ -373,6 +483,9 @@ CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass) CDAC_GLOBAL_POINTER(SyncTableEntries, &::g_pSyncTable) CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) +CDAC_GLOBAL_POINTER(PrecodeMachineDescriptor, &::g_PrecodeMachineDescriptor) +CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) +CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 38c47a9749684..c7085026fb814 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/codeman.h b/src/coreclr/vm/codeman.h index 0ff86927b6a58..ad69a91c98096 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -555,6 +555,8 @@ class Range { return end; } + + template friend struct ::cdac_data; }; struct RangeSection @@ -626,6 +628,19 @@ struct RangeSection RangeSection* _pRangeSectionNextForDelete = NULL; // Used for adding to the cleanup list + + template friend struct ::cdac_data; +}; + +template<> struct cdac_data +{ + static constexpr size_t RangeBegin = offsetof(RangeSection, _range.begin); + static constexpr size_t RangeEndOpen = offsetof(RangeSection, _range.end); + static constexpr size_t NextForDelete = offsetof(RangeSection, _pRangeSectionNextForDelete); + static constexpr size_t JitManager = offsetof(RangeSection, _pjit); + static constexpr size_t Flags = offsetof(RangeSection, _flags); + static constexpr size_t HeapList = offsetof(RangeSection, _pHeapList); + static constexpr size_t R2RModule = offsetof(RangeSection, _pR2RModule); }; enum class RangeSectionLockState @@ -835,7 +850,7 @@ class RangeSectionMap { // Upgrade to non-collectible #ifdef _DEBUG - TADDR initialValue = + TADDR initialValue = #endif InterlockedCompareExchangeT(&_ptr, ptr - 1, ptr); assert(initialValue == ptr || initialValue == (ptr - 1)); @@ -951,7 +966,7 @@ class RangeSectionMap auto levelNew = static_castVolatileLoad(NULL))[0])>(AllocateLevel()); if (levelNew == NULL) return NULL; - + if (!outerLevel->Install(levelNew, collectible)) { // Handle race where another thread grew the table @@ -1017,7 +1032,7 @@ class RangeSectionMap auto rangeSectionL3 = rangeSectionL3Ptr->VolatileLoadWithoutBarrier(pLockState); if (rangeSectionL3 == NULL) return NULL; - + auto rangeSectionL2Ptr = &((*rangeSectionL3)[EffectiveBitsForLevel(address, 3)]); if (level == 2) return rangeSectionL2Ptr; @@ -1071,7 +1086,7 @@ class RangeSectionMap // Account for the range not starting at the beginning of a last level fragment rangeSize += pRangeSection->_range.RangeStart() & (bytesAtLastLevel - 1); - + uintptr_t fragmentCount = ((rangeSize - 1) / bytesAtLastLevel) + 1; return fragmentCount; } @@ -1314,7 +1329,7 @@ class RangeSectionMap else { // Since the fragment linked lists are sorted such that the collectible ones are always after the non-collectible ones, this should never happen. - assert(!seenCollectibleRangeList); + assert(!seenCollectibleRangeList); } #endif entryInMapToUpdate = &(entryInMapToUpdate->VolatileLoadWithoutBarrier(pLockState))->pRangeSectionFragmentNext; @@ -1355,7 +1370,7 @@ class RangeSectionMap if (foundMeaningfulValue) break; - + // This level is completely empty. Free it, and then null out the pointer to it. pointerToLevelData->Uninstall(); #if defined(__GNUC__) @@ -1432,6 +1447,21 @@ class RangeSectionMap } #endif// DACCESS_COMPILE + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t TopLevelData = offsetof(RangeSectionMap, _topLevelData); + + struct RangeSectionFragment + { + static constexpr size_t RangeBegin = offsetof(RangeSectionMap::RangeSectionFragment, _range.begin); + static constexpr size_t RangeEndOpen = offsetof(RangeSectionMap::RangeSectionFragment, _range.end); + static constexpr size_t RangeSection = offsetof(RangeSectionMap::RangeSectionFragment, pRangeSection); + static constexpr size_t Next = offsetof(RangeSectionMap::RangeSectionFragment, pRangeSectionFragmentNext); + }; }; struct RangeSectionMapData @@ -2246,8 +2276,18 @@ class ExecutionManager JumpStubBlockHeader * m_pBlocks; JumpStubTable m_Table; }; + + template friend struct ::cdac_data; }; +#ifndef DACCESS_COMPILE +template<> +struct cdac_data +{ + static constexpr void* const CodeRangeMapAddress = (void*)&ExecutionManager::g_codeRangeMap.Data[0]; +}; +#endif + inline CodeHeader * EEJitManager::GetCodeHeader(const METHODTOKEN& MethodToken) { LIMITED_METHOD_DAC_CONTRACT; @@ -2552,6 +2592,8 @@ class EECodeInfo // Simple helper to return a pointer to the UNWIND_INFO given the offset to the unwind info. UNWIND_INFO * GetUnwindInfoHelper(ULONG unwindInfoOffset); #endif // TARGET_AMD64 + + template friend struct ::cdac_data; }; #include "codeman.inl" diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index faf0157883686..1eec36c7d68fe 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -248,8 +248,10 @@ class ILCodeVersion mdMethodDef m_methodDef; } m_synthetic; }; -}; + // cDAC accesses fields via ILCodeVersioningState.m_activeVersion + template friend struct ::cdac_data; +}; class NativeCodeVersionNode { @@ -316,6 +318,16 @@ class NativeCodeVersionNode IsActiveChildFlag = 1 }; DWORD m_flags; + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Next = offsetof(NativeCodeVersionNode, m_pNextMethodDescSibling); + static constexpr size_t MethodDesc = offsetof(NativeCodeVersionNode, m_pMethodDesc); + static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode); }; class NativeCodeVersionCollection @@ -473,6 +485,15 @@ class MethodDescVersioningState BYTE m_flags; NativeCodeVersionId m_nextId; PTR_NativeCodeVersionNode m_pFirstVersionNode; + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t NativeCodeVersionNode = offsetof(MethodDescVersioningState, m_pFirstVersionNode); + static constexpr size_t Flags = offsetof(MethodDescVersioningState, m_flags); }; class ILCodeVersioningState @@ -505,6 +526,18 @@ class ILCodeVersioningState PTR_ILCodeVersionNode m_pFirstVersionNode; PTR_Module m_pModule; mdMethodDef m_methodDef; + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Node = offsetof(ILCodeVersioningState, m_pFirstVersionNode); + static constexpr size_t ActiveVersionKind = offsetof(ILCodeVersioningState, m_activeVersion.m_storageKind); + static constexpr size_t ActiveVersionNode = offsetof(ILCodeVersioningState, m_activeVersion.m_pVersionNode); + static constexpr size_t ActiveVersionModule = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_pModule); + static constexpr size_t ActiveVersionMethodDef = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_methodDef); }; class CodeVersionManager diff --git a/src/coreclr/vm/loaderallocator.cpp b/src/coreclr/vm/loaderallocator.cpp index dde9db0f76a00..0102bf33089e0 100644 --- a/src/coreclr/vm/loaderallocator.cpp +++ b/src/coreclr/vm/loaderallocator.cpp @@ -67,7 +67,7 @@ LoaderAllocator::LoaderAllocator(bool collectible) : m_pLastUsedCodeHeap = NULL; m_pLastUsedDynamicCodeHeap = NULL; m_pJumpStubCache = NULL; - m_IsCollectible = collectible; + m_IsCollectible = collectible ? 1 : 0; m_pMarshalingData = NULL; diff --git a/src/coreclr/vm/loaderallocator.hpp b/src/coreclr/vm/loaderallocator.hpp index 2bce73a85cc24..429d54faf6aa3 100644 --- a/src/coreclr/vm/loaderallocator.hpp +++ b/src/coreclr/vm/loaderallocator.hpp @@ -47,7 +47,7 @@ class CodeRangeMapRangeList : public RangeList VPTR_VTABLE_CLASS(CodeRangeMapRangeList, RangeList) #if defined(DACCESS_COMPILE) || !defined(TARGET_WINDOWS) - CodeRangeMapRangeList() : + CodeRangeMapRangeList() : _RangeListRWLock(COOPERATIVE_OR_PREEMPTIVE, LOCK_TYPE_DEFAULT), _rangeListType(STUB_CODE_BLOCK_UNKNOWN), _id(NULL), @@ -55,7 +55,7 @@ class CodeRangeMapRangeList : public RangeList {} #endif - CodeRangeMapRangeList(StubCodeBlockKind rangeListType, bool collectible) : + CodeRangeMapRangeList(StubCodeBlockKind rangeListType, bool collectible) : _RangeListRWLock(COOPERATIVE_OR_PREEMPTIVE, LOCK_TYPE_DEFAULT), _rangeListType(rangeListType), _id(NULL), @@ -84,7 +84,7 @@ class CodeRangeMapRangeList : public RangeList _ASSERTE(id == _id || _id == NULL); _id = id; - // Grow the array first, so that a failure cannot break the + // Grow the array first, so that a failure cannot break the RangeSection::RangeSectionFlags flags = RangeSection::RANGE_SECTION_RANGELIST; if (_collectible) @@ -92,7 +92,7 @@ class CodeRangeMapRangeList : public RangeList _starts.Preallocate(_starts.GetCount() + 1); flags = (RangeSection::RangeSectionFlags)(flags | RangeSection::RANGE_SECTION_COLLECTIBLE); } - + ExecutionManager::AddCodeRange(start, end, ExecutionManager::GetEEJitManager(), flags, this); if (_collectible) @@ -145,7 +145,7 @@ class CodeRangeMapRangeList : public RangeList // This implementation only works for the case where the RangeList is used in a single LoaderHeap _ASSERTE(start == NULL); _ASSERTE(end == NULL); - + SimpleWriteLockHolder lh(&_RangeListRWLock); _ASSERTE(id == _id || (_id == NULL && _starts.IsEmpty())); @@ -171,7 +171,7 @@ class CodeRangeMapRangeList : public RangeList return FALSE; if ((pRS->_flags & RangeSection::RANGE_SECTION_RANGELIST) == 0) return FALSE; - + return (pRS->_pRangeList == this); } @@ -334,7 +334,7 @@ class LoaderAllocator bool m_fTerminated; bool m_fMarked; int m_nGCCount; - bool m_IsCollectible; + BYTE m_IsCollectible; // Pre-allocated blocks of heap for collectible assemblies. Will be set to NULL as soon as it is // used. See code in GetVSDHeapInitialBlock and GetCodeHeapInitialBlock @@ -570,7 +570,7 @@ class LoaderAllocator DispatchToken GetDispatchToken(UINT32 typeId, UINT32 slotNumber); virtual LoaderAllocatorID* Id() =0; - BOOL IsCollectible() { WRAPPER_NO_CONTRACT; return m_IsCollectible; } + BOOL IsCollectible() { WRAPPER_NO_CONTRACT; return m_IsCollectible != 0; } // This function may only be called while the runtime is suspended // As it does not lock around access to a RangeList @@ -864,8 +864,16 @@ class LoaderAllocator virtual void UnregisterDependentHandleToNativeObjectFromCleanup(LADependentHandleToNativeObject *dependentHandle) {}; virtual void CleanupDependentHandlesToNativeObjects() {}; #endif + + template friend struct ::cdac_data; }; // class LoaderAllocator +template<> +struct cdac_data +{ + static constexpr size_t IsCollectible = offsetof(LoaderAllocator, m_IsCollectible); +}; + typedef VPTR(LoaderAllocator) PTR_LoaderAllocator; extern "C" BOOL QCALLTYPE LoaderAllocator_Destroy(QCall::LoaderAllocatorHandle pLoaderAllocator); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 35882fdde5ddf..2f1bdcacfe593 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1916,6 +1916,8 @@ template<> struct cdac_data static constexpr size_t Slot = offsetof(MethodDesc, m_wSlotNumber); static constexpr size_t Flags = offsetof(MethodDesc, m_wFlags); static constexpr size_t Flags3AndTokenRemainder = offsetof(MethodDesc, m_wFlags3AndTokenRemainder); + static constexpr size_t EntryPointFlags = offsetof(MethodDesc, m_bFlags4); + static constexpr size_t CodeData = offsetof(MethodDesc, m_codeData); }; #ifndef DACCESS_COMPILE 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/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index 5353fed750344..3ca4c46326f3d 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -38,5 +38,21 @@ internal static class Globals internal const string MethodDescTokenRemainderBitCount = nameof(MethodDescTokenRemainderBitCount); internal const string DirectorySeparator = nameof(DirectorySeparator); + + internal const string PrecodeMachineDescriptor = nameof(PrecodeMachineDescriptor); + + internal const string ExecutionManagerCodeRangeMapAddress = nameof(ExecutionManagerCodeRangeMapAddress); + internal const string StubCodeBlockLast = nameof(StubCodeBlockLast); + internal const string ProfilerControlBlock = nameof(ProfilerControlBlock); + } + + internal static class EcmaMetadata + { + internal const int RowIdBitCount = 24; + internal const uint RIDMask = (1 << RowIdBitCount) - 1; + + internal static uint GetRowId(uint token) => token & RIDMask; + + internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); } } diff --git a/src/native/managed/cdacreader/src/Contracts/CodeVersions.cs b/src/native/managed/cdacreader/src/Contracts/CodeVersions.cs new file mode 100644 index 0000000000000..1dcd6732ad6f5 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/CodeVersions.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 System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal interface ICodeVersions : IContract +{ + static string IContract.Name { get; } = nameof(CodeVersions); + static IContract IContract.Create(Target target, int version) + { + return version switch + { + 1 => new CodeVersions_1(target), + _ => default(CodeVersions), + }; + } + + public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) => throw new NotImplementedException(); + public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc) => throw new NotImplementedException(); + + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException(); + + public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + +} + +internal struct NativeCodeVersionHandle +{ + // no public constructors + internal readonly TargetPointer MethodDescAddress; + internal readonly TargetPointer CodeVersionNodeAddress; + internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) + { + if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null) + { + throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null"); + } + MethodDescAddress = methodDescAddress; + CodeVersionNodeAddress = codeVersionNodeAddress; + } + + internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null); + public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null; + +} + +internal readonly struct CodeVersions : ICodeVersions +{ + // throws NotImplementedException for all methods +} diff --git a/src/native/managed/cdacreader/src/Contracts/CodeVersions_1.cs b/src/native/managed/cdacreader/src/Contracts/CodeVersions_1.cs new file mode 100644 index 0000000000000..b22d507e638b5 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/CodeVersions_1.cs @@ -0,0 +1,243 @@ +// 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 CodeVersions_1 : ICodeVersions +{ + private readonly Target _target; + + + public CodeVersions_1(Target target) + { + _target = target; + } + + NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointer ip) + { + // ExecutionManager::GetNativeCodeVersion(PCODE ip)) + // and EECodeInfo::GetNativeCodeVersion + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip); + if (!info.HasValue) + { + return NativeCodeVersionHandle.Invalid; + } + TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value); + if (methodDescAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (!rts.IsVersionable(md)) + { + return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + } + else + { + TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value); + return GetSpecificNativeCodeVersion(rts, md, startAddress); + } + } + + NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) + { + // CodeVersionManager::GetActiveILCodeVersion + // then ILCodeVersion::GetActiveNativeCodeVersion + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(typeHandle); + uint methodDefToken = rts.GetMethodToken(md); + ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); + if (!methodDefActiveVersion.IsValid) + { + return NativeCodeVersionHandle.Invalid; + } + return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); + } + bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddress) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (rts.IsDynamicMethod(md)) + return false; + if (rts.IsCollectibleMethod(md)) + return false; + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle mt = rts.GetTypeHandle(mtAddr); + TargetPointer modAddr = rts.GetModule(mt); + ILoader loader = _target.Contracts.Loader; + ModuleHandle mod = loader.GetModuleHandle(modAddr); + ModuleFlags modFlags = loader.GetFlags(mod); + if (modFlags.HasFlag(ModuleFlags.EditAndContinue)) + return false; + return true; + } + + TargetCodePointer ICodeVersions.GetNativeCode(NativeCodeVersionHandle codeVersionHandle) + { + if (codeVersionHandle.MethodDescAddress != TargetPointer.Null) + { + MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(codeVersionHandle.MethodDescAddress); + return _target.Contracts.RuntimeTypeSystem.GetNativeCode(md); + } + else if (codeVersionHandle.CodeVersionNodeAddress != TargetPointer.Null) + { + throw new NotImplementedException(); // TODO[cdac]: get native code from NativeCodeVersionNode + } + else + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + } + + internal struct ILCodeVersionHandle + { + internal readonly TargetPointer Module; + internal uint MethodDefinition; + internal readonly TargetPointer ILCodeVersionNode; + internal readonly uint RejitId; + + internal ILCodeVersionHandle(TargetPointer module, uint methodDef, TargetPointer ilCodeVersionNodeAddress) + { + Module = module; + MethodDefinition = methodDef; + ILCodeVersionNode = ilCodeVersionNodeAddress; + if (Module != TargetPointer.Null && ILCodeVersionNode != TargetPointer.Null) + { + throw new ArgumentException("Both MethodDesc and ILCodeVersionNode cannot be non-null"); + + } + if (Module != TargetPointer.Null && MethodDefinition == 0) + { + throw new ArgumentException("MethodDefinition must be non-zero if Module is non-null"); + } + } + public static ILCodeVersionHandle Invalid => new ILCodeVersionHandle(TargetPointer.Null, 0, TargetPointer.Null); + public bool IsValid => Module != TargetPointer.Null || ILCodeVersionNode != TargetPointer.Null; + } + + [Flags] + internal enum MethodDescVersioningStateFlags : byte + { + IsDefaultVersionActiveChildFlag = 0x4 + }; + + + private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, TargetCodePointer startAddress) + { + TargetPointer methodDescVersioningStateAddress = rts.GetMethodDescVersioningState(md); + if (methodDescVersioningStateAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd(methodDescVersioningStateAddress); + // CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc, PCODE startAddress) + return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) => + { + return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; + }); + } + + private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) + { + // NativeCodeVersion::Next, heavily inlined + TargetPointer currentAddress = versioningState.NativeCodeVersionNode; + while (currentAddress != TargetPointer.Null) + { + Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); + if (predicate(current)) + { + return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); + } + currentAddress = current.Next; + } + return NativeCodeVersionHandle.Invalid; + } + + + private enum ILCodeVersionKind + { + Unknown = 0, + Explicit = 1, // means Node is set + Synthetic = 2, // means Module and Token are set + } + private static ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState) + { + switch ((ILCodeVersionKind)ilState.ActiveVersionKind) + { + case ILCodeVersionKind.Explicit: + return new ILCodeVersionHandle(module: TargetPointer.Null, methodDef: 0, ilState.ActiveVersionNode); + case ILCodeVersionKind.Synthetic: + case ILCodeVersionKind.Unknown: + return new ILCodeVersionHandle(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef, TargetPointer.Null); + default: + throw new InvalidOperationException($"Unknown ILCodeVersionKind {ilState.ActiveVersionKind}"); + } + } + + private ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition) + { + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); + TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; + TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefinition, out var _); + if (ilVersionStateAddress == TargetPointer.Null) + { + return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null); + } + Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); + return ILCodeVersionHandleFromState(ilState); + } + + private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) + { + if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null) + { + MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress); + TargetPointer versioningStateAddress = _target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); + if (versioningStateAddress == TargetPointer.Null) + { + return true; + } + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); + return (((MethodDescVersioningStateFlags)versioningState.Flags) & MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag) != 0; + } + else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) + { + // NativeCodeVersionNode::IsActiveChildVersion + // Data.NativeCodeVersionNode codeVersion = _target.ProcessedData.GetOrAdd(nativeCodeVersion.CodeVersionNodeAddress); + // return codeVersion has flag IsActive + throw new NotImplementedException(); // TODO[cdac]: IsActiveNativeCodeVersion - explicit + } + else + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + } + + private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) + { + if (methodDefActiveVersion.Module != TargetPointer.Null) + { + NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + if (IsActiveNativeCodeVersion(provisionalHandle)) + { + return provisionalHandle; + } + else + { + throw new NotImplementedException(); // TODO[cdac]: iterate through versioning state nodes + } + } + else + { + throw new NotImplementedException(); // TODO: [cdac] find explicit il code version + } + } + +} diff --git a/src/native/managed/cdacreader/src/Contracts/ExecutionManager.cs b/src/native/managed/cdacreader/src/Contracts/ExecutionManager.cs new file mode 100644 index 0000000000000..131b7bf992acf --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/ExecutionManager.cs @@ -0,0 +1,37 @@ +// 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 struct EECodeInfoHandle +{ + public readonly TargetPointer Address; + internal EECodeInfoHandle(TargetPointer address) => Address = address; +} + +internal interface IExecutionManager : IContract +{ + static string IContract.Name { get; } = nameof(ExecutionManager); + static IContract IContract.Create(Target target, int version) + { + TargetPointer executionManagerCodeRangeMapAddress = target.ReadGlobalPointer(Constants.Globals.ExecutionManagerCodeRangeMapAddress); + Data.RangeSectionMap rangeSectionMap = target.ProcessedData.GetOrAdd(executionManagerCodeRangeMapAddress); + return version switch + { + 1 => new ExecutionManager_1(target, rangeSectionMap), + _ => default(ExecutionManager), + }; + } + + EECodeInfoHandle? GetEECodeInfoHandle(TargetCodePointer ip) => throw new NotImplementedException(); + TargetPointer GetMethodDesc(EECodeInfoHandle codeInfoHandle) => throw new NotImplementedException(); + TargetCodePointer GetStartAddress(EECodeInfoHandle codeInfoHandle) => throw new NotImplementedException(); + +} + +internal readonly struct ExecutionManager : IExecutionManager +{ + +} diff --git a/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.EEJitManager.cs b/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.EEJitManager.cs new file mode 100644 index 0000000000000..8969d150f6589 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.EEJitManager.cs @@ -0,0 +1,71 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly partial struct ExecutionManager_1 : IExecutionManager +{ + private class EEJitManager : JitManager + { + private readonly NibbleMap _nibbleMap; + public EEJitManager(Target target, NibbleMap nibbleMap) : base(target) + { + _nibbleMap = nibbleMap; + } + + public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out EECodeInfo? info) + { + info = null; + // EEJitManager::JitCodeToMethodInfo + if (rangeSection.IsRangeList) + { + return false; + } + TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); + if (start == TargetPointer.Null) + { + return false; + } + Debug.Assert(start.Value <= jittedCodeAddress.Value); + TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - start.Value); + int codeHeaderOffset = Target.PointerSize; + TargetPointer codeHeaderIndirect = new TargetPointer(start - (ulong)codeHeaderOffset); + if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) + { + return false; + } + TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); + Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); + info = new EECodeInfo(jittedCodeAddress, codeHeaderOffset, relativeOffset, realCodeHeader, rangeSection.Data!.JitManager); + return true; + } + + private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) + { + // EEJitManager::FindMethodCode + if (rangeSection.Data == null) + { + throw new InvalidOperationException(); + } + if (!rangeSection.IsCodeHeap) + { + throw new InvalidOperationException("RangeSection is not a code heap"); + } + TargetPointer heapListAddress = rangeSection.Data.HeapList; + Data.HeapList heapList = Target.ProcessedData.GetOrAdd(heapListAddress); + if (jittedCodeAddress < heapList.StartAddress || jittedCodeAddress > heapList.EndAddress) + { + return TargetPointer.Null; + } + TargetPointer mapBase = heapList.MapBase; + TargetPointer mapStart = heapList.HeaderMap; + return _nibbleMap.FindMethodCode(mapBase, mapStart, jittedCodeAddress); + } + + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.NibbleMap.cs b/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.NibbleMap.cs new file mode 100644 index 0000000000000..77e0e6d660994 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.NibbleMap.cs @@ -0,0 +1,175 @@ +// 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.Numerics; + +using MapUnit = uint; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + + +#pragma warning disable SA1121 // Use built in alias +internal readonly partial struct ExecutionManager_1 : IExecutionManager +{ + // Given a contiguous region of memory in which we lay out a collection of non-overlapping code blocks that are + // not too small (so that two adjacent ones aren't too close together) and where the start of each code block is preceeded by a code header aligned on some power of 2, + // we can break up the whole memory space into buckets of a fixed size (32-bytes in the current implementation), where + // each bucket either has a code block header or not. + // Thinking of each code block header address as a hex number, we can view it as: [index, offset, zeros] + // where each index gives us a bucket and the offset gives us the position of the header within the bucket. + // We encode each offset into a 4-bit nibble, reserving the special value 0 to mark the places in the map where a method doesn't start. + // + // To find the start of a method given an address we first convert it into a bucket index (giving the map unit) + // and an offset which we can then turn into the index of the nibble that covers that address. + // If the nibble is non-zero, we have the start of a method and it is near the given address. + // If the nibble is zero, we have to search backward first through the current map unit, and then through previous map + // units until we find a non-zero nibble. + internal sealed class NibbleMap + { + + public static NibbleMap Create(Target target) + { + uint codeHeaderSize = (uint)target.PointerSize; + return new NibbleMap(target, codeHeaderSize); + } + + private readonly Target _target; + private readonly uint _codeHeaderSize; + private NibbleMap(Target target, uint codeHeaderSize) + { + _target = target; + _codeHeaderSize = codeHeaderSize; + } + + // Shift the next nibble into the least significant position. + private static T NextNibble(T n) where T : IBinaryInteger + { + return n >>> 4; + } + + + private const uint MapUnitBytes = sizeof(MapUnit); // our units are going to be 32-bit integers + private const MapUnit NibbleMask = 0x0F; + private const ulong NibblesPerMapUnit = 2 * MapUnitBytes; // 2 nibbles per byte * N bytes per map unit + + // we will partition the address space into buckets of this many bytes. + // There is at most one code block header per bucket. + // normally we would then need Log2(BytesPerBucket) bits to find the exact start address, + // but because code headers are aligned, we can store the offset in a 4-bit nibble instead and shift appropriately to compute + // the effective address + private const ulong BytesPerBucket = 8 * sizeof(MapUnit); + + + // given the index of a nibble in the map, compute how much we have to shift a MapUnit to put that + // nible in the least significant position. + private static int ComputeNibbleShift(ulong mapIdx) + { + // the low bits of the nibble index give us how many nibbles we have to shift by. + int nibbleOffsetInMapUnit = (int)(mapIdx & (NibblesPerMapUnit - 1)); + return 28 - (nibbleOffsetInMapUnit * 4); // bit shift - 4 bits per nibble + } + + private static ulong ComputeByteOffset(ulong mapIdx, uint nibble) + { + return mapIdx * BytesPerBucket + (nibble - 1) * MapUnitBytes; + } + private static TargetPointer GetAbsoluteAddress(TargetPointer baseAddress, ulong mapIdx, uint nibble) + { + return baseAddress + ComputeByteOffset(mapIdx, nibble); + } + + // Given a relative address, decompose it into + // the bucket index and an offset within the bucket. + private static void DecomposeAddress(TargetNUInt relative, out ulong mapIdx, out uint bucketByteIndex) + { + mapIdx = relative.Value / BytesPerBucket; + bucketByteIndex = ((uint)(relative.Value & (BytesPerBucket - 1)) / MapUnitBytes) + 1; + System.Diagnostics.Debug.Assert(bucketByteIndex == (bucketByteIndex & NibbleMask)); + } + + private static TargetPointer GetMapUnitAddress(TargetPointer mapStart, ulong mapIdx) + { + return mapStart + (mapIdx / NibblesPerMapUnit) * MapUnitBytes; + } + + public TargetPointer FindMethodCode(TargetPointer mapBase, TargetPointer mapStart, TargetCodePointer currentPC) + { + TargetNUInt relativeAddress = new TargetNUInt(currentPC.Value - mapBase.Value); + DecomposeAddress(relativeAddress, out ulong mapIdx, out uint bucketByteIndex); + + MapUnit t = _target.Read(GetMapUnitAddress(mapStart, mapIdx)); + + // shift the nibble we want to the least significant position + t = t >>> ComputeNibbleShift(mapIdx); + uint nibble = t & NibbleMask; + if (nibble != 0 && nibble <= bucketByteIndex) + { + return GetAbsoluteAddress(mapBase, mapIdx, nibble); + } + + // search backwards through the current map unit + // we processed the lsb nibble, move to the next one + t = NextNibble(t); + + // if there's any nibble set in the current unit, find it + if (t != 0) + { + mapIdx--; + nibble = t & NibbleMask; + while (nibble == 0) + { + t = NextNibble(t); + mapIdx--; + nibble = t & NibbleMask; + } + return GetAbsoluteAddress(mapBase, mapIdx, nibble); + } + + // if we were near the beginning of the address space, there is not enough space for the code header, + // so we can stop + if (mapIdx < NibblesPerMapUnit) + { + return TargetPointer.Null; + } + + // We're now done with the current map index. + // Align the map index and move to the previous map unit, then move back one nibble. +#pragma warning disable IDE0054 // use compound assignment + mapIdx = mapIdx & (~(NibblesPerMapUnit - 1)) - 1; +#pragma warning restore IDE0054 // use compound assignment + + // read the map unit containing mapIdx and skip over it if it is all zeros + while (mapIdx >= NibblesPerMapUnit) + { + t = _target.Read(GetMapUnitAddress(mapStart, mapIdx)); + if (t != 0) + break; + mapIdx -= NibblesPerMapUnit; + } + + // if we went all the way to the front, we didn't find a code header + if (mapIdx < NibblesPerMapUnit) + { + return TargetPointer.Null; + } + + // move to the correct nibble in the map unit + while (mapIdx != 0 && (t & NibbleMask) == 0) + { + t = NextNibble(t); + mapIdx--; + } + + if (mapIdx == 0 && t == 0) + { + return TargetPointer.Null; + } + + nibble = t & NibbleMask; + return GetAbsoluteAddress(mapBase, mapIdx, nibble); + } + + } +} +#pragma warning restore SA1121 // Use built in alias diff --git a/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.cs b/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.cs new file mode 100644 index 0000000000000..8cdd844fce83e --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/ExecutionManager_1.cs @@ -0,0 +1,288 @@ +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly partial struct ExecutionManager_1 : IExecutionManager +{ + internal readonly Target _target; + + // maps EECodeInfoHandle.Address (which is the CodeHeaderAddress) to the EECodeInfo + private readonly Dictionary _codeInfos = new(); + private readonly Data.RangeSectionMap _topRangeSectionMap; + private readonly RangeSectionLookupAlgorithm _rangeSectionLookupAlgorithm; + private readonly EEJitManager _eeJitManager; + private readonly ReadyToRunJitManager _r2rJitManager; + + public ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionMap) + { + _target = target; + _topRangeSectionMap = topRangeSectionMap; + _rangeSectionLookupAlgorithm = RangeSectionLookupAlgorithm.Create(target); + NibbleMap nibbleMap = NibbleMap.Create(target); + _eeJitManager = new EEJitManager(target, nibbleMap); + _r2rJitManager = new ReadyToRunJitManager(target); + } + + private class EECodeInfo + { + private readonly int _codeHeaderOffset; + + public TargetCodePointer StartAddress { get; } + // note: this is the address of the pointer to the "real code header", you need to + // dereference it to get the address of _codeHeaderData + public TargetPointer CodeHeaderAddress => StartAddress.Value - (ulong)_codeHeaderOffset; + private Data.RealCodeHeader _codeHeaderData; + public TargetPointer JitManagerAddress { get; } + public TargetNUInt RelativeOffset { get; } + public EECodeInfo(TargetCodePointer startAddress, int codeHeaderOffset, TargetNUInt relativeOffset, Data.RealCodeHeader codeHeaderData, TargetPointer jitManagerAddress) + { + _codeHeaderOffset = codeHeaderOffset; + StartAddress = startAddress; + _codeHeaderData = codeHeaderData; + RelativeOffset = relativeOffset; + JitManagerAddress = jitManagerAddress; + } + + public TargetPointer MethodDescAddress => _codeHeaderData.MethodDesc; + public bool Valid => JitManagerAddress != TargetPointer.Null; + } + + // "ExecutionManagerPointer": a pointer to a RangeFragment and RangeSection. + // The pointers have a collectible flag on the lowest bit + private struct ExMgrPtr + { + public readonly TargetPointer RawValue; + + public TargetPointer Address => RawValue & ~1ul; + + public bool IsNull => Address == TargetPointer.Null; + + public static ExMgrPtr Null => new ExMgrPtr(TargetPointer.Null); + + public ExMgrPtr(TargetPointer value) + { + RawValue = value; + } + + public ExMgrPtr Offset(int stride, int idx) + { + return new ExMgrPtr(RawValue.Value + (ulong)(stride * idx)); + } + + public T Load(Target target) where T : Data.IData + { + return target.ProcessedData.GetOrAdd(Address); + } + + public ExMgrPtr LoadPointer(Target target) + { + return new ExMgrPtr(target.ReadPointer(Address)); + } + } + + private readonly struct RangeSectionLookupAlgorithm + { + private int MapLevels { get; } + private int BitsPerLevel { get; } = 8; + private int MaxSetBit { get; } + private int EntriesPerMapLevel { get; } = 256; + + private RangeSectionLookupAlgorithm(int mapLevels, int maxSetBit) + { + MapLevels = mapLevels; + MaxSetBit = maxSetBit; + } + public static RangeSectionLookupAlgorithm Create(Target target) + { + if (target.PointerSize == 4) + { + return new(mapLevels: 2, maxSetBit: 31); // 0 indexed + } + else if (target.PointerSize == 8) + { + return new(mapLevels: 5, maxSetBit: 56); // 0 indexed + } + else + { + throw new InvalidOperationException("Invalid pointer size"); + } + } + + // note: level is 1-indexed + private int EffectiveBitsForLevel(TargetCodePointer address, int level) + { + ulong addressAsInt = address.Value; + ulong addressBitsUsedInMap = addressAsInt >> (MaxSetBit + 1 - (MapLevels * BitsPerLevel)); + ulong addressBitsShifted = addressBitsUsedInMap >> ((level - 1) * BitsPerLevel); + ulong addressBitsUsedInLevel = (ulong)(EntriesPerMapLevel - 1) & addressBitsShifted; + return checked((int)addressBitsUsedInLevel); + } + + internal ExMgrPtr /*PTR_RangeSectionFragment*/ FindFragment(Target target, Data.RangeSectionMap topRangeSectionMap, TargetCodePointer jittedCodeAddress) + { + /* The outer levels are all pointer arrays to the next level down. Level 1 is an array of pointers to a RangeSectionFragment */ + int topLevelIndex = EffectiveBitsForLevel(jittedCodeAddress, MapLevels); + + ExMgrPtr top = new ExMgrPtr(topRangeSectionMap.TopLevelData); + + ExMgrPtr nextLevelAddress = top.Offset(target.PointerSize, topLevelIndex); + for (int level = MapLevels - 1; level >= 1; level--) + { + ExMgrPtr rangeSectionL = nextLevelAddress.LoadPointer(target); + if (rangeSectionL.IsNull) + return ExMgrPtr.Null; + nextLevelAddress = rangeSectionL.Offset(target.PointerSize, EffectiveBitsForLevel(jittedCodeAddress, level)); + } + return nextLevelAddress; + } + } + + [Flags] + private enum RangeSectionFlags : int + { + CodeHeap = 0x02, + RangeList = 0x04, + } + private abstract class JitManager + { + public Target Target { get; } + + protected JitManager(Target target) + { + Target = target; + } + + public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out EECodeInfo? info); + + } + + private class ReadyToRunJitManager : JitManager + { + public ReadyToRunJitManager(Target target) : base(target) + { + } + public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out EECodeInfo? info) + { + throw new NotImplementedException(); // TODO(cdac): ReadyToRunJitManager::JitCodeToMethodInfo + } + } + + private sealed class RangeSection + { + public readonly Data.RangeSection? Data; + + public RangeSection() + { + Data = default; + } + public RangeSection(Data.RangeSection rangeSection) + { + Data = rangeSection; + } + + private bool HasFlags(RangeSectionFlags mask) => (Data!.Flags & (int)mask) != 0; + internal bool IsRangeList => HasFlags(RangeSectionFlags.RangeList); + internal bool IsCodeHeap => HasFlags(RangeSectionFlags.CodeHeap); + + internal static bool IsStubCodeBlock(Target target, TargetPointer codeHeaderIndirect) + { + uint stubCodeBlockLast = target.ReadGlobal(Constants.Globals.StubCodeBlockLast); + return codeHeaderIndirect.Value <= stubCodeBlockLast; + } + + internal static RangeSection Find(Target target, Data.RangeSectionMap topRangeSectionMap, RangeSectionLookupAlgorithm rangeSectionLookup, TargetCodePointer jittedCodeAddress) + { + ExMgrPtr rangeSectionFragmentPtr = rangeSectionLookup.FindFragment(target, topRangeSectionMap, jittedCodeAddress); + if (rangeSectionFragmentPtr.IsNull) + { + return new RangeSection(); + } + while (!rangeSectionFragmentPtr.IsNull) + { + Data.RangeSectionFragment fragment = rangeSectionFragmentPtr.Load(target); + if (fragment.Contains(jittedCodeAddress)) + { + break; + } + rangeSectionFragmentPtr = new ExMgrPtr(fragment.Next); + } + if (!rangeSectionFragmentPtr.IsNull) + { + Data.RangeSectionFragment fragment = rangeSectionFragmentPtr.Load(target); + Data.RangeSection rangeSection = target.ProcessedData.GetOrAdd(fragment.RangeSection); + if (rangeSection.NextForDelete != TargetPointer.Null) + { + return new RangeSection(); + } + return new RangeSection(rangeSection); + } + return new RangeSection(); + } + } + + private JitManager GetJitManager(Data.RangeSection rangeSectionData) + { + if (rangeSectionData.R2RModule == TargetPointer.Null) + { + return _eeJitManager; + } + else + { + return _r2rJitManager; + } + } + + private EECodeInfo? GetEECodeInfo(TargetCodePointer jittedCodeAddress) + { + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionLookupAlgorithm, jittedCodeAddress); + if (range.Data == null) + { + return null; + } + JitManager jitManager = GetJitManager(range.Data); + if (jitManager.GetMethodInfo(range, jittedCodeAddress, out EECodeInfo? info)) + { + return info; + } + else + { + return null; + } + } + EECodeInfoHandle? IExecutionManager.GetEECodeInfoHandle(TargetCodePointer ip) + { + // TODO: some kind of cache based on ip, too? + // we don't know if the ip is the start of the code. + EECodeInfo? info = GetEECodeInfo(ip); + if (info == null || !info.Valid) + { + return null; + } + TargetPointer key = info.CodeHeaderAddress; + _codeInfos.TryAdd(key, info); + return new EECodeInfoHandle(key); + } + + TargetPointer IExecutionManager.GetMethodDesc(EECodeInfoHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out EECodeInfo? info)) + { + throw new InvalidOperationException("EECodeInfo not found"); + } + return info.MethodDescAddress; + } + + TargetCodePointer IExecutionManager.GetStartAddress(EECodeInfoHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out EECodeInfo? info)) + { + throw new InvalidOperationException("EECodeInfo not found"); + } + return info.StartAddress; + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Loader.cs b/src/native/managed/cdacreader/src/Contracts/Loader.cs index a71bdd4d12951..e62b45967d5d9 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader.cs @@ -54,6 +54,9 @@ static IContract IContract.Create(Target target, int version) public virtual TargetPointer GetThunkHeap(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); + + public virtual TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags) => throw new NotImplementedException(); + public virtual bool IsCollectibleLoaderAllocator(ModuleHandle handle) => throw new NotImplementedException(); } internal readonly struct Loader : ILoader diff --git a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs index ca61e0174b064..58a99f7988632 100644 --- a/src/native/managed/cdacreader/src/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/Loader_1.cs @@ -70,4 +70,43 @@ ModuleLookupTables ILoader.GetLookupTables(ModuleHandle handle) module.TypeRefToMethodTableMap, module.MethodDefToILCodeVersioningStateMap); } + + TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) + { + uint rid = Constants.EcmaMetadata.GetRowId(token); + ArgumentOutOfRangeException.ThrowIfZero(rid); + flags = new TargetNUInt(0); + if (table == TargetPointer.Null) + return TargetPointer.Null; + uint index = rid; + Data.ModuleLookupMap lookupMap = _target.ProcessedData.GetOrAdd(table); + // have to read lookupMap an extra time upfront because only the first map + // has valid supportedFlagsMask + TargetNUInt supportedFlagsMask = lookupMap.SupportedFlagsMask; + do + { + lookupMap = _target.ProcessedData.GetOrAdd(table); + if (index < lookupMap.Count) + { + TargetPointer entryAddress = lookupMap.TableData + (ulong)(index * _target.PointerSize); + TargetPointer rawValue = _target.ReadPointer(entryAddress); + flags = new TargetNUInt(rawValue & supportedFlagsMask.Value); + return rawValue & ~(supportedFlagsMask.Value); + } + else + { + table = lookupMap.Next; + index -= lookupMap.Count; + } + } while (table != TargetPointer.Null); + return TargetPointer.Null; + } + + bool ILoader.IsCollectibleLoaderAllocator(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + TargetPointer loaderAllocator = module.LoaderAllocator; + Data.LoaderAllocator la = _target.ProcessedData.GetOrAdd(loaderAllocator); + return la.IsCollectible != 0; + } } diff --git a/src/native/managed/cdacreader/src/Contracts/PrecodeStubs.cs b/src/native/managed/cdacreader/src/Contracts/PrecodeStubs.cs new file mode 100644 index 0000000000000..753d21b73e8c8 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/PrecodeStubs.cs @@ -0,0 +1,29 @@ +// 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); + static IContract IContract.Create(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), + }; + } + + TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint) => throw new NotImplementedException(); + +} + +internal readonly struct PrecodeStubs : IPrecodeStubs +{ + +} diff --git a/src/native/managed/cdacreader/src/Contracts/PrecodeStubs_1.cs b/src/native/managed/cdacreader/src/Contracts/PrecodeStubs_1.cs new file mode 100644 index 0000000000000..d2ae91a1f27a9 --- /dev/null +++ b/src/native/managed/cdacreader/src/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/src/Contracts/ReJIT.cs b/src/native/managed/cdacreader/src/Contracts/ReJIT.cs new file mode 100644 index 0000000000000..00c174e6a18f9 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/ReJIT.cs @@ -0,0 +1,29 @@ +// 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 IReJIT : IContract +{ + static string IContract.Name { get; } = nameof(ReJIT); + static IContract IContract.Create(Target target, int version) + { + TargetPointer profControlBlockAddress = target.ReadGlobalPointer(Constants.Globals.ProfilerControlBlock); + Data.ProfControlBlock profControlBlock = target.ProcessedData.GetOrAdd(profControlBlockAddress); + return version switch + { + 1 => new ReJIT_1(target, profControlBlock), + _ => default(ExecutionManager), + }; + } + + bool IsEnabled() => throw new NotImplementedException(); +} + +internal readonly struct ReJIT : IExecutionManager +{ + +} diff --git a/src/native/managed/cdacreader/src/Contracts/ReJIT_1.cs b/src/native/managed/cdacreader/src/Contracts/ReJIT_1.cs new file mode 100644 index 0000000000000..c75aa4a2affcb --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/ReJIT_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 ReJIT_1 : IReJIT +{ + internal readonly Target _target; + private readonly Data.ProfControlBlock _profControlBlock; + + public ReJIT_1(Target target, Data.ProfControlBlock profControlBlock) + { + _target = target; + _profControlBlock = profControlBlock; + } + + bool IReJIT.IsEnabled() + { + bool profEnabledReJIT = (_profControlBlock.GlobalEventMask & (ulong)Legacy.COR_PRF_MONITOR.COR_PRF_ENABLE_REJIT) != 0; + // FIXME: it is very likely this is always true in the DAC + // Most people don't set DOTNET_ProfAPI_RejitOnAttach = 0 + // See https://github.com/dotnet/runtime/issues/106148 + bool clrConfigEnabledReJIT = true; + return profEnabledReJIT || clrConfigEnabledReJIT; + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index a5d3721f06f88..9b3f6aa9a6715 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -25,6 +25,10 @@ public Registry(Target target) public IThread Thread => GetContract(); public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); public IDacStreams DacStreams => GetContract(); + public ICodeVersions CodeVersions => GetContract(); + public IPrecodeStubs PrecodeStubs => GetContract(); + public IExecutionManager ExecutionManager => GetContract(); + public IReJIT ReJIT => GetContract(); private T GetContract() where T : IContract { diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs index 67fd6db279604..7d28076f24de3 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem.cs @@ -164,6 +164,19 @@ static IContract IContract.Create(Target target, int version) // A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsILStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); + public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + public virtual bool IsVersionable(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual ushort GetSlotNumber(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc) => throw new NotImplementedException(); + #endregion MethodDesc inspection APIs } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs index b319f728eaf91..039bdeb875ff9 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.NonValidated.cs @@ -98,14 +98,64 @@ internal MethodDesc(Target target, Data.MethodDesc desc, Data.MethodDescChunk ch _chunk = chunk; } - private bool HasFlag(MethodDescFlags flag) => (_desc.Flags & (ushort)flag) != 0; + private bool HasFlags(MethodDescFlags flag) => (_desc.Flags & (ushort)flag) != 0; + private bool HasFlags(MethodDescEntryPointFlags flag) => (_desc.EntryPointFlags & (byte)flag) != 0; + + private bool HasFlags(MethodDescFlags3 flag) => (_desc.Flags3AndTokenRemainder & (ushort)flag) != 0; internal byte ChunkIndex => _desc.ChunkIndex; internal TargetPointer MethodTable => _chunk.MethodTable; internal ushort Slot => _desc.Slot; - internal bool HasNonVtableSlot => HasFlag(MethodDescFlags.HasNonVtableSlot); + internal bool HasNonVtableSlot => HasFlags(MethodDescFlags.HasNonVtableSlot); + internal bool HasMethodImpl => HasFlags(MethodDescFlags.HasMethodImpl); + internal bool HasNativeCodeSlot => HasFlags(MethodDescFlags.HasNativeCodeSlot); + + internal bool TemporaryEntryPointAssigned => HasFlags(MethodDescEntryPointFlags.TemporaryEntryPointAssigned); + + internal TargetPointer CodeData => _desc.CodeData; + + internal MethodClassification Classification => (MethodClassification)(_desc.Flags & (ushort)MethodDescFlags.ClassificationMask); + internal bool IsFCall => Classification == MethodClassification.FCall; + + #region Additional Pointers + private int AdditionalPointersHelper(MethodDescFlags extraFlags) + => int.PopCount(_desc.Flags & (ushort)extraFlags); + + // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly + // in the order: [non-vtable; methhod impl; native code]. + internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException("no non-vtable slot"); + internal int MethodImplIndex + { + get + { + if (!HasMethodImpl) + { + throw new InvalidOperationException("no method impl slot"); + } + return AdditionalPointersHelper(MethodDescFlags.HasNonVtableSlot); + } + } + internal int NativeCodeSlotIndex + { + get + { + if (!HasNativeCodeSlot) + { + throw new InvalidOperationException("no native code slot"); + } + return AdditionalPointersHelper(MethodDescFlags.HasNonVtableSlot | MethodDescFlags.HasMethodImpl); + } + } + + internal int AdditionalPointersCount => AdditionalPointersHelper(MethodDescFlags.MethodDescAdditionalPointersMask); + #endregion Additional Pointers + + internal bool HasStableEntryPoint => HasFlags(MethodDescFlags3.HasStableEntryPoint); + internal bool HasPrecode => HasFlags(MethodDescFlags3.HasPrecode); + } + internal static MethodTable GetMethodTableData(Target target, TargetPointer methodTablePointer) { return new MethodTable(target, methodTablePointer); @@ -244,6 +294,113 @@ private NonValidated.MethodDesc GetMethodDescThrowing(TargetPointer methodDescPo return new NonValidated.MethodDesc(_target, desc, chunk); } + private TargetCodePointer GetTemporaryEntryPointIfExists(NonValidated.MethodDesc umd) + { + if (!umd.TemporaryEntryPointAssigned || umd.CodeData == TargetPointer.Null) + { + return TargetCodePointer.Null; + } + Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd(umd.CodeData); + return codeData.TemporaryEntryPoint; + } + + private TargetPointer GetAddrOfNativeCodeSlot(TargetPointer methodDescPointer, NonValidated.MethodDesc umd) + { + uint offset = MethodDescAdditionalPointersOffset(umd); + offset += (uint)(_target.PointerSize * umd.NativeCodeSlotIndex); + return methodDescPointer.Value + offset; + } + + private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, NonValidated.MethodDesc umd) + { + uint offset = MethodDescAdditionalPointersOffset(umd); + offset += (uint)(_target.PointerSize * umd.NonVtableSlotIndex); + return methodDescPointer.Value + offset; + } + + private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonValidated.MethodDesc umd) + { + // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot()); + if (umd.HasNativeCodeSlot) + { + // When profiler is enabled, profiler may ask to rejit a code even though we + // we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub). + // This means that *ppCode is not stable. It can turn from non-zero to zero. + TargetPointer ppCode = GetAddrOfNativeCodeSlot(methodDescPointer, umd); + TargetCodePointer pCode = _target.ReadCodePointer(ppCode); + + // if arm32, set the thumb bit + Data.PrecodeMachineDescriptor precodeMachineDescriptor = _target.ProcessedData.GetOrAdd(_target.ReadGlobalPointer(Constants.Globals.PrecodeMachineDescriptor)); + pCode = (TargetCodePointer)(pCode.Value | ~precodeMachineDescriptor.CodePointerToInstrPointerMask.Value); + + return pCode; + } + + if (!umd.HasStableEntryPoint || umd.HasPrecode) + return TargetCodePointer.Null; + + return GetStableEntryPoint(methodDescPointer, umd); + } + + private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescPointer, NonValidated.MethodDesc umd) + { + // TODO(cdac): _ASSERTE(HasStableEntryPoint()); + // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch()); + + return GetMethodEntryPointIfExists(methodDescPointer, umd); + } + + private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, NonValidated.MethodDesc umd) + { + if (umd.HasNonVtableSlot) + { + TargetPointer pSlot = GetAddressOfNonVtableSlot(methodDescAddress, umd); + + return _target.ReadCodePointer(pSlot); + } + + TargetPointer methodTablePointer = umd.MethodTable; + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); + // TODO: cdac: _ASSERTE(GetMethodTable()->IsCanonicalMethodTable()); + TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, umd.Slot); + return _target.ReadCodePointer(addrOfSlot); + } + + private uint MethodDescAdditionalPointersOffset(NonValidated.MethodDesc umd) + { + MethodClassification cls = umd.Classification; + switch (cls) + { + case MethodClassification.IL: + return _target.GetTypeInfo(DataType.MethodDesc).Size ?? throw new InvalidOperationException("size of MethodDesc not known"); + case MethodClassification.FCall: + throw new NotImplementedException(); + case MethodClassification.PInvoke: + throw new NotImplementedException(); + case MethodClassification.EEImpl: + throw new NotImplementedException(); + case MethodClassification.Array: + throw new NotImplementedException(); + case MethodClassification.Instantiated: + throw new NotImplementedException(); + case MethodClassification.ComInterop: + throw new NotImplementedException(); + case MethodClassification.Dynamic: + throw new NotImplementedException(); + default: + throw new InvalidOperationException($"Unexpected method classification 0x{cls:x2} for MethodDesc"); + } + } + + internal uint GetMethodDescBaseSize(NonValidated.MethodDesc umd) + { + uint baseSize = MethodDescAdditionalPointersOffset(umd); + baseSize += (uint)(_target.PointerSize * umd.AdditionalPointersCount); + return baseSize; + } + + private bool HasNativeCode(TargetPointer methodDescPointer, NonValidated.MethodDesc umd) => GetCodePointer(methodDescPointer, umd) != TargetCodePointer.Null; + private bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNullWhen(true)] out TargetPointer methodDescChunkPointer) { methodDescChunkPointer = TargetPointer.Null; @@ -263,42 +420,37 @@ private bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNull { return false; } - // TODO: request.cpp - // TODO[cdac]: this needs a Precode lookup - // see MethodDescChunk::GetTemporaryEntryPoint -#if false - MethodDesc *pMDCheck = MethodDesc::GetMethodDescFromStubAddr(pMD->GetTemporaryEntryPoint(), TRUE); - if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck)) + TargetCodePointer temporaryEntryPoint = GetTemporaryEntryPointIfExists(umd); + if (temporaryEntryPoint != TargetCodePointer.Null) { - retval = FALSE; + Contracts.IPrecodeStubs precode = _target.Contracts.PrecodeStubs; + TargetPointer methodDesc = precode.GetMethodDescFromStubAddress(temporaryEntryPoint); + if (methodDesc != methodDescPointer) + { + return false; + } } -#endif - - // TODO: request.cpp - // TODO[cdac]: needs MethodDesc::GetNativeCode and MethodDesc::GetMethodEntryPoint() -#if false - if (retval && pMD->HasNativeCode() && !pMD->IsFCall()) - { - PCODE jitCodeAddr = pMD->GetNativeCode(); - MethodDesc *pMDCheck = ExecutionManager::GetCodeMethodDesc(jitCodeAddr); - if (pMDCheck) + if (HasNativeCode(methodDescPointer, umd) && !umd.IsFCall) { - // Check that the given MethodDesc matches the MethodDesc from - // the CodeHeader - if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck)) + TargetCodePointer jitCodeAddr = GetCodePointer(methodDescPointer, umd); + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + EECodeInfoHandle? codeInfo = executionManager.GetEECodeInfoHandle(jitCodeAddr); + if (!codeInfo.HasValue) { - retval = FALSE; + return false; + } + TargetPointer methodDesc = executionManager.GetMethodDesc(codeInfo.Value); + if (methodDesc == TargetPointer.Null) + { + return false; + } + if (methodDesc != methodDescPointer) + { + return false; } } - else - { - retval = FALSE; - } - } -#endif - } catch (System.Exception) { @@ -310,4 +462,5 @@ private bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNull } return true; } + } diff --git a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs index 654651ce20d5e..9776f2ef373fa 100644 --- a/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using System.Text; using Microsoft.Diagnostics.DataContractReader.Contracts.RuntimeTypeSystem_1_NS; using Microsoft.Diagnostics.DataContractReader.Data; @@ -88,7 +91,24 @@ internal enum MethodClassification internal enum MethodDescFlags : ushort { ClassificationMask = 0x7, + #region Additional pointers + // The below flags each imply that there's an extra pointer-sized piece of data after the MethodDesc in the MethodDescChunk HasNonVtableSlot = 0x0008, + HasMethodImpl = 0x0010, + HasNativeCodeSlot = 0x0020, + // Mask for the above flags + MethodDescAdditionalPointersMask = 0x0038, + #endregion Additional pointers + } + + [Flags] + internal enum MethodDescFlags3 : ushort + { + // HasPrecode implies that HasStableEntryPoint is set. + HasStableEntryPoint = 0x1000, // The method entrypoint is stable (either precode or actual code) + HasPrecode = 0x2000, // Precode has been allocated for this method + IsUnboxingStub = 0x4000, + IsEligibleForTieredCompilation = 0x8000, } internal enum InstantiatedMethodDescFlags2 : ushort @@ -107,6 +127,12 @@ internal enum DynamicMethodDescExtendedFlags : uint IsILStub = 0x00008000, } + [Flags] + internal enum MethodDescEntryPointFlags : byte + { + TemporaryEntryPointAssigned = 0x04, + } + internal struct MethodDesc { private readonly Data.MethodDesc _desc; @@ -131,8 +157,8 @@ internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodD private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) { int tokenRemainderBitCount = target.ReadGlobal(Constants.Globals.MethodDescTokenRemainderBitCount); - int tokenRangeBitCount = 24 - tokenRemainderBitCount; - uint allRidBitsSet = 0xFFFFFF; + int tokenRangeBitCount = Constants.EcmaMetadata.RowIdBitCount - tokenRemainderBitCount; + uint allRidBitsSet = Constants.EcmaMetadata.RIDMask; uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount; uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount; @@ -143,6 +169,58 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho } public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags.ClassificationMask); + + private bool HasFlags(MethodDescFlags flags) => (_desc.Flags & (ushort)flags) != 0; + internal bool HasFlags(MethodDescFlags3 flags) => (_desc.Flags3AndTokenRemainder & (ushort)flags) != 0; + + public bool IsEligibleForTieredCompilation => HasFlags(MethodDescFlags3.IsEligibleForTieredCompilation); + + + public bool IsUnboxingStub => HasFlags(MethodDescFlags3.IsUnboxingStub); + + public TargetPointer CodeData => _desc.CodeData; + public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated; + + public bool HasNativeCodeSlot => HasFlags(MethodDescFlags.HasNativeCodeSlot); + internal bool HasNonVtableSlot => HasFlags(MethodDescFlags.HasNonVtableSlot); + internal bool HasMethodImpl => HasFlags(MethodDescFlags.HasMethodImpl); + + internal bool HasStableEntryPoint => HasFlags(MethodDescFlags3.HasStableEntryPoint); + internal bool HasPrecode => HasFlags(MethodDescFlags3.HasPrecode); + + #region Additional Pointers + private int AdditionalPointersHelper(MethodDescFlags extraFlags) + => int.PopCount(_desc.Flags & (ushort)extraFlags); + + // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly + // in the order: [non-vtable; methhod impl; native code]. + internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException("no non-vtable slot"); + internal int MethodImplIndex + { + get + { + if (!HasMethodImpl) + { + throw new InvalidOperationException("no method impl slot"); + } + return AdditionalPointersHelper(MethodDescFlags.HasNonVtableSlot); + } + } + internal int NativeCodeSlotIndex + { + get + { + if (!HasNativeCodeSlot) + { + throw new InvalidOperationException("no native code slot"); + } + return AdditionalPointersHelper(MethodDescFlags.HasNonVtableSlot | MethodDescFlags.HasMethodImpl); + } + } + + internal int AdditionalPointersCount => AdditionalPointersHelper(MethodDescFlags.MethodDescAdditionalPointersMask); + #endregion Additional Pointers + } private class InstantiatedMethodDesc : IData @@ -767,4 +845,215 @@ private MethodTable GetOrCreateMethodTable(MethodDesc methodDesc) _ = GetTypeHandle(methodDesc.MethodTable); return _methodTables[methodDesc.MethodTable]; } + + private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum) + { + if (!typeHandle.IsMethodTable()) + throw new InvalidOperationException("typeHandle is not a MethodTable"); + MethodTable mt = _methodTables[typeHandle.Address]; + // MethodTable::GetSlotPtrRaw + // TODO(cdac): CONSISTENCY_CHECK(slotNum < GetNumVtableSlots()); + + if (slotNum < mt.NumVirtuals) + { + // Virtual slots live in chunks pointed to by vtable indirections +#if false + return GetVtableIndirections()[GetIndexOfVtableIndirection(slotNum)] + GetIndexAfterVtableIndirection(slotNum); +#endif + throw new NotImplementedException(); // TODO(cdac): + } + else + { + // Non-virtual slots < GetNumVtableSlots live before the MethodTableAuxiliaryData. The array grows backwards + // TODO(cdac): _ASSERTE(HasNonVirtualSlots()); +#if false + return MethodTableAuxiliaryData::GetNonVirtualSlotsArray(GetAuxiliaryDataForWrite()) - (1 + (slotNum - GetNumVirtuals())); +#endif + throw new NotImplementedException(); // TODO(cdac): + } + + } + + private bool IsWrapperStub(MethodDesc md) + { + return md.IsUnboxingStub || IsInstantiatingStub(md); + } + + private bool IsInstantiatingStub(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && !md.IsUnboxingStub && AsInstantiatedMethodDesc(md).IsWrapperStubWithInstantiations; + } + + private bool HasMethodInstantiation(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).HasMethodInstantiation; + } + + private TargetPointer GetLoaderModule(TypeHandle typeHandle) + { + throw new NotImplementedException(); + } + + private bool IsGenericMethodDefinition(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).IsGenericMethodDefinition; + } + + private TargetPointer GetLoaderModule(MethodDesc md) + { + + if (HasMethodInstantiation(md) && !IsGenericMethodDefinition(md)) + { + // TODO[cdac]: don't reimplement ComputeLoaderModuleWorker, + // but try caching the LoaderModule (or just the LoaderAllocator?) on the + // MethodDescChunk (and maybe MethodTable?). + throw new NotImplementedException(); + } + else + { + TargetPointer mtAddr = GetMethodTable(new MethodDescHandle(md.Address)); + TypeHandle mt = GetTypeHandle(mtAddr); + return GetLoaderModule(mt); + } + } + + bool IRuntimeTypeSystem.IsCollectibleMethod(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + TargetPointer loaderModuleAddr = GetLoaderModule(md); + ModuleHandle mod = _target.Contracts.Loader.GetModuleHandle(loaderModuleAddr); + return _target.Contracts.Loader.IsCollectibleLoaderAllocator(mod); // TODO[cdac]: return pMethodDesc->GetLoaderAllocator()->IsCollectible() + } + + bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + if (md.IsEligibleForTieredCompilation) + return true; + // MethodDesc::IsEligibleForReJIT + if (_target.Contracts.ReJIT.IsEnabled()) + { + if (!md.IsIL) + return false; + if (IsWrapperStub(md)) + return false; + return _target.Contracts.CodeVersions.CodeVersionManagerSupportsMethod(methodDesc.Address); + } + return false; + } + + TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + TargetPointer codeDataAddress = md.CodeData; + Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd(codeDataAddress); + return codeData.VersioningState; + } + + uint IRuntimeTypeSystem.GetMethodToken(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + return methodDesc.Token; + } + + ushort IRuntimeTypeSystem.GetSlotNumber(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + return md.Slot; + } + bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + return md.HasNativeCodeSlot; + } + + private uint MethodDescAdditionalPointersOffset(MethodDesc md) + { + MethodClassification cls = md.Classification; + switch (cls) + { + case MethodClassification.IL: + return _target.GetTypeInfo(DataType.MethodDesc).Size ?? throw new InvalidOperationException("size of MethodDesc not known"); + case MethodClassification.FCall: + throw new NotImplementedException(); + case MethodClassification.PInvoke: + throw new NotImplementedException(); + case MethodClassification.EEImpl: + throw new NotImplementedException(); + case MethodClassification.Array: + throw new NotImplementedException(); + case MethodClassification.Instantiated: + throw new NotImplementedException(); + case MethodClassification.ComInterop: + throw new NotImplementedException(); + case MethodClassification.Dynamic: + throw new NotImplementedException(); + default: + throw new InvalidOperationException($"Unexpected method classification 0x{cls:x2} for MethodDesc"); + } + } + + TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + uint offset = MethodDescAdditionalPointersOffset(md); + offset += (uint)(_target.PointerSize * md.NativeCodeSlotIndex); + return methodDesc.Address + offset; + } + private TargetPointer GetAddresOfNonVtableSlot(TargetPointer methodDescPointer, MethodDesc md) + { + uint offset = MethodDescAdditionalPointersOffset(md); + offset += (uint)(_target.PointerSize * md.NonVtableSlotIndex); + return methodDescPointer.Value + offset; + } + + TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot()); + if (md.HasNativeCodeSlot) + { + // When profiler is enabled, profiler may ask to rejit a code even though we + // we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub). + // This means that *ppCode is not stable. It can turn from non-zero to zero. + TargetPointer ppCode = ((IRuntimeTypeSystem)this).GetAddressOfNativeCodeSlot(methodDescHandle); + TargetCodePointer pCode = _target.ReadCodePointer(ppCode); + + // if arm32, set the thumb bit + Data.PrecodeMachineDescriptor precodeMachineDescriptor = _target.ProcessedData.GetOrAdd(_target.ReadGlobalPointer(Constants.Globals.PrecodeMachineDescriptor)); + pCode = (TargetCodePointer)(pCode.Value | ~precodeMachineDescriptor.CodePointerToInstrPointerMask.Value); + + return pCode; + } + + if (!md.HasStableEntryPoint || md.HasPrecode) + return TargetCodePointer.Null; + + return GetStableEntryPoint(methodDescHandle.Address, md); + } + + private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescAddress, MethodDesc md) + { + // TODO(cdac): _ASSERTE(HasStableEntryPoint()); + // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch()); + + return GetMethodEntryPointIfExists(methodDescAddress, md); + } + + private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, MethodDesc md) + { + if (md.HasNonVtableSlot) + { + TargetPointer pSlot = GetAddresOfNonVtableSlot(methodDescAddress, md); + + return _target.ReadCodePointer(pSlot); + } + + TargetPointer methodTablePointer = md.MethodTable; + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); + // TODO: cdac: _ASSERTE(GetMethodTable()->IsCanonicalMethodTable()); + TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, md.Slot); + return _target.ReadCodePointer(addrOfSlot); + } + } diff --git a/src/native/managed/cdacreader/src/Data/FixupPrecodeData.cs b/src/native/managed/cdacreader/src/Data/FixupPrecodeData.cs new file mode 100644 index 0000000000000..811508cc4354a --- /dev/null +++ b/src/native/managed/cdacreader/src/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/src/Data/HeapList.cs b/src/native/managed/cdacreader/src/Data/HeapList.cs new file mode 100644 index 0000000000000..b87dfdaad89fa --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/HeapList.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. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class HeapList : IData +{ + static HeapList IData.Create(Target target, TargetPointer address) + => new HeapList(target, address); + + public HeapList(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HeapList); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + StartAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(StartAddress)].Offset); + EndAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(EndAddress)].Offset); + MapBase = target.ReadPointer(address + (ulong)type.Fields[nameof(MapBase)].Offset); + HeaderMap = target.ReadPointer(address + (ulong)type.Fields[nameof(HeaderMap)].Offset); + } + + public TargetPointer Next { get; init; } + public TargetPointer StartAddress { get; init; } + public TargetPointer EndAddress { get; init; } + + public TargetPointer MapBase { get; init; } + + public TargetPointer HeaderMap { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/ILCodeVersioningState.cs b/src/native/managed/cdacreader/src/Data/ILCodeVersioningState.cs new file mode 100644 index 0000000000000..34256abe54569 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/ILCodeVersioningState.cs @@ -0,0 +1,27 @@ +// 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 ILCodeVersioningState : IData +{ + static ILCodeVersioningState IData.Create(Target target, TargetPointer address) + => new ILCodeVersioningState(target, address); + + public ILCodeVersioningState(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ILCodeVersioningState); + + Node = target.ReadPointer(address + (ulong)type.Fields[nameof(Node)].Offset); + ActiveVersionKind = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionKind)].Offset); + ActiveVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionNode)].Offset); + ActiveVersionModule = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionModule)].Offset); + ActiveVersionMethodDef = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionMethodDef)].Offset); + } + + public TargetPointer Node { get; init; } + public uint ActiveVersionKind { get; set; } + public TargetPointer ActiveVersionNode { get; set; } + public TargetPointer ActiveVersionModule { get; set; } + public uint ActiveVersionMethodDef { get; set; } +} diff --git a/src/native/managed/cdacreader/src/Data/LoaderAllocator.cs b/src/native/managed/cdacreader/src/Data/LoaderAllocator.cs new file mode 100644 index 0000000000000..7d5a2ebc5f04a --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/LoaderAllocator.cs @@ -0,0 +1,19 @@ +// 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.Data; + +internal sealed class LoaderAllocator : IData +{ + static LoaderAllocator IData.Create(Target target, TargetPointer address) => new LoaderAllocator(target, address); + public LoaderAllocator(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.LoaderAllocator); + + IsCollectible = target.Read(address + (ulong)type.Fields[nameof(IsCollectible)].Offset); + } + + public byte IsCollectible { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/MethodDesc.cs b/src/native/managed/cdacreader/src/Data/MethodDesc.cs index d51153ab53e59..eea2a61619421 100644 --- a/src/native/managed/cdacreader/src/Data/MethodDesc.cs +++ b/src/native/managed/cdacreader/src/Data/MethodDesc.cs @@ -16,12 +16,17 @@ public MethodDesc(Target target, TargetPointer address) Slot = target.Read(address + (ulong)type.Fields[nameof(Slot)].Offset); Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); Flags3AndTokenRemainder = target.Read(address + (ulong)type.Fields[nameof(Flags3AndTokenRemainder)].Offset); + EntryPointFlags = target.Read(address + (ulong)type.Fields[nameof(EntryPointFlags)].Offset); + CodeData = target.ReadPointer(address + (ulong)type.Fields[nameof(CodeData)].Offset); } public byte ChunkIndex { get; init; } public ushort Slot { get; init; } public ushort Flags { get; init; } public ushort Flags3AndTokenRemainder { get; init; } + public byte EntryPointFlags { get; init; } + + public TargetPointer CodeData { get; set; } } internal sealed class InstantiatedMethodDesc : IData diff --git a/src/native/managed/cdacreader/src/Data/MethodDescCodeData.cs b/src/native/managed/cdacreader/src/Data/MethodDescCodeData.cs new file mode 100644 index 0000000000000..dc76b8981b610 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodDescCodeData.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 MethodDescCodeData : IData +{ + static MethodDescCodeData IData.Create(Target target, TargetPointer address) => new MethodDescCodeData(target, address); + public MethodDescCodeData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodDescCodeData); + + TemporaryEntryPoint = target.ReadCodePointer(address + (ulong)type.Fields[nameof(TemporaryEntryPoint)].Offset); + VersioningState = target.ReadPointer(address + (ulong)type.Fields[nameof(VersioningState)].Offset); + } + + public TargetCodePointer TemporaryEntryPoint { get; set; } + public TargetPointer VersioningState { get; set; } +} diff --git a/src/native/managed/cdacreader/src/Data/MethodDescVersioningState.cs b/src/native/managed/cdacreader/src/Data/MethodDescVersioningState.cs new file mode 100644 index 0000000000000..95b404afa2950 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/MethodDescVersioningState.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. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodDescVersioningState : IData +{ + static MethodDescVersioningState IData.Create(Target target, TargetPointer address) => new MethodDescVersioningState(target, address); + public MethodDescVersioningState(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodDescVersioningState); + + NativeCodeVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(NativeCodeVersionNode)].Offset); + Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); + } + + public TargetPointer NativeCodeVersionNode { get; init; } + public byte Flags { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/ModuleLookupMap.cs b/src/native/managed/cdacreader/src/Data/ModuleLookupMap.cs index db69c68fe2c13..449dfb0cb0452 100644 --- a/src/native/managed/cdacreader/src/Data/ModuleLookupMap.cs +++ b/src/native/managed/cdacreader/src/Data/ModuleLookupMap.cs @@ -12,7 +12,13 @@ private ModuleLookupMap(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.ModuleLookupMap); TableData = target.ReadPointer(address + (ulong)type.Fields[nameof(TableData)].Offset); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + Count = target.Read(address + (ulong)type.Fields[nameof(Count)].Offset); + SupportedFlagsMask = target.ReadNUInt(address + (ulong)type.Fields[nameof(SupportedFlagsMask)].Offset); } public TargetPointer TableData { get; init; } + public TargetPointer Next { get; init; } + public uint Count { get; init; } + public TargetNUInt SupportedFlagsMask { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/NativeCodeVersionNode.cs b/src/native/managed/cdacreader/src/Data/NativeCodeVersionNode.cs new file mode 100644 index 0000000000000..87b89201a97fc --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/NativeCodeVersionNode.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.Data; + +internal sealed class NativeCodeVersionNode : IData +{ + static NativeCodeVersionNode IData.Create(Target target, TargetPointer address) => new NativeCodeVersionNode(target, address); + public NativeCodeVersionNode(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.NativeCodeVersionNode); + + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + NativeCode = target.ReadCodePointer(address + (ulong)type.Fields[nameof(NativeCode)].Offset); + } + + public TargetPointer Next { get; init; } + public TargetPointer MethodDesc { get; init; } + + public TargetCodePointer NativeCode { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/PrecodeMachineDescriptor.cs b/src/native/managed/cdacreader/src/Data/PrecodeMachineDescriptor.cs new file mode 100644 index 0000000000000..6861e21e8ac31 --- /dev/null +++ b/src/native/managed/cdacreader/src/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/src/Data/ProfControlBlock.cs b/src/native/managed/cdacreader/src/Data/ProfControlBlock.cs new file mode 100644 index 0000000000000..e598647b7dee6 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/ProfControlBlock.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 ProfControlBlock : IData +{ + static ProfControlBlock IData.Create(Target target, TargetPointer address) + => new ProfControlBlock(target, address); + + public ProfControlBlock(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ProfControlBlock); + GlobalEventMask = target.Read(address + (ulong)type.Fields[nameof(GlobalEventMask)].Offset); + } + + public ulong GlobalEventMask { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/RangeSection.cs b/src/native/managed/cdacreader/src/Data/RangeSection.cs new file mode 100644 index 0000000000000..2cd68418ce85c --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/RangeSection.cs @@ -0,0 +1,30 @@ +// 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 RangeSection : IData +{ + static RangeSection IData.Create(Target target, TargetPointer address) + => new RangeSection(target, address); + + public RangeSection(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.RangeSection); + RangeBegin = target.ReadPointer(address + (ulong)type.Fields[nameof(RangeBegin)].Offset); + RangeEndOpen = target.ReadPointer(address + (ulong)type.Fields[nameof(RangeEndOpen)].Offset); + NextForDelete = target.ReadPointer(address + (ulong)type.Fields[nameof(NextForDelete)].Offset); + JitManager = target.ReadPointer(address + (ulong)type.Fields[nameof(JitManager)].Offset); + Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); + HeapList = target.ReadPointer(address + (ulong)type.Fields[nameof(HeapList)].Offset); + R2RModule = target.ReadPointer(address + (ulong)type.Fields[nameof(R2RModule)].Offset); + } + + public TargetPointer RangeBegin { get; init; } + public TargetPointer RangeEndOpen { get; init; } + public TargetPointer NextForDelete { get; init; } + public TargetPointer JitManager { get; init; } + public TargetPointer HeapList { get; init; } + public int Flags { get; init; } + public TargetPointer R2RModule { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/RangeSectionFragment.cs b/src/native/managed/cdacreader/src/Data/RangeSectionFragment.cs new file mode 100644 index 0000000000000..b6c1540978c0d --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/RangeSectionFragment.cs @@ -0,0 +1,27 @@ +// 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 RangeSectionFragment : IData +{ + static RangeSectionFragment IData.Create(Target target, TargetPointer address) + => new RangeSectionFragment(target, address); + + public RangeSectionFragment(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.RangeSectionFragment); + RangeBegin = target.ReadPointer(address + (ulong)type.Fields[nameof(RangeBegin)].Offset); + RangeEndOpen = target.ReadPointer(address + (ulong)type.Fields[nameof(RangeEndOpen)].Offset); + RangeSection = target.ReadPointer(address + (ulong)type.Fields[nameof(RangeSection)].Offset); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + } + + public TargetPointer RangeBegin { get; init; } + public TargetPointer RangeEndOpen { get; init; } + public TargetPointer RangeSection { get; init; } + public TargetPointer Next { get; init; } + + public bool Contains(TargetCodePointer address) + => RangeBegin <= address && address < RangeEndOpen; +} diff --git a/src/native/managed/cdacreader/src/Data/RangeSectionMap.cs b/src/native/managed/cdacreader/src/Data/RangeSectionMap.cs new file mode 100644 index 0000000000000..aa0539cedddcb --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/RangeSectionMap.cs @@ -0,0 +1,19 @@ +// 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 RangeSectionMap : IData +{ + static RangeSectionMap IData.Create(Target target, TargetPointer address) + => new RangeSectionMap(target, address); + + public RangeSectionMap(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.RangeSectionMap); + TopLevelData = new TargetPointer(address + (ulong)type.Fields[nameof(TopLevelData)].Offset); + } + + // pointer to first element + public TargetPointer TopLevelData { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/RealCodeHeader.cs b/src/native/managed/cdacreader/src/Data/RealCodeHeader.cs new file mode 100644 index 0000000000000..22a33b4820e32 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/RealCodeHeader.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 RealCodeHeader : IData +{ + static RealCodeHeader IData.Create(Target target, TargetPointer address) + => new RealCodeHeader(target, address); + + public RealCodeHeader(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); + MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + } + + public TargetPointer MethodDesc { get; init; } +} diff --git a/src/native/managed/cdacreader/src/Data/StubPrecodeData.cs b/src/native/managed/cdacreader/src/Data/StubPrecodeData.cs new file mode 100644 index 0000000000000..320795b41d542 --- /dev/null +++ b/src/native/managed/cdacreader/src/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/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index 3d9926eb4f505..a4c843d2241dd 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -20,6 +20,7 @@ public enum DataType pointer, GCHandle, + CodePointer, Thread, ThreadStore, GCAllocContext, @@ -28,6 +29,7 @@ public enum DataType RuntimeThreadLocals, Module, ModuleLookupMap, + LoaderAllocator, MethodTable, EEClass, ArrayClass, @@ -42,6 +44,10 @@ public enum DataType String, MethodDesc, MethodDescChunk, + MethodDescCodeData, + PrecodeMachineDescriptor, + StubPrecodeData, + FixupPrecodeData, Array, SyncBlock, SyncTableEntry, @@ -49,4 +55,14 @@ public enum DataType InstantiatedMethodDesc, DynamicMethodDesc, StoredSigMethodDesc, + RangeSectionMap, + RangeSectionFragment, + RangeSection, + RealCodeHeader, + HeapList, + MethodDescVersioningState, + ILCodeVersioningState, + NativeCodeVersionNode, + ProfControlBlock, + } diff --git a/src/native/managed/cdacreader/src/Legacy/ICorProfiler.cs b/src/native/managed/cdacreader/src/Legacy/ICorProfiler.cs new file mode 100644 index 0000000000000..f9173590eefbf --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/ICorProfiler.cs @@ -0,0 +1,13 @@ +// 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.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +internal enum COR_PRF_MONITOR +{ + COR_PRF_ENABLE_REJIT = 0x00040000, +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 37e513995767b..c95cb6b2307f9 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -3,6 +3,7 @@ using Microsoft.Diagnostics.DataContractReader.Contracts; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; @@ -100,14 +101,94 @@ public unsafe int GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDescDa // elements we return return HResults.E_INVALIDARG; } + if (cRevertedRejitVersions != 0) + { + return HResults.E_NOTIMPL; // TODO[cdac]: rejit + } try { Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc); + Contracts.ICodeVersions nativeCodeContract = _target.Contracts.CodeVersions; - data->MethodTablePtr = rtsContract.GetMethodTable(methodDescHandle); + if (rgRevertedRejitData != null) + { + NativeMemory.Clear(rgRevertedRejitData, (nuint)(sizeof(DacpReJitData) * cRevertedRejitVersions)); + } + if (pcNeededRevertedRejitData != null) + { + *pcNeededRevertedRejitData = 0; + } - return HResults.E_NOTIMPL; + NativeCodeVersionHandle requestedNativeCodeVersion; + NativeCodeVersionHandle? activeNativeCodeVersion = null; + if (ip != 0) + { + requestedNativeCodeVersion = nativeCodeContract.GetNativeCodeVersionForIP(new TargetCodePointer(ip)); + } + else + { + requestedNativeCodeVersion = nativeCodeContract.GetActiveNativeCodeVersion(new TargetPointer(methodDesc)); + activeNativeCodeVersion = requestedNativeCodeVersion; + } + + data->requestedIP = ip; + data->bIsDynamic = rtsContract.IsDynamicMethod(methodDescHandle) ? 1 : 0; + data->wSlotNumber = rtsContract.GetSlotNumber(methodDescHandle); + TargetCodePointer nativeCodeAddr = TargetCodePointer.Null; + if (requestedNativeCodeVersion.Valid) + { + nativeCodeAddr = nativeCodeContract.GetNativeCode(requestedNativeCodeVersion); + } + if (nativeCodeAddr != TargetCodePointer.Null) + { + data->bHasNativeCode = 1; + data->NativeCodeAddr = nativeCodeAddr; + } + else + { + data->bHasNativeCode = 0; + data->NativeCodeAddr = 0xffffffff_fffffffful; + } + if (rtsContract.HasNativeCodeSlot(methodDescHandle)) + { + data->AddressOfNativeCodeSlot = rtsContract.GetAddressOfNativeCodeSlot(methodDescHandle); + } + else + { + data->AddressOfNativeCodeSlot = 0; + } + data->MDToken = rtsContract.GetMethodToken(methodDescHandle); + data->MethodDescPtr = methodDesc; + TargetPointer methodTableAddr = rtsContract.GetMethodTable(methodDescHandle); + data->MethodTablePtr = methodTableAddr; + TypeHandle typeHandle = rtsContract.GetTypeHandle(methodTableAddr); + data->ModulePtr = rtsContract.GetModule(typeHandle); + + // TODO[cdac]: everything in the ReJIT TRY/CATCH in GetMethodDescDataImpl in request.cpp + if (pcNeededRevertedRejitData != null) + { + + throw new NotImplementedException(); // TODO[cdac]: rejit stuff + } + +#if false // TODO[cdac]: HAVE_GCCOVER + if (requestedNativeCodeVersion.Valid) + { + TargetPointer gcCoverAddr = nativeCodeContract.GetGCCoverageInfo(requestedNativeCodeVersion); + if (gcCoverAddr != TargetPointer.Null) + { + return HResults.E_NOTIMPL; // TODO[cdac]: gc stress code copy + } + } +#endif + + if (data->bIsDynamic != 0) + { + return HResults.E_NOTIMPL; // TODO[cdac]: get the dynamic method managed object + } + + return HResults.S_OK; } catch (global::System.Exception ex) { diff --git a/src/native/managed/cdacreader/src/Target.cs b/src/native/managed/cdacreader/src/Target.cs index afa989a66f4b0..3f15a4551ad0a 100644 --- a/src/native/managed/cdacreader/src/Target.cs +++ b/src/native/managed/cdacreader/src/Target.cs @@ -33,6 +33,29 @@ namespace Microsoft.Diagnostics.DataContractReader; public override int GetHashCode() => Value.GetHashCode(); } +public readonly struct TargetCodePointer : IEquatable +{ + public static TargetCodePointer Null = new(0); + public readonly ulong Value; + public TargetCodePointer(ulong value) => Value = value; + + public static implicit operator ulong(TargetCodePointer p) => p.Value; + public static implicit operator TargetCodePointer(ulong v) => new TargetCodePointer(v); + + public static bool operator ==(TargetCodePointer left, TargetCodePointer right) => left.Value == right.Value; + public static bool operator !=(TargetCodePointer left, TargetCodePointer right) => left.Value != right.Value; + + public override bool Equals(object? obj) => obj is TargetCodePointer pointer && Equals(pointer); + public bool Equals(TargetCodePointer other) => Value == other.Value; + + public override int GetHashCode() => Value.GetHashCode(); + + public bool Equals(TargetCodePointer x, TargetCodePointer y) => x.Value == y.Value; + public int GetHashCode(TargetCodePointer obj) => obj.Value.GetHashCode(); + + public TargetPointer AsTargetPointer => new(Value); +} + public readonly struct TargetNUInt { public readonly ulong Value; @@ -350,6 +373,20 @@ public TargetPointer ReadPointerFromSpan(ReadOnlySpan bytes) } } + public TargetCodePointer ReadCodePointer(ulong address) + { + TypeInfo codePointerTypeInfo = GetTypeInfo(DataType.CodePointer); + if (codePointerTypeInfo.Size is sizeof(uint)) + { + return new TargetCodePointer(Read(address)); + } + else if (codePointerTypeInfo.Size is sizeof(ulong)) + { + return new TargetCodePointer(Read(address)); + } + throw new InvalidOperationException($"Failed to read code pointer at 0x{address:x8} because CodePointer size is not 4 or 8"); + } + public void ReadPointers(ulong address, Span buffer) { // TODO(cdac) - This could do a single read, and then swizzle in place if it is useful for performance