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] Implement GCCover portion of SOSDacImpl::GetMethodDescData #110322

Merged
merged 20 commits into from
Dec 13, 2024
Merged
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to refer to it as 'GCCoverage' or 'GCStress'? I kind of think of GC coverage as the more basic feature/info that GC stress uses. I don't feel too strongly either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCCoverageInfo is a runtime type which holds state about the functions GCCoverage, including a copy of the original code before any GC points were inserted. In the runtime this field is savedCode.

The DAC type DacpMethodDescData has a field which should be populated with a pointer to the gcCoverageInfo.savedCode, GCStressCodeCopy.

My preference is to use GetGCStressCodeCopy to mimic the DAC type and because it better describes what is actually being returned.

```
### 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 is nullable as it 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.
max-charlamb marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -522,6 +525,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 @@ -535,6 +541,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 @@ -76,4 +76,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 @@ -349,16 +349,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

if (data->bIsDynamic != 0)
{
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;
}
Comment on lines +257 to +273
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous CodeVersion tests use custom "mock" classes. Now that we have Moq integrated, I used it to create the new GCStressCodeCopy tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this change, but might be nice to switch the previous/existing tests to this as well.


[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 @@ -69,6 +69,8 @@ public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch)
Assert.False(isCollectible);
TargetPointer versioning = rts.GetMethodDescVersioningState(handle);
Assert.Equal(TargetPointer.Null, versioning);
TargetPointer gcStressCodeCopy = rts.GetGCStressCodeCopy(handle);
Assert.Equal(TargetPointer.Null, gcStressCodeCopy);
}

public static IEnumerable<object[]> StdArchOptionalSlotsData()
Expand Down
Loading
Loading