Skip to content

Commit

Permalink
[cdac] PrecodeStubs and CDacMetadata contracts (dotnet#109030)
Browse files Browse the repository at this point in the history
Adds a `PrecodeStubs` contract for working with precode.
Adds a `CDacMetadata` contract for exposing useful platform-specific properties

Contributes to dotnet#99302 
Contributes to dotnet#108553 

* [cdac] PrecodeStubs contract
* move PrecodeStubs to a separate contract
* remove instance field initialization from PrecodeMachineDescriptor
* PrecodeMachineDescriptor::Init() will initialize g_PrecodeMachineDescriptor
* fix thumb flag logic
* add FixupPrecodeData
* checkpoint: StubPrecodeData, StubCodePageSize
* checkpoint: PrecodeMachineDescriptor and KnownPrecodeType
* WIP: start adding PrecodeStubs test infrastructure
* checkpoint: stub precode test passing
* set the "thumb" bit
* cleanup thumb bit
* add a CDacMetadata struct
* permute PrecodeMachineDescriptor fields for better packability
* reader for CDacMetadata contract
* WIP: code pointer flags
* checkpoint tests with CDacMetadata
* rename CDacMetadata -> PlatformMetadata
* add a define for Loongarch64 stub precode shift
* conditionally define fields for precode absent precode types
* clarify how stub and pinvoke precodes work

---------

Co-authored-by: Elinor Fung <[email protected]>
  • Loading branch information
2 people authored and mikelle-rogers committed Dec 4, 2024
1 parent 18fcbcb commit 0795a6d
Show file tree
Hide file tree
Showing 30 changed files with 1,230 additions and 2 deletions.
52 changes: 52 additions & 0 deletions docs/design/datacontracts/PlatformMetadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Contract PlatformMetadata

This contract exposes properties that describe the target platform

## APIs of contract

```csharp
internal enum CodePointerFlags : byte
{
// Set if the target process is executing on arm32
HasArm32ThumbBit = 0x1,
// Set if arm64e pointer authentication is used in the target process
HasArm64PtrAuth = 0x2,
}
// Returns a pointer to a structure describing platform-specific precode stubs properties
TargetPointer GetPrecodeMachineDescriptor();

// Returns flags describing the behavior of code pointers
CodePointerFlags GetCodePointerFlags();
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| PlatformMetadata | PrecodeMachineDescriptor | precode stub-related platform specific properties |
| PlatformMetadata | CodePointerFlags | fields describing the behavior of target code pointers |

Global variables used:
| Global Name | Type | Purpose |
| --- | --- | --- |
| PlatformMetadata | pointer | address of the `PlatformMetadata` data |

Contracts used:
| Contract Name |
| --- |
| *none* |

```csharp
TargetPointer GetPrecodeMachineDescriptor()
{
TargetPointer metadataAddress = _target.ReadGlobalPointer("PlatformMetadata");
return metadataAddress + /* PlatformMetadata::PrecodeMachineDescriptor */
}

CodePointerFlags GetCodePointerFlags()
{
TargetPointer metadataAddress = _target.ReadGlobalPointer("PlatformMetadata");
return (CodePointerFlags)_target.Read<byte>(metadataAddress + /*PlatformMetadata::CodePointerFlags*/);
}
```
213 changes: 213 additions & 0 deletions docs/design/datacontracts/PrecodeStubs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Contract PrecodeStubs

This contract provides support for examining [precode](../coreclr/botr/method-descriptor.md#precode): small fragments of code used to implement temporary entry points and an efficient wrapper for stubs.

## APIs of contract

```csharp
// Gets a pointer to the MethodDesc for a given stub entrypoint
TargetPointer GetMethodDescFromStubAddress(TargetCodePointer entryPoint);
```

## Version 1

Data descriptors used:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| PrecodeMachineDescriptor | OffsetOfPrecodeType | See `ReadPrecodeType` |
| PrecodeMachineDescriptor | ShiftOfPrecodeType | See `ReadPrecodeType` |
| PrecodeMachineDescriptor | ReadWidthOfPrecodeType | See `ReadPrecodeType` |
| PrecodeMachineDescriptor | StubCodePageSize | Size of a precode code page (in bytes) |
| PrecodeMachineDescriptor | CodePointerToInstrPointerMask | mask to apply to code pointers to get an address (see arm32 note)
| PrecodeMachineDescriptor | StubPrecodeType | precode sort byte for stub precodes |
| PrecodeMachineDescriptor | HasPInvokeImportPrecode | 1 if platform supports PInvoke precode stubs |
| PrecodeMachineDescriptor | PInvokeImportPrecodeType| precode sort byte for PInvoke precode stubs, if supported |
| PrecodeMachineDescriptor | HasFixupPrecode | 1 if platform supports fixup precode stubs |
| PrecodeMachineDescriptor | FixupPrecodeType| precode sort byte for fixup precode stubs, if supported |
| StubPrecodeData | MethodDesc | pointer to the MethodDesc associated with this stub precode |
| StubPrecodeData | Type | precise sort of stub precode |
| FixupPrecodeData | MethodDesc | pointer to the MethodDesc associated with this fixup precode |

arm32 note: the `CodePointerToInstrPointerMask` is used to convert IP values that may include an arm Thumb bit (for example extracted from disassembling a call instruction or from a snapshot of the registers) into an address. On other architectures applying the mask is a no-op.


Global variables used:
| Global Name | Type | Purpose |
| --- | --- | --- |
| PrecodeMachineDescriptor | pointer | address of the `PrecodeMachineDescriptor` data |

Contracts used:
| Contract Name |
| --- |
| `PlatformMetadata` |

### Determining the precode type

An initial approximation of the precode type relies on a particular pattern at a known offset from the precode entrypoint.
The precode type is expected to be encoded as an immediate. On some platforms the value is spread over multiple instruction bytes and may need to be right-shifted.

```
private byte ReadPrecodeType(TargetPointer instrPointer)
{
if (MachineDescriptor.ReadWidthOfPrecodeType == 1)
{
byte precodeType = _target.Read<byte>(instrPointer + MachineDescriptor.OffsetOfPrecodeType);
return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType);
}
else if (MachineDescriptor.ReadWidthOfPrecodeType == 2)
{
ushort precodeType = _target.Read<ushort>(instrPointer + MachineDescriptor.OffsetOfPrecodeType);
return (byte)(precodeType >> MachineDescriptor.ShiftOfPrecodeType);
}
else
{
throw new InvalidOperationException($"Invalid precode type width {MachineDescriptor.ReadWidthOfPrecodeType}");
}
}
```

After the initial precode type is determined, for stub precodes a refined precode type is extracted from the stub precode data.

```csharp
private KnownPrecodeType? TryGetKnownPrecodeType(TargetPointer instrAddress)
{
// We get the precode type in two phases:
// 1. Read the precode type from the intruction address.
// 2. If it's "stub", look at the stub data and get the actual precode type - it could be stub,
// but it could also be a pinvoke precode
// precode.h Precode::GetType()
byte approxPrecodeType = ReadPrecodeType(instrAddress);
byte exactPrecodeType;
if (approxPrecodeType == MachineDescriptor.StubPrecodeType)
{
// get the actual type from the StubPrecodeData
Data.StubPrecodeData stubPrecodeData = GetStubPrecodeData(instrAddress);
exactPrecodeType = stubPrecodeData.Type;
}
else
{
exactPrecodeType = approxPrecodeType;
}

if (exactPrecodeType == MachineDescriptor.StubPrecodeType)
{
return KnownPrecodeType.Stub;
}
else if (MachineDescriptor.PInvokeImportPrecodeType is byte ndType && exactPrecodeType == ndType)
{
return KnownPrecodeType.PInvokeImport;
}
else if (MachineDescriptor.FixupPrecodeType is byte fixupType && exactPrecodeType == fixupType)
{
return KnownPrecodeType.Fixup;
}
else if (MachineDescriptor.ThisPointerRetBufPrecodeType is byte thisPtrRetBufType && exactPrecodeType == thisPtrRetBufType)
{
return KnownPrecodeType.ThisPtrRetBuf;
}
else
{
return null;
}
}
```

### `MethodDescFromStubAddress`

```csharp
internal enum KnownPrecodeType
{
Stub = 1,
PInvokeImport, // also known as NDirectImport in the runtime
Fixup,
ThisPtrRetBuf,
}

