diff --git a/docs/design/datacontracts/DebugInfo.md b/docs/design/datacontracts/DebugInfo.md index 33c837b655dc97..fd5539bdd6a5ae 100644 --- a/docs/design/datacontracts/DebugInfo.md +++ b/docs/design/datacontracts/DebugInfo.md @@ -337,6 +337,7 @@ public readonly struct DebugVarInfo public uint VarNumber { get; init; } public DebugVarLocKind Kind { get; init; } public bool IsByRef { get; init; } + public bool IsFloatingPoint { get; init; } public uint Register { get; init; } public uint Register2 { get; init; } public uint BaseRegister { get; init; } diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 1850b20f30c382..2563b28595b751 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -232,9 +232,6 @@ partial interface IRuntimeTypeSystem : IContract // A no metadata method is also a StoredSigMethodDesc public virtual bool IsNoMetadataMethod(MethodDescHandle methodDesc, out string methodName); - // A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata. - public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan signature); - // Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class // A DynamicMethod is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsDynamicMethod(MethodDescHandle methodDesc); @@ -257,6 +254,9 @@ partial interface IRuntimeTypeSystem : IContract // Corresponds to native MethodDesc::IsAsyncMethod(). public virtual bool IsAsyncMethod(MethodDescHandle methodDesc); + // Return true and the raw signature bytes for a MethodDesc, or false if no signature could be resolved. + public virtual bool TryGetMethodSignature(MethodDescHandle methodDesc, out ReadOnlySpan signature); + // Return true if a MethodDesc is in a collectible module public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc); @@ -1332,6 +1332,10 @@ 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 | +| `AsyncMethodData` | `Flags` | Async method flags | +| `AsyncMethodData` | `Signature` | The async variant's signature (see `Signature`) | +| `Signature` | `SignaturePointer` | Pointer to the raw signature blob | +| `Signature` | `SignatureLength` | Count of bytes in the raw signature blob | | `GCCoverageInfo` | `SavedCode` | Pointer to the GCCover saved code copy, if supported | The following data descriptor types are used only for their sizes when computing the total size of a `MethodDesc` instance. @@ -1595,6 +1599,50 @@ And the various apis are implemented with the following algorithms return 0x06000000 | tokenRange | tokenRemainder; } + public bool TryGetMethodSignature(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + // Dynamic, EEImpl and Array methods store their signature directly on the MethodDesc. + MethodClassification classification = (MethodClassification)(methodDesc.Flags & MethodDescFlags.ClassificationMask); + if (classification is MethodClassification.Dynamic or MethodClassification.EEImpl or MethodClassification.Array) + { + signature = // StoredSigMethodDesc.Signature blob for methodDesc + return true; + } + + // Async variant methods carry their signature in the AsyncMethodData. + if (HasFlag(methodDesc, MethodDescFlags.HasAsyncMethodData)) + { + AsyncMethodData asyncData = // Read AsyncMethodData for methodDesc + if (((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.IsAsyncVariant)) + { + signature = // Read asyncData.Signature.SignatureLength bytes from asyncData.Signature.SignaturePointer + return true; + } + } + + // Otherwise resolve the signature from ECMA metadata using the MethodDef token. + uint token = GetMethodToken(methodDescHandle); + if (EcmaMetadataUtils.GetRowId(token) == 0) + { + signature = default; + return false; + } + + MetadataReader? mdReader = // Get metadata reader for the method's module, or null if unavailable + if (mdReader is null) + { + signature = default; + return false; + } + + MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token)); + MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); + signature = mdReader.GetBlobBytes(methodDef.Signature); + return true; + } + public uint GetMethodDescSize(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -1667,30 +1715,6 @@ And the various apis are implemented with the following algorithms return true; } - public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) - { - MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - - switch (methodDesc.Classification) - { - case MethodDescClassification.Dynamic: - case MethodDescClassification.EEImpl: - case MethodDescClassification.Array: - break; // These have stored sigs - - default: - signature = default; - return false; - } - - TargetPointer Sig = // Read Sig field from StoredSigMethodDesc contract using address methodDescHandle.Address - uint cSig = // Read cSig field from StoredSigMethodDesc contract using address methodDescHandle.Address - - TargetPointer methodNamePointer = // Read S field from DynamicMethodDesc contract using address methodDescHandle.Address - signature = // Read buffer from target memory starting at address Sig, with cSig bytes in it. - return true; - } - public bool IsDynamicMethod(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; diff --git a/docs/design/datacontracts/Signature.md b/docs/design/datacontracts/Signature.md index 6d91152e913c1d..1af22ad18e4dc1 100644 --- a/docs/design/datacontracts/Signature.md +++ b/docs/design/datacontracts/Signature.md @@ -31,8 +31,9 @@ Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | `VASigCookie` | `SizeOfArgs` | Total size in bytes of the pushed argument list. Used on x86 to locate the args base. | -| `VASigCookie` | `SignaturePointer` | Target address of the raw vararg signature blob. | -| `VASigCookie` | `SignatureLength` | Length in bytes of the raw vararg signature blob. | +| `VASigCookie` | `Signature` | The raw vararg signature (see `Signature`). | +| `Signature` | `SignaturePointer` | Target address of the raw signature blob. | +| `Signature` | `SignatureLength` | Length in bytes of the raw signature blob. | Global variables used: | Global Name | Type | Purpose | @@ -104,7 +105,7 @@ void ISignature.GetVarArgSignature(TargetPointer vaSigCookieAddr, out TargetPoin TargetPointer vaSigCookie = _target.ReadPointer(vaSigCookieAddr); VASigCookie cookie = _target.ProcessedData.GetOrAdd(vaSigCookie); - signatureAddress = cookie.SignaturePointer; - signatureLength = cookie.SignatureLength; + signatureAddress = cookie.Signature.SignaturePointer; + signatureLength = cookie.Signature.SignatureLength; } ``` diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 9b44cbd886a896..c3cfa0796655c3 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -756,8 +756,14 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::SetCompilerFlags(VMPTR_Assembly v // sequence points and var info //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// Initialize the native/IL sequence points and native var info for a function. -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddress, BOOL fCodeAvailable, OUT NativeVarData * pNativeVarData, OUT SequencePoints * pSequencePoints) +// Allocator to pass to the debug-info-stores... +static BYTE* InfoStoreNew(void * pData, size_t cBytes) +{ + return new (nothrow) BYTE[cBytes]; +} + +// Get the native/IL sequence points and native var info for a function via callbacks. +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddress, BOOL fCodeAvailable, OUT ULONG32 * pFixedArgCount, FP_NATIVEVARINFO_CALLBACK fpVarInfoCallback, FP_SEQUENCEPOINT_CALLBACK fpSeqPointCallback, CALLBACK_DATA pUserData) { DD_ENTER_MAY_THROW; @@ -771,11 +777,46 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVar _ASSERTE(fCodeAvailable != 0); - // get information about the locations of arguments and local variables - GetNativeVarData(pMD, startAddress, GetArgCount(pMD), pNativeVarData); + // Return the fixed argument count + if (pFixedArgCount != NULL) + { + *pFixedArgCount = (ULONG32)GetArgCount(pMD); + } + + DebugInfoRequest request; + request.InitFromStartingAddr(pMD, CORDB_ADDRESS_TO_TADDR(startAddress)); + + // Retrieve sequence points and native variable info in a single request + NewArrayHolder mapCopy(NULL); + NewArrayHolder nativeVars(NULL); + ULONG32 seqEntryCount = 0; + ULONG32 varEntryCount = 0; + + BOOL success = DebugInfoManager::GetBoundariesAndVars(request, + InfoStoreNew, NULL, + BoundsType::Uninstrumented, + &seqEntryCount, &mapCopy, + &varEntryCount, &nativeVars); + if (!success) + ThrowHR(E_FAIL); + + // Invoke the native variable info callback for each entry + if (fpVarInfoCallback != NULL) + { + for (ULONG32 i = 0; i < varEntryCount; i++) + { + fpVarInfoCallback(&nativeVars[i], pUserData); + } + } - // get the sequence points - GetSequencePoints(pMD, startAddress, pSequencePoints); + // Invoke the sequence point callback for each entry + if (fpSeqPointCallback != NULL) + { + for (ULONG32 i = 0; i < seqEntryCount; i++) + { + fpSeqPointCallback(&mapCopy[i], pUserData); + } + } } EX_CATCH_HRESULT(hr); @@ -829,108 +870,6 @@ SIZE_T DacDbiInterfaceImpl::GetArgCount(MethodDesc * pMD) return NumArguments; } //GetArgCount -// Allocator to pass to the debug-info-stores... -BYTE* InfoStoreNew(void * pData, size_t cBytes) -{ - return new BYTE[cBytes]; -} - -//----------------------------------------------------------------------------- -// Get locations and code offsets for local variables and arguments in a function -// This information is used to find the location of a value at a given IP. -// Arguments: -// input: -// pMethodDesc pointer to the method desc for the function -// startAddr starting address of the function--used to differentiate -// EnC versions -// fixedArgCount number of fixed arguments to the function -// output: -// pVarInfo data structure containing a list of variable and -// argument locations by range of IP offsets -// Note: this function may throw -//----------------------------------------------------------------------------- -void DacDbiInterfaceImpl::GetNativeVarData(MethodDesc * pMethodDesc, - CORDB_ADDRESS startAddr, - SIZE_T fixedArgCount, - NativeVarData * pVarInfo) -{ - // make sure we haven't done this already - if (pVarInfo->IsInitialized()) - { - return; - } - - NewArrayHolder nativeVars(NULL); - - DebugInfoRequest request; - request.InitFromStartingAddr(pMethodDesc, CORDB_ADDRESS_TO_TADDR(startAddr)); - - ULONG32 entryCount; - - BOOL success = DebugInfoManager::GetBoundariesAndVars(request, - InfoStoreNew, NULL, // allocator - BoundsType::Instrumented, - NULL, NULL, - &entryCount, &nativeVars); - - if (!success) - ThrowHR(E_FAIL); - - // set key fields of pVarInfo - pVarInfo->InitVarDataList(nativeVars, (int)fixedArgCount, (int)entryCount); -} // GetNativeVarData - - - - -//----------------------------------------------------------------------------- -// Get the native/IL sequence points for a function -// Arguments: -// input: -// pMethodDesc pointer to the method desc for the function -// startAddr starting address of the function--used to differentiate -// output: -// pNativeMap data structure containing a list of sequence points -// Note: this function may throw -//----------------------------------------------------------------------------- -void DacDbiInterfaceImpl::GetSequencePoints(MethodDesc * pMethodDesc, - CORDB_ADDRESS startAddr, - SequencePoints * pSeqPoints) -{ - - // make sure we haven't done this already - if (pSeqPoints->IsInitialized()) - { - return; - } - - // Use the DebugInfoStore to get IL->Native maps. - // It doesn't matter whether we're jitted, ngenned etc. - DebugInfoRequest request; - request.InitFromStartingAddr(pMethodDesc, CORDB_ADDRESS_TO_TADDR(startAddr)); - - - // Bounds info. - NewArrayHolder mapCopy(NULL); - - ULONG32 entryCount; - BOOL success = DebugInfoManager::GetBoundariesAndVars(request, - InfoStoreNew, - NULL, // allocator - BoundsType::Uninstrumented, - &entryCount, &mapCopy, - NULL, NULL); - if (!success) - ThrowHR(E_FAIL); - - pSeqPoints->InitSequencePoints(entryCount); - - // mapCopy and pSeqPoints have elements of different types. Thus, we - // need to copy the individual members from the elements of mapCopy to the - // elements of pSeqPoints. Once we're done, we can release mapCopy - pSeqPoints->CopyAndSortSequencePoints(mapCopy); - -} // GetSequencePoints //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Function Data diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index 23a3095166821d..a882d43fe89fca 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -102,7 +102,7 @@ class DacDbiInterfaceImpl : // Initialize the native/IL sequence points and native var info for a function. - HRESULT STDMETHODCALLTYPE GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddress, BOOL fCodeAvailable, OUT NativeVarData * pNativeVarData, OUT SequencePoints * pSequencePoints); + HRESULT STDMETHODCALLTYPE GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddress, BOOL fCodeAvailable, OUT ULONG32 * pFixedArgCount, FP_NATIVEVARINFO_CALLBACK fpVarInfoCallback, FP_SEQUENCEPOINT_CALLBACK fpSeqPointCallback, CALLBACK_DATA pUserData); HRESULT STDMETHODCALLTYPE IsThreadSuspendedOrHijacked(VMPTR_Thread vmThread, OUT BOOL * pResult); @@ -159,17 +159,6 @@ class DacDbiInterfaceImpl : // Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer. SIZE_T GetArgCount(MethodDesc * pMD); - // Get locations and code offsets for local variables and arguments in a function - void GetNativeVarData(MethodDesc * pMethodDesc, - CORDB_ADDRESS startAddr, - SIZE_T fixedArgCount, - NativeVarData * pVarInfo); - - // Get the native/IL sequence points for a function - void GetSequencePoints(MethodDesc * pMethodDesc, - CORDB_ADDRESS startAddr, - SequencePoints * pNativeMap); - public: //---------------------------------------------------------------------------------- // class MapSortILMap: A template class that will sort an array of DebuggerILToNativeMap. diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 052dc4413676da..c261d88df78c6b 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -4771,11 +4771,42 @@ void CordbNativeCode::LoadNativeInfo() if (m_fCodeAvailable) { RSLockHolder lockHolder(pProcess->GetProcessLock()); - IfFailThrow(pProcess->GetDAC()->GetNativeCodeSequencePointsAndVarInfo(GetVMNativeCodeMethodDescToken(), - GetAddress(), - m_fCodeAvailable, - &m_nativeVarData, - &m_sequencePoints)); + + struct CallbackData + { + CallbackAccumulator varInfos; + CallbackAccumulator seqPoints; + }; + + CallbackData data; + + ULONG32 fixedArgCount = 0; + IfFailThrow(pProcess->GetDAC()->GetNativeCodeSequencePointsAndVarInfo( + GetVMNativeCodeMethodDescToken(), + GetAddress(), + m_fCodeAvailable, + &fixedArgCount, + [](ICorDebugInfo::NativeVarInfo *pVarInfo, void *pUserData) + { + static_cast(pUserData)->varInfos.Push(*pVarInfo); + }, + [](ICorDebugInfo::OffsetMapping *pMapping, void *pUserData) + { + static_cast(pUserData)->seqPoints.Push(*pMapping); + }, + &data)); + IfFailThrow(data.varInfos.hrError); + IfFailThrow(data.seqPoints.hrError); + + // Initialize native var data from collected entries + m_nativeVarData.InitVarDataList(data.varInfos.items.Ptr(), (int)fixedArgCount, (int)data.varInfos.items.Size()); + + // Initialize sequence points from collected entries + m_sequencePoints.InitSequencePoints((ULONG32)data.seqPoints.items.Size()); + if (data.seqPoints.items.Size() > 0) + { + m_sequencePoints.CopyAndSortSequencePoints(data.seqPoints.items.Ptr()); + } } } // CordbNativeCode::LoadNativeInfo diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 2b43a2cfa8eb66..08f5316815f6f6 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1013,22 +1013,27 @@ IDacDbiInterface : public IUnknown // for a function. // Arguments: // input: - // vmMethodDesc MethodDesc of the function - // startAddr starting address of the function--this serves to - // differentiate various EnC versions of the function + // vmMethodDesc MethodDesc of the function + // startAddr starting address of the function--this serves to + // differentiate various EnC versions of the function // fCodeAvailable + // fpVarInfoCallback callback invoked once per native variable info entry + // fpSeqPointCallback callback invoked once per sequence point entry + // pUserData user data passed to the callbacks // output: - // pNativeVarData space for the native code offset information for locals - // pSequencePoints space for the IL/native sequence points + // pFixedArgCount number of fixed arguments (explicit args + this) // Return value: // S_OK on success; otherwise, an appropriate failure HRESULT. // Assumptions: - // vmMethodDesc, pNativeVarInfo and pSequencePoints are non-NULL + // vmMethodDesc is non-NULL // Notes: //----------------------------------------------------------------------------- - virtual HRESULT STDMETHODCALLTYPE GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddress, BOOL fCodeAvailable, OUT NativeVarData * pNativeVarData, OUT SequencePoints * pSequencePoints) = 0; + typedef void (*FP_NATIVEVARINFO_CALLBACK)(ICorDebugInfo::NativeVarInfo *pVarInfo, CALLBACK_DATA pUserData); + typedef void (*FP_SEQUENCEPOINT_CALLBACK)(ICorDebugInfo::OffsetMapping *pMapping, CALLBACK_DATA pUserData); + + virtual HRESULT STDMETHODCALLTYPE GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, CORDB_ADDRESS startAddress, BOOL fCodeAvailable, OUT ULONG32 * pFixedArgCount, FP_NATIVEVARINFO_CALLBACK fpVarInfoCallback, FP_SEQUENCEPOINT_CALLBACK fpSeqPointCallback, CALLBACK_DATA pUserData) = 0; // // Get the filter CONTEXT on the LS. Once we move entirely over to the new managed pipeline diff --git a/src/coreclr/inc/cordebuginfo.h b/src/coreclr/inc/cordebuginfo.h index ba5a545e3ed909..1d6bdfcec3bfcc 100644 --- a/src/coreclr/inc/cordebuginfo.h +++ b/src/coreclr/inc/cordebuginfo.h @@ -51,6 +51,7 @@ class ICorDebugInfo }; + // [cDAC]: Mirrored in managed code (IDacDbiInterface.cs). struct OffsetMapping { uint32_t nativeOffset; @@ -248,6 +249,7 @@ class ICorDebugInfo // VarLoc describes the location of a native variable. Note that currently, VLT_REG_BYREF and VLT_STK_BYREF // are only used for value types on X64. + // [cDAC]: Mirrored in managed code (IDacDbiInterface.cs). enum VarLocType { @@ -352,14 +354,7 @@ class ICorDebugInfo unsigned vlfvOffset; }; - // VLT_MEMORY - - struct vlMemory - { - void *rpValue; // pointer to the in-process - // location of the value. - }; - + // [cDAC]: Mirrored in managed code (IDacDbiInterface.cs). struct VarLoc { VarLocType vlType; @@ -374,7 +369,6 @@ class ICorDebugInfo ICorDebugInfo::vlStk2 vlStk2; ICorDebugInfo::vlFPstk vlFPstk; ICorDebugInfo::vlFixedVarArg vlFixedVarArg; - ICorDebugInfo::vlMemory vlMemory; }; }; @@ -401,6 +395,7 @@ class ICorDebugInfo uint32_t varNumber; }; + // [cDAC]: Mirrored in managed code (IDacDbiInterface.cs). struct NativeVarInfo { uint32_t startOffset; diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index 903b2ee3bd66d8..5a17770405c9df 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -15,8 +15,8 @@ struct TypeRefData; struct TargetBuffer; struct ModuleInfo; struct DacThreadAllocInfo; -struct NativeVarData; -struct SequencePoints; +struct NativeVarInfo; +struct OffsetMapping; struct Debugger_STRData; struct NativeCodeFunctionData; struct FieldData; @@ -143,6 +143,8 @@ typedef void (*FP_RCW_INTERFACE_CALLBACK)(CORDB_ADDRESS itfPtr, CALLBACK_DATA pU typedef void (*FP_EXCEPTION_STACK_FRAME_CALLBACK)(VMPTR_AppDomain vmAppDomain, VMPTR_Assembly vmAssembly, CORDB_ADDRESS ip, mdMethodDef methodDef, BOOL isLastForeignExceptionFrame, CALLBACK_DATA pUserData); typedef void (*FP_ASYNC_LOCAL_CALLBACK)(AsyncLocalData *pLocal, CALLBACK_DATA pUserData); typedef void (*FP_FIELDDATA_CALLBACK)(struct FieldData * pFieldData, CALLBACK_DATA pUserData); +typedef void (*FP_NATIVEVARINFO_CALLBACK)(struct NativeVarInfo * pVarInfo, CALLBACK_DATA pUserData); +typedef void (*FP_SEQUENCEPOINT_CALLBACK)(struct OffsetMapping * pMapping, CALLBACK_DATA pUserData); // @@ -264,8 +266,10 @@ interface IDacDbiInterface : IUnknown [in] VMPTR_MethodDesc vmMethodDesc, [in] CORDB_ADDRESS startAddress, [in] BOOL fCodeAvailable, - [out] struct NativeVarData * pNativeVarData, - [out] struct SequencePoints * pSequencePoints); + [out] ULONG32 * pFixedArgCount, + [in] FP_NATIVEVARINFO_CALLBACK fpVarInfoCallback, + [in] FP_SEQUENCEPOINT_CALLBACK fpSeqPointCallback, + [in] CALLBACK_DATA pUserData); // Context and Stack Walking HRESULT GetManagedStoppedContext([in] VMPTR_Thread vmThread, [out] VMPTR_CONTEXT * pRetVal); diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 6e64f7d1e4a1a8..2e2b7b91b04503 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 58dd28fc-38f0-4782-989a-25e2d721a98b */ - 0x58dd28fc, - 0x38f0, - 0x4782, - {0x98, 0x9a, 0x25, 0xe2, 0xd7, 0x21, 0xa9, 0x8b} +constexpr GUID JITEEVersionIdentifier = { /* a09d20fa-2c93-476b-86aa-7daaa3e52ddf */ + 0xa09d20fa, + 0x2c93, + 0x476b, + {0x86, 0xaa, 0x7d, 0xaa, 0xa3, 0xe5, 0x2d, 0xdf} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index fac4b90855a287..5214749d6b2eaa 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -614,14 +614,6 @@ class CodeGenInterface { unsigned vlfvOffset; } vlFixedVarArg; - - // VLT_MEMORY - - struct - { - void* rpValue; // pointer to the in-process - // location of the value. - } vlMemory; }; // Helper functions diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs index e307bcb2844616..7b72cc95bece2b 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.VarInfo.cs @@ -1,7 +1,6 @@ // 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; // @@ -43,12 +42,12 @@ public struct NativeVarInfo [StructLayout(LayoutKind.Sequential)] public struct VarLoc { - public IntPtr A; // vlType + padding + public uint A; // vlType public int B; public int C; public int D; - public VarLocType LocationType => (VarLocType)(A.ToInt64() & 0xFFFFFFFF); + public VarLocType LocationType => (VarLocType)A; /* Changes to the following types may require revisiting the above layout. @@ -167,14 +166,6 @@ struct VarLoc { unsigned vlfvOffset; } vlFixedVarArg; - - // VLT_MEMORY - - struct - { - void *rpValue; // pointer to the in-process - // location of the value. - } vlMemory; }; }; */ diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 8a8349272025bc..f6d50544407342 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -354,8 +354,7 @@ struct VASigCookie template<> struct cdac_data { - static constexpr size_t SignaturePointer = offsetof(VASigCookie, signature) + offsetof(Signature, m_pSig); - static constexpr size_t SignatureLength = offsetof(VASigCookie, signature) + offsetof(Signature, m_cbSig); + static constexpr size_t Signature = offsetof(VASigCookie, signature); }; // diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index a09d65ecae7c26..3f526e398a9e34 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -523,11 +523,16 @@ CDAC_TYPE_FIELD(ArrayListBlock, T_UINT32, Size, cdac_data::Size) CDAC_TYPE_FIELD(ArrayListBlock, T_POINTER, ArrayStart, cdac_data::ArrayStart) CDAC_TYPE_END(ArrayListBlock) +CDAC_TYPE_BEGIN(Signature) +CDAC_TYPE_SIZE(sizeof(Signature)) +CDAC_TYPE_FIELD(Signature, T_POINTER, SignaturePointer, cdac_data::SignaturePointer) +CDAC_TYPE_FIELD(Signature, T_UINT32, SignatureLength, cdac_data::SignatureLength) +CDAC_TYPE_END(Signature) + CDAC_TYPE_BEGIN(VASigCookie) CDAC_TYPE_INDETERMINATE(VASigCookie) CDAC_TYPE_FIELD(VASigCookie, T_UINT32, SizeOfArgs, offsetof(VASigCookie, sizeOfArgs)) -CDAC_TYPE_FIELD(VASigCookie, T_POINTER, SignaturePointer, cdac_data::SignaturePointer) -CDAC_TYPE_FIELD(VASigCookie, T_UINT32, SignatureLength, cdac_data::SignatureLength) +CDAC_TYPE_FIELD(VASigCookie, TYPE(Signature), Signature, cdac_data::Signature) CDAC_TYPE_END(VASigCookie) // RuntimeTypeSystem @@ -724,7 +729,8 @@ CDAC_TYPE_END(NativeCodeSlot) CDAC_TYPE_BEGIN(AsyncMethodData) CDAC_TYPE_SIZE(sizeof(AsyncMethodData)) -CDAC_TYPE_FIELD(AsyncMethodData, T_UINT32, Flags, offsetof(AsyncMethodData, flags)) +CDAC_TYPE_FIELD(AsyncMethodData, T_UINT32, Flags, cdac_data::Flags) +CDAC_TYPE_FIELD(AsyncMethodData, TYPE(Signature), Signature, cdac_data::Signature) CDAC_TYPE_END(AsyncMethodData) CDAC_TYPE_BEGIN(InstantiatedMethodDesc) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index afb45ad0d4a204..34aae34dc52420 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -60,7 +60,7 @@ EXTERN_C VOID STDCALL PInvokeImportThunk(); #define METHOD_TOKEN_RANGE_BIT_COUNT (24 - METHOD_TOKEN_REMAINDER_BIT_COUNT) #define METHOD_TOKEN_RANGE_MASK ((1 << METHOD_TOKEN_RANGE_BIT_COUNT) - 1) -// [cDAC] [RuntimeTypeSystem]: Contract depends on the values of Thunk, None. +// [cDAC] [RuntimeTypeSystem]: Contract depends on the values of Thunk, None, IsAsyncVariant. enum class AsyncMethodFlags { // Method uses CORINFO_CALLCONV_ASYNCCALL call convention. @@ -149,6 +149,13 @@ struct AsyncMethodData typedef DPTR(struct AsyncMethodData) PTR_AsyncMethodData; +template<> +struct cdac_data +{ + static constexpr size_t Flags = offsetof(AsyncMethodData, flags); + static constexpr size_t Signature = offsetof(AsyncMethodData, sig); +}; + //============================================================= // Splits methoddef token into two pieces for // storage inside a methoddesc. diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index c19a75c5f8fefc..94ad9143303ed9 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -297,6 +297,7 @@ class SigPointer : public SigParser // forward declarations needed for the friends declared in Signature struct FrameInfo; struct VASigCookie; +struct AsyncMethodData; #if defined(DACCESS_COMPILE) class DacDbiInterfaceImpl; #endif // DACCESS_COMPILE @@ -354,11 +355,20 @@ class Signature private: friend struct ::cdac_data; + friend struct ::cdac_data; + friend struct ::cdac_data; PCCOR_SIGNATURE m_pSig; DWORD m_cbSig; }; // class Signature +template<> +struct cdac_data +{ + static constexpr size_t SignaturePointer = offsetof(Signature, m_pSig); + static constexpr size_t SignatureLength = offsetof(Signature, m_cbSig); +}; + #ifdef _DEBUG #define MAX_CACHED_SIG_SIZE 3 // To excercize non-cached code path diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs index 23d777bd9d1532..7bfcf45c0d000a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugInfo.cs @@ -59,6 +59,7 @@ public readonly struct DebugVarInfo public uint VarNumber { get; init; } public DebugVarLocKind Kind { get; init; } public bool IsByRef { get; init; } + public bool IsFloatingPoint { get; init; } /// Primary register number (Register, RegisterRegister, RegisterStack, StackRegister). public uint Register { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 253e4e2f0f3e6a..fc3d8b031c6051 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -258,8 +258,10 @@ bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize) // Or something else similar. // A no metadata method is also a StoredSigMethodDesc bool IsNoMetadataMethod(MethodDescHandle methodDesc, out string methodName) => throw new NotImplementedException(); - // A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata. - bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan signature) => throw new NotImplementedException(); + + // Gets the raw signature bytes for a MethodDesc by checking stored signature, async variant signature, then metadata. + // Returns false if no signature could be resolved. + bool TryGetMethodSignature(MethodDescHandle methodDesc, out ReadOnlySpan signature) => throw new NotImplementedException(); // Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class // A DynamicMethod is also a StoredSigMethodDesc, and a NoMetadataMethod diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs index 86be2e82a66fb4..e2598a756e4d31 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CallingConvention_1.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; using Internal.CallingConvention; using Internal.CorConstants; using Internal.JitInterface; @@ -309,27 +308,17 @@ private MethodSignature DecodeMethodSignature( RuntimeSignatureDecoder decoder = new( provider, _target, mdReader, context); - if (rts.IsStoredSigMethodDesc(methodDesc, out ReadOnlySpan storedSig)) + if (!rts.TryGetMethodSignature(methodDesc, out ReadOnlySpan methodSig)) + throw new InvalidOperationException("Method has no signature"); + + unsafe { - unsafe + fixed (byte* pSig = methodSig) { - fixed (byte* pStoredSig = storedSig) - { - BlobReader blobReader = new(pStoredSig, storedSig.Length); - return decoder.DecodeMethodSignature(ref blobReader); - } + BlobReader blobReader = new(pSig, methodSig.Length); + return decoder.DecodeMethodSignature(ref blobReader); } } - - uint methodToken = rts.GetMethodToken(methodDesc); - if (methodToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef) - throw new InvalidOperationException("Method has no token"); - - MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle( - (int)EcmaMetadataUtils.GetRowId(methodToken)); - MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); - BlobReader sigReader = mdReader.GetBlobReader(methodDef.Signature); - return decoder.DecodeMethodSignature(ref sigReader); } // Re-decode the method signature using a wrapper provider that records @@ -357,30 +346,18 @@ private ParamTypeInfo[] DecodeParamTypeInfo(IRuntimeTypeSystem rts, MethodDescHa RuntimeSignatureDecoder decoder = new( provider, _target, mdReader, context); + if (!rts.TryGetMethodSignature(methodDesc, out ReadOnlySpan methodSig)) + return new ParamTypeInfo[paramCount]; + MethodSignature sig; - if (rts.IsStoredSigMethodDesc(methodDesc, out ReadOnlySpan storedSig)) + unsafe { - unsafe + fixed (byte* pSig = methodSig) { - fixed (byte* pStoredSig = storedSig) - { - BlobReader blobReader = new(pStoredSig, storedSig.Length); - sig = decoder.DecodeMethodSignature(ref blobReader); - } + BlobReader blobReader = new(pSig, methodSig.Length); + sig = decoder.DecodeMethodSignature(ref blobReader); } } - else - { - uint methodToken = rts.GetMethodToken(methodDesc); - if (methodToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef) - return new ParamTypeInfo[paramCount]; - - MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle( - (int)EcmaMetadataUtils.GetRowId(methodToken)); - MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); - BlobReader sigReader = mdReader.GetBlobReader(methodDef.Signature); - sig = decoder.DecodeMethodSignature(ref sigReader); - } ParamTypeInfo[] result = new ParamTypeInfo[paramCount]; int count = Math.Min(paramCount, sig.ParameterTypes.Length); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs index 0bb840dae0328e..9dc0686c83ccb9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfoHelpers.cs @@ -139,11 +139,16 @@ internal static IEnumerable DoVars(NativeReader nativeReader, bool yield return locType switch { - VarLocType.VLT_REG or VarLocType.VLT_REG_FP => new DebugVarInfo + VarLocType.VLT_REG => new DebugVarInfo { StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, Kind = DebugVarLocKind.Register, Register = reader.ReadUInt(), }, + VarLocType.VLT_REG_FP => new DebugVarInfo + { + StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, + Kind = DebugVarLocKind.Register, IsFloatingPoint = true, Register = reader.ReadUInt(), + }, VarLocType.VLT_REG_BYREF => new DebugVarInfo { StartOffset = startOffset, EndOffset = endOffset, VarNumber = varNumber, CallReturnValueILOffset = callReturnValueILOffset, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 0ad790a94a02e0..bde7fb097ec63f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -144,6 +144,7 @@ internal enum AsyncMethodFlags : uint { None = 0, AsyncCall = 0x1, + IsAsyncVariant = 0x4, Thunk = 16, } @@ -1699,7 +1700,7 @@ public bool IsNoMetadataMethod(MethodDescHandle methodDescHandle, out string met return true; } - public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) + private bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -1719,6 +1720,49 @@ public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnl return true; } + public bool TryGetMethodSignature(MethodDescHandle methodDescHandle, out ReadOnlySpan signature) + { + if (IsStoredSigMethodDesc(methodDescHandle, out signature)) + { + return true; + } + + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + if (methodDesc.HasAsyncMethodData) + { + Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd(methodDesc.GetAddressOfAsyncMethodData()); + if (((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.IsAsyncVariant)) + { + byte[] sig = new byte[asyncData.Signature.SignatureLength]; + _target.ReadBuffer(asyncData.Signature.SignaturePointer, sig.AsSpan()); + signature = sig; + return true; + } + } + + uint token = methodDesc.Token; + if (EcmaMetadataUtils.GetRowId(token) == 0) + { + signature = default; + return false; + } + + TargetPointer modulePtr = GetOrCreateMethodTable(methodDesc).Module; + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); + MetadataReader? mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (mdReader is null) + { + signature = default; + return false; + } + + MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token)); + MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); + signature = mdReader.GetBlobBytes(methodDef.Signature); + return true; + } + public bool IsDynamicMethod(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs index ee7cf0485ee0e8..e3c2531d8d4be6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs @@ -73,8 +73,8 @@ void ISignature.GetVarArgSignature(TargetPointer vaSigCookieAddr, out TargetPoin { Data.VASigCookie cookie = GetCookie(vaSigCookieAddr); - signatureAddress = cookie.SignaturePointer; - signatureLength = cookie.SignatureLength; + signatureAddress = cookie.Signature.SignaturePointer; + signatureLength = cookie.Signature.SignatureLength; Debug.Assert(signatureAddress != TargetPointer.Null || signatureLength == 0, "VASigCookie has a non-zero signature length but a null signature pointer."); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs index f91f93bfcc7a23..8818534b094456 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs @@ -7,4 +7,5 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed partial class AsyncMethodData : IData { [Field] public uint Flags { get; } + [Field] public Signature Signature { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Signature.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Signature.cs new file mode 100644 index 00000000000000..44a58fd646dce8 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Signature.cs @@ -0,0 +1,11 @@ +// 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; + +[CdacType(nameof(DataType.Signature))] +internal sealed partial class Signature : IData +{ + [Field] public TargetPointer SignaturePointer { get; } + [Field] public uint SignatureLength { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VASigCookie.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VASigCookie.cs index ed7fa43b07401f..137ad880517a6e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VASigCookie.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/VASigCookie.cs @@ -7,6 +7,5 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed partial class VASigCookie : IData { [Field] public uint SizeOfArgs { get; } - [Field] public TargetPointer SignaturePointer { get; } - [Field] public uint SignatureLength { get; } + [Field] public Signature Signature { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index 365c5c142e86bc..c814aeef64c408 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -193,6 +193,7 @@ public enum DataType InternalComInterfaceDispatch, AuxiliarySymbolInfo, VASigCookie, + Signature, CodeRangeMapRangeList, /* GC Data Types */ diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs index 4e127457b5aae8..1052e9519020cd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs @@ -140,8 +140,12 @@ int IXCLRDataFrame.GetNumArguments(uint* numArgs) try { *numArgs = 0; - GetMethodInfo(out _, out MetadataReader mdReader, out MethodDefinition methodDef, out _, out _); - GetMethodSignatureInfo(mdReader, methodDef, out _, out uint numArgsResult); + MethodDescHandle mdh = GetFrameMethodDesc(out _); + + if (!_target.Contracts.RuntimeTypeSystem.TryGetMethodSignature(mdh, out ReadOnlySpan signature)) + throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; + + MethodSignatureHelpers.GetSignatureInfo(signature, out _, out uint numArgsResult); *numArgs = numArgsResult; if (*numArgs == 0) hr = HResults.S_FALSE; @@ -189,8 +193,13 @@ int IXCLRDataFrame.GetArgumentByIndex( if (nameLen is not null) *nameLen = 0; - GetMethodInfo(out MethodDescHandle mdh, out MetadataReader mdReader, out MethodDefinition methodDef, out Contracts.ModuleHandle moduleHandle, out _); - GetMethodSignatureInfo(mdReader, methodDef, out SignatureHeader header, out uint numArgs); + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle mdh = GetFrameMethodDesc(out Contracts.ModuleHandle moduleHandle); + + if (!rts.TryGetMethodSignature(mdh, out ReadOnlySpan signature)) + throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; + + MethodSignatureHelpers.GetSignatureInfo(signature, out SignatureHeader header, out uint numArgs); if (index >= numArgs) throw Marshal.GetExceptionForHR(HResults.E_INVALIDARG)!; @@ -198,7 +207,6 @@ int IXCLRDataFrame.GetArgumentByIndex( // Resolve parameter name if ((bufLen > 0 && name is not null) || nameLen is not null) { - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; if (index == 0 && header.IsInstance) { OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, "this"); @@ -208,8 +216,23 @@ int IXCLRDataFrame.GetArgumentByIndex( // Param indexing is 1-based in metadata. 'this' isn't in the // signature, so for instance methods adjust the index down. int mdIndex = (int)(header.IsInstance ? index : index + 1); - string? paramName = GetParameterName(mdReader, methodDef, mdIndex); - OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, paramName ?? string.Empty); + uint token = rts.GetMethodToken(mdh); + // Array Get/Set/Address methods aren't no-metadata methods but still + // carry a nil MethodDef token. Resolving a name for one would throw, so + // name resolution is skipped in that case to match native behavior. + MetadataReader? mdReader = (EcmaMetadataUtils.GetRowId(token) != 0) + ? _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle) + : null; + if (mdReader is not null) + { + MethodDefinition methodDef = mdReader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token))); + string? paramName = GetParameterName(mdReader, methodDef, mdIndex); + OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, paramName ?? string.Empty); + } + else + { + OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, string.Empty); + } } else { @@ -246,7 +269,7 @@ int IXCLRDataFrame.GetNumLocalVariables(uint* numLocals) try { *numLocals = 0; - GetMethodInfo(out MethodDescHandle mdh, out _, out _, out Contracts.ModuleHandle moduleHandle, out _); + MethodDescHandle mdh = GetFrameMethodDesc(out Contracts.ModuleHandle moduleHandle); *numLocals = GetLocalVariableCount(mdh, moduleHandle); } catch (System.Exception ex) @@ -292,8 +315,13 @@ int IXCLRDataFrame.GetLocalVariableByIndex( if (nameLen is not null) *nameLen = 0; - GetMethodInfo(out MethodDescHandle mdh, out MetadataReader mdReader, out MethodDefinition methodDef, out Contracts.ModuleHandle moduleHandle, out _); - GetMethodSignatureInfo(mdReader, methodDef, out SignatureHeader argHeader, out uint numArgs); + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle mdh = GetFrameMethodDesc(out Contracts.ModuleHandle moduleHandle); + + if (!rts.TryGetMethodSignature(mdh, out ReadOnlySpan signature)) + throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; + + MethodSignatureHelpers.GetSignatureInfo(signature, out SignatureHeader argHeader, out uint numArgs); uint numLocals = GetLocalVariableCount(mdh, moduleHandle); @@ -348,15 +376,8 @@ int IXCLRDataFrame.GetMethodInstance(DacComNullableByRef // ========== Metadata resolution helpers ========== /// - /// Resolves the frame's MethodDesc into its module-level metadata objects. - /// Throws on failure (no MethodDesc, no metadata, etc.). + /// Resolves the frame's MethodDesc and its containing module. + /// Throws on failure (no MethodDesc). /// - private void GetMethodInfo(out MethodDescHandle mdh, out MetadataReader mdReader, out MethodDefinition methodDef, out Contracts.ModuleHandle moduleHandle, out uint token) + private MethodDescHandle GetFrameMethodDesc(out Contracts.ModuleHandle moduleHandle) { - IStackWalk stackWalk = _target.Contracts.StackWalk; - TargetPointer methodDescPtr = stackWalk.GetMethodDescPtr(_dataFrame); + TargetPointer methodDescPtr = _target.Contracts.StackWalk.GetMethodDescPtr(_dataFrame); if (methodDescPtr == TargetPointer.Null) throw new InvalidCastException(); // E_NOINTERFACE IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - mdh = rts.GetMethodDescHandle(methodDescPtr); + MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr); + TargetPointer mtAddr = rts.GetMethodTable(mdh); TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); TargetPointer modulePtr = rts.GetModule(typeHandle); - ILoader loader = _target.Contracts.Loader; - moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); - token = rts.GetMethodToken(mdh); - - IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata; - MetadataReader? reader = ecmaMetadataContract.GetMetadata(moduleHandle); - if (reader is null) - throw new NotImplementedException(); - mdReader = reader; + moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); - MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)token); - methodDef = mdReader.GetMethodDefinition(methodDefHandle); - } - - /// - /// Parses the method signature to determine argument count and signature header. - /// - private static void GetMethodSignatureInfo(MetadataReader mdReader, MethodDefinition methodDef, out SignatureHeader header, out uint numArgs) - { - BlobReader blobReader = mdReader.GetBlobReader(methodDef.Signature); - header = blobReader.ReadSignatureHeader(); - if (header.Kind != SignatureKind.Method) - throw new BadImageFormatException(); - if (header.IsGeneric) - blobReader.ReadCompressedInteger(); // skip generic arity - uint paramCount = (uint)blobReader.ReadCompressedInteger(); - numArgs = paramCount + (header.IsInstance ? 1u : 0u); + return mdh; } /// @@ -574,7 +571,9 @@ private uint GetLocalVariableCount(MethodDescHandle mdh, Contracts.ModuleHandle { try { - GetMethodInfo(out _, out MetadataReader mdReader, out MethodDefinition methodDef, out _, out _); + MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle) ?? throw new NotImplementedException(); + uint token = _target.Contracts.RuntimeTypeSystem.GetMethodToken(mdh); + MethodDefinition methodDef = mdReader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token))); FlagSignatureTypeProvider provider = new(_target, moduleHandle); SignatureDecoder<(uint Flags, int Size), MethodDescHandle> decoder = new(provider, mdReader, mdh); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.NativeCodeInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.NativeCodeInfo.cs new file mode 100644 index 00000000000000..b5f9e266d7eee9 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.NativeCodeInfo.cs @@ -0,0 +1,259 @@ +// 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.Reflection.Metadata; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +public sealed unsafe partial class DacDbiImpl +{ + // Gets the number of fixed arguments (i.e., the explicit args and the "this" pointer) from the method signature. + // This does not include other implicit arguments or varargs. + private uint GetArgCount(ulong vmMethodDesc) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle mdh = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc)); + + if (!rts.TryGetMethodSignature(mdh, out ReadOnlySpan signature)) + throw Marshal.GetExceptionForHR(HResults.E_FAIL)!; + + MethodSignatureHelpers.GetSignatureInfo(signature, out _, out uint numArgs); + return numArgs; + } + + internal static NativeVarInfo ConvertToNativeVarInfo(DebugVarInfo varInfo) + { + NativeVarInfo nvi = default; + nvi.startOffset = varInfo.StartOffset; + nvi.endOffset = varInfo.EndOffset; + nvi.callReturnValueILOffset = varInfo.CallReturnValueILOffset; + nvi.varNumber = varInfo.VarNumber; + nvi.loc = ConvertToVarLoc(varInfo); + return nvi; + } + + internal static DbiOffsetMapping ConvertToDbiOffsetMapping(Contracts.OffsetMapping mapping) + { + DbiOffsetMapping nativeMapping = default; + nativeMapping.nativeOffset = mapping.NativeOffset; + nativeMapping.ilOffset = mapping.ILOffset; + nativeMapping.source = ConvertSourceTypesToNative(mapping.SourceType); + return nativeMapping; + } + + internal static VarLoc ConvertToVarLoc(DebugVarInfo varInfo) + { + VarLoc loc = default; + loc.vlType = (varInfo.Kind, varInfo.IsByRef, varInfo.IsFloatingPoint) switch + { + (DebugVarLocKind.Register, false, false) => VarLocType.VLT_REG, + (DebugVarLocKind.Register, false, true) => VarLocType.VLT_REG_FP, + (DebugVarLocKind.Register, true, _) => VarLocType.VLT_REG_BYREF, + (DebugVarLocKind.Stack, false, _) => VarLocType.VLT_STK, + (DebugVarLocKind.Stack, true, _) => VarLocType.VLT_STK_BYREF, + (DebugVarLocKind.RegisterRegister, _, _) => VarLocType.VLT_REG_REG, + (DebugVarLocKind.RegisterStack, _, _) => VarLocType.VLT_REG_STK, + (DebugVarLocKind.StackRegister, _, _) => VarLocType.VLT_STK_REG, + (DebugVarLocKind.DoubleStack, _, _) => VarLocType.VLT_STK2, + _ => VarLocType.VLT_INVALID, + }; + + switch (varInfo.Kind) + { + case DebugVarLocKind.Register: + loc.vlrReg = varInfo.Register; + break; + case DebugVarLocKind.Stack: + loc.vlsBaseReg = varInfo.BaseRegister; + loc.vlsOffset = varInfo.StackOffset; + break; + case DebugVarLocKind.RegisterRegister: + loc.vlrrReg1 = varInfo.Register; + loc.vlrrReg2 = varInfo.Register2; + break; + case DebugVarLocKind.RegisterStack: + loc.vlrsReg = varInfo.Register; + loc.vlrssBaseReg = varInfo.BaseRegister2; + loc.vlrssOffset = varInfo.StackOffset2; + break; + case DebugVarLocKind.StackRegister: + loc.vlsrsBaseReg = varInfo.BaseRegister; + loc.vlsrsOffset = varInfo.StackOffset; + loc.vlsrReg = varInfo.Register; + break; + case DebugVarLocKind.DoubleStack: + loc.vlsBaseReg = varInfo.BaseRegister; + loc.vlsOffset = varInfo.StackOffset; + break; + } + + return loc; + } + + // Converts cDAC Contracts.SourceTypes to native ICorDebugInfo::SourceTypes values. + // The cDAC uses compact bit positions while the native enum uses different bit values. + internal static DbiSourceTypes ConvertSourceTypesToNative(Contracts.SourceTypes source) + { + DbiSourceTypes result = DbiSourceTypes.SourceTypeInvalid; + if ((source & Contracts.SourceTypes.StackEmpty) != 0) + result |= DbiSourceTypes.StackEmpty; + if ((source & Contracts.SourceTypes.CallInstruction) != 0) + result |= DbiSourceTypes.CallInstruction; + if ((source & Contracts.SourceTypes.Async) != 0) + result |= DbiSourceTypes.Async; + + return result; + } + +#if DEBUG + private void ValidateNativeCodeInfoAgainstLegacy( + ulong vmMethodDesc, + ulong startAddress, + Interop.BOOL fCodeAvailable, + uint* pFixedArgCount, + List cdacVarInfos, + List cdacSeqPoints, + int hr, + bool varInfoRequested, + bool seqPointsRequested) + { + uint dacFixedArgCount = 0; + var dacData = new DebugNativeCodeData(); + GCHandle dacHandle = GCHandle.Alloc(dacData); + int hrLocal; + try + { + hrLocal = _legacy!.GetNativeCodeSequencePointsAndVarInfo( + vmMethodDesc, startAddress, fCodeAvailable, &dacFixedArgCount, + (delegate* unmanaged)&CollectNativeVarInfoCallback, + (delegate* unmanaged)&CollectOffsetMappingCallback, + GCHandle.ToIntPtr(dacHandle)); + } + finally + { + dacHandle.Free(); + } + + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + if (pFixedArgCount != null) + { + Debug.Assert(*pFixedArgCount == dacFixedArgCount, + $"fixedArgCount mismatch - cDAC: {*pFixedArgCount}, DAC: {dacFixedArgCount}"); + } + + // Only compare lists whose callback was supplied. + if (seqPointsRequested) + AssertSeqPointsEqual(cdacSeqPoints, dacData.SeqPoints); + if (varInfoRequested) + AssertVarInfosEqual(cdacVarInfos, dacData.VarInfos); + } + } + + private static void AssertSeqPointsEqual(List cdac, List dac) + { + Debug.Assert(cdac.Count == dac.Count, + $"SeqPoint count mismatch - cDAC: {cdac.Count}, DAC: {dac.Count}"); + int n = Math.Min(cdac.Count, dac.Count); + for (int i = 0; i < n; i++) + { + DbiOffsetMapping c = cdac[i]; + DbiOffsetMapping d = dac[i]; + Debug.Assert(c.nativeOffset == d.nativeOffset, + $"SeqPoint[{i}] nativeOffset mismatch - cDAC: {c.nativeOffset}, DAC: {d.nativeOffset}"); + Debug.Assert(c.ilOffset == d.ilOffset, + $"SeqPoint[{i}] ilOffset mismatch - cDAC: {c.ilOffset}, DAC: {d.ilOffset}"); + Debug.Assert(c.source == d.source, + $"SeqPoint[{i}] source mismatch - cDAC: 0x{c.source:X}, DAC: 0x{d.source:X}"); + } + } + + private static void AssertVarInfosEqual(List cdac, List dac) + { + Debug.Assert(cdac.Count == dac.Count, + $"VarInfo count mismatch - cDAC: {cdac.Count}, DAC: {dac.Count}"); + int n = Math.Min(cdac.Count, dac.Count); + for (int i = 0; i < n; i++) + { + NativeVarInfo c = cdac[i]; + NativeVarInfo d = dac[i]; + Debug.Assert(c.startOffset == d.startOffset, + $"VarInfo[{i}] startOffset mismatch - cDAC: {c.startOffset}, DAC: {d.startOffset}"); + Debug.Assert(c.endOffset == d.endOffset, + $"VarInfo[{i}] endOffset mismatch - cDAC: {c.endOffset}, DAC: {d.endOffset}"); + Debug.Assert(c.callReturnValueILOffset == d.callReturnValueILOffset, + $"VarInfo[{i}] callReturnValueILOffset mismatch - cDAC: {c.callReturnValueILOffset}, DAC: {d.callReturnValueILOffset}"); + Debug.Assert(c.varNumber == d.varNumber, + $"VarInfo[{i}] varNumber mismatch - cDAC: {c.varNumber}, DAC: {d.varNumber}"); + Debug.Assert(c.loc.vlType == d.loc.vlType, + $"VarInfo[{i}] vlType mismatch - cDAC: {c.loc.vlType}, DAC: {d.loc.vlType}"); + + switch (c.loc.vlType) + { + case VarLocType.VLT_REG: + case VarLocType.VLT_REG_FP: + case VarLocType.VLT_REG_BYREF: + Debug.Assert(c.loc.vlrReg == d.loc.vlrReg, + $"VarInfo[{i}] vlrReg mismatch - cDAC: {c.loc.vlrReg}, DAC: {d.loc.vlrReg}"); + break; + case VarLocType.VLT_STK: + case VarLocType.VLT_STK_BYREF: + case VarLocType.VLT_STK2: + Debug.Assert(c.loc.vlsBaseReg == d.loc.vlsBaseReg, + $"VarInfo[{i}] vlsBaseReg mismatch - cDAC: {c.loc.vlsBaseReg}, DAC: {d.loc.vlsBaseReg}"); + Debug.Assert(c.loc.vlsOffset == d.loc.vlsOffset, + $"VarInfo[{i}] vlsOffset mismatch - cDAC: {c.loc.vlsOffset}, DAC: {d.loc.vlsOffset}"); + break; + case VarLocType.VLT_REG_REG: + Debug.Assert(c.loc.vlrrReg1 == d.loc.vlrrReg1, + $"VarInfo[{i}] vlrrReg1 mismatch - cDAC: {c.loc.vlrrReg1}, DAC: {d.loc.vlrrReg1}"); + Debug.Assert(c.loc.vlrrReg2 == d.loc.vlrrReg2, + $"VarInfo[{i}] vlrrReg2 mismatch - cDAC: {c.loc.vlrrReg2}, DAC: {d.loc.vlrrReg2}"); + break; + case VarLocType.VLT_REG_STK: + Debug.Assert(c.loc.vlrsReg == d.loc.vlrsReg, + $"VarInfo[{i}] vlrsReg mismatch - cDAC: {c.loc.vlrsReg}, DAC: {d.loc.vlrsReg}"); + Debug.Assert(c.loc.vlrssBaseReg == d.loc.vlrssBaseReg, + $"VarInfo[{i}] vlrssBaseReg mismatch - cDAC: {c.loc.vlrssBaseReg}, DAC: {d.loc.vlrssBaseReg}"); + Debug.Assert(c.loc.vlrssOffset == d.loc.vlrssOffset, + $"VarInfo[{i}] vlrssOffset mismatch - cDAC: {c.loc.vlrssOffset}, DAC: {d.loc.vlrssOffset}"); + break; + case VarLocType.VLT_STK_REG: + Debug.Assert(c.loc.vlsrsBaseReg == d.loc.vlsrsBaseReg, + $"VarInfo[{i}] vlsrsBaseReg mismatch - cDAC: {c.loc.vlsrsBaseReg}, DAC: {d.loc.vlsrsBaseReg}"); + Debug.Assert(c.loc.vlsrsOffset == d.loc.vlsrsOffset, + $"VarInfo[{i}] vlsrsOffset mismatch - cDAC: {c.loc.vlsrsOffset}, DAC: {d.loc.vlsrsOffset}"); + Debug.Assert(c.loc.vlsrReg == d.loc.vlsrReg, + $"VarInfo[{i}] vlsrReg mismatch - cDAC: {c.loc.vlsrReg}, DAC: {d.loc.vlsrReg}"); + break; + } + } + } + + private sealed class DebugNativeCodeData + { + public List VarInfos { get; } = new(); + public List SeqPoints { get; } = new(); + } + + [UnmanagedCallersOnly] + private static void CollectNativeVarInfoCallback(NativeVarInfo* data, void* pUserData) + { + GCHandle handle = GCHandle.FromIntPtr((nint)pUserData); + ((DebugNativeCodeData)handle.Target!).VarInfos.Add(*data); + } + + [UnmanagedCallersOnly] + private static void CollectOffsetMappingCallback(DbiOffsetMapping* data, void* pUserData) + { + GCHandle handle = GCHandle.FromIntPtr((nint)pUserData); + ((DebugNativeCodeData)handle.Target!).SeqPoints.Add(*data); + } +#endif +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 71c973ecb6d881..039ea8b30b3ba5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1334,8 +1334,84 @@ public int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal) return hr; } - public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL; + public int GetNativeCodeSequencePointsAndVarInfo( + ulong vmMethodDesc, + ulong startAddress, + Interop.BOOL fCodeAvailable, + uint* pFixedArgCount, + delegate* unmanaged fpVarInfoCallback, + delegate* unmanaged fpSeqPointCallback, + nint pUserData) + { + // Fully materialize both arrays before invoking any callback to avoid delivering partial results on failure. + List cdacVarInfos = new(); + List cdacSeqPoints = new(); + int hr = HResults.S_OK; + if (pFixedArgCount != null) + *pFixedArgCount = 0; + try + { + Debug.Assert(vmMethodDesc != 0, $"vmMethodDesc is null"); + Debug.Assert(fCodeAvailable != 0, $"fCodeAvailable is false"); + + Contracts.IDebugInfo debugInfo = _target.Contracts.DebugInfo; + TargetCodePointer codePointer = new TargetCodePointer(startAddress); + + if (pFixedArgCount != null) + *pFixedArgCount = GetArgCount(vmMethodDesc); + + bool hasDebugInfo = debugInfo.HasDebugInfo(codePointer); + if (!hasDebugInfo && (fpVarInfoCallback != null || fpSeqPointCallback != null)) + { + hr = HResults.E_FAIL; + } + + if (fpVarInfoCallback != null && hasDebugInfo) + { + IEnumerable varInfos = debugInfo.GetMethodVarInfo(codePointer, out _); + foreach (DebugVarInfo varInfo in varInfos) + { + cdacVarInfos.Add(ConvertToNativeVarInfo(varInfo)); + } + } + + if (fpSeqPointCallback != null && hasDebugInfo) + { + IEnumerable sequencePoints = debugInfo.GetMethodNativeMap(codePointer, preferUninstrumented: true, out _); + foreach (Contracts.OffsetMapping mapping in sequencePoints) + { + cdacSeqPoints.Add(ConvertToDbiOffsetMapping(mapping)); + } + } + + foreach (NativeVarInfo nvi in cdacVarInfos) + { + NativeVarInfo entry = nvi; + fpVarInfoCallback(&entry, (void*)pUserData); + } + + foreach (DbiOffsetMapping mapping in cdacSeqPoints) + { + DbiOffsetMapping entry = mapping; + fpSeqPointCallback(&entry, (void*)pUserData); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ValidateNativeCodeInfoAgainstLegacy( + vmMethodDesc, startAddress, fCodeAvailable, + pFixedArgCount, cdacVarInfos, cdacSeqPoints, hr, + varInfoRequested: fpVarInfoCallback != null, + seqPointsRequested: fpSeqPointCallback != null); + } +#endif + return hr; + } public int GetManagedStoppedContext(ulong vmThread, ulong* pRetVal) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index a51ebc9d7d1c07..070b339618262f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -35,6 +35,92 @@ public struct FieldData public ulong m_vmFieldDesc; } +public enum VarLocType +{ + VLT_REG, + VLT_REG_BYREF, + VLT_REG_FP, + VLT_STK, + VLT_STK_BYREF, + VLT_REG_REG, + VLT_REG_STK, + VLT_STK_REG, + VLT_STK2, + VLT_FPSTK, + VLT_FIXED_VA, + VLT_COUNT, + VLT_INVALID, +} + +// Mirrors the native ICorDebugInfo::VarLoc tagged union: a VarLocType selector plus a union +// payload, modelled as three 4-byte slots with typed accessors per VarLocType below. +[StructLayout(LayoutKind.Sequential)] +public struct VarLoc +{ + public VarLocType vlType; + + private uint _field1; + private int _field2; + private int _field3; + + // vlReg / vlReg_BYREF + public uint vlrReg { get => _field1; set => _field1 = value; } + + // vlStk / vlStk_BYREF / vlStk2 + public uint vlsBaseReg { get => _field1; set => _field1 = value; } + public int vlsOffset { get => _field2; set => _field2 = value; } + + // vlRegReg + public uint vlrrReg1 { get => _field1; set => _field1 = value; } + public uint vlrrReg2 { get => (uint)_field2; set => _field2 = (int)value; } + + // vlRegStk + public uint vlrsReg { get => _field1; set => _field1 = value; } + public uint vlrssBaseReg { get => (uint)_field2; set => _field2 = (int)value; } + public int vlrssOffset { get => _field3; set => _field3 = value; } + + // vlStkReg + public uint vlsrsBaseReg { get => _field1; set => _field1 = value; } + public int vlsrsOffset { get => _field2; set => _field2 = value; } + public uint vlsrReg { get => (uint)_field3; set => _field3 = (int)value; } + + // vlFPstk + public uint vlfReg { get => _field1; set => _field1 = value; } + + // vlFixedVarArg + public uint vlfvOffset { get => _field1; set => _field1 = value; } +} + +[StructLayout(LayoutKind.Sequential)] +public struct NativeVarInfo +{ + public uint startOffset; + public uint endOffset; + public uint callReturnValueILOffset; + public uint varNumber; + public VarLoc loc; +} + +[Flags] +public enum DbiSourceTypes : uint +{ + SourceTypeInvalid = 0x00, + SequencePoint = 0x01, + StackEmpty = 0x02, + CallSite = 0x04, + NativeEndOffsetUnknown = 0x08, + CallInstruction = 0x10, + Async = 0x20, +} + +[StructLayout(LayoutKind.Sequential)] +public struct DbiOffsetMapping +{ + public uint nativeOffset; + public uint ilOffset; + public DbiSourceTypes source; +} + #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value [StructLayout(LayoutKind.Sequential)] @@ -537,7 +623,7 @@ public unsafe partial interface IDacDbiInterface int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal); [PreserveSig] - int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints); + int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, uint* pFixedArgCount, delegate* unmanaged fpVarInfoCallback, delegate* unmanaged fpSeqPointCallback, nint pUserData); [PreserveSig] int GetManagedStoppedContext(ulong vmThread, ulong* pRetVal); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MethodSignatureHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MethodSignatureHelpers.cs new file mode 100644 index 00000000000000..6eaf8164ef6efc --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MethodSignatureHelpers.cs @@ -0,0 +1,31 @@ +// 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.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +/// +/// Shared helpers for resolving MethodDesc signature information. +/// +internal static class MethodSignatureHelpers +{ + /// + /// Parses raw signature bytes to determine the signature header and argument count. + /// + public static unsafe void GetSignatureInfo(ReadOnlySpan signature, out SignatureHeader header, out uint numArgs) + { + fixed (byte* pSig = signature) + { + BlobReader blobReader = new BlobReader(pSig, signature.Length); + header = blobReader.ReadSignatureHeader(); + if (header.Kind != SignatureKind.Method) + throw new BadImageFormatException(); + if (header.IsGeneric) + blobReader.ReadCompressedInteger(); // skip generic arity + uint paramCount = (uint)blobReader.ReadCompressedInteger(); + numArgs = paramCount + (header.IsInstance ? 1u : 0u); + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/TypeNameBuilder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/TypeNameBuilder.cs index 4372ca366ad388..e481d0cc471fe3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/TypeNameBuilder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/TypeNameBuilder.cs @@ -68,7 +68,6 @@ public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, ILoader loader = target.Contracts.Loader; string methodName; TypeHandle th = default; - Contracts.ModuleHandle module = default; bool isNoMetadataMethod = runtimeTypeSystem.IsNoMetadataMethod(method, out methodName); if (isNoMetadataMethod) @@ -120,10 +119,14 @@ public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, } else { - module = loader.GetModuleHandleFromModulePtr(runtimeTypeSystem.GetModule(th)); - MetadataReader reader = target.Contracts.EcmaMetadata.GetMetadata(module)!; - MethodDefinition methodDef = reader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)runtimeTypeSystem.GetMethodToken(method))); - stringBuilder.Append(reader.GetString(methodDef.Name)); + uint rowId = EcmaMetadataUtils.GetRowId(runtimeTypeSystem.GetMethodToken(method)); + if (rowId != 0) + { + Contracts.ModuleHandle module = loader.GetModuleHandleFromModulePtr(runtimeTypeSystem.GetModule(th)); + MetadataReader reader = target.Contracts.EcmaMetadata.GetMetadata(module)!; + MethodDefinition methodDef = reader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)rowId)); + stringBuilder.Append(reader.GetString(methodDef.Name)); + } } ReadOnlySpan genericMethodInstantiation = runtimeTypeSystem.GetGenericMethodInstantiation(method); @@ -132,19 +135,12 @@ public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, AppendInst(target, stringBuilder, genericMethodInstantiation, format); } - if (format.HasFlag(TypeNameFormat.FormatSignature)) + if (format.HasFlag(TypeNameFormat.FormatSignature) && + runtimeTypeSystem.TryGetMethodSignature(method, out ReadOnlySpan signature)) { - ReadOnlySpan signature; - MetadataReader? reader = default; - if (!runtimeTypeSystem.IsStoredSigMethodDesc(method, out signature)) - { - reader = target.Contracts.EcmaMetadata.GetMetadata(module); - if (reader is not null) - { - MethodDefinition methodDef = reader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)runtimeTypeSystem.GetMethodToken(method))); - signature = reader.GetBlobBytes(methodDef.Signature); - } - } + Contracts.ModuleHandle methodModule = loader.GetModuleHandleFromModulePtr( + runtimeTypeSystem.GetModule(runtimeTypeSystem.GetTypeHandle(runtimeTypeSystem.GetMethodTable(method)))); + MetadataReader? reader = target.Contracts.EcmaMetadata.GetMetadata(methodModule); ReadOnlySpan typeInstantiationSigFormat = default; if (!th.IsNull) diff --git a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs index 0d2e46764d1c6b..65ae21b0fb7396 100644 --- a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs @@ -937,4 +937,123 @@ public void GetManagedStoppedContext_NoContextAvailable(MockTarget.Architecture Assert.Equal(System.HResults.S_OK, hr); Assert.Equal(0UL, retVal); } + + [Theory] + [InlineData(DebugVarLocKind.Register, false, false, VarLocType.VLT_REG)] + [InlineData(DebugVarLocKind.Register, false, true, VarLocType.VLT_REG_FP)] + [InlineData(DebugVarLocKind.Register, true, false, VarLocType.VLT_REG_BYREF)] + [InlineData(DebugVarLocKind.Stack, false, false, VarLocType.VLT_STK)] + [InlineData(DebugVarLocKind.Stack, true, false, VarLocType.VLT_STK_BYREF)] + [InlineData(DebugVarLocKind.RegisterRegister, false, false, VarLocType.VLT_REG_REG)] + [InlineData(DebugVarLocKind.RegisterStack, false, false, VarLocType.VLT_REG_STK)] + [InlineData(DebugVarLocKind.StackRegister, false, false, VarLocType.VLT_STK_REG)] + [InlineData(DebugVarLocKind.DoubleStack, false, false, VarLocType.VLT_STK2)] + public void ConvertToVarLoc_MapsVarLocTypeCorrectly(DebugVarLocKind kind, bool isByRef, bool isFloatingPoint, VarLocType expected) + { + var varInfo = new DebugVarInfo { Kind = kind, IsByRef = isByRef, IsFloatingPoint = isFloatingPoint }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(expected, result.vlType); + } + + [Fact] + public void ConvertToVarLoc_Register_SetsRegisterField() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.Register, Register = 7 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(7u, result.vlrReg); + } + + [Fact] + public void ConvertToVarLoc_RegisterFP_SetsRegisterField() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.Register, IsFloatingPoint = true, Register = 9 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(VarLocType.VLT_REG_FP, result.vlType); + Assert.Equal(9u, result.vlrReg); + } + + [Fact] + public void ConvertToVarLoc_Stack_SetsBaseRegAndOffset() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.Stack, BaseRegister = 5, StackOffset = -0x28 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(5u, result.vlsBaseReg); + Assert.Equal(-0x28, result.vlsOffset); + } + + [Fact] + public void ConvertToVarLoc_RegisterRegister_SetsBothRegisters() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.RegisterRegister, Register = 3, Register2 = 4 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(3u, result.vlrrReg1); + Assert.Equal(4u, result.vlrrReg2); + } + + [Fact] + public void ConvertToVarLoc_RegisterStack_SetsRegAndStack() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.RegisterStack, Register = 2, BaseRegister2 = 6, StackOffset2 = 0x10 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(2u, result.vlrsReg); + Assert.Equal(6u, result.vlrssBaseReg); + Assert.Equal(0x10, result.vlrssOffset); + } + + [Fact] + public void ConvertToVarLoc_StackRegister_SetsStackAndReg() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.StackRegister, BaseRegister = 5, StackOffset = -8, Register = 1 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(5u, result.vlsrsBaseReg); + Assert.Equal(-8, result.vlsrsOffset); + Assert.Equal(1u, result.vlsrReg); + } + + [Fact] + public void ConvertToVarLoc_DoubleStack_SetsBaseRegAndOffset() + { + var varInfo = new DebugVarInfo { Kind = DebugVarLocKind.DoubleStack, BaseRegister = 4, StackOffset = 0x20 }; + VarLoc result = DacDbiImpl.ConvertToVarLoc(varInfo); + Assert.Equal(VarLocType.VLT_STK2, result.vlType); + Assert.Equal(4u, result.vlsBaseReg); + Assert.Equal(0x20, result.vlsOffset); + } + + [Theory] + [InlineData(SourceTypes.Default, 0x00u)] + [InlineData(SourceTypes.StackEmpty, 0x02u)] + [InlineData(SourceTypes.CallInstruction, 0x10u)] + [InlineData(SourceTypes.Async, 0x20u)] + [InlineData(SourceTypes.StackEmpty | SourceTypes.CallInstruction, 0x12u)] + [InlineData(SourceTypes.StackEmpty | SourceTypes.CallInstruction | SourceTypes.Async, 0x32u)] + public void ConvertSourceTypesToNative_MapsCorrectly(SourceTypes source, uint expected) + { + DbiSourceTypes result = DacDbiImpl.ConvertSourceTypesToNative(source); + Assert.Equal(expected, (uint)result); + } + + [Fact] + public void ConvertToNativeVarInfo_MapsAllFields() + { + var varInfo = new DebugVarInfo + { + Kind = DebugVarLocKind.Stack, + IsByRef = false, + StartOffset = 10, + EndOffset = 50, + CallReturnValueILOffset = 42, + VarNumber = 3, + BaseRegister = 5, + StackOffset = -0x28, + }; + NativeVarInfo nvi = DacDbiImpl.ConvertToNativeVarInfo(varInfo); + Assert.Equal(10u, nvi.startOffset); + Assert.Equal(50u, nvi.endOffset); + Assert.Equal(42u, nvi.callReturnValueILOffset); + Assert.Equal(3u, nvi.varNumber); + Assert.Equal(VarLocType.VLT_STK, nvi.loc.vlType); + Assert.Equal(5u, nvi.loc.vlsBaseReg); + Assert.Equal(-0x28, nvi.loc.vlsOffset); + } } diff --git a/src/native/managed/cdac/tests/UnitTests/MethodDescTests.cs b/src/native/managed/cdac/tests/UnitTests/MethodDescTests.cs index e16b21181b980f..3eab3dd5c4914e 100644 --- a/src/native/managed/cdac/tests/UnitTests/MethodDescTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/MethodDescTests.cs @@ -32,6 +32,16 @@ public class MethodDescTests Fields = new Dictionary { [nameof(Data.AsyncMethodData.Flags)] = new Target.FieldInfo { Offset = 0 }, + [nameof(Data.AsyncMethodData.Signature)] = new Target.FieldInfo { Offset = (int)methodDescBuilder.TargetTestHelpers.PointerSize, TypeName = nameof(DataType.Signature) }, + }, + }, + [DataType.Signature] = new Target.TypeInfo + { + Size = (uint)(methodDescBuilder.TargetTestHelpers.PointerSize * 2), + Fields = new Dictionary + { + [nameof(Data.Signature.SignaturePointer)] = new Target.FieldInfo { Offset = 0 }, + [nameof(Data.Signature.SignatureLength)] = new Target.FieldInfo { Offset = (int)methodDescBuilder.TargetTestHelpers.PointerSize }, }, }, [DataType.ArrayMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.ArrayMethodDescSize }, @@ -149,7 +159,6 @@ public void GetMethodDescHandle_ILMethod_GetBasicData(MockTarget.Architecture ar TargetPointer gcStressCodeCopy = rts.GetGCStressCodeCopy(handle); Assert.Equal(TargetPointer.Null, gcStressCodeCopy); - Assert.False(rts.IsStoredSigMethodDesc(handle, out _)); Assert.False(rts.IsNoMetadataMethod(handle, out _)); Assert.False(rts.IsDynamicMethod(handle)); Assert.False(rts.IsILStub(handle)); @@ -192,7 +201,6 @@ public void IsArrayMethod(MockTarget.Architecture arch) { MethodDescHandle handle = rts.GetMethodDescHandle(arrayMethods[i]); Assert.NotEqual(TargetPointer.Null, handle.Address); - Assert.True(rts.IsStoredSigMethodDesc(handle, out _)); Assert.True(rts.IsArrayMethod(handle, out ArrayFunctionType functionType)); ArrayFunctionType expectedFunctionType = i <= (byte)ArrayFunctionType.Constructor @@ -238,7 +246,6 @@ public void IsDynamicMethod(MockTarget.Architecture arch) { MethodDescHandle handle = rts.GetMethodDescHandle(dynamicMethod); Assert.NotEqual(TargetPointer.Null, handle.Address); - Assert.True(rts.IsStoredSigMethodDesc(handle, out _)); Assert.True(rts.IsNoMetadataMethod(handle, out _)); Assert.True(rts.IsDynamicMethod(handle)); Assert.False(rts.IsILStub(handle)); @@ -246,13 +253,106 @@ public void IsDynamicMethod(MockTarget.Architecture arch) { MethodDescHandle handle = rts.GetMethodDescHandle(ilStubMethod); Assert.NotEqual(TargetPointer.Null, handle.Address); - Assert.True(rts.IsStoredSigMethodDesc(handle, out _)); Assert.True(rts.IsNoMetadataMethod(handle, out _)); Assert.False(rts.IsDynamicMethod(handle)); Assert.True(rts.IsILStub(handle)); } } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetMethodSignature_StoredSig_ReturnsStoredSignature(MockTarget.Architecture arch) + { + byte[] expectedSig = [0x20, 0x01, 0x01, 0x0e]; + TargetPointer dynamicMethod = TargetPointer.Null; + + IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(arch, methodDescBuilder => + { + TargetPointer methodTable = AddMethodTable(methodDescBuilder.RTSBuilder); + + uint methodDescSize = (uint)methodDescBuilder.DynamicMethodDescLayout.Size; + byte chunkSize = (byte)(methodDescSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("storedSig", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + + MockDynamicMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.DynamicMethodDescLayout); + md.Flags = (ushort)MethodClassification.Dynamic; + md.Sig = methodDescBuilder.AddSignatureBuffer(expectedSig).Value; + md.CSig = (uint)expectedSig.Length; + dynamicMethod = new TargetPointer(md.Address); + }); + + MethodDescHandle handle = rts.GetMethodDescHandle(dynamicMethod); + Assert.True(rts.TryGetMethodSignature(handle, out ReadOnlySpan signature)); + Assert.Equal(expectedSig, signature.ToArray()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetMethodSignature_AsyncVariant_ReturnsAsyncSignature(MockTarget.Architecture arch) + { + byte[] expectedSig = [0x00, 0x02, 0x08, 0x08, 0x0e]; + TargetPointer asyncVariantMethod = TargetPointer.Null; + + IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(arch, methodDescBuilder => + { + TargetTestHelpers helpers = methodDescBuilder.TargetTestHelpers; + TargetPointer methodTable = AddMethodTable(methodDescBuilder.RTSBuilder); + + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncVariant", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + asyncVariantMethod = new TargetPointer(md.Address); + + int pointerSize = helpers.PointerSize; + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; + TargetPointer sigBuffer = methodDescBuilder.AddSignatureBuffer(expectedSig); + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.IsAsyncVariant); + helpers.WritePointer(chunk.Memory.Span.Slice(asyncDataOffset + pointerSize, pointerSize), sigBuffer.Value); + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset + pointerSize * 2, sizeof(uint)), (uint)expectedSig.Length); + }); + + MethodDescHandle handle = rts.GetMethodDescHandle(asyncVariantMethod); + Assert.True(rts.TryGetMethodSignature(handle, out ReadOnlySpan signature)); + Assert.Equal(expectedSig, signature.ToArray()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetMethodSignature_NilToken_ReturnsFalse(MockTarget.Architecture arch) + { + TargetPointer ilMethod = TargetPointer.Null; + + IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(arch, methodDescBuilder => + { + TargetPointer methodTable = AddMethodTable(methodDescBuilder.RTSBuilder); + + byte methodDescSize = (byte)(methodDescBuilder.MethodDescLayout.Size / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("ilMethod", methodDescSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = methodDescSize; + chunk.Count = 1; + + // Leave FlagsAndTokenRange / Flags3AndTokenRemainder at 0 so the MethodDef token has rowId 0. + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)MethodClassification.IL; + ilMethod = new TargetPointer(md.Address); + }); + + MethodDescHandle handle = rts.GetMethodDescHandle(ilMethod); + Assert.False(rts.TryGetMethodSignature(handle, out ReadOnlySpan signature)); + Assert.True(signature.IsEmpty); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void IsGenericMethodDefinition(MockTarget.Architecture arch) diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index e388a570c63bbe..969201fe756068 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -259,7 +259,7 @@ internal sealed class MockMethodDescriptorsBuilder public uint NonVtableSlotSize => (uint)TargetTestHelpers.PointerSize; public uint MethodImplSize => (uint)(TargetTestHelpers.PointerSize * 2); public uint NativeCodeSlotSize => (uint)TargetTestHelpers.PointerSize; - public uint AsyncMethodDataSize => (uint)(TargetTestHelpers.PointerSize * 2); + public uint AsyncMethodDataSize => (uint)(TargetTestHelpers.PointerSize * 3); public uint ArrayMethodDescSize => (uint)StoredSigMethodDescLayout.Size; public uint FCallMethodDescSize => (uint)(MethodDescLayout.Size + TargetTestHelpers.PointerSize); public uint PInvokeMethodDescSize => (uint)(MethodDescLayout.Size + TargetTestHelpers.PointerSize); @@ -302,6 +302,15 @@ internal MockMethodDescChunk AddMethodDescChunk(string name, byte size) return chunk; } + internal TargetPointer AddSignatureBuffer(byte[] signature) + { + ArgumentNullException.ThrowIfNull(signature); + + MockMemorySpace.HeapFragment fragment = _allocator.Allocate((ulong)signature.Length, "MethodSignature"); + signature.CopyTo(fragment.Data.AsSpan()); + return new TargetPointer(fragment.Address); + } + internal MockPerInstInfo AddPerInstInfo(ulong[] typeArgs) { ArgumentNullException.ThrowIfNull(typeArgs); diff --git a/src/native/managed/cdac/tests/UnitTests/SignatureTests.cs b/src/native/managed/cdac/tests/UnitTests/SignatureTests.cs index 6f54a3e86a7030..a4df278d3d5871 100644 --- a/src/native/managed/cdac/tests/UnitTests/SignatureTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/SignatureTests.cs @@ -11,16 +11,35 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public class SignatureTests { - private static TargetTestHelpers.LayoutResult GetVASigCookieLayout(TargetTestHelpers helpers) + private static TargetTestHelpers.LayoutResult GetSignatureLayout(TargetTestHelpers helpers) { return helpers.LayoutFields( [ - new(nameof(Data.VASigCookie.SizeOfArgs), DataType.uint32), - new(nameof(Data.VASigCookie.SignaturePointer), DataType.pointer), - new(nameof(Data.VASigCookie.SignatureLength), DataType.uint32), + new(nameof(Data.Signature.SignaturePointer), DataType.pointer), + new(nameof(Data.Signature.SignatureLength), DataType.uint32), ]); } + // Built manually rather than via LayoutFields, which can't model the embedded Signature struct. + private static TargetTestHelpers.LayoutResult GetVASigCookieLayout(TargetTestHelpers helpers, TargetTestHelpers.LayoutResult signatureLayout) + { + int sizeOfArgsOffset = 0; + int signatureOffset = AlignUp(sizeof(uint), (int)signatureLayout.MaxAlign); + int stride = signatureOffset + (int)signatureLayout.Stride; + return new TargetTestHelpers.LayoutResult + { + Fields = new Dictionary + { + [nameof(Data.VASigCookie.SizeOfArgs)] = new Target.FieldInfo { Offset = sizeOfArgsOffset, TypeName = DataType.uint32.ToString() }, + [nameof(Data.VASigCookie.Signature)] = new Target.FieldInfo { Offset = signatureOffset, TypeName = nameof(DataType.Signature) }, + }, + Stride = (uint)stride, + MaxAlign = signatureLayout.MaxAlign, + }; + } + + private static int AlignUp(int offset, int align) => (offset + align - 1) & ~(align - 1); + /// /// Build a target with a single VASigCookie at a known address and a slot containing /// a pointer to it (i.e., the "VASigCookieAddr" passed to the contract APIs). @@ -40,17 +59,20 @@ private static TestPlaceholderTarget BuildTarget( MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); - TargetTestHelpers.LayoutResult layout = GetVASigCookieLayout(helpers); + TargetTestHelpers.LayoutResult signatureLayout = GetSignatureLayout(helpers); + TargetTestHelpers.LayoutResult layout = GetVASigCookieLayout(helpers, signatureLayout); builder.AddTypes(new Dictionary { [DataType.VASigCookie] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }, + [DataType.Signature] = new Target.TypeInfo() { Fields = signatureLayout.Fields, Size = signatureLayout.Stride }, }); // Allocate and populate the VASigCookie struct. + int signatureOffset = layout.Fields[nameof(Data.VASigCookie.Signature)].Offset; MockMemorySpace.HeapFragment cookieFrag = allocator.Allocate(layout.Stride, "VASigCookie"); helpers.Write(cookieFrag.Data.AsSpan(layout.Fields[nameof(Data.VASigCookie.SizeOfArgs)].Offset, sizeof(uint)), sizeOfArgs); - helpers.WritePointer(cookieFrag.Data.AsSpan(layout.Fields[nameof(Data.VASigCookie.SignaturePointer)].Offset, helpers.PointerSize), signaturePointer); - helpers.Write(cookieFrag.Data.AsSpan(layout.Fields[nameof(Data.VASigCookie.SignatureLength)].Offset, sizeof(uint)), signatureLength); + helpers.WritePointer(cookieFrag.Data.AsSpan(signatureOffset + signatureLayout.Fields[nameof(Data.Signature.SignaturePointer)].Offset, helpers.PointerSize), signaturePointer); + helpers.Write(cookieFrag.Data.AsSpan(signatureOffset + signatureLayout.Fields[nameof(Data.Signature.SignatureLength)].Offset, sizeof(uint)), signatureLength); vaSigCookiePtr = cookieFrag.Address; // Allocate the slot that holds the pointer to the VASigCookie. This is the address