diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 0ce6cca2e5a141..3f8e67281caa8a 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -157,6 +157,15 @@ public enum OptimizationTier } ``` +```csharp +public enum GenericContextLoc +{ + None, + InstArg, + ThisPtr, +} +``` + ```csharp partial interface IRuntimeTypeSystem : IContract { @@ -199,9 +208,9 @@ partial interface IRuntimeTypeSystem : IContract // Return true if a MethodDesc represents an IL stub with a special MethodDesc context arg public virtual bool HasMDContextArg(MethodDescHandle); - // Return true if the method requires a hidden instantiation argument (generic context parameter). - // Corresponds to native MethodDesc::RequiresInstArg(). - public virtual bool RequiresInstArg(MethodDescHandle methodDesc); + // Determine where a shared generic method obtains its generic context. + // Returns None if not shared, InstArg if via a hidden parameter, or ThisPtr if via the 'this' pointer's MethodTable. + public virtual GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDesc); // Return true if the method uses the async calling convention. // Corresponds to native MethodDesc::IsAsyncMethod(). @@ -1637,26 +1646,36 @@ Determining if a method is an async thunk method: } ``` -Determining if a method requires a hidden instantiation argument (generic context parameter): +Determining where a shared generic method obtains its generic context: ```csharp - public bool RequiresInstArg(MethodDescHandle methodDescHandle) + public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle) { MethodDesc md = _methodDescs[methodDescHandle.Address]; - - // RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface) if (!IsSharedByGenericInstantiations(md)) - return false; + return GenericContextLoc.None; + else if (RequiresInstArg(md)) + return GenericContextLoc.InstArg; + else + return GenericContextLoc.ThisPtr; + } + private bool RequiresInstArg(MethodDesc md) + { if (HasMethodInstantiation(md)) return true; - // md.IsStatic reads from MethodDescFlags.Static (0x0080) if (md.IsStatic) return true; MethodTable mt = _methodTables[md.MethodTable]; - return mt.Flags.IsInterface || mt.Flags.IsValueType; + if (mt.Flags.IsValueType) + return true; + + if (mt.Flags.IsInterface && !IsAbstract(md) /* checked from md classification and metadata attributes */) + return true; + + return false; } private bool IsSharedByGenericInstantiations(MethodDesc md) 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 3d1c8cf60fbaaf..3c93c621120c41 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 @@ -89,6 +89,13 @@ public enum OptimizationTier : uint OptimizationTier1Instrumented, } +public enum GenericContextLoc +{ + None, + InstArg, + ThisPtr, +} + public interface IRuntimeTypeSystem : IContract { @@ -193,9 +200,7 @@ public interface IRuntimeTypeSystem : IContract bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException(); ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException(); - // Return true if the method requires a hidden instantiation argument (generic context parameter). - // This corresponds to native MethodDesc::RequiresInstArg(). - bool RequiresInstArg(MethodDescHandle methodDesc) => throw new NotImplementedException(); + GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); // Return true if the method uses the async calling convention (CORINFO_CALLCONV_ASYNCCALL). // This corresponds to native MethodDesc::IsAsyncMethod(). diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs index 63971df217ae9a..1ebab693d898ed 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -10,5 +10,6 @@ public static class CorDbgHResults public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49); public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW) public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303); + public const int CORDBG_E_TARGET_INCONSISTENT = unchecked((int)0x80131c36); public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13); } 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 cd9fb666c51962..4ff9fa28275f2f 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 @@ -1348,18 +1348,8 @@ public ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle m return AsInstantiatedMethodDesc(methodDesc).Instantiation; } - /// - /// Returns true if the method requires a hidden instantiation argument (generic context parameter). - /// Matches native MethodDesc::RequiresInstArg(). - /// - public bool RequiresInstArg(MethodDescHandle methodDescHandle) + private bool RequiresInstArg(MethodDesc methodDesc) { - MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - - // RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface) - if (!IsSharedByGenericInstantiations(methodDesc)) - return false; - if (HasMethodInstantiation(methodDesc)) return true; @@ -1367,15 +1357,26 @@ public bool RequiresInstArg(MethodDescHandle methodDescHandle) return true; MethodTable mt = _methodTables[methodDesc.MethodTable]; - if (mt.Flags.IsInterface) + if (mt.Flags.IsValueType) return true; - if (mt.Flags.IsValueType) + if (mt.Flags.IsInterface && !IsAbstract(methodDesc)) return true; return false; } + public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + if (!IsSharedByGenericInstantiations(methodDesc)) + return GenericContextLoc.None; + else if (RequiresInstArg(methodDesc)) + return GenericContextLoc.InstArg; + else + return GenericContextLoc.ThisPtr; + } + private bool IsSharedByGenericInstantiations(MethodDesc methodDesc) { // Check method-level sharing: InstantiatedMethodDesc with SharedMethodInstantiation @@ -1397,6 +1398,25 @@ private bool IsSharedByGenericInstantiations(MethodDesc methodDesc) return mt.IsCanonMT && mt.Flags.HasInstantiation; } + private bool IsAbstract(MethodDesc methodDesc) + { + if (methodDesc.Classification == MethodClassification.Array || methodDesc.Classification == MethodClassification.Dynamic) + return false; + uint token = methodDesc.Token; + if (EcmaMetadataUtils.GetRowId(token) == 0) + return false; + + TargetPointer modulePtr = _methodTables[methodDesc.MethodTable].Module; + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr); + MetadataReader? mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (mdReader is null) + return false; + + MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token)); + MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle); + return (methodDef.Attributes & MethodAttributes.Abstract) != 0; + } + public bool IsAsyncMethod(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs index d050adb8617be2..27f06946abba40 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs @@ -369,7 +369,7 @@ private void PromoteCallerStack( try { - requiresInstArg = rts.RequiresInstArg(mdh); + requiresInstArg = rts.GetGenericContextLoc(mdh) == GenericContextLoc.InstArg; isAsync = rts.IsAsyncMethod(mdh); } catch 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 508aaeb440d322..ea39615dc75c34 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 @@ -1013,7 +1013,37 @@ public int GetCurrentAppDomain(ulong* pRetVal) } public int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveAssembly(vmScope, tkAssemblyRef, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle scopeModule = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmScope)); + Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(scopeModule); + TargetPointer referencedModule = loader.GetModuleLookupMapElement(lookupTables.ManifestModuleReferences, tkAssemblyRef, out _); + if (referencedModule != TargetPointer.Null) + { + Contracts.ModuleHandle referencedModuleHandle = loader.GetModuleHandleFromModulePtr(referencedModule); + *pRetVal = loader.GetAssembly(referencedModuleHandle).Value; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.ResolveAssembly(vmScope, tkAssemblyRef, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + 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; @@ -1206,7 +1236,56 @@ public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult) } public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + if (dwExactGenericArgsTokenIndex == 0) + { + // In a rare case of VS4Mac debugging VS4Mac ARM64 optimized code we get a null + // generics argument token. We aren't sure why the token is null, it may be a bug + // or it may be by design in the runtime. In the interest of time we are working + // around the issue rather than investigating the root cause. + if (rawToken == 0) + { + *pRetVal = rawToken; + } + else + { + // The real generics type token is the MethodTable of the "this" object. + // The incoming rawToken is a target address of the 'this' Object. + *pRetVal = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(rawToken)).Value; + } + } + else if (dwExactGenericArgsTokenIndex == unchecked((uint)IlNum.TYPECTXT_ILNUM)) + { + // rawToken is already the real generics type token. Nothing to do. + *pRetVal = rawToken; + } + else + { + // The index of the generics type token should not be anything else. + Debug.Fail($"Unexpected generics type token index: {dwExactGenericArgsTokenIndex}"); + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } public int GetILCodeAndSig(ulong vmAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetILCodeAndSig(vmAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL; @@ -1379,7 +1458,49 @@ public int GetTypeHandleParams(ulong vmTypeHandle, nint pParams) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypeHandleParams(vmTypeHandle, pParams) : HResults.E_NOTIMPL; public int GetSimpleType(int simpleType, uint* pMetadataToken, ulong* pVmModule) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetSimpleType(simpleType, pMetadataToken, pVmModule) : HResults.E_NOTIMPL; + { + Debug.Assert(pVmModule != null); + *pVmModule = 0; + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.TypeHandle typeHandle = rts.GetPrimitiveType((CorElementType)simpleType); + + if (typeHandle.IsNull) + { + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + } + + Debug.Assert(pMetadataToken != null); + *pMetadataToken = rts.GetTypeDefToken(typeHandle); + + TargetPointer module = rts.GetModule(typeHandle); + if (module == TargetPointer.Null) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!; + + *pVmModule = module.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint metadataTokenLocal; + ulong vmModuleLocal; + int hrLocal = _legacy.GetSimpleType(simpleType, &metadataTokenLocal, &vmModuleLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(*pMetadataToken == metadataTokenLocal, $"cDAC: {*pMetadataToken}, DAC: {metadataTokenLocal}"); + Debug.Assert(*pVmModule == vmModuleLocal, $"cDAC: {*pVmModule}, DAC: {vmModuleLocal}"); + } + } +#endif + return hr; + } public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL; @@ -2201,6 +2322,47 @@ public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyn => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL; public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetGenericArgTokenIndex(vmMethod, pIndex) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if (vmMethod == 0) + throw new ArgumentException("vmMethod must not be zero.", nameof(vmMethod)); + if (pIndex is null) + throw new ArgumentException("pIndex must not be null.", nameof(pIndex)); + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethod)); + + switch (rts.GetGenericContextLoc(md)) + { + case GenericContextLoc.None: + hr = HResults.S_FALSE; + break; + case GenericContextLoc.InstArg: + *pIndex = unchecked((uint)IlNum.TYPECTXT_ILNUM); + break; + case GenericContextLoc.ThisPtr: + *pIndex = 0u; + break; + default: + throw new InvalidOperationException($"Unexpected generic context location: {rts.GetGenericContextLoc(md)}"); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint indexLocal; + int hrLocal = _legacy.GetGenericArgTokenIndex(vmMethod, &indexLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pIndex == indexLocal, $"cDAC: {*pIndex}, DAC: {indexLocal}"); + } +#endif + return hr; + } } 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 e3a2e87bd70421..503c7de2853d09 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 @@ -170,6 +170,11 @@ public enum CorDebugUserState USER_THREADPOOL = 0x100, } +public enum IlNum : int +{ + TYPECTXT_ILNUM = -3, +} + // Name-surface projection of IDacDbiInterface in native method order for COM binding validation. // Parameter shapes are intentionally coarse placeholders and will be refined with method implementation work. [GeneratedComInterface]