internal abstract class ValidPrecode
{
public TargetPointer InstrPointer { get; }
public KnownPrecodeType PrecodeType { get; }

protected ValidPrecode(TargetPointer instrPointer, KnownPrecodeType precodeType)
{
InstrPointer = instrPointer;
PrecodeType = precodeType;
}

internal abstract TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor);

}

internal class StubPrecode : ValidPrecode
{
internal StubPrecode(TargetPointer instrPointer, KnownPrecodeType type = KnownPrecodeType.Stub) : base(instrPointer, type) { }

internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor)
{
TargetPointer stubPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize;
return target.ReadPointer (stubPrecodeDataAddress + /* offset of StubPrecodeData.MethodDesc */ );
}
}

internal sealed class PInvokeImportPrecode : StubPrecode
{
internal PInvokeImportPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.PInvokeImport) { }
}

internal sealed class FixupPrecode : ValidPrecode
{
internal FixupPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.Fixup) { }
internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor)
{
TargetPointer fixupPrecodeDataAddress = InstrPointer + precodeMachineDescriptor.StubCodePageSize;
return target.ReadPointer (fixupPrecodeDataAddress + /* offset of FixupPrecodeData.MethodDesc */);
}
}

internal sealed class ThisPtrRetBufPrecode : ValidPrecode
{
internal ThisPtrRetBufPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.ThisPtrRetBuf) { }

internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachineDescriptor precodeMachineDescriptor)
{
throw new NotImplementedException(); // TODO(cdac)
}
}

internal TargetPointer CodePointerReadableInstrPointer(TargetCodePointer codePointer)
{
// Mask off the thumb bit, if we're on arm32, to get the actual instruction pointer
ulong instrPointer = (ulong)codePointer.AsTargetPointer & MachineDescriptor.CodePointerToInstrPointerMask.Value;
return new TargetPointer(instrPointer);
}


internal ValidPrecode GetPrecodeFromEntryPoint(TargetCodePointer entryPoint)
{
TargetPointer instrPointer = CodePointerReadableInstrPointer(entryPoint);
if (IsAlignedInstrPointer(instrPointer) && TryGetKnownPrecodeType(instrPointer) is KnownPrecodeType precodeType)
{
switch (precodeType)
{
case KnownPrecodeType.Stub:
return new StubPrecode(instrPointer);
case KnownPrecodeType.Fixup:
return new FixupPrecode(instrPointer);
case KnownPrecodeType.PInvokeImport:
return new PInvokeImportPrecode(instrPointer);
case KnownPrecodeType.ThisPtrRetBuf:
return new ThisPtrRetBufPrecode(instrPointer);
default:
break;
}
}
throw new InvalidOperationException($"Invalid precode type 0x{instrPointer:x16}");
}

TargetPointer IPrecodeStubs.GetMethodDescFromStubAddress(TargetCodePointer entryPoint)
{
ValidPrecode precode = GetPrecodeFromEntryPoint(entryPoint);

return precode.GetMethodDesc(_target, MachineDescriptor);
}
```
1 change: 1 addition & 0 deletions src/coreclr/debug/runtimeinfo/contractpointerdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <stddef.h>
#include <stdint.h>

#include "cdacplatformmetadata.hpp"
#include "threads.h"
#include "vars.hpp"

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/debug/runtimeinfo/contracts.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"ExecutionManager": 1,
"Loader": 1,
"Object": 1,
"PlatformMetadata": 1,
"PrecodeStubs": 1,
"RuntimeTypeSystem": 1,
"Thread": 1
}
1 change: 1 addition & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "static_assert.h"

#include <sospriv.h>
#include "cdacplatformmetadata.hpp"
#include "methodtable.h"
#include "threads.h"

Expand Down
37 changes: 37 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,42 @@ CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, c
CDAC_TYPE_FIELD(MethodDescVersioningState, /*uint8*/, Flags, cdac_data<MethodDescVersioningState>::Flags)
CDAC_TYPE_END(MethodDescVersioningState)

CDAC_TYPE_BEGIN(PrecodeMachineDescriptor)
CDAC_TYPE_INDETERMINATE(PrecodeMachineDescriptor)
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, ReadWidthOfPrecodeType, offsetof(PrecodeMachineDescriptor, ReadWidthOfPrecodeType))
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, ShiftOfPrecodeType, offsetof(PrecodeMachineDescriptor, ShiftOfPrecodeType))
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, OffsetOfPrecodeType, offsetof(PrecodeMachineDescriptor, OffsetOfPrecodeType))
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, InvalidPrecodeType, offsetof(PrecodeMachineDescriptor, InvalidPrecodeType))
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, StubPrecodeType, offsetof(PrecodeMachineDescriptor, StubPrecodeType))
#ifdef HAS_NDIRECT_IMPORT_PRECODE
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, PInvokeImportPrecodeType, offsetof(PrecodeMachineDescriptor, PInvokeImportPrecodeType))
#endif
#ifdef HAS_FIXUP_PRECODE
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, FixupPrecodeType, offsetof(PrecodeMachineDescriptor, FixupPrecodeType))
#endif
#ifdef HAS_THISPTR_RETBUF_PRECODE
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint8*/, ThisPointerRetBufPrecodeType, offsetof(PrecodeMachineDescriptor, ThisPointerRetBufPrecodeType))
#endif
CDAC_TYPE_FIELD(PrecodeMachineDescriptor, /*uint32*/, StubCodePageSize, offsetof(PrecodeMachineDescriptor, StubCodePageSize))
CDAC_TYPE_END(PrecodeMachineDescriptor)

CDAC_TYPE_BEGIN(PlatformMetadata)
CDAC_TYPE_INDETERMINATE(PlatformMetadata)
CDAC_TYPE_FIELD(PlatformMetadata, /*PrecodeMachineDescriptor*/, PrecodeMachineDescriptor, offsetof(CDacPlatformMetadata, precode))
CDAC_TYPE_FIELD(PlatformMetadata, /*uint8*/, CodePointerFlags, offsetof(CDacPlatformMetadata, codePointerFlags))
CDAC_TYPE_END(PlatformMetadata)

CDAC_TYPE_BEGIN(StubPrecodeData)
CDAC_TYPE_INDETERMINATE(StubPrecodeData)
CDAC_TYPE_FIELD(StubPrecodeData, /*pointer*/, MethodDesc, offsetof(StubPrecodeData, MethodDesc))
CDAC_TYPE_FIELD(StubPrecodeData, /*uint8*/, Type, offsetof(StubPrecodeData, Type))
CDAC_TYPE_END(StubPrecodeData)

CDAC_TYPE_BEGIN(FixupPrecodeData)
CDAC_TYPE_INDETERMINATE(FixupPrecodeData)
CDAC_TYPE_FIELD(FixupPrecodeData, /*pointer*/, MethodDesc, offsetof(FixupPrecodeData, MethodDesc))
CDAC_TYPE_END(FixupPrecodeData)

CDAC_TYPE_BEGIN(RangeSectionMap)
CDAC_TYPE_INDETERMINATE(RangeSectionMap)
CDAC_TYPE_FIELD(RangeSectionMap, /*pointer*/, TopLevelData, cdac_data<RangeSectionMap>::TopLevelData)
Expand Down Expand Up @@ -444,6 +480,7 @@ CDAC_GLOBAL_POINTER(SyncTableEntries, &::g_pSyncTable)
CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress)
CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize)
CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data<ExecutionManager>::CodeRangeMapAddress)
CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata)
CDAC_GLOBALS_END()

#undef CDAC_BASELINE
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON
bundle.cpp
castcache.cpp
callcounting.cpp
cdacplatformmetadata.cpp
ceeload.cpp
class.cpp
classhash.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/cdacdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//
// This struct enables exposing information that is private to a class to the cDAC. For example,
// if class C has private information that must be provided, declare cdac_data<D> as a friend of C
// where D is the specialization of cdac_data that will expose the information. Then provide a
// where D is the specialization of cdac_data that will expose the information. Then provide a
// specialization cdac_data<D> with constexpr members exposing the information.
//
// Note: in the common case, type D will be type C.
Expand Down
22 changes: 22 additions & 0 deletions src/coreclr/vm/cdacplatformmetadata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "cdacplatformmetadata.hpp"

#ifndef DACCESS_COMPILE
CDacPlatformMetadata g_cdacPlatformMetadata;

void CDacPlatformMetadata::Init()
{
PrecodeMachineDescriptor::Init(&g_cdacPlatformMetadata.precode);
#if defined(TARGET_ARM)
g_cdacPlatformMetadata.codePointerFlags = CDacCodePointerFlags::HasArm32ThumbBit;
#elif defined(TARGET_ARM64) && defined(TARGET_OSX)
// TODO set HasArm64PtrAuth if arm64e
g_cdacPlatformMetadata.codePointerFlags = CDacCodePointerFlags::None;
#else
g_cdacPlatformMetadata.codePointerFlags = CDacCodePointerFlags::None;
#endif
}

#endif // !DACCESS_COMPILE
Loading

0 comments on commit 0795a6d

Please sign in to comment.