Skip to content

Commit

Permalink
[cDAC] Implement GCCover portion of SOSDacImpl::GetMethodDescData (#1…
Browse files Browse the repository at this point in the history
…10322)

* Add GetGCStressCodeCopy to ICodeVersions and IRuntimeTypeSystem
  • Loading branch information
max-charlamb authored Dec 13, 2024
1 parent 90dfc04 commit 87f0e70
Show file tree
Hide file tree
Showing 20 changed files with 198 additions and 11 deletions.
13 changes: 13 additions & 0 deletions docs/design/datacontracts/CodeVersions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ 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);

// Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null
public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle);
```
### Extension Methods
```csharp
Expand All @@ -61,13 +64,15 @@ Data descriptors used:
| NativeCodeVersionNode | NativeCode | indicates an explicit native code version node |
| NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below |
| NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL code version |
| NativeCodeVersionNode | GCCoverageInfo | GCStress debug info, if supported |
| ILCodeVersioningState | FirstVersionNode | pointer to the first `ILCodeVersionNode` |
| ILCodeVersioningState | ActiveVersionKind | an `ILCodeVersionKind` value indicating which fields of the active version are value |
| ILCodeVersioningState | ActiveVersionNode | if the active version is explicit, the NativeCodeVersionNode for the active version |
| ILCodeVersioningState | ActiveVersionModule | if the active version is synthetic or unknown, the pointer to the Module that defines the method |
| ILCodeVersioningState | ActiveVersionMethodDef | if the active version is synthetic or unknown, the MethodDef token for the method |
| ILCodeVersionNode | VersionId | Version ID of the node |
| ILCodeVersionNode | Next | Pointer to the next `ILCodeVersionNode`|
| GCCoverageInfo | SavedCode | Pointer to the GCCover saved code copy, if supported |

The flag indicates that the default version of the code for a method desc is active:
```csharp
Expand Down Expand Up @@ -249,3 +254,11 @@ bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddr
return true;
}
```

### Finding GCStress Code Copy
```csharp
public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle);
```

1. If `codeVersionHandle` is synthetic, use the `IRuntimeTypeSystem` to find the GCStressCodeCopy.
2. If `codeVersionHandle` is explicit, read the `NativeCodeVersionNode` for the `GCCoverageInfo` pointer. This value only exists in some builds. If the value doesn't exist or is a nullptr, return `TargetPointer.Null`. Otherwise return the `SavedCode` pointer from the `GCCoverageInfo` struct.
4 changes: 4 additions & 0 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ partial interface IRuntimeTypeSystem : IContract
// Get an instruction pointer that can be called to cause the MethodDesc to be executed
public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc);

// Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null
public virtual TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc);
}
```

Expand Down Expand Up @@ -638,6 +640,7 @@ We depend on the following data descriptors:
| `MethodDesc` | `Slot` | The method's slot |
| `MethodDesc` | `Flags` | The method's flags |
| `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID |
| `MethodDesc` | `GCCoverageInfo` | The method's GCCover debug info, if supported |
| `MethodDescCodeData` | `VersioningState` | The IL versioning state associated with a method descriptor
| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to |
| `MethodDescChunk` | `Next` | The next chunk of methods |
Expand All @@ -654,6 +657,7 @@ We depend on the following data descriptors:
| `StoredSigMethodDesc` | `cSig` | Count of bytes in the metadata signature |
| `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` |
| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc |
| `GCCoverageInfo` | `SavedCode` | Pointer to the GCCover saved code copy, if supported |


The contract depends on the following other contracts
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include "methodtable.h"
#include "threads.h"

#ifdef HAVE_GCCOVER
#include "gccover.h"
#endif // HAVE_GCCOVER

// begin blob definition

extern "C"
Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_data<MethodDesc>::Flags)
CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags3AndTokenRemainder, cdac_data<MethodDesc>::Flags3AndTokenRemainder)
CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, EntryPointFlags, cdac_data<MethodDesc>::EntryPointFlags)
CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, CodeData, cdac_data<MethodDesc>::CodeData)
#ifdef HAVE_GCCOVER
CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, GCCoverageInfo, offsetof(MethodDesc, m_GcCover))
#endif // HAVE_GCCOVER
CDAC_TYPE_END(MethodDesc)

CDAC_TYPE_BEGIN(MethodDescChunk)
Expand Down Expand Up @@ -544,6 +547,9 @@ CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data<Native
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, NativeCode, cdac_data<NativeCodeVersionNode>::NativeCode)
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*uint32*/, Flags, cdac_data<NativeCodeVersionNode>::Flags)
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*nuint*/, ILVersionId, cdac_data<NativeCodeVersionNode>::ILVersionId)
#ifdef HAVE_GCCOVER
CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, GCCoverageInfo, cdac_data<NativeCodeVersionNode>::GCCoverageInfo)
#endif // HAVE_GCCOVER
CDAC_TYPE_END(NativeCodeVersionNode)

CDAC_TYPE_BEGIN(ILCodeVersionNode)
Expand All @@ -557,6 +563,13 @@ CDAC_TYPE_BEGIN(ProfControlBlock)
CDAC_TYPE_FIELD(ProfControlBlock, /*uint64*/, GlobalEventMask, offsetof(ProfControlBlock, globalEventMask))
CDAC_TYPE_END(ProfControlBlock)

#ifdef HAVE_GCCOVER
CDAC_TYPE_BEGIN(GCCoverageInfo)
CDAC_TYPE_INDETERMINATE(GCCoverageInfo)
CDAC_TYPE_FIELD(GCCoverageInfo, /*pointer*/, SavedCode, offsetof(GCCoverageInfo, savedCode))
CDAC_TYPE_END(GCCoverageInfo)
#endif // HAVE_GCCOVER

CDAC_TYPES_END()

CDAC_GLOBALS_BEGIN()
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/vm/codeversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ struct cdac_data<NativeCodeVersionNode>
static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode);
static constexpr size_t Flags = offsetof(NativeCodeVersionNode, m_flags);
static constexpr size_t ILVersionId = offsetof(NativeCodeVersionNode, m_parentId);
#ifdef HAVE_GCCOVER
static constexpr size_t GCCoverageInfo = offsetof(NativeCodeVersionNode, m_gcCover);
#endif // HAVE_GCCOVER
};

class NativeCodeVersionCollection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ internal abstract class ContractRegistry
/// <summary>
/// Gets an instance of the ReJIT contract for the target.
/// </summary>
public abstract IReJIT ReJIT { get; }
public abstract IReJIT ReJIT { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ internal interface ICodeVersions : IContract

public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException();

public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException();

public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ internal interface IRuntimeTypeSystem : IContract
public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException();

public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException();

public virtual TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc) => throw new NotImplementedException();
#endregion MethodDesc inspection APIs
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,5 @@ public enum DataType
NonVtableSlot,
MethodImpl,
NativeCodeSlot,
GCCoverageInfo,
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,30 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion
});
}

TargetPointer ICodeVersions.GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle)
{
Debug.Assert(codeVersionHandle.Valid);

if (!codeVersionHandle.IsExplicit)
{
// NativeCodeVersion::GetGCCoverageInfo
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
MethodDescHandle md = rts.GetMethodDescHandle(codeVersionHandle.MethodDescAddress);
return rts.GetGCStressCodeCopy(md);
}
else
{
// NativeCodeVersionNode::GetGCCoverageInfo
NativeCodeVersionNode codeVersionNode = AsNode(codeVersionHandle);
if (codeVersionNode.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null)
{
Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo);
return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset;
}
return TargetPointer.Null;
}
}

[Flags]
internal enum MethodDescVersioningStateFlags : byte
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho
public bool IsUnboxingStub => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub);

public TargetPointer CodeData => _desc.CodeData;

public TargetPointer? GCCoverageInfo => _desc.GCCoverageInfo;

public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated;

internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags);
Expand Down Expand Up @@ -1012,6 +1015,17 @@ private TargetCodePointer GetMethodEntryPointIfExists(MethodDesc md)
return _target.ReadCodePointer(addrOfSlot);
}

TargetPointer IRuntimeTypeSystem.GetGCStressCodeCopy(MethodDescHandle methodDesc)
{
MethodDesc md = _methodDescs[methodDesc.Address];
if (md.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null)
{
Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo);
return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset;
}
return TargetPointer.Null;
}

private class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries
{
private readonly RuntimeTypeSystem_1 _rts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public MethodDesc(Target target, TargetPointer address)
Flags3AndTokenRemainder = target.Read<ushort>(address + (ulong)type.Fields[nameof(Flags3AndTokenRemainder)].Offset);
EntryPointFlags = target.Read<byte>(address + (ulong)type.Fields[nameof(EntryPointFlags)].Offset);
CodeData = target.ReadPointer(address + (ulong)type.Fields[nameof(CodeData)].Offset);
if (type.Fields.ContainsKey(nameof(GCCoverageInfo)))
{
GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset);
}
}

public byte ChunkIndex { get; init; }
Expand All @@ -26,7 +30,9 @@ public MethodDesc(Target target, TargetPointer address)
public ushort Flags3AndTokenRemainder { get; init; }
public byte EntryPointFlags { get; init; }

public TargetPointer CodeData { get; set; }
public TargetPointer CodeData { get; init; }

public TargetPointer? GCCoverageInfo { get; init; }
}

internal sealed class InstantiatedMethodDesc : IData<InstantiatedMethodDesc>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ public NativeCodeVersionNode(Target target, TargetPointer address)
NativeCode = target.ReadCodePointer(address + (ulong)type.Fields[nameof(NativeCode)].Offset);
Flags = target.Read<uint>(address + (ulong)type.Fields[nameof(Flags)].Offset);
ILVersionId = target.ReadNUInt(address + (ulong)type.Fields[nameof(ILVersionId)].Offset);
if (type.Fields.ContainsKey(nameof(GCCoverageInfo)))
{
GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset);
}
}

public TargetPointer Next { get; init; }
public TargetPointer MethodDesc { get; init; }

public TargetCodePointer NativeCode { get; init; }
public uint Flags{ get; init; }
public uint Flags { get; init; }
public TargetNUInt ILVersionId { get; init; }

public TargetPointer? GCCoverageInfo { get; init; }
}
12 changes: 5 additions & 7 deletions src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,16 +350,14 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes
}
}

#if false // TODO[cdac]: HAVE_GCCOVER
// HAVE_GCCOVER
if (requestedNativeCodeVersion.Valid)
{
TargetPointer gcCoverAddr = nativeCodeContract.GetGCCoverageInfo(requestedNativeCodeVersion);
if (gcCoverAddr != TargetPointer.Null)
{
throw new NotImplementedException(); // TODO[cdac]: gc stress code copy
}
// TargetPointer.Null if GCCover information is not available.
// In certain minidumps, we won't save the GCCover information.
// (it would be unwise to do so, it is heavy and not a customer scenario).
data->GCStressCodeCopy = nativeCodeContract.GetGCStressCodeCopy(requestedNativeCodeVersion);
}
#endif

// Unlike the legacy implementation, the cDAC does not currently populate
// data->managedDynamicMethodObject. This field is unused in both SOS and CLRMD
Expand Down
79 changes: 79 additions & 0 deletions src/native/managed/cdacreader/tests/CodeVersionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,24 @@ internal static Target CreateTarget(
return target;
}

internal static Target CreateTarget(
MockTarget.Architecture arch,
MockCodeVersions builder,
Mock<IRuntimeTypeSystem> mockRuntimeTypeSystem)
{
TestPlaceholderTarget target = new TestPlaceholderTarget(
arch,
builder.Builder.GetReadContext().ReadFromTarget,
builder.Types);

IContractFactory<ICodeVersions> cvfactory = new CodeVersionsFactory();
ContractRegistry reg = Mock.Of<ContractRegistry>(
c => c.CodeVersions == cvfactory.CreateContract(target, 1)
&& c.RuntimeTypeSystem == mockRuntimeTypeSystem.Object);
target.SetContracts(reg);
return target;
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetNativeCodeVersion_Null(MockTarget.Architecture arch)
Expand Down Expand Up @@ -678,4 +696,65 @@ public void IlToNativeToIlCodeVersion_SyntheticAndExplicit(MockTarget.Architectu
NativeCodeVersionHandle syntheticNativeCodeVersion = codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, syntheticILcodeVersion);
Assert.True(syntheticILcodeVersion.Equals(codeVersions.GetILCodeVersion(syntheticNativeCodeVersion)));
}

private void GetGCStressCodeCopy_Impl(MockTarget.Architecture arch, bool returnsNull)
{
MockCodeVersions builder = new(arch);
Mock<IRuntimeTypeSystem> mockRTS = new();

// Setup synthetic NativeCodeVersion
TargetPointer expectedSyntheticCodeCopyAddr = returnsNull ? TargetPointer.Null : new(0x2345_6789);
TargetPointer syntheticMethodDescAddr = new(0x2345_8000);
NativeCodeVersionHandle syntheticHandle = NativeCodeVersionHandle.CreateSynthetic(syntheticMethodDescAddr);
MethodDescHandle methodDescHandle = new MethodDescHandle(syntheticMethodDescAddr);
mockRTS.Setup(rts => rts.GetMethodDescHandle(syntheticMethodDescAddr)).Returns(methodDescHandle);
mockRTS.Setup(rts => rts.GetGCStressCodeCopy(methodDescHandle)).Returns(expectedSyntheticCodeCopyAddr);

// Setup explicit NativeCodeVersion
TargetPointer? explicitGCCoverageInfoAddr = returnsNull ? TargetPointer.Null : new(0x1234_5678);
TargetPointer nativeCodeVersionNode = builder.AddNativeCodeVersionNode();
builder.FillNativeCodeVersionNode(
nativeCodeVersionNode,
methodDesc: TargetPointer.Null,
nativeCode: TargetCodePointer.Null,
next: TargetPointer.Null,
isActive: true,
ilVersionId: new(1),
gcCoverageInfo: explicitGCCoverageInfoAddr);
NativeCodeVersionHandle explicitHandle = NativeCodeVersionHandle.CreateExplicit(nativeCodeVersionNode);

var target = CreateTarget(arch, builder, mockRTS);
var codeVersions = target.Contracts.CodeVersions;

// TEST
TargetPointer actualSyntheticCodeCopyAddr = codeVersions.GetGCStressCodeCopy(syntheticHandle);
Assert.Equal(expectedSyntheticCodeCopyAddr, actualSyntheticCodeCopyAddr);

if(returnsNull)
{
TargetPointer actualExplicitCodeCopyAddr = codeVersions.GetGCStressCodeCopy(explicitHandle);
Assert.Equal(TargetPointer.Null, actualExplicitCodeCopyAddr);
}
else
{
Target.TypeInfo gcCoverageInfoType = target.GetTypeInfo(DataType.GCCoverageInfo);
TargetPointer expectedExplicitCodeCopyAddr = explicitGCCoverageInfoAddr.Value + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset;
TargetPointer actualExplicitCodeCopyAddr = codeVersions.GetGCStressCodeCopy(explicitHandle);
Assert.Equal(expectedExplicitCodeCopyAddr, actualExplicitCodeCopyAddr);
}
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetGCStressCodeCopy_Null(MockTarget.Architecture arch)
{
GetGCStressCodeCopy_Impl(arch, returnsNull: true);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void GetGCStressCodeCopy_NotNull(MockTarget.Architecture arch)
{
GetGCStressCodeCopy_Impl(arch, returnsNull: false);
}
}
2 changes: 2 additions & 0 deletions src/native/managed/cdacreader/tests/MethodDescTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public void GetMethodDescHandle_ILMethod_GetBasicData(MockTarget.Architecture ar
Assert.False(isCollectible);
TargetPointer versioning = rts.GetMethodDescVersioningState(handle);
Assert.Equal(TargetPointer.Null, versioning);
TargetPointer gcStressCodeCopy = rts.GetGCStressCodeCopy(handle);
Assert.Equal(TargetPointer.Null, gcStressCodeCopy);

// Method classification - IL method
Assert.False(rts.IsStoredSigMethodDesc(handle, out _));
Expand Down
Loading

0 comments on commit 87f0e70

Please sign in to comment.