Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cdac] PrecodeStubs contract #109030

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions docs/design/datacontracts/PrecodeStubs.md
Original file line number Diff line number Diff line change
@@ -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<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)
{
// 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<byte>(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);
}
```
1 change: 1 addition & 0 deletions src/coreclr/debug/runtimeinfo/contracts.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"ExecutionManager": 1,
"Loader": 1,
"Object": 1,
"PrecodeStubs": 1,
"RuntimeTypeSystem": 1,
"Thread": 1
}
27 changes: 27 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,32 @@ 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, /*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<RangeSectionMap>::TopLevelData)
Expand Down Expand Up @@ -444,6 +470,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(PrecodeMachineDescriptor, &::g_PrecodeMachineDescriptor)
CDAC_GLOBALS_END()

#undef CDAC_BASELINE
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/ceemain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
50 changes: 50 additions & 0 deletions src/coreclr/vm/precode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

36 changes: 36 additions & 0 deletions src/coreclr/vm/precode.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,7 @@ internal abstract class ContractRegistry
/// Gets an instance of the CodeVersions contract for the target.
/// </summary>
public abstract ICodeVersions CodeVersions { get; }
/// Gets an instance of the PrecodeStubs contract for the target.
/// </summary>
public abstract IPrecodeStubs PrecodeStubs { get; }
}
Loading
Loading