|
// 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.Linq;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.Marshalling;
using System.Text;
using Microsoft.Diagnostics.DataContractReader;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
namespace Microsoft.Diagnostics.DataContractReader.Legacy;
/// <summary>
/// Implementation of ISOSDacInterface* interfaces intended to be passed out to consumers
/// interacting with the DAC via those COM interfaces.
/// </summary>
/// <remarks>
/// Functions on <see cref="ISOSDacInterface"/> are defined with PreserveSig. Target and Contracts
/// throw on errors. Implementations in this class should wrap logic in a try-catch and return the
/// corresponding error code.
/// </remarks>
[GeneratedComClass]
public sealed unsafe partial class SOSDacImpl
: ISOSDacInterface, ISOSDacInterface2, ISOSDacInterface3, ISOSDacInterface4, ISOSDacInterface5,
ISOSDacInterface6, ISOSDacInterface7, ISOSDacInterface8, ISOSDacInterface9, ISOSDacInterface10,
ISOSDacInterface11, ISOSDacInterface12, ISOSDacInterface13, ISOSDacInterface14, ISOSDacInterface15,
ISOSDacInterface16
{
private readonly Target _target;
// When this class is created, the runtime may not have loaded the string and object method tables and set the global pointers.
// This is also the case for the GetUsefulGlobals API, which can be called as part of load notifications before runtime start.
// They should be set when actually requested via other DAC APIs, so we lazily read the global pointers.
private readonly Lazy<TargetPointer> _stringMethodTable;
private readonly Lazy<TargetPointer> _objectMethodTable;
private readonly ulong _rcwMask = 1UL;
private readonly ISOSDacInterface? _legacyImpl;
private readonly ISOSDacInterface2? _legacyImpl2;
private readonly ISOSDacInterface3? _legacyImpl3;
private readonly ISOSDacInterface4? _legacyImpl4;
private readonly ISOSDacInterface5? _legacyImpl5;
private readonly ISOSDacInterface6? _legacyImpl6;
private readonly ISOSDacInterface7? _legacyImpl7;
private readonly ISOSDacInterface8? _legacyImpl8;
private readonly ISOSDacInterface9? _legacyImpl9;
private readonly ISOSDacInterface10? _legacyImpl10;
private readonly ISOSDacInterface11? _legacyImpl11;
private readonly ISOSDacInterface12? _legacyImpl12;
private readonly ISOSDacInterface13? _legacyImpl13;
private readonly ISOSDacInterface14? _legacyImpl14;
private readonly ISOSDacInterface15? _legacyImpl15;
private readonly ISOSDacInterface16? _legacyImpl16;
private readonly IXCLRDataProcess? _legacyProcess;
private readonly IXCLRDataProcess2? _legacyProcess2;
private readonly ICLRDataEnumMemoryRegions? _legacyEnumMemory;
public SOSDacImpl(Target target, object? legacyObj)
{
_target = target;
_stringMethodTable = new Lazy<TargetPointer>(
() => _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.StringMethodTable)));
_objectMethodTable = new Lazy<TargetPointer>(
() => _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.ObjectMethodTable)));
// Get all the interfaces for delegating to the legacy DAC
if (legacyObj is not null)
{
_legacyImpl = legacyObj as ISOSDacInterface;
_legacyImpl2 = legacyObj as ISOSDacInterface2;
_legacyImpl3 = legacyObj as ISOSDacInterface3;
_legacyImpl4 = legacyObj as ISOSDacInterface4;
_legacyImpl5 = legacyObj as ISOSDacInterface5;
_legacyImpl6 = legacyObj as ISOSDacInterface6;
_legacyImpl7 = legacyObj as ISOSDacInterface7;
_legacyImpl8 = legacyObj as ISOSDacInterface8;
_legacyImpl9 = legacyObj as ISOSDacInterface9;
_legacyImpl10 = legacyObj as ISOSDacInterface10;
_legacyImpl11 = legacyObj as ISOSDacInterface11;
_legacyImpl12 = legacyObj as ISOSDacInterface12;
_legacyImpl13 = legacyObj as ISOSDacInterface13;
_legacyImpl14 = legacyObj as ISOSDacInterface14;
_legacyImpl15 = legacyObj as ISOSDacInterface15;
_legacyImpl16 = legacyObj as ISOSDacInterface16;
_legacyProcess = legacyObj as IXCLRDataProcess;
_legacyProcess2 = legacyObj as IXCLRDataProcess2;
_legacyEnumMemory = legacyObj as ICLRDataEnumMemoryRegions;
}
}
#region ISOSDacInterface
int ISOSDacInterface.GetAppDomainConfigFile(ClrDataAddress appDomain, int count, char* configFile, uint* pNeeded)
{
// Method is not supported on CoreCLR
int hr = HResults.E_FAIL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetAppDomainConfigFile(appDomain, count, configFile, pNeeded);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetAppDomainData(ClrDataAddress addr, DacpAppDomainData* data)
{
int hr = HResults.S_OK;
try
{
if (addr == 0)
throw new ArgumentException();
*data = default;
data->AppDomainPtr = addr;
TargetPointer systemDomainPointer = _target.ReadGlobalPointer(Constants.Globals.SystemDomain);
ClrDataAddress systemDomain = _target.ReadPointer(systemDomainPointer).ToClrDataAddress(_target);
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer globalLoaderAllocator = loader.GetGlobalLoaderAllocator();
data->pHighFrequencyHeap = loader.GetHighFrequencyHeap(globalLoaderAllocator).ToClrDataAddress(_target);
data->pLowFrequencyHeap = loader.GetLowFrequencyHeap(globalLoaderAllocator).ToClrDataAddress(_target);
data->pStubHeap = loader.GetStubHeap(globalLoaderAllocator).ToClrDataAddress(_target);
data->appDomainStage = DacpAppDomainDataStage.STAGE_OPEN;
if (addr != systemDomain)
{
TargetPointer pAppDomain = addr.ToTargetPointer(_target);
data->dwId = _target.ReadGlobal<uint>(Constants.Globals.DefaultADID);
IEnumerable<Contracts.ModuleHandle> modules = loader.GetModuleHandles(
pAppDomain,
AssemblyIterationFlags.IncludeLoading |
AssemblyIterationFlags.IncludeLoaded |
AssemblyIterationFlags.IncludeExecution);
foreach (Contracts.ModuleHandle module in modules)
{
if (loader.IsAssemblyLoaded(module))
{
data->AssemblyCount++;
}
}
IEnumerable<Contracts.ModuleHandle> failedModules = loader.GetModuleHandles(
pAppDomain,
AssemblyIterationFlags.IncludeFailedToLoad);
data->FailedAssemblyCount = failedModules.Count();
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpAppDomainData dataLocal = default;
int hrLocal = _legacyImpl.GetAppDomainData(addr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->AppDomainPtr == dataLocal.AppDomainPtr);
Debug.Assert(data->pHighFrequencyHeap == dataLocal.pHighFrequencyHeap);
Debug.Assert(data->pLowFrequencyHeap == dataLocal.pLowFrequencyHeap);
Debug.Assert(data->pStubHeap == dataLocal.pStubHeap);
Debug.Assert(data->DomainLocalBlock == dataLocal.DomainLocalBlock);
Debug.Assert(data->pDomainLocalModules == dataLocal.pDomainLocalModules);
Debug.Assert(data->dwId == dataLocal.dwId);
Debug.Assert(data->appDomainStage == dataLocal.appDomainStage);
Debug.Assert(data->AssemblyCount == dataLocal.AssemblyCount);
Debug.Assert(data->FailedAssemblyCount == dataLocal.FailedAssemblyCount);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetAppDomainList(uint count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[] values, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
uint i = 0;
TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomain = _target.ReadPointer(appDomainPointer);
if (appDomain != TargetPointer.Null && values.Length > 0)
{
values[0] = appDomain.ToClrDataAddress(_target);
i = 1;
}
if (pNeeded is not null)
{
*pNeeded = i;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress[] valuesLocal = new ClrDataAddress[count];
uint neededLocal;
int hrLocal = _legacyImpl.GetAppDomainList(count, valuesLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
if (values is not null && values.Length > 0 && valuesLocal.Length > 0)
{
Debug.Assert(values[0] == valuesLocal[0], $"cDAC: {values[0]:x}, DAC: {valuesLocal[0]:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetAppDomainName(ClrDataAddress addr, uint count, char* name, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
ILoader loader = _target.Contracts.Loader;
TargetPointer systemDomainPtr = _target.ReadGlobalPointer(Constants.Globals.SystemDomain);
ClrDataAddress systemDomain = _target.ReadPointer(systemDomainPtr).ToClrDataAddress(_target);
string? friendlyName = null;
if (addr != systemDomain)
{
try
{
friendlyName = loader.GetAppDomainFriendlyName();
}
catch (VirtualReadException)
{
// The FriendlyName field is a PTR_CWSTR (pointer to wide char string).
// ReadUtf16String throws VirtualReadException when the pointer targets
// unreadable memory (e.g. the name is not yet set during early init).
// The native DAC handles this via PTR_AppDomain->m_friendlyName.IsValid()
// and falls through to return an empty string. Match that behavior here.
}
}
if (friendlyName is null || friendlyName.Length == 0)
{
if (pNeeded is not null)
{
*pNeeded = 1;
}
if (name is not null && count > 0)
{
name[0] = '\0';
}
}
else
{
if (pNeeded is not null)
{
*pNeeded = (uint)(friendlyName.Length + 1);
}
if (name is not null && count > 0)
{
OutputBufferHelpers.CopyStringToBuffer(name, count, pNeeded, friendlyName);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint neededLocal;
char[] nameLocal = new char[count];
int hrLocal;
fixed (char* ptr = nameLocal)
{
hrLocal = _legacyImpl.GetAppDomainName(addr, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(name == null || new ReadOnlySpan<char>(nameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(name)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetAppDomainStoreData(void* data)
{
DacpAppDomainStoreData* appDomainStoreData = (DacpAppDomainStoreData*)data;
int hr = HResults.S_OK;
try
{
appDomainStoreData->sharedDomain = 0;
TargetPointer systemDomainPtr = _target.ReadGlobalPointer(Constants.Globals.SystemDomain);
appDomainStoreData->systemDomain = _target.ReadPointer(systemDomainPtr).ToClrDataAddress(_target);
TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
appDomainStoreData->DomainCount = _target.ReadPointer(appDomainPtr) != 0 ? 1 : 0;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
{
if (_legacyImpl is not null)
{
DacpAppDomainStoreData dataLocal = default;
int hrLocal = _legacyImpl.GetAppDomainStoreData(&dataLocal);
Debug.ValidateHResult(hr, hrLocal);
Debug.Assert(appDomainStoreData->sharedDomain == dataLocal.sharedDomain, $"cDAC: {appDomainStoreData->sharedDomain:x}, DAC: {dataLocal.sharedDomain:x}");
Debug.Assert(appDomainStoreData->systemDomain == dataLocal.systemDomain, $"cDAC: {appDomainStoreData->systemDomain:x}, DAC: {dataLocal.systemDomain:x}");
Debug.Assert(appDomainStoreData->DomainCount == dataLocal.DomainCount, $"cDAC: {appDomainStoreData->DomainCount}, DAC: {dataLocal.DomainCount}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetApplicationBase(ClrDataAddress appDomain, int count, char* appBase, uint* pNeeded)
{
// Method is not supported on CoreCLR
int hr = HResults.E_FAIL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetApplicationBase(appDomain, count, appBase, pNeeded);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetAssemblyData(ClrDataAddress domain, ClrDataAddress assembly, DacpAssemblyData* data)
{
int hr = HResults.S_OK;
try
{
if (assembly == 0 && domain == 0)
throw new ArgumentException();
// Zero out data structure
*data = default;
data->AssemblyPtr = assembly;
data->DomainPtr = domain;
TargetPointer ppAppDomain = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer pAppDomain = _target.ReadPointer(ppAppDomain);
data->ParentDomain = pAppDomain.ToClrDataAddress(_target);
ILoader loader = _target.Contracts.Loader;
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(assembly.ToTargetPointer(_target));
data->isDynamic = loader.IsDynamic(moduleHandle) ? 1 : 0;
// The DAC increments ModuleCount to 1 if assembly->GetModule() is valid,
// the cDAC assumes that all assemblies will have a module and the above logic relies on that.
// Therefore we always set ModuleCount to 1.
data->ModuleCount = 1;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpAssemblyData dataLocal = default;
int hrLocal = _legacyImpl.GetAssemblyData(domain, assembly, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->AssemblyPtr == dataLocal.AssemblyPtr, $"cDAC: {data->AssemblyPtr:x}, DAC: {dataLocal.AssemblyPtr:x}");
Debug.Assert(data->ClassLoader == dataLocal.ClassLoader, $"cDAC: {data->ClassLoader:x}, DAC: {dataLocal.ClassLoader:x}");
Debug.Assert(data->ParentDomain == dataLocal.ParentDomain, $"cDAC: {data->ParentDomain:x}, DAC: {dataLocal.ParentDomain:x}");
Debug.Assert(data->DomainPtr == dataLocal.DomainPtr, $"cDAC: {data->DomainPtr:x}, DAC: {dataLocal.DomainPtr:x}");
Debug.Assert(data->AssemblySecDesc == dataLocal.AssemblySecDesc, $"cDAC: {data->AssemblySecDesc:x}, DAC: {dataLocal.AssemblySecDesc:x}");
Debug.Assert(data->isDynamic == dataLocal.isDynamic, $"cDAC: {data->isDynamic}, DAC: {dataLocal.isDynamic}");
Debug.Assert(data->ModuleCount == dataLocal.ModuleCount, $"cDAC: {data->ModuleCount}, DAC: {dataLocal.ModuleCount}");
Debug.Assert(data->LoadContext == dataLocal.LoadContext, $"cDAC: {data->LoadContext:x}, DAC: {dataLocal.LoadContext:x}");
Debug.Assert(data->isDomainNeutral == dataLocal.isDomainNeutral, $"cDAC: {data->isDomainNeutral}, DAC: {dataLocal.isDomainNeutral}");
Debug.Assert(data->dwLocationFlags == dataLocal.dwLocationFlags, $"cDAC: {data->dwLocationFlags:x}, DAC: {dataLocal.dwLocationFlags:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetAssemblyList(ClrDataAddress addr, int count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[]? values, int* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (addr == 0)
throw new ArgumentException();
TargetPointer appDomain = addr.ToTargetPointer(_target);
TargetPointer systemDomainPtr = _target.ReadGlobalPointer(Constants.Globals.SystemDomain);
ClrDataAddress systemDomain = _target.ReadPointer(systemDomainPtr).ToClrDataAddress(_target);
if (addr == systemDomain)
// We shouldn't be asking for the assemblies in SystemDomain
throw new ArgumentException();
ILoader loader = _target.Contracts.Loader;
List<Contracts.ModuleHandle> modules = loader.GetModuleHandles(
appDomain,
AssemblyIterationFlags.IncludeLoading |
AssemblyIterationFlags.IncludeLoaded |
AssemblyIterationFlags.IncludeExecution).ToList();
int n = 0; // number of Assemblies that will be returned
if (values is not null)
{
for (int i = 0; i < modules.Count && n < count; i++)
{
Contracts.ModuleHandle module = modules[i];
if (loader.IsAssemblyLoaded(module))
{
values[n++] = loader.GetAssembly(module).ToClrDataAddress(_target);
}
}
}
else
{
for (int i = 0; i < modules.Count; i++)
{
Contracts.ModuleHandle module = modules[i];
if (loader.IsAssemblyLoaded(module))
{
n++;
}
}
}
if (pNeeded is not null)
{
*pNeeded = n;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress[]? valuesLocal = values != null ? new ClrDataAddress[count] : null;
int neededLocal;
int hrLocal = _legacyImpl.GetAssemblyList(addr, count, valuesLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
if (values is not null)
{
// in theory, these don't need to be in the same order, but for consistency it is
// easiest for consumers and verification if the DAC and cDAC return the same order
for (int i = 0; i < neededLocal; i++)
{
Debug.Assert(values[i] == valuesLocal![i], $"cDAC: {values[i]:x}, DAC: {valuesLocal[i]:x}");
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetAssemblyLocation(ClrDataAddress assembly, int count, char* location, uint* pNeeded)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetAssemblyLocation(assembly, count, location, pNeeded) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetAssemblyModuleList(ClrDataAddress assembly, uint count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[]? modules, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (assembly == 0)
throw new ArgumentException();
if (modules is not null && modules.Length > 0 && count > 0)
{
TargetPointer addr = assembly.ToTargetPointer(_target);
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(addr);
TargetPointer modulePointer = loader.GetModule(handle);
modules[0] = modulePointer.ToClrDataAddress(_target);
}
if (pNeeded is not null)
{
*pNeeded = 1;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress[] modulesLocal = new ClrDataAddress[count];
uint neededLocal;
int hrLocal = _legacyImpl.GetAssemblyModuleList(assembly, count, modulesLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
if (modules is not null && modules.Length > 0)
{
Debug.Assert(modules[0] == modulesLocal[0], $"cDAC: {modules[0]:x}, DAC: {modulesLocal[0]:x}");
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetAssemblyName(ClrDataAddress assembly, uint count, char* name, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (name is not null)
name[0] = '\0';
Contracts.ILoader contract = _target.Contracts.Loader;
Contracts.ModuleHandle handle = contract.GetModuleHandleFromAssemblyPtr(assembly.ToTargetPointer(_target));
string path = contract.GetPath(handle);
// Return not implemented for empty paths for non-reflection emit assemblies (for example, loaded from memory)
if (string.IsNullOrEmpty(path))
{
Contracts.ModuleFlags flags = contract.GetFlags(handle);
if (!flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit))
hr = HResults.E_NOTIMPL;
else
hr = HResults.E_FAIL;
}
OutputBufferHelpers.CopyStringToBuffer(name, count, pNeeded, path);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] fileNameLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = fileNameLocal)
{
hrLocal = _legacyImpl.GetAssemblyName(assembly, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(name == null || new ReadOnlySpan<char>(fileNameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(name)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetCCWData(ClrDataAddress ccw, DacpCCWData* data)
{
int hr = HResults.S_OK;
try
{
if (ccw == 0 || data == null)
throw new ArgumentException();
*data = default;
Contracts.IBuiltInCOM contract = _target.Contracts.BuiltInCOM;
// Try to resolve as a COM interface pointer; if not recognised, treat as a direct CCW pointer.
TargetPointer ccwPtr = contract.GetCCWFromInterfacePointer(ccw.ToTargetPointer(_target));
if (ccwPtr == TargetPointer.Null)
ccwPtr = ccw.ToTargetPointer(_target);
// Navigate to the start wrapper, mirroring DACGetCCWFromAddress.
ccwPtr = contract.GetStartWrapper(ccwPtr);
SimpleComCallWrapperData sccwData = contract.GetSimpleComCallWrapperData(ccwPtr);
int refCount = (int)sccwData.RefCount;
data->outerIUnknown = sccwData.OuterIUnknown.ToClrDataAddress(_target);
TargetPointer handle = contract.GetObjectHandle(ccwPtr);
data->handle = handle.ToClrDataAddress(_target);
if (handle != TargetPointer.Null)
{
data->managedObject = _target.ReadPointer(handle).ToClrDataAddress(_target);
}
data->ccwAddress = ccwPtr.ToClrDataAddress(_target);
data->refCount = refCount;
data->interfaceCount = contract.GetCCWInterfaces(ccwPtr).Count();
data->isNeutered = sccwData.IsNeutered ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
data->jupiterRefCount = 0;
data->isPegged = Interop.BOOL.FALSE;
data->isGlobalPegged = Interop.BOOL.FALSE;
data->hasStrongRef = (refCount > 0) && !sccwData.IsHandleWeak ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
data->isExtendsCOMObject = sccwData.IsExtendsCOMObject ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
data->isAggregated = sccwData.IsAggregated ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpCCWData dataLocal = default;
int hrLocal = _legacyImpl.GetCCWData(ccw, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->outerIUnknown == dataLocal.outerIUnknown, $"cDAC outerIUnknown: {data->outerIUnknown:x}, DAC: {dataLocal.outerIUnknown:x}");
Debug.Assert(data->managedObject == dataLocal.managedObject, $"cDAC managedObject: {data->managedObject:x}, DAC: {dataLocal.managedObject:x}");
Debug.Assert(data->handle == dataLocal.handle, $"cDAC handle: {data->handle:x}, DAC: {dataLocal.handle:x}");
Debug.Assert(data->ccwAddress == dataLocal.ccwAddress, $"cDAC ccwAddress: {data->ccwAddress:x}, DAC: {dataLocal.ccwAddress:x}");
Debug.Assert(data->refCount == dataLocal.refCount, $"cDAC refCount: {data->refCount}, DAC: {dataLocal.refCount}");
Debug.Assert(data->interfaceCount == dataLocal.interfaceCount, $"cDAC interfaceCount: {data->interfaceCount}, DAC: {dataLocal.interfaceCount}");
Debug.Assert(data->isNeutered == dataLocal.isNeutered, $"cDAC isNeutered: {data->isNeutered}, DAC: {dataLocal.isNeutered}");
Debug.Assert(data->jupiterRefCount == dataLocal.jupiterRefCount, $"cDAC jupiterRefCount: {data->jupiterRefCount}, DAC: {dataLocal.jupiterRefCount}");
Debug.Assert(data->isPegged == dataLocal.isPegged, $"cDAC isPegged: {data->isPegged}, DAC: {dataLocal.isPegged}");
Debug.Assert(data->isGlobalPegged == dataLocal.isGlobalPegged, $"cDAC isGlobalPegged: {data->isGlobalPegged}, DAC: {dataLocal.isGlobalPegged}");
Debug.Assert(data->hasStrongRef == dataLocal.hasStrongRef, $"cDAC hasStrongRef: {data->hasStrongRef}, DAC: {dataLocal.hasStrongRef}");
Debug.Assert(data->isExtendsCOMObject == dataLocal.isExtendsCOMObject, $"cDAC isExtendsCOMObject: {data->isExtendsCOMObject}, DAC: {dataLocal.isExtendsCOMObject}");
Debug.Assert(data->isAggregated == dataLocal.isAggregated, $"cDAC isAggregated: {data->isAggregated}, DAC: {dataLocal.isAggregated}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetCCWInterfaces(ClrDataAddress ccw, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpCOMInterfacePointerData[]? interfaces, uint* pNeeded)
{
int hr = HResults.S_OK;
#if DEBUG
int numWritten = 0;
#endif
try
{
if (ccw == 0 || (interfaces == null && pNeeded == null))
throw new ArgumentException();
Contracts.IBuiltInCOM builtInCOMContract = _target.Contracts.BuiltInCOM; // E_NOTIMPL if contract is not present
// Try to resolve as a COM interface pointer; if not recognised, treat as a direct CCW pointer.
// GetCCWInterfaces navigates to the start of the chain in both cases.
TargetPointer startCCW = builtInCOMContract.GetCCWFromInterfacePointer(ccw.ToTargetPointer(_target));
if (startCCW == TargetPointer.Null)
startCCW = ccw.ToTargetPointer(_target);
IEnumerable<Contracts.COMInterfacePointerData> result =
builtInCOMContract.GetCCWInterfaces(startCCW);
if (interfaces == null)
{
uint c = (uint)result.Count();
#if DEBUG
numWritten = (int)c;
#endif
if (pNeeded is not null)
*pNeeded = c;
}
else
{
uint itemIndex = 0;
foreach (Contracts.COMInterfacePointerData item in result)
{
if (itemIndex >= count)
{
#if DEBUG
numWritten = (int)itemIndex;
#endif
throw new ArgumentException();
}
interfaces[itemIndex].methodTable = item.MethodTable.ToClrDataAddress(_target);
interfaces[itemIndex].interfacePtr = item.InterfacePointerAddress.ToClrDataAddress(_target);
interfaces[itemIndex].comContext = 0;
itemIndex++;
}
#if DEBUG
numWritten = (int)itemIndex;
#endif
if (pNeeded is not null)
{
*pNeeded = itemIndex;
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpCOMInterfacePointerData[]? interfacesLocal = interfaces != null ? new DacpCOMInterfacePointerData[(int)count] : null;
uint pNeededLocal = 0;
int hrLocal = _legacyImpl.GetCCWInterfaces(ccw, count, interfacesLocal, pNeeded == null && interfacesLocal == null ? null : &pNeededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC count: {(pNeeded is null ? "null" : (*pNeeded).ToString())}, DAC count: {pNeededLocal}");
if (interfaces != null && interfacesLocal != null)
{
for (uint i = 0; i < (int)pNeededLocal; i++)
{
Debug.Assert(interfaces[i].methodTable == interfacesLocal![i].methodTable, $"cDAC methodTable[{i}]: {interfaces[i].methodTable:x}, DAC: {interfacesLocal[i].methodTable:x}");
Debug.Assert(interfaces[i].interfacePtr == interfacesLocal![i].interfacePtr, $"cDAC interfacePtr[{i}]: {interfaces[i].interfacePtr:x}, DAC: {interfacesLocal[i].interfacePtr:x}");
Debug.Assert(interfaces[i].comContext == interfacesLocal![i].comContext, $"cDAC comContext[{i}]: {interfaces[i].comContext:x}, DAC: {interfacesLocal[i].comContext:x}");
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetClrWatsonBuckets(ClrDataAddress thread, void* pGenericModeBlock)
{
int hr = HResults.S_OK;
Contracts.IThread threadContract = _target.Contracts.Thread;
byte[] buckets = Array.Empty<byte>();
try
{
if (_target.Contracts.RuntimeInfo.GetTargetOperatingSystem() != RuntimeInfoOperatingSystem.Windows)
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
if (thread == 0 || pGenericModeBlock == null)
throw new ArgumentException();
buckets = threadContract.GetWatsonBuckets(thread.ToTargetPointer(_target));
if (buckets.Length != 0)
{
var dest = new Span<byte>(pGenericModeBlock, buckets.Length);
buckets.AsSpan().CopyTo(dest);
}
else
{
hr = HResults.S_FALSE; // No Watson buckets found
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal;
int sizeOfGenericModeBlock = (int)_target.ReadGlobal<uint>(Constants.Globals.SizeOfGenericModeBlock);
byte[] genericModeBlockLocal = new byte[sizeOfGenericModeBlock];
fixed (byte* ptr = genericModeBlockLocal)
{
hrLocal = _legacyImpl.GetClrWatsonBuckets(thread, ptr);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(new ReadOnlySpan<byte>(genericModeBlockLocal, 0, sizeOfGenericModeBlock).SequenceEqual(new Span<byte>(pGenericModeBlock, sizeOfGenericModeBlock)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetCodeHeaderData(ClrDataAddress ip, DacpCodeHeaderData* data)
{
int hr = HResults.S_OK;
try
{
if (ip == 0 || data == null)
throw new ArgumentException();
IExecutionManager eman = _target.Contracts.ExecutionManager;
IGCInfo gcInfo = _target.Contracts.GCInfo;
TargetCodePointer targetCodePointer = ip.ToTargetCodePointer(_target);
if (eman.GetCodeBlockHandle(targetCodePointer) is not CodeBlockHandle cbh)
{
TargetPointer methodDesc = eman.NonVirtualEntry2MethodDesc(targetCodePointer);
if (methodDesc == TargetPointer.Null)
throw new ArgumentException();
data->MethodDescPtr = methodDesc.ToClrDataAddress(_target);
data->JITType = JitTypes.TYPE_UNKNOWN;
data->GCInfo = 0;
data->MethodStart = 0;
data->MethodSize = 0;
data->HotRegionSize = 0;
data->ColdRegionSize = 0;
data->ColdRegionStart = 0;
}
else
{
data->MethodDescPtr = eman.GetMethodDesc(cbh).ToClrDataAddress(_target);
Contracts.CodeKind codeKind = eman.GetCodeKind(targetCodePointer);
data->JITType = codeKind switch
{
Contracts.CodeKind.Jitted => JitTypes.TYPE_JIT,
Contracts.CodeKind.ReadyToRun => JitTypes.TYPE_PJIT,
Contracts.CodeKind.Interpreter => JitTypes.TYPE_INTERPRETER,
_ => JitTypes.TYPE_UNKNOWN,
};
eman.GetGCInfo(cbh, out TargetPointer pGcInfo, out uint gcVersion);
data->GCInfo = pGcInfo.ToClrDataAddress(_target);
data->MethodStart = eman.GetStartAddress(cbh).Value;
// Mirrors native ClrDataAccess::GetCodeHeaderData which routes through
// EECodeInfo::GetCodeManager()->GetFunctionSize: interpreter code uses the
// interpreter-specific GC info encoding, all other code uses the platform
// GC info encoding.
IGCInfoHandle gcInfoHandle = codeKind == Contracts.CodeKind.Interpreter
? gcInfo.DecodeInterpreterGCInfo(pGcInfo, gcVersion)
: gcInfo.DecodePlatformSpecificGCInfo(pGcInfo, gcVersion);
data->MethodSize = gcInfo.GetCodeLength(gcInfoHandle);
eman.GetMethodRegionInfo(cbh, out uint hotRegionSize, out TargetPointer coldRegionStart, out uint coldRegionSize);
data->HotRegionSize = hotRegionSize;
data->ColdRegionSize = coldRegionSize;
data->ColdRegionStart = coldRegionStart.ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpCodeHeaderData dataLocal = default;
int hrLocal = _legacyImpl.GetCodeHeaderData(ip, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->MethodDescPtr == dataLocal.MethodDescPtr, $"cDAC: {data->MethodDescPtr:x}, DAC: {dataLocal.MethodDescPtr:x}");
Debug.Assert(data->JITType == dataLocal.JITType, $"cDAC: {data->JITType}, DAC: {dataLocal.JITType}");
Debug.Assert(data->GCInfo == dataLocal.GCInfo, $"cDAC: {data->GCInfo:x}, DAC: {dataLocal.GCInfo:x}");
Debug.Assert(data->MethodStart == dataLocal.MethodStart, $"cDAC: {data->MethodStart:x}, DAC: {dataLocal.MethodStart:x}");
Debug.Assert(data->MethodSize == dataLocal.MethodSize, $"cDAC: {data->MethodSize}, DAC: {dataLocal.MethodSize}");
Debug.Assert(data->HotRegionSize == dataLocal.HotRegionSize, $"cDAC: {data->HotRegionSize}, DAC: {dataLocal.HotRegionSize}");
Debug.Assert(data->ColdRegionStart == dataLocal.ColdRegionStart, $"cDAC: {data->ColdRegionStart:x}, DAC: {dataLocal.ColdRegionStart:x}");
Debug.Assert(data->ColdRegionSize == dataLocal.ColdRegionSize, $"cDAC: {data->ColdRegionSize}, DAC: {dataLocal.ColdRegionSize}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetCodeHeapList(ClrDataAddress jitManager, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpJitCodeHeapInfo[]? codeHeaps, uint* pNeeded)
{
// returns the code heap list from the single global EEJitManager.
int hr = HResults.S_OK;
try
{
if (jitManager == 0 || (codeHeaps == null && pNeeded == null))
throw new ArgumentException();
IExecutionManager em = _target.Contracts.ExecutionManager;
#if DEBUG
Contracts.JitManagerInfo jitManagerInfo = em.GetEEJitManagerInfo();
Debug.Assert(jitManager.ToTargetPointer(_target) == jitManagerInfo.ManagerAddress);
#endif
List<ICodeHeapInfo> heapInfos = em.GetCodeHeapInfos().ToList();
int i = 0;
if (codeHeaps is not null)
{
for (; i < heapInfos.Count && i < count; i++)
{
codeHeaps[i] = default;
switch (heapInfos[i])
{
case Contracts.LoaderCodeHeapInfo loader:
codeHeaps[i].codeHeapType = DacpJitCodeHeapInfo.CodeHeapType.CODEHEAP_LOADER;
codeHeaps[i].LoaderHeap = loader.LoaderHeapAddress.ToClrDataAddress(_target);
break;
case Contracts.HostCodeHeapInfo host:
codeHeaps[i].codeHeapType = DacpJitCodeHeapInfo.CodeHeapType.CODEHEAP_HOST;
codeHeaps[i].baseAddr = host.BaseAddress.ToClrDataAddress(_target);
codeHeaps[i].currentAddr = host.CurrentAddress.ToClrDataAddress(_target);
break;
default:
codeHeaps[i].codeHeapType = DacpJitCodeHeapInfo.CodeHeapType.CODEHEAP_UNKNOWN;
break;
}
}
}
if (pNeeded is not null)
*pNeeded = codeHeaps is not null ? (uint)i : (uint)heapInfos.Count;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint neededLocal = 0;
DacpJitCodeHeapInfo[]? legacyHeaps = codeHeaps is not null ? new DacpJitCodeHeapInfo[(int)count] : null;
int hrLocal = _legacyImpl.GetCodeHeapList(jitManager, count, legacyHeaps, codeHeaps is null && pNeeded is null ? null : &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
if (pNeeded != null)
{
Debug.Assert(*pNeeded == neededLocal, $"cDAC count: {(*pNeeded):x}, DAC count: {neededLocal:x}");
}
if (codeHeaps != null && legacyHeaps != null)
{
for (uint i = 0; i < neededLocal && i < count; i++)
{
Debug.Assert(codeHeaps[i].codeHeapType == legacyHeaps![i].codeHeapType,
$"cDAC heap[{i}] type: {codeHeaps[i].codeHeapType}, DAC: {legacyHeaps[i].codeHeapType}");
if (codeHeaps[i].codeHeapType == DacpJitCodeHeapInfo.CodeHeapType.CODEHEAP_LOADER)
Debug.Assert(codeHeaps[i].LoaderHeap == legacyHeaps[i].LoaderHeap,
$"cDAC heap[{i}] LoaderHeap: {codeHeaps[i].LoaderHeap:x}, DAC: {legacyHeaps[i].LoaderHeap:x}");
else if (codeHeaps[i].codeHeapType == DacpJitCodeHeapInfo.CodeHeapType.CODEHEAP_HOST)
{
Debug.Assert(codeHeaps[i].baseAddr == legacyHeaps[i].baseAddr,
$"cDAC heap[{i}] baseAddr: {codeHeaps[i].baseAddr:x}, DAC: {legacyHeaps[i].baseAddr:x}");
Debug.Assert(codeHeaps[i].currentAddr == legacyHeaps[i].currentAddr,
$"cDAC heap[{i}] currentAddr: {codeHeaps[i].currentAddr:x}, DAC: {legacyHeaps[i].currentAddr:x}");
}
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetDacModuleHandle(void* phModule)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetDacModuleHandle(phModule) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetDomainFromContext(ClrDataAddress context, ClrDataAddress* domain)
{
int hr = HResults.S_OK;
try
{
if (context == 0 || domain == null)
throw new ArgumentException();
*domain = context;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress domainLocal;
int hrLocal = _legacyImpl.GetDomainFromContext(context, &domainLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(domainLocal == context, $"cDAC: {context:x}, DAC: {domainLocal:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetDomainLocalModuleData(ClrDataAddress addr, void* data)
{
// CoreCLR does not use domain local modules anymore
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetDomainLocalModuleData(addr, data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetDomainLocalModuleDataFromAppDomain(ClrDataAddress appDomainAddr, int moduleID, void* data)
{
// CoreCLR does not support multi-appdomain shared assembly loading. Thus, a non-pointer sized moduleID cannot exist.
int hr = HResults.E_INVALIDARG;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetDomainLocalModuleDataFromAppDomain(appDomainAddr, moduleID, data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetDomainLocalModuleDataFromModule(ClrDataAddress moduleAddr, void* data)
{
// CoreCLR does not use domain local modules anymore
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetDomainLocalModuleDataFromModule(moduleAddr, data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetFailedAssemblyData(ClrDataAddress assembly, uint* pContext, int* pResult)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetFailedAssemblyData(assembly, pContext, pResult) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetFailedAssemblyDisplayName(ClrDataAddress assembly, uint count, char* name, uint* pNeeded)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetFailedAssemblyDisplayName(assembly, count, name, pNeeded) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetFailedAssemblyList(ClrDataAddress appDomain, int count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[] values, uint* pNeeded)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetFailedAssemblyList(appDomain, count, values, pNeeded) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetFailedAssemblyLocation(ClrDataAddress assembly, uint count, char* location, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (assembly == 0 || (location == null && pNeeded == null) || (location != null && count == 0))
throw new ArgumentException();
if (pNeeded != null)
*pNeeded = 1;
if (location != null)
location[0] = '\0';
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] locationLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = locationLocal)
{
hrLocal = _legacyImpl.GetFailedAssemblyLocation(assembly, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(location == null || new ReadOnlySpan<char>(locationLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(location)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescData* data)
{
int hr = HResults.S_OK;
try
{
if (fieldDesc == 0 || data == null)
throw new ArgumentException();
IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
IEcmaMetadata ecmaMetadataContract = _target.Contracts.EcmaMetadata;
ISignature signatureContract = _target.Contracts.Signature;
TargetPointer fieldDescTargetPtr = fieldDesc.ToTargetPointer(_target);
CorElementType fieldDescType = rtsContract.GetFieldDescType(fieldDescTargetPtr);
data->Type = fieldDescType;
data->sigType = fieldDescType;
uint token = rtsContract.GetFieldDescMemberDef(fieldDescTargetPtr);
FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token);
TargetPointer enclosingMT = rtsContract.GetMTOfEnclosingClass(fieldDescTargetPtr);
TypeHandle ctx = rtsContract.GetTypeHandle(enclosingMT);
TargetPointer modulePtr = rtsContract.GetModule(ctx);
Contracts.ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
MetadataReader mdReader = ecmaMetadataContract.GetMetadata(moduleHandle)!;
FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle);
try
{
// try to completely decode the signature
TypeHandle foundTypeHandle = signatureContract.DecodeFieldSignature(fieldDef.Signature, moduleHandle, ctx);
// get the MT of the type
// This is an implementation detail of the DAC that we replicate here to get method tables for non-MT types
// that we can return to SOS for pretty-printing.
// In the future we may want to return a TypeHandle instead of a MethodTable, and modify SOS to do more complete pretty-printing.
// DAC equivalent: src/coreclr/vm/typehandle.inl TypeHandle::GetMethodTable
if (rtsContract.IsFunctionPointer(foundTypeHandle, out _, out _) || rtsContract.IsPointer(foundTypeHandle))
data->MTOfType = rtsContract.GetPrimitiveType(CorElementType.U).Address.ToClrDataAddress(_target);
// array MTs
else if (rtsContract.IsArray(foundTypeHandle, out _))
data->MTOfType = foundTypeHandle.Address.ToClrDataAddress(_target);
else
{
try
{
// value typedescs
TypeHandle paramTypeHandle = rtsContract.GetTypeParam(foundTypeHandle);
data->MTOfType = paramTypeHandle.Address.ToClrDataAddress(_target);
}
catch (ArgumentException)
{
// non-array MTs
data->MTOfType = foundTypeHandle.Address.ToClrDataAddress(_target);
}
}
}
catch (VirtualReadException)
{
// if we can't find the MT (e.g in a minidump)
data->MTOfType = 0;
}
// partial decoding of signature
BlobReader blobReader = mdReader.GetBlobReader(fieldDef.Signature);
SignatureHeader header = blobReader.ReadSignatureHeader();
// read the header byte and check for correctness
if (header.Kind != SignatureKind.Field)
throw new BadImageFormatException();
// read the top-level type
CorElementType typeCode;
EntityHandle entityHandle;
// in a loop, read custom modifiers until we get to the underlying type
do
{
typeCode = (CorElementType)blobReader.ReadByte();
entityHandle = blobReader.ReadTypeHandle(); // consume the type
} while (typeCode is CorElementType.CModReqd or CorElementType.CModOpt); // eat custom modifiers
if (typeCode is CorElementType.Class or CorElementType.ValueType)
{
// if the typecode is class or value, we have been able to read the token that follows in the sig
data->TokenOfType = (uint)MetadataTokens.GetToken(entityHandle);
}
else
{
// otherwise we have not found the token here, but we can encode the underlying type in sigType
data->TokenOfType = (uint)EcmaMetadataUtils.TokenType.mdtTypeDef;
if (data->MTOfType == 0)
data->sigType = typeCode;
}
data->ModuleOfType = modulePtr.ToClrDataAddress(_target);
data->mb = token;
data->MTOfEnclosingClass = ctx.Address.ToClrDataAddress(_target);
data->dwOffset = rtsContract.GetFieldDescOffset(fieldDescTargetPtr, fieldDef);
data->bIsThreadLocal = rtsContract.IsFieldDescThreadStatic(fieldDescTargetPtr) ? 1 : 0;
data->bIsContextLocal = 0;
data->bIsStatic = rtsContract.IsFieldDescStatic(fieldDescTargetPtr) ? 1 : 0;
data->NextField = fieldDescTargetPtr + _target.GetTypeInfo(DataType.FieldDesc).Size!.Value;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpFieldDescData dataLocal = default;
int hrLocal = _legacyImpl.GetFieldDescData(fieldDesc, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->Type == dataLocal.Type, $"cDAC: {data->Type}, DAC: {dataLocal.Type}");
Debug.Assert(data->sigType == dataLocal.sigType, $"cDAC: {data->sigType}, DAC: {dataLocal.sigType}");
Debug.Assert(data->TokenOfType == dataLocal.TokenOfType, $"cDAC: {data->TokenOfType:x}, DAC: {dataLocal.TokenOfType:x}");
Debug.Assert(data->MTOfType == dataLocal.MTOfType, $"cDAC: {data->MTOfType:x}, DAC: {dataLocal.MTOfType:x}");
Debug.Assert(data->ModuleOfType == dataLocal.ModuleOfType, $"cDAC: {data->ModuleOfType:x}, DAC: {dataLocal.ModuleOfType:x}");
Debug.Assert(data->mb == dataLocal.mb, $"cDAC: {data->mb:x}, DAC: {dataLocal.mb:x}");
Debug.Assert(data->MTOfEnclosingClass == dataLocal.MTOfEnclosingClass, $"cDAC: {data->MTOfEnclosingClass:x}, DAC: {dataLocal.MTOfEnclosingClass:x}");
Debug.Assert(data->dwOffset == dataLocal.dwOffset, $"cDAC: {data->dwOffset:x}, DAC: {dataLocal.dwOffset:x}");
Debug.Assert(data->bIsThreadLocal == dataLocal.bIsThreadLocal, $"cDAC: {data->bIsThreadLocal}, DAC: {dataLocal.bIsThreadLocal}");
Debug.Assert(data->bIsContextLocal == dataLocal.bIsContextLocal, $"cDAC: {data->bIsContextLocal}, DAC: {dataLocal.bIsContextLocal}");
Debug.Assert(data->bIsStatic == dataLocal.bIsStatic, $"cDAC: {data->bIsStatic}, DAC: {dataLocal.bIsStatic}");
Debug.Assert(data->NextField == dataLocal.NextField, $"cDAC: {data->NextField:x}, DAC: {dataLocal.NextField:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetFrameName(ClrDataAddress vtable, uint count, char* frameName, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (vtable == 0)
throw new ArgumentException();
IStackWalk stackWalk = _target.Contracts.StackWalk;
string name = stackWalk.GetFrameName(new(vtable));
if (string.IsNullOrEmpty(name))
throw new ArgumentException();
OutputBufferHelpers.CopyStringToBuffer(frameName, count, pNeeded, name);
if (frameName is not null && pNeeded is not null)
{
// the DAC version of this API does not count the trailing null terminator
// if a buffer is provided
(*pNeeded)--;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] nameLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = nameLocal)
{
hrLocal = _legacyImpl.GetFrameName(vtable, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(frameName == null || new ReadOnlySpan<char>(nameLocal, 0, (int)neededLocal).SequenceEqual(new string(frameName)),
$"cDAC: {new string(frameName)}, DAC: {new string(nameLocal, 0, (int)neededLocal)}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetGCHeapData(DacpGcHeapData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] heapType = gc.GetGCIdentifiers();
if (!heapType.Contains(GCIdentifiers.Workstation) && !heapType.Contains(GCIdentifiers.Server))
{
// If the GC type is not recognized, we cannot provide heap data
hr = HResults.E_FAIL;
}
else
{
data->g_max_generation = gc.GetMaxGeneration();
data->bServerMode = heapType.Contains(GCIdentifiers.Server) ? 1 : 0;
data->bGcStructuresValid = gc.GetGCStructuresValid() ? 1 : 0;
data->HeapCount = gc.GetGCHeapCount();
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpGcHeapData dataLocal = default;
int hrLocal = _legacyImpl.GetGCHeapData(&dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->bServerMode == dataLocal.bServerMode, $"cDAC: {data->bServerMode}, DAC: {dataLocal.bServerMode}");
Debug.Assert(data->bGcStructuresValid == dataLocal.bGcStructuresValid, $"cDAC: {data->bGcStructuresValid}, DAC: {dataLocal.bGcStructuresValid}");
Debug.Assert(data->HeapCount == dataLocal.HeapCount, $"cDAC: {data->HeapCount}, DAC: {dataLocal.HeapCount}");
Debug.Assert(data->g_max_generation == dataLocal.g_max_generation, $"cDAC: {data->g_max_generation}, DAC: {dataLocal.g_max_generation}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetGCHeapList(uint count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[] heaps, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
IGC gc = _target.Contracts.GC;
string[] heapType = gc.GetGCIdentifiers();
if (!heapType.Contains(GCIdentifiers.Server))
{
// If GC type is not server, this API is not supported
hr = HResults.E_FAIL;
}
else
{
uint heapCount = gc.GetGCHeapCount();
if (pNeeded is not null)
{
*pNeeded = heapCount;
}
if (heaps.Length == heapCount)
{
List<TargetPointer> gcHeaps = gc.GetGCHeaps().ToList();
Debug.Assert(gcHeaps.Count == heapCount, "Expected the number of GC heaps to match the count returned by GetGCHeapCount");
for (uint i = 0; i < heapCount; i++)
{
heaps[i] = gcHeaps[(int)i].ToClrDataAddress(_target);
}
}
else if (heaps.Length != 0)
{
hr = HResults.E_INVALIDARG;
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress[] heapsLocal = new ClrDataAddress[count];
uint neededLocal;
int hrLocal = _legacyImpl.GetGCHeapList(count, heapsLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
// in theory, these don't need to be in the same order, but for consistency it is
// easiest for consumers and verification if the DAC and cDAC return the same order
for (int i = 0; i < neededLocal; i++)
{
Debug.Assert(heaps[i] == heapsLocal[i], $"cDAC: {heaps[i]:x}, DAC: {heapsLocal[i]:x}");
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, DacpGcHeapDetails* details)
{
int hr = HResults.S_OK;
try
{
if (heap == 0 || details == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// doesn't make sense to call this on WKS mode
if (!gcIdentifiers.Contains(GCIdentifiers.Server))
throw new ArgumentException();
GCHeapData heapData = gc.GetHeapData(heap.ToTargetPointer(_target));
details->heapAddr = heap;
gc.GetGCBounds(out TargetPointer minAddress, out TargetPointer maxAddress);
details->lowest_address = minAddress.ToClrDataAddress(_target);
details->highest_address = maxAddress.ToClrDataAddress(_target);
if (gcIdentifiers.Contains(GCIdentifiers.Background))
{
details->current_c_gc_state = gc.GetCurrentGCState();
details->mark_array = heapData.MarkArray.ToClrDataAddress(_target);
details->next_sweep_obj = heapData.NextSweepObject.ToClrDataAddress(_target);
details->background_saved_lowest_address = heapData.BackGroundSavedMinAddress.ToClrDataAddress(_target);
details->background_saved_highest_address = heapData.BackGroundSavedMaxAddress.ToClrDataAddress(_target);
}
else
{
details->current_c_gc_state = 0;
details->mark_array = unchecked((ulong)-1);
details->next_sweep_obj = 0;
details->background_saved_lowest_address = 0;
details->background_saved_highest_address = 0;
}
// now get information specific to this heap (server mode gives us several heaps; we're getting
// information about only one of them.
details->alloc_allocated = heapData.AllocAllocated.ToClrDataAddress(_target);
details->ephemeral_heap_segment = heapData.EphemeralHeapSegment.ToClrDataAddress(_target);
details->card_table = heapData.CardTable.ToClrDataAddress(_target);
if (gcIdentifiers.Contains(GCIdentifiers.Regions))
{
// with regions, we don't have these variables anymore
// use special value -1 in saved_sweep_ephemeral_seg to signal the region case
details->saved_sweep_ephemeral_seg = unchecked((ulong)-1);
details->saved_sweep_ephemeral_start = 0;
}
else
{
if (gcIdentifiers.Contains(GCIdentifiers.Background))
{
details->saved_sweep_ephemeral_seg = heapData.SavedSweepEphemeralSegment.ToClrDataAddress(_target);
details->saved_sweep_ephemeral_start = heapData.SavedSweepEphemeralStart.ToClrDataAddress(_target);
}
else
{
details->saved_sweep_ephemeral_seg = 0;
details->saved_sweep_ephemeral_start = 0;
}
}
// get bounds for the different generations
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS && i < heapData.GenerationTable.Count; i++)
{
GCGenerationData genData = heapData.GenerationTable[i];
details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target);
details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target);
details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target);
details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target);
}
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3 && i < heapData.FillPointers.Count; i++)
{
details->finalization_fill_pointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpGcHeapDetails detailsLocal = default;
int hrLocal = _legacyImpl.GetGCHeapDetails(heap, &detailsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(details->heapAddr == detailsLocal.heapAddr, $"cDAC: {details->heapAddr:x}, DAC: {detailsLocal.heapAddr:x}");
Debug.Assert(details->alloc_allocated == detailsLocal.alloc_allocated, $"cDAC: {details->alloc_allocated:x}, DAC: {detailsLocal.alloc_allocated:x}");
Debug.Assert(details->mark_array == detailsLocal.mark_array, $"cDAC: {details->mark_array:x}, DAC: {detailsLocal.mark_array:x}");
Debug.Assert(details->current_c_gc_state == detailsLocal.current_c_gc_state, $"cDAC: {details->current_c_gc_state:x}, DAC: {detailsLocal.current_c_gc_state:x}");
Debug.Assert(details->next_sweep_obj == detailsLocal.next_sweep_obj, $"cDAC: {details->next_sweep_obj:x}, DAC: {detailsLocal.next_sweep_obj:x}");
Debug.Assert(details->saved_sweep_ephemeral_seg == detailsLocal.saved_sweep_ephemeral_seg, $"cDAC: {details->saved_sweep_ephemeral_seg:x}, DAC: {detailsLocal.saved_sweep_ephemeral_seg:x}");
Debug.Assert(details->saved_sweep_ephemeral_start == detailsLocal.saved_sweep_ephemeral_start, $"cDAC: {details->saved_sweep_ephemeral_start:x}, DAC: {detailsLocal.saved_sweep_ephemeral_start:x}");
Debug.Assert(details->background_saved_lowest_address == detailsLocal.background_saved_lowest_address, $"cDAC: {details->background_saved_lowest_address:x}, DAC: {detailsLocal.background_saved_lowest_address:x}");
Debug.Assert(details->background_saved_highest_address == detailsLocal.background_saved_highest_address, $"cDAC: {details->background_saved_highest_address:x}, DAC: {detailsLocal.background_saved_highest_address:x}");
// Verify generation table data
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS; i++)
{
Debug.Assert(details->generation_table[i].start_segment == detailsLocal.generation_table[i].start_segment, $"cDAC gen[{i}].start_segment: {details->generation_table[i].start_segment:x}, DAC: {detailsLocal.generation_table[i].start_segment:x}");
Debug.Assert(details->generation_table[i].allocation_start == detailsLocal.generation_table[i].allocation_start, $"cDAC gen[{i}].allocation_start: {details->generation_table[i].allocation_start:x}, DAC: {detailsLocal.generation_table[i].allocation_start:x}");
Debug.Assert(details->generation_table[i].allocContextPtr == detailsLocal.generation_table[i].allocContextPtr, $"cDAC gen[{i}].allocContextPtr: {details->generation_table[i].allocContextPtr:x}, DAC: {detailsLocal.generation_table[i].allocContextPtr:x}");
Debug.Assert(details->generation_table[i].allocContextLimit == detailsLocal.generation_table[i].allocContextLimit, $"cDAC gen[{i}].allocContextLimit: {details->generation_table[i].allocContextLimit:x}, DAC: {detailsLocal.generation_table[i].allocContextLimit:x}");
}
Debug.Assert(details->ephemeral_heap_segment == detailsLocal.ephemeral_heap_segment, $"cDAC: {details->ephemeral_heap_segment:x}, DAC: {detailsLocal.ephemeral_heap_segment:x}");
// Verify finalization fill pointers
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3; i++)
{
Debug.Assert(details->finalization_fill_pointers[i] == detailsLocal.finalization_fill_pointers[i], $"cDAC finalization_fill_pointers[{i}]: {details->finalization_fill_pointers[i]:x}, DAC: {detailsLocal.finalization_fill_pointers[i]:x}");
}
Debug.Assert(details->lowest_address == detailsLocal.lowest_address, $"cDAC: {details->lowest_address:x}, DAC: {detailsLocal.lowest_address:x}");
Debug.Assert(details->highest_address == detailsLocal.highest_address, $"cDAC: {details->highest_address:x}, DAC: {detailsLocal.highest_address:x}");
Debug.Assert(details->card_table == detailsLocal.card_table, $"cDAC: {details->card_table:x}, DAC: {detailsLocal.card_table:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details)
{
int hr = HResults.S_OK;
try
{
if (details == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// doesn't make sense to call this on SVR mode
if (!gcIdentifiers.Contains(GCIdentifiers.Workstation))
throw new ArgumentException();
GCHeapData heapData = gc.GetHeapData();
details->heapAddr = 0;
gc.GetGCBounds(out TargetPointer minAddress, out TargetPointer maxAddress);
details->lowest_address = minAddress.ToClrDataAddress(_target);
details->highest_address = maxAddress.ToClrDataAddress(_target);
if (gcIdentifiers.Contains(GCIdentifiers.Background))
{
details->current_c_gc_state = gc.GetCurrentGCState();
details->mark_array = heapData.MarkArray.ToClrDataAddress(_target);
details->next_sweep_obj = heapData.NextSweepObject.ToClrDataAddress(_target);
details->background_saved_lowest_address = heapData.BackGroundSavedMinAddress.ToClrDataAddress(_target);
details->background_saved_highest_address = heapData.BackGroundSavedMaxAddress.ToClrDataAddress(_target);
}
else
{
details->current_c_gc_state = 0;
details->mark_array = unchecked((ulong)-1);
details->next_sweep_obj = 0;
details->background_saved_lowest_address = 0;
details->background_saved_highest_address = 0;
}
// now get information specific to this heap (server mode gives us several heaps; we're getting
// information about only one of them.
details->alloc_allocated = heapData.AllocAllocated.ToClrDataAddress(_target);
details->ephemeral_heap_segment = heapData.EphemeralHeapSegment.ToClrDataAddress(_target);
details->card_table = heapData.CardTable.ToClrDataAddress(_target);
if (gcIdentifiers.Contains(GCIdentifiers.Regions))
{
// with regions, we don't have these variables anymore
// use special value -1 in saved_sweep_ephemeral_seg to signal the region case
details->saved_sweep_ephemeral_seg = unchecked((ulong)-1);
details->saved_sweep_ephemeral_start = 0;
}
else
{
if (gcIdentifiers.Contains(GCIdentifiers.Background))
{
details->saved_sweep_ephemeral_seg = heapData.SavedSweepEphemeralSegment.ToClrDataAddress(_target);
details->saved_sweep_ephemeral_start = heapData.SavedSweepEphemeralStart.ToClrDataAddress(_target);
}
else
{
details->saved_sweep_ephemeral_seg = 0;
details->saved_sweep_ephemeral_start = 0;
}
}
// get bounds for the different generations
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS && i < heapData.GenerationTable.Count; i++)
{
GCGenerationData genData = heapData.GenerationTable[i];
details->generation_table[i].start_segment = genData.StartSegment.ToClrDataAddress(_target);
details->generation_table[i].allocation_start = genData.AllocationStart.ToClrDataAddress(_target);
details->generation_table[i].allocContextPtr = genData.AllocationContextPointer.ToClrDataAddress(_target);
details->generation_table[i].allocContextLimit = genData.AllocationContextLimit.ToClrDataAddress(_target);
}
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3 && i < heapData.FillPointers.Count; i++)
{
details->finalization_fill_pointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpGcHeapDetails detailsLocal = default;
int hrLocal = _legacyImpl.GetGCHeapStaticData(&detailsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(details->heapAddr == detailsLocal.heapAddr, $"cDAC: {details->heapAddr:x}, DAC: {detailsLocal.heapAddr:x}");
Debug.Assert(details->alloc_allocated == detailsLocal.alloc_allocated, $"cDAC: {details->alloc_allocated:x}, DAC: {detailsLocal.alloc_allocated:x}");
Debug.Assert(details->mark_array == detailsLocal.mark_array, $"cDAC: {details->mark_array:x}, DAC: {detailsLocal.mark_array:x}");
Debug.Assert(details->current_c_gc_state == detailsLocal.current_c_gc_state, $"cDAC: {details->current_c_gc_state:x}, DAC: {detailsLocal.current_c_gc_state:x}");
Debug.Assert(details->next_sweep_obj == detailsLocal.next_sweep_obj, $"cDAC: {details->next_sweep_obj:x}, DAC: {detailsLocal.next_sweep_obj:x}");
Debug.Assert(details->saved_sweep_ephemeral_seg == detailsLocal.saved_sweep_ephemeral_seg, $"cDAC: {details->saved_sweep_ephemeral_seg:x}, DAC: {detailsLocal.saved_sweep_ephemeral_seg:x}");
Debug.Assert(details->saved_sweep_ephemeral_start == detailsLocal.saved_sweep_ephemeral_start, $"cDAC: {details->saved_sweep_ephemeral_start:x}, DAC: {detailsLocal.saved_sweep_ephemeral_start:x}");
Debug.Assert(details->background_saved_lowest_address == detailsLocal.background_saved_lowest_address, $"cDAC: {details->background_saved_lowest_address:x}, DAC: {detailsLocal.background_saved_lowest_address:x}");
Debug.Assert(details->background_saved_highest_address == detailsLocal.background_saved_highest_address, $"cDAC: {details->background_saved_highest_address:x}, DAC: {detailsLocal.background_saved_highest_address:x}");
// Verify generation table data
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS; i++)
{
Debug.Assert(details->generation_table[i].start_segment == detailsLocal.generation_table[i].start_segment, $"cDAC gen[{i}].start_segment: {details->generation_table[i].start_segment:x}, DAC: {detailsLocal.generation_table[i].start_segment:x}");
Debug.Assert(details->generation_table[i].allocation_start == detailsLocal.generation_table[i].allocation_start, $"cDAC gen[{i}].allocation_start: {details->generation_table[i].allocation_start:x}, DAC: {detailsLocal.generation_table[i].allocation_start:x}");
Debug.Assert(details->generation_table[i].allocContextPtr == detailsLocal.generation_table[i].allocContextPtr, $"cDAC gen[{i}].allocContextPtr: {details->generation_table[i].allocContextPtr:x}, DAC: {detailsLocal.generation_table[i].allocContextPtr:x}");
Debug.Assert(details->generation_table[i].allocContextLimit == detailsLocal.generation_table[i].allocContextLimit, $"cDAC gen[{i}].allocContextLimit: {details->generation_table[i].allocContextLimit:x}, DAC: {detailsLocal.generation_table[i].allocContextLimit:x}");
}
Debug.Assert(details->ephemeral_heap_segment == detailsLocal.ephemeral_heap_segment, $"cDAC: {details->ephemeral_heap_segment:x}, DAC: {detailsLocal.ephemeral_heap_segment:x}");
// Verify finalization fill pointers
for (int i = 0; i < GCConstants.DAC_NUMBERGENERATIONS + 3; i++)
{
Debug.Assert(details->finalization_fill_pointers[i] == detailsLocal.finalization_fill_pointers[i], $"cDAC finalization_fill_pointers[{i}]: {details->finalization_fill_pointers[i]:x}, DAC: {detailsLocal.finalization_fill_pointers[i]:x}");
}
Debug.Assert(details->lowest_address == detailsLocal.lowest_address, $"cDAC: {details->lowest_address:x}, DAC: {detailsLocal.lowest_address:x}");
Debug.Assert(details->highest_address == detailsLocal.highest_address, $"cDAC: {details->highest_address:x}, DAC: {detailsLocal.highest_address:x}");
Debug.Assert(details->card_table == detailsLocal.card_table, $"cDAC: {details->card_table:x}, DAC: {detailsLocal.card_table:x}");
}
}
#endif
return hr;
}
[GeneratedComClass]
internal sealed unsafe partial class SOSHandleEnum : ISOSHandleEnum
{
private readonly Target _target;
private readonly SOSHandleData[] _handles;
private readonly ISOSHandleEnum? _legacyHandleEnum;
private uint _index;
public SOSHandleEnum(Target target, HandleType[] types, ISOSHandleEnum? legacyHandleEnum)
{
_target = target;
_legacyHandleEnum = legacyHandleEnum;
_handles = GetHandles(types);
}
private SOSHandleData[] GetHandles(HandleType[] types)
{
IGC gc = _target.Contracts.GC;
List<HandleData> handles = gc.GetHandles(types);
TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomain = _target.ReadPointer(appDomainPointer);
ClrDataAddress appDomainClrAddress = appDomain.ToClrDataAddress(_target);
SOSHandleData[] sosHandles = new SOSHandleData[handles.Count];
for (int i = 0; i < handles.Count; i++)
{
HandleData h = handles[i];
sosHandles[i] = new SOSHandleData
{
AppDomain = appDomainClrAddress,
Handle = h.Handle.ToClrDataAddress(_target),
Secondary = h.Secondary.ToClrDataAddress(_target),
Type = h.Type,
StrongReference = h.StrongReference ? 1 : 0,
RefCount = h.RefCount,
JupiterRefCount = h.JupiterRefCount,
IsPegged = h.IsPegged ? 1 : 0,
};
}
return sosHandles;
}
int ISOSHandleEnum.Next(uint count, SOSHandleData[] handles, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (pNeeded is null || handles is null)
throw new NullReferenceException();
uint written = 0;
while (written < count && _index < _handles.Length)
handles[written++] = _handles[(int)_index++];
*pNeeded = written;
hr = _index < _handles.Length ? HResults.S_FALSE : HResults.S_OK;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyHandleEnum is not null)
{
SOSHandleData[] handlesLocal = new SOSHandleData[count];
uint neededLocal;
int hrLocal = _legacyHandleEnum.Next(count, handlesLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*pNeeded == neededLocal, $"cDAC: {*pNeeded}, DAC: {neededLocal}");
for (int i = 0; i < neededLocal; i++)
{
Debug.Assert(handles[i].AppDomain == handlesLocal[i].AppDomain, $"cDAC: {handles[i].AppDomain:x}, DAC: {handlesLocal[i].AppDomain:x}");
Debug.Assert(handles[i].Handle == handlesLocal[i].Handle, $"cDAC: {handles[i].Handle:x}, DAC: {handlesLocal[i].Handle:x}");
Debug.Assert(handles[i].Secondary == handlesLocal[i].Secondary, $"cDAC: {handles[i].Secondary:x}, DAC: {handlesLocal[i].Secondary:x}");
Debug.Assert(handles[i].Type == handlesLocal[i].Type, $"cDAC: {handles[i].Type}, DAC: {handlesLocal[i].Type}");
Debug.Assert(handles[i].StrongReference == handlesLocal[i].StrongReference, $"cDAC: {handles[i].StrongReference}, DAC: {handlesLocal[i].StrongReference}");
Debug.Assert(handles[i].RefCount == handlesLocal[i].RefCount, $"cDAC: {handles[i].RefCount}, DAC: {handlesLocal[i].RefCount}");
Debug.Assert(handles[i].JupiterRefCount == handlesLocal[i].JupiterRefCount, $"cDAC: {handles[i].JupiterRefCount}, DAC: {handlesLocal[i].JupiterRefCount}");
Debug.Assert(handles[i].IsPegged == handlesLocal[i].IsPegged, $"cDAC: {handles[i].IsPegged}, DAC: {handlesLocal[i].IsPegged}");
}
}
}
#endif
return hr;
}
int ISOSEnum.Skip(uint count)
{
_index += count;
#if DEBUG
_legacyHandleEnum?.Skip(count);
#endif
return HResults.S_OK;
}
int ISOSEnum.Reset()
{
_index = 0;
#if DEBUG
_legacyHandleEnum?.Reset();
#endif
return HResults.S_OK;
}
int ISOSEnum.GetCount(uint* pCount)
{
if (pCount is null) return HResults.E_POINTER;
*pCount = (uint)_handles.Length;
#if DEBUG
if (_legacyHandleEnum is not null)
{
uint countLocal;
_legacyHandleEnum.GetCount(&countLocal);
Debug.Assert(countLocal == (uint)_handles.Length);
}
#endif
return HResults.S_OK;
}
}
[GeneratedComClass]
internal sealed unsafe partial class SOSMemoryEnum : ISOSMemoryEnum
{
private readonly SOSMemoryRegion[] _regions;
private readonly ISOSMemoryEnum? _legacyMemoryEnum;
private uint _index;
public SOSMemoryEnum(Target target, IReadOnlyList<GCMemoryRegionData> regions, ISOSMemoryEnum? legacyMemoryEnum = null)
{
_legacyMemoryEnum = legacyMemoryEnum;
_regions = new SOSMemoryRegion[regions.Count];
for (int i = 0; i < regions.Count; i++)
{
_regions[i] = new SOSMemoryRegion
{
Start = regions[i].Start.ToClrDataAddress(target),
Size = (ClrDataAddress)regions[i].Size,
ExtraData = (ClrDataAddress)regions[i].ExtraData,
Heap = regions[i].Heap,
};
}
}
int ISOSMemoryEnum.Next(uint count, SOSMemoryRegion[] memRegions, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (pNeeded is null || memRegions is null)
throw new NullReferenceException();
uint written = 0;
while (written < count && _index < _regions.Length)
memRegions[written++] = _regions[(int)_index++];
*pNeeded = written;
hr = written < count ? HResults.S_FALSE : HResults.S_OK;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyMemoryEnum is not null)
{
SOSMemoryRegion[] regionsLocal = new SOSMemoryRegion[count];
uint neededLocal;
int hrLocal = _legacyMemoryEnum.Next(count, regionsLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*pNeeded == neededLocal, $"cDAC: {*pNeeded}, DAC: {neededLocal}");
for (int i = 0; i < neededLocal; i++)
{
Debug.Assert(memRegions[i].Start == regionsLocal[i].Start, $"cDAC: {memRegions[i].Start:x}, DAC: {regionsLocal[i].Start:x}");
Debug.Assert(memRegions[i].Size == regionsLocal[i].Size, $"cDAC: {memRegions[i].Size:x}, DAC: {regionsLocal[i].Size:x}");
Debug.Assert(memRegions[i].ExtraData == regionsLocal[i].ExtraData, $"cDAC: {memRegions[i].ExtraData:x}, DAC: {regionsLocal[i].ExtraData:x}");
Debug.Assert(memRegions[i].Heap == regionsLocal[i].Heap, $"cDAC: {memRegions[i].Heap}, DAC: {regionsLocal[i].Heap}");
}
}
}
#endif
return hr;
}
int ISOSEnum.Skip(uint count)
{
_index += count;
#if DEBUG
_legacyMemoryEnum?.Skip(count);
#endif
return HResults.S_OK;
}
int ISOSEnum.Reset()
{
_index = 0;
#if DEBUG
_legacyMemoryEnum?.Reset();
#endif
return HResults.S_OK;
}
int ISOSEnum.GetCount(uint* pCount)
{
if (pCount is null)
return HResults.E_POINTER;
*pCount = (uint)_regions.Length;
#if DEBUG
if (_legacyMemoryEnum is not null)
{
uint countLocal;
_legacyMemoryEnum.GetCount(&countLocal);
Debug.Assert(countLocal == (uint)_regions.Length);
}
#endif
return HResults.S_OK;
}
}
int ISOSDacInterface.GetHandleEnum(DacComNullableByRef<ISOSHandleEnum> ppHandleEnum)
{
int hr = HResults.S_OK;
try
{
IGC gc = _target.Contracts.GC;
HandleType[] supportedHandleTypes = gc.GetSupportedHandleTypes();
ISOSHandleEnum? legacyHandleEnum = null;
#if DEBUG
if (_legacyImpl is not null)
{
DacComNullableByRef<ISOSHandleEnum> legacyOut = new(isNullRef: false);
int hrLocal = _legacyImpl.GetHandleEnum(legacyOut);
Debug.ValidateHResult(hr, hrLocal);
legacyHandleEnum = legacyOut.Interface;
}
#endif
ppHandleEnum.Interface = new SOSHandleEnum(_target, supportedHandleTypes, legacyHandleEnum);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
int ISOSDacInterface.GetHandleEnumForGC(uint gen, DacComNullableByRef<ISOSHandleEnum> ppHandleEnum)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetHandleEnumForGC(gen, ppHandleEnum) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetHandleEnumForTypes([In, MarshalUsing(CountElementName = "count")] uint[] types, uint count, DacComNullableByRef<ISOSHandleEnum> ppHandleEnum)
{
int hr = HResults.S_OK;
try
{
ISOSHandleEnum? legacyHandleEnum = null;
#if DEBUG
if (_legacyImpl is not null)
{
DacComNullableByRef<ISOSHandleEnum> legacyOut = new(isNullRef: false);
int hrLocal = _legacyImpl.GetHandleEnumForTypes(types, count, legacyOut);
Debug.ValidateHResult(hr, hrLocal);
legacyHandleEnum = legacyOut.Interface;
}
#endif
IGC gc = _target.Contracts.GC;
HandleType[] handleTypes = gc.GetHandleTypes(types);
ppHandleEnum.Interface = new SOSHandleEnum(_target, handleTypes, legacyHandleEnum);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
int ISOSDacInterface.GetHeapAllocData(uint count, void* data, uint* pNeeded)
=> LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetHeapAllocData(count, data, pNeeded) : HResults.E_NOTIMPL;
int ISOSDacInterface.GetHeapAnalyzeData(ClrDataAddress addr, DacpGcHeapAnalyzeData* data)
{
int hr = HResults.S_OK;
try
{
if (addr == 0 || data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// doesn't make sense to call this on WKS mode
if (!gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCHeapData heapData = gc.GetHeapData(addr.ToTargetPointer(_target));
data->heapAddr = addr;
data->internal_root_array = heapData.InternalRootArray.ToClrDataAddress(_target);
data->internal_root_array_index = heapData.InternalRootArrayIndex.Value;
data->heap_analyze_success = heapData.HeapAnalyzeSuccess ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpGcHeapAnalyzeData dataLocal = default;
int hrLocal = _legacyImpl.GetHeapAnalyzeData(addr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->heapAddr == dataLocal.heapAddr, $"cDAC: {data->heapAddr:x}, DAC: {dataLocal.heapAddr:x}");
Debug.Assert(data->internal_root_array == dataLocal.internal_root_array, $"cDAC: {data->internal_root_array:x}, DAC: {dataLocal.internal_root_array:x}");
Debug.Assert(data->internal_root_array_index == dataLocal.internal_root_array_index, $"cDAC: {data->internal_root_array_index}, DAC: {dataLocal.internal_root_array_index}");
Debug.Assert(data->heap_analyze_success == dataLocal.heap_analyze_success, $"cDAC: {data->heap_analyze_success}, DAC: {dataLocal.heap_analyze_success}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetHeapAnalyzeStaticData(DacpGcHeapAnalyzeData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// doesn't make sense to call this on SVR mode
if (!gcIdentifiers.Contains(GCIdentifiers.Workstation))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
// For workstation GC, use GetHeapData()
GCHeapData heapData = gc.GetHeapData();
data->heapAddr = 0; // Not applicable for static data
data->internal_root_array = heapData.InternalRootArray.ToClrDataAddress(_target);
data->internal_root_array_index = heapData.InternalRootArrayIndex.Value;
data->heap_analyze_success = heapData.HeapAnalyzeSuccess ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpGcHeapAnalyzeData dataLocal = default;
int hrLocal = _legacyImpl.GetHeapAnalyzeStaticData(&dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->heapAddr == dataLocal.heapAddr, $"cDAC: {data->heapAddr:x}, DAC: {dataLocal.heapAddr:x}");
Debug.Assert(data->internal_root_array == dataLocal.internal_root_array, $"cDAC: {data->internal_root_array:x}, DAC: {dataLocal.internal_root_array:x}");
Debug.Assert(data->internal_root_array_index == dataLocal.internal_root_array_index, $"cDAC: {data->internal_root_array_index}, DAC: {dataLocal.internal_root_array_index}");
Debug.Assert(data->heap_analyze_success == dataLocal.heap_analyze_success, $"cDAC: {data->heap_analyze_success}, DAC: {dataLocal.heap_analyze_success}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetHeapSegmentData(ClrDataAddress seg, DacpHeapSegmentData* data)
{
int hr = HResults.S_OK;
try
{
if (seg == 0 || data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
GCHeapSegmentData segmentData = gc.GetHeapSegmentData(seg.ToTargetPointer(_target));
data->segmentAddr = seg;
data->allocated = segmentData.Allocated.ToClrDataAddress(_target);
data->committed = segmentData.Committed.ToClrDataAddress(_target);
data->reserved = segmentData.Reserved.ToClrDataAddress(_target);
data->used = segmentData.Used.ToClrDataAddress(_target);
data->mem = segmentData.Mem.ToClrDataAddress(_target);
data->next = segmentData.Next.ToClrDataAddress(_target);
data->gc_heap = segmentData.Heap.ToClrDataAddress(_target);
data->flags = (nuint)segmentData.Flags.Value;
data->background_allocated = segmentData.BackgroundAllocated.ToClrDataAddress(_target);
// TODO: Compute highAllocMark - need to determine if this is the ephemeral segment
// and get the allocation mark from the appropriate heap data
// For now, use allocated as a fallback (similar to non-ephemeral segments in legacy code)
data->highAllocMark = data->allocated;
GCHeapData heapData = gcIdentifiers.Contains(GCIdentifiers.Server) ? gc.GetHeapData(segmentData.Heap) : gc.GetHeapData();
if (seg.ToTargetPointer(_target) == heapData.EphemeralHeapSegment)
{
data->highAllocMark = heapData.AllocAllocated.ToClrDataAddress(_target);
}
else
{
data->highAllocMark = data->allocated;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpHeapSegmentData dataLocal = default;
int hrLocal = _legacyImpl.GetHeapSegmentData(seg, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->segmentAddr == dataLocal.segmentAddr, $"segmentAddr - cDAC: {data->segmentAddr:x}, DAC: {dataLocal.segmentAddr:x}");
Debug.Assert(data->allocated == dataLocal.allocated, $"allocated - cDAC: {data->allocated:x}, DAC: {dataLocal.allocated:x}");
Debug.Assert(data->committed == dataLocal.committed, $"committed - cDAC: {data->committed:x}, DAC: {dataLocal.committed:x}");
Debug.Assert(data->reserved == dataLocal.reserved, $"reserved - cDAC: {data->reserved:x}, DAC: {dataLocal.reserved:x}");
Debug.Assert(data->used == dataLocal.used, $"used - cDAC: {data->used:x}, DAC: {dataLocal.used:x}");
Debug.Assert(data->mem == dataLocal.mem, $"mem - cDAC: {data->mem:x}, DAC: {dataLocal.mem:x}");
Debug.Assert(data->next == dataLocal.next, $"next - cDAC: {data->next:x}, DAC: {dataLocal.next:x}");
Debug.Assert(data->gc_heap == dataLocal.gc_heap, $"gc_heap - cDAC: {data->gc_heap:x}, DAC: {dataLocal.gc_heap:x}");
Debug.Assert(data->highAllocMark == dataLocal.highAllocMark, $"highAllocMark - cDAC: {data->highAllocMark:x}, DAC: {dataLocal.highAllocMark:x}");
Debug.Assert(data->flags == dataLocal.flags, $"flags - cDAC: {data->flags:x}, DAC: {dataLocal.flags:x}");
Debug.Assert(data->background_allocated == dataLocal.background_allocated, $"background_allocated - cDAC: {data->background_allocated:x}, DAC: {dataLocal.background_allocated:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetHillClimbingLogEntry(ClrDataAddress addr, void* data)
{
// This API is not implemented by the legacy DAC
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetHillClimbingLogEntry(addr, data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetILForModule(ClrDataAddress moduleAddr, int rva, ClrDataAddress* il)
{
int hr = HResults.S_OK;
try
{
if (moduleAddr == 0 || il == null)
throw new ArgumentException();
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer module = moduleAddr.ToTargetPointer(_target);
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module);
TargetPointer peAssemblyPtr = loader.GetPEAssembly(moduleHandle);
*il = loader.GetILAddr(peAssemblyPtr, rva).ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress ilLocal;
int hrLocal = _legacyImpl.GetILForModule(moduleAddr, rva, &ilLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*il == ilLocal, $"cDAC: {*il:x}, DAC: {ilLocal:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetJitHelperFunctionName(ClrDataAddress ip, uint count, byte* name, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (!_target.Contracts.AuxiliarySymbols.TryGetAuxiliarySymbolName(ip.ToTargetPointer(_target), out string? symbolName))
throw new ArgumentException();
uint needed = 0;
OutputBufferHelpers.CopyUtf8StringToBuffer(name, count, &needed, symbolName);
if (needed > count && name != null)
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
if (pNeeded != null)
*pNeeded = needed;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
byte[]? nameLocal = name != null && count > 0 ? new byte[count] : null;
uint neededLocal;
int hrLocal;
fixed (byte* ptr = nameLocal)
{
hrLocal = _legacyImpl.GetJitHelperFunctionName(ip, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(name == null || new ReadOnlySpan<byte>(name, (int)neededLocal).SequenceEqual(nameLocal!.AsSpan(0, (int)neededLocal)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetJitManagerList(uint count, DacpJitManagerInfo* managers, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (managers is not null)
{
if (count >= 1)
{
*managers = default;
Contracts.JitManagerInfo jitManagerInfo = _target.Contracts.ExecutionManager.GetEEJitManagerInfo();
managers->managerAddr = jitManagerInfo.ManagerAddress.ToClrDataAddress(_target);
managers->codeType = jitManagerInfo.CodeType;
managers->ptrHeapList = jitManagerInfo.HeapListAddress.ToClrDataAddress(_target);
}
}
else if (pNeeded is not null)
{
*pNeeded = 1;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
if (managers is not null)
{
DacpJitManagerInfo managerLocal = default;
int hrLocal = _legacyImpl.GetJitManagerList(count, &managerLocal, null);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK && count >= 1)
{
Debug.Assert(managers->managerAddr == managerLocal.managerAddr);
Debug.Assert(managers->codeType == managerLocal.codeType);
Debug.Assert(managers->ptrHeapList == managerLocal.ptrHeapList);
}
}
else
{
uint neededLocal;
int hrLocal = _legacyImpl.GetJitManagerList(0, null, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK && pNeeded is not null)
{
Debug.Assert(*pNeeded == neededLocal);
}
}
}
#endif
return hr;
}
private bool IsJumpRel64(TargetPointer pThunk)
=> 0x48 == _target.Read<byte>(pThunk) &&
0xB8 == _target.Read<byte>(pThunk + 1) &&
0xFF == _target.Read<byte>(pThunk + 10) &&
0xE0 == _target.Read<byte>(pThunk + 11);
private TargetPointer DecodeJump64(TargetPointer pThunk)
{
Debug.Assert(IsJumpRel64(pThunk), "Expected a jump thunk");
return _target.ReadPointer(pThunk + 2);
}
int ISOSDacInterface.GetJumpThunkTarget(void* ctx, ClrDataAddress* targetIP, ClrDataAddress* targetMD)
{
int hr = HResults.S_OK;
try
{
if (ctx == null || targetIP == null || targetMD == null)
throw new ArgumentException();
// API is implemented for x64 only
if (_target.Contracts.RuntimeInfo.GetTargetArchitecture() != RuntimeInfoArchitecture.X64)
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target);
// Context is not stored in the target, but in our own process
context.FillFromBuffer(new Span<byte>(ctx, (int)context.Size));
TargetPointer pThunk = context.InstructionPointer;
if (IsJumpRel64(pThunk))
{
*targetMD = 0;
*targetIP = DecodeJump64(pThunk).ToClrDataAddress(_target);
}
else
{
hr = HResults.E_FAIL;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress targetIPLocal;
ClrDataAddress targetMDLocal;
int hrLocal = _legacyImpl.GetJumpThunkTarget(ctx, &targetIPLocal, &targetMDLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*targetIP == targetIPLocal, $"cDAC: {*targetIP:x}, DAC: {targetIPLocal:x}");
Debug.Assert(*targetMD == targetMDLocal, $"cDAC: {*targetMD:x}, DAC: {targetMDLocal:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodDescData(ClrDataAddress addr, ClrDataAddress ip, DacpMethodDescData* data, uint cRevertedRejitVersions, DacpReJitData* rgRevertedRejitData, uint* pcNeededRevertedRejitData)
{
int hr = HResults.S_OK;
try
{
if (addr == 0)
throw new ArgumentException();
if (cRevertedRejitVersions != 0 && rgRevertedRejitData == null)
throw new ArgumentException();
if (rgRevertedRejitData != null && pcNeededRevertedRejitData == null)
// If you're asking for reverted rejit data, you'd better ask for the number of
// elements we return
throw new ArgumentException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
TargetPointer methodDesc = addr.ToTargetPointer(_target);
Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc);
Contracts.ICodeVersions nativeCodeContract = _target.Contracts.CodeVersions;
Contracts.IReJIT rejitContract = _target.Contracts.ReJIT;
if (rgRevertedRejitData != null)
{
NativeMemory.Clear(rgRevertedRejitData, (nuint)(sizeof(DacpReJitData) * cRevertedRejitVersions));
}
if (pcNeededRevertedRejitData != null)
{
*pcNeededRevertedRejitData = 0;
}
NativeCodeVersionHandle requestedNativeCodeVersion;
NativeCodeVersionHandle? activeNativeCodeVersion = null;
if (ip != 0)
{
requestedNativeCodeVersion = nativeCodeContract.GetNativeCodeVersionForIP(ip.ToTargetCodePointer(_target));
}
else
{
requestedNativeCodeVersion = nativeCodeContract.GetActiveNativeCodeVersion(new TargetPointer(methodDesc));
activeNativeCodeVersion = requestedNativeCodeVersion;
}
data->requestedIP = ip;
data->bIsDynamic = rtsContract.IsDynamicMethod(methodDescHandle) ? 1 : 0;
data->wSlotNumber = rtsContract.GetSlotNumber(methodDescHandle);
TargetCodePointer nativeCodeAddr = TargetCodePointer.Null;
if (requestedNativeCodeVersion.Valid)
{
nativeCodeAddr = nativeCodeContract.GetNativeCode(requestedNativeCodeVersion);
}
if (nativeCodeAddr != TargetCodePointer.Null)
{
data->bHasNativeCode = 1;
data->NativeCodeAddr = _target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(nativeCodeAddr).ToAddress(_target).ToClrDataAddress(_target);
}
else
{
data->bHasNativeCode = 0;
data->NativeCodeAddr = 0xffffffff_fffffffful;
}
if (rtsContract.HasNativeCodeSlot(methodDescHandle))
{
data->AddressOfNativeCodeSlot = rtsContract.GetAddressOfNativeCodeSlot(methodDescHandle).ToClrDataAddress(_target);
}
else
{
data->AddressOfNativeCodeSlot = 0;
}
data->MDToken = rtsContract.GetMethodToken(methodDescHandle);
data->MethodDescPtr = addr;
TargetPointer methodTableAddr = rtsContract.GetMethodTable(methodDescHandle);
data->MethodTablePtr = methodTableAddr.ToClrDataAddress(_target);
TypeHandle typeHandle = rtsContract.GetTypeHandle(methodTableAddr);
data->ModulePtr = rtsContract.GetModule(typeHandle).ToClrDataAddress(_target);
// If rejit info is appropriate, get the following:
// * ReJitInfo for the current, active version of the method
// * ReJitInfo for the requested IP (for !ip2md and !u)
// * ReJitInfos for all reverted versions of the method (up to
// cRevertedRejitVersions)
//
// Minidumps will not have all this rejit info, and failure to get rejit info
// should not be fatal. So enclose all rejit stuff in a try.
try
{
if (activeNativeCodeVersion is null || !activeNativeCodeVersion.Value.Valid)
{
activeNativeCodeVersion = nativeCodeContract.GetActiveNativeCodeVersion(new TargetPointer(methodDesc));
}
if (activeNativeCodeVersion is null || !activeNativeCodeVersion.Value.Valid)
{
throw new InvalidOperationException("No active native code version found");
}
// Active ReJitInfo
CopyNativeCodeVersionToReJitData(
activeNativeCodeVersion.Value,
activeNativeCodeVersion.Value,
&data->rejitDataCurrent);
// Requested ReJitInfo
Debug.Assert(data->rejitDataRequested.rejitID == 0);
if (ip != 0 && requestedNativeCodeVersion.Valid)
{
CopyNativeCodeVersionToReJitData(
requestedNativeCodeVersion,
activeNativeCodeVersion.Value,
&data->rejitDataRequested);
}
// Total number of jitted rejit versions
int cJittedRejitVersions = rejitContract.GetRejitIds(_target, methodDescHandle.Address).Count();
data->cJittedRejitVersions = (uint)cJittedRejitVersions;
// Reverted ReJitInfos
if (rgRevertedRejitData == null)
{
// No reverted rejit versions will be returned, but maybe caller wants a
// count of all versions
if (pcNeededRevertedRejitData != null)
{
*pcNeededRevertedRejitData = data->cJittedRejitVersions;
}
}
else
{
// Caller wants some reverted rejit versions. Gather reverted rejit version data to return
// Returns all available rejitids, including the rejitid for the one non-reverted
// current version.
List<TargetNUInt> reJitIds = rejitContract.GetRejitIds(_target, methodDescHandle.Address).ToList();
// Go through rejitids. For each reverted one, populate a entry in rgRevertedRejitData
uint iRejitDataReverted = 0;
ILCodeVersionHandle activeVersion = nativeCodeContract.GetActiveILCodeVersion(methodDesc);
TargetNUInt activeVersionId = rejitContract.GetRejitId(activeVersion);
for (int i = 0; (i < reJitIds.Count) && (iRejitDataReverted < cRevertedRejitVersions); i++)
{
ILCodeVersionHandle ilCodeVersion = nativeCodeContract.GetILCodeVersions(methodDesc)
.FirstOrDefault(ilcode => rejitContract.GetRejitId(ilcode) == reJitIds[i],
ILCodeVersionHandle.Invalid);
if (!ilCodeVersion.IsValid || rejitContract.GetRejitId(ilCodeVersion) == activeVersionId)
{
continue;
}
NativeCodeVersionHandle activeRejitChild = nativeCodeContract.GetActiveNativeCodeVersionForILCodeVersion(methodDesc, ilCodeVersion);
CopyNativeCodeVersionToReJitData(
activeRejitChild,
activeNativeCodeVersion.Value,
&rgRevertedRejitData[iRejitDataReverted]);
iRejitDataReverted++;
}
// We already checked that pcNeededRevertedRejitData != NULL because rgRevertedRejitData != NULL
*pcNeededRevertedRejitData = iRejitDataReverted;
}
}
catch (global::System.Exception)
{
if (pcNeededRevertedRejitData != null)
{
*pcNeededRevertedRejitData = 0;
}
}
// HAVE_GCCOVER
if (requestedNativeCodeVersion.Valid)
{
// TargetPointer.Null if GCCover information is not available.
// In certain minidumps, we won't save the GCCover information.
// (it would be unwise to do so, it is heavy and not a customer scenario).
data->GCStressCodeCopy = nativeCodeContract.GetGCStressCodeCopy(requestedNativeCodeVersion).ToClrDataAddress(_target);
}
// Unlike the legacy implementation, the cDAC does not currently populate
// data->managedDynamicMethodObject. This field is unused in both SOS and CLRMD
// and would require accessing CorLib bound managed fields which the cDAC does not
// currently support. However, it must remain in the return type for compatibility.
data->managedDynamicMethodObject = 0;
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpMethodDescData dataLocal = default;
DacpReJitData[]? rgRevertedRejitDataLocal = null;
if (rgRevertedRejitData != null)
{
rgRevertedRejitDataLocal = new DacpReJitData[cRevertedRejitVersions];
}
uint cNeededRevertedRejitDataLocal = 0;
uint* pcNeededRevertedRejitDataLocal = null;
if (pcNeededRevertedRejitData != null)
{
pcNeededRevertedRejitDataLocal = &cNeededRevertedRejitDataLocal;
}
int hrLocal;
fixed (DacpReJitData* rgRevertedRejitDataLocalPtr = rgRevertedRejitDataLocal)
{
hrLocal = _legacyImpl.GetMethodDescData(addr, ip, &dataLocal, cRevertedRejitVersions, rgRevertedRejitDataLocalPtr, pcNeededRevertedRejitDataLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->bHasNativeCode == dataLocal.bHasNativeCode, $"cDAC: {data->bHasNativeCode}, DAC: {dataLocal.bHasNativeCode}");
Debug.Assert(data->bIsDynamic == dataLocal.bIsDynamic, $"cDAC: {data->bIsDynamic}, DAC: {dataLocal.bIsDynamic}");
Debug.Assert(data->wSlotNumber == dataLocal.wSlotNumber, $"cDAC: {data->wSlotNumber}, DAC: {dataLocal.wSlotNumber}");
Debug.Assert(data->NativeCodeAddr == dataLocal.NativeCodeAddr, $"cDAC: {data->NativeCodeAddr:x}, DAC: {dataLocal.NativeCodeAddr:x}");
Debug.Assert(data->AddressOfNativeCodeSlot == dataLocal.AddressOfNativeCodeSlot, $"cDAC: {data->AddressOfNativeCodeSlot:x}, DAC: {dataLocal.AddressOfNativeCodeSlot:x}");
Debug.Assert(data->MethodDescPtr == dataLocal.MethodDescPtr, $"cDAC: {data->MethodDescPtr:x}, DAC: {dataLocal.MethodDescPtr:x}");
Debug.Assert(data->MethodTablePtr == dataLocal.MethodTablePtr, $"cDAC: {data->MethodTablePtr:x}, DAC: {dataLocal.MethodTablePtr:x}");
Debug.Assert(data->ModulePtr == dataLocal.ModulePtr, $"cDAC: {data->ModulePtr:x}, DAC: {dataLocal.ModulePtr:x}");
Debug.Assert(data->MDToken == dataLocal.MDToken, $"cDAC: {data->MDToken:x}, DAC: {dataLocal.MDToken:x}");
Debug.Assert(data->GCInfo == dataLocal.GCInfo, $"cDAC: {data->GCInfo:x}, DAC: {dataLocal.GCInfo:x}");
Debug.Assert(data->GCStressCodeCopy == dataLocal.GCStressCodeCopy, $"cDAC: {data->GCStressCodeCopy:x}, DAC: {dataLocal.GCStressCodeCopy:x}");
// managedDynamicMethodObject is not currently populated by the cDAC API and may differ from legacyImpl.
Debug.Assert(data->managedDynamicMethodObject == 0);
Debug.Assert(data->requestedIP == dataLocal.requestedIP, $"cDAC: {data->requestedIP:x}, DAC: {dataLocal.requestedIP:x}");
Debug.Assert(data->cJittedRejitVersions == dataLocal.cJittedRejitVersions, $"cDAC: {data->cJittedRejitVersions}, DAC: {dataLocal.cJittedRejitVersions}");
// rejitDataCurrent
Debug.Assert(data->rejitDataCurrent.rejitID == dataLocal.rejitDataCurrent.rejitID, $"cDAC: {data->rejitDataCurrent.rejitID}, DAC: {dataLocal.rejitDataCurrent.rejitID}");
Debug.Assert(data->rejitDataCurrent.NativeCodeAddr == dataLocal.rejitDataCurrent.NativeCodeAddr, $"cDAC: {data->rejitDataCurrent.NativeCodeAddr:x}, DAC: {dataLocal.rejitDataCurrent.NativeCodeAddr:x}");
Debug.Assert(data->rejitDataCurrent.flags == dataLocal.rejitDataCurrent.flags, $"cDAC: {data->rejitDataCurrent.flags}, DAC: {dataLocal.rejitDataCurrent.flags}");
// rejitDataRequested
Debug.Assert(data->rejitDataRequested.rejitID == dataLocal.rejitDataRequested.rejitID, $"cDAC: {data->rejitDataRequested.rejitID}, DAC: {dataLocal.rejitDataRequested.rejitID}");
Debug.Assert(data->rejitDataRequested.NativeCodeAddr == dataLocal.rejitDataRequested.NativeCodeAddr, $"cDAC: {data->rejitDataRequested.NativeCodeAddr:x}, DAC: {dataLocal.rejitDataRequested.NativeCodeAddr:x}");
Debug.Assert(data->rejitDataRequested.flags == dataLocal.rejitDataRequested.flags, $"cDAC: {data->rejitDataRequested.flags}, DAC: {dataLocal.rejitDataRequested.flags}");
// rgRevertedRejitData
if (rgRevertedRejitData != null && rgRevertedRejitDataLocal != null)
{
Debug.Assert(cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData, $"cDAC: {*pcNeededRevertedRejitData}, DAC: {cNeededRevertedRejitDataLocal}");
for (ulong i = 0; i < cNeededRevertedRejitDataLocal; i++)
{
Debug.Assert(rgRevertedRejitData[i].rejitID == rgRevertedRejitDataLocal[i].rejitID, $"cDAC: {rgRevertedRejitData[i].rejitID}, DAC: {rgRevertedRejitDataLocal[i].rejitID}");
Debug.Assert(rgRevertedRejitData[i].NativeCodeAddr == rgRevertedRejitDataLocal[i].NativeCodeAddr, $"cDAC: {rgRevertedRejitData[i].NativeCodeAddr:x}, DAC: {rgRevertedRejitDataLocal[i].NativeCodeAddr:x}");
Debug.Assert(rgRevertedRejitData[i].flags == rgRevertedRejitDataLocal[i].flags, $"cDAC: {rgRevertedRejitData[i].flags}, DAC: {rgRevertedRejitDataLocal[i].flags}");
}
}
}
}
#endif
return hr;
}
private void CopyNativeCodeVersionToReJitData(
NativeCodeVersionHandle nativeCodeVersion,
NativeCodeVersionHandle activeNativeCodeVersion,
DacpReJitData* pReJitData)
{
ICodeVersions cv = _target.Contracts.CodeVersions;
IReJIT rejit = _target.Contracts.ReJIT;
ILCodeVersionHandle ilCodeVersion = cv.GetILCodeVersion(nativeCodeVersion);
pReJitData->rejitID = rejit.GetRejitId(ilCodeVersion).Value;
TargetCodePointer nativeCode = cv.GetNativeCode(nativeCodeVersion);
pReJitData->NativeCodeAddr = _target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(nativeCode).Value;
if (nativeCodeVersion.CodeVersionNodeAddress != activeNativeCodeVersion.CodeVersionNodeAddress ||
nativeCodeVersion.MethodDescAddress != activeNativeCodeVersion.MethodDescAddress)
{
pReJitData->flags = DacpReJitData.Flags.kReverted;
}
else
{
DacpReJitData.Flags flags = DacpReJitData.Flags.kUnknown;
switch (rejit.GetRejitState(ilCodeVersion))
{
// kStateRequested
case RejitState.Requested:
flags = DacpReJitData.Flags.kRequested;
break;
// kStateActive
case RejitState.Active:
flags = DacpReJitData.Flags.kActive;
break;
default:
Debug.Fail("Unknown RejitState. cDAC should be updated to understand this new state.");
break;
}
pReJitData->flags = flags;
}
}
int ISOSDacInterface.GetMethodDescFromToken(ClrDataAddress moduleAddr, uint token, ClrDataAddress* methodDesc)
{
int hr = HResults.S_OK;
try
{
if (moduleAddr == 0 || methodDesc == null)
throw new ArgumentException();
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer module = moduleAddr.ToTargetPointer(_target);
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module);
Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle);
switch ((EcmaMetadataUtils.TokenType)(token & EcmaMetadataUtils.TokenTypeMask))
{
case EcmaMetadataUtils.TokenType.mdtFieldDef:
*methodDesc = loader.GetModuleLookupMapElement(lookupTables.FieldDefToDesc, token, out var _).ToClrDataAddress(_target);
break;
case EcmaMetadataUtils.TokenType.mdtMethodDef:
*methodDesc = loader.GetModuleLookupMapElement(lookupTables.MethodDefToDesc, token, out var _).ToClrDataAddress(_target);
break;
case EcmaMetadataUtils.TokenType.mdtTypeDef:
*methodDesc = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, token, out var _).ToClrDataAddress(_target);
break;
case EcmaMetadataUtils.TokenType.mdtTypeRef:
*methodDesc = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, token, out var _).ToClrDataAddress(_target);
break;
default:
throw new ArgumentException();
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress methodDescLocal;
int hrLocal = _legacyImpl.GetMethodDescFromToken(moduleAddr, token, &methodDescLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*methodDesc == methodDescLocal, $"cDAC: {*methodDesc:x}, DAC: {methodDescLocal:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodDescName(ClrDataAddress addr, uint count, char* name, uint* pNeeded)
{
int hr = HResults.S_OK;
if (pNeeded != null)
*pNeeded = 0;
try
{
if (addr == 0)
throw new ArgumentException();
TargetPointer methodDesc = addr.ToTargetPointer(_target);
StringBuilder stringBuilder = new StringBuilder();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc);
try
{
TypeNameBuilder.AppendMethodInternal(_target, stringBuilder, methodDescHandle, TypeNameFormat.FormatSignature | TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst);
}
catch
{
hr = HResults.E_FAIL;
if (rtsContract.IsNoMetadataMethod(methodDescHandle, out _))
{
// In heap dumps, trying to format the signature can fail
// in certain cases.
stringBuilder.Clear();
TypeNameBuilder.AppendMethodInternal(_target, stringBuilder, methodDescHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst);
hr = HResults.S_OK;
}
else
{
string? fallbackNameString = _target.Contracts.DacStreams.StringFromEEAddress(methodDesc);
if (!string.IsNullOrEmpty(fallbackNameString))
{
stringBuilder.Clear();
stringBuilder.Append(fallbackNameString);
hr = HResults.S_OK;
}
else
{
TargetPointer modulePtr = rtsContract.GetModule(rtsContract.GetTypeHandle(rtsContract.GetMethodTable(methodDescHandle)));
Contracts.ModuleHandle module = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
string modulePath = _target.Contracts.Loader.GetPath(module);
ReadOnlySpan<char> moduleSpan = modulePath.AsSpan();
char directorySeparator = (char)_target.ReadGlobal<byte>(Constants.Globals.DirectorySeparator);
int pathNameSpanIndex = moduleSpan.LastIndexOf(directorySeparator);
if (pathNameSpanIndex != -1)
{
moduleSpan = moduleSpan.Slice(pathNameSpanIndex + 1);
}
stringBuilder.Clear();
stringBuilder.Append(moduleSpan);
stringBuilder.Append("!Unknown");
hr = HResults.S_OK;
}
}
}
if (hr == HResults.S_OK)
{
OutputBufferHelpers.CopyStringToBuffer(name, count, pNeeded, stringBuilder.ToString());
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] nameLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = nameLocal)
{
hrLocal = _legacyImpl.GetMethodDescName(addr, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(name == null || new ReadOnlySpan<char>(nameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(name)), $"cDAC: {new string(name)}, DAC: {new string(nameLocal, 0, (int)neededLocal - 1)}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodDescPtrFromFrame(ClrDataAddress frameAddr, ClrDataAddress* ppMD)
{
int hr = HResults.S_OK;
try
{
if (frameAddr == 0 || ppMD is null)
throw new ArgumentException();
Contracts.IStackWalk stackWalkContract = _target.Contracts.StackWalk;
TargetPointer methodDescPtr = stackWalkContract.GetMethodDescPtr(frameAddr.ToTargetPointer(_target));
if (methodDescPtr == TargetPointer.Null)
throw new ArgumentException();
_target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(methodDescPtr); // validation
*ppMD = methodDescPtr.ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress ppMDLocal;
int hrLocal = _legacyImpl.GetMethodDescPtrFromFrame(frameAddr, &ppMDLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*ppMD == ppMDLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodDescPtrFromIP(ClrDataAddress ip, ClrDataAddress* ppMD)
{
int hr = HResults.E_NOTIMPL;
try
{
if (ip == 0 || ppMD == null)
throw new ArgumentException();
IExecutionManager executionManager = _target.Contracts.ExecutionManager;
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
CodeBlockHandle? handle = executionManager.GetCodeBlockHandle(ip.ToTargetCodePointer(_target));
if (handle is not CodeBlockHandle codeHandle)
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
TargetPointer methodDescAddr = executionManager.GetMethodDesc(codeHandle);
try
{
// Runs validation of MethodDesc
// if validation fails, should return E_INVALIDARG
rts.GetMethodDescHandle(methodDescAddr);
*ppMD = methodDescAddr.ToClrDataAddress(_target);
hr = HResults.S_OK;
}
catch (System.Exception)
{
hr = HResults.E_INVALIDARG;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress ppMDLocal;
int hrLocal = _legacyImpl.GetMethodDescPtrFromIP(ip, &ppMDLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*ppMD == ppMDLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodDescTransparencyData(ClrDataAddress methodDesc, DacpMethodDescTransparencyData* data)
{
int hr = HResults.S_OK;
try
{
if (methodDesc == 0 || data is null)
throw new ArgumentException();
// Called for validation
_target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(methodDesc.ToTargetPointer(_target));
// Zero memory
*data = default;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
int ISOSDacInterface.GetMethodTableData(ClrDataAddress mt, DacpMethodTableData* data)
{
int hr = HResults.S_OK;
try
{
if (mt == 0 || data == null)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem;
Contracts.TypeHandle methodTable = contract.GetTypeHandle(mt.ToTargetPointer(_target));
DacpMethodTableData result = default;
result.baseSize = contract.GetBaseSize(methodTable);
// [compat] SOS DAC APIs added this base size adjustment for strings
// due to: "2008/09/25 Title: New implementation of StringBuilder and improvements in String class"
// which changed StringBuilder not to use a String as an internal buffer and in the process
// changed the String internals so that StringObject::GetBaseSize() now includes the nul terminator character,
// which is apparently not expected by SOS.
if (contract.IsString(methodTable))
result.baseSize -= sizeof(char);
result.componentSize = contract.GetComponentSize(methodTable);
bool isFreeObjectMT = contract.IsFreeObjectMethodTable(methodTable);
result.bIsFree = isFreeObjectMT ? 1 : 0;
if (!isFreeObjectMT)
{
result.module = contract.GetModule(methodTable).ToClrDataAddress(_target);
// Note: really the canonical method table, not the EEClass, which we don't expose
result.klass = contract.GetCanonicalMethodTable(methodTable).ToClrDataAddress(_target);
result.parentMethodTable = contract.GetParentMethodTable(methodTable).ToClrDataAddress(_target);
result.wNumInterfaces = contract.GetNumInterfaces(methodTable);
result.wNumMethods = contract.GetNumMethods(methodTable);
result.wNumVtableSlots = 0; // always return 0 since .NET 9
result.wNumVirtuals = 0; // always return 0 since .NET 9
result.cl = contract.GetTypeDefToken(methodTable);
result.dwAttrClass = contract.GetTypeDefTypeAttributes(methodTable);
result.bContainsGCPointers = contract.ContainsGCPointers(methodTable) ? 1 : 0;
result.bIsShared = 0;
result.bIsDynamic = contract.IsDynamicStatics(methodTable) ? 1 : 0;
}
*data = result;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpMethodTableData dataLocal;
int hrLocal = _legacyImpl.GetMethodTableData(mt, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->module == dataLocal.module);
Debug.Assert(data->klass == dataLocal.klass);
Debug.Assert(data->parentMethodTable == dataLocal.parentMethodTable);
Debug.Assert(data->wNumInterfaces == dataLocal.wNumInterfaces);
Debug.Assert(data->wNumMethods == dataLocal.wNumMethods);
Debug.Assert(data->wNumVtableSlots == dataLocal.wNumVtableSlots);
Debug.Assert(data->wNumVirtuals == dataLocal.wNumVirtuals);
Debug.Assert(data->cl == dataLocal.cl);
Debug.Assert(data->dwAttrClass == dataLocal.dwAttrClass);
Debug.Assert(data->bContainsGCPointers == dataLocal.bContainsGCPointers);
Debug.Assert(data->bIsShared == dataLocal.bIsShared);
Debug.Assert(data->bIsDynamic == dataLocal.bIsDynamic);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodTableFieldData(ClrDataAddress mt, DacpMethodTableFieldData* data)
{
int hr = HResults.S_OK;
try
{
if (mt == 0 || data == null)
throw new ArgumentException();
TargetPointer mtAddress = mt.ToTargetPointer(_target);
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
TypeHandle typeHandle = rtsContract.GetTypeHandle(mtAddress);
data->FirstField = rtsContract.GetFieldDescList(typeHandle).ToClrDataAddress(_target);
data->wNumInstanceFields = rtsContract.GetNumInstanceFields(typeHandle);
data->wNumStaticFields = rtsContract.GetNumStaticFields(typeHandle);
data->wNumThreadStaticFields = rtsContract.GetNumThreadStaticFields(typeHandle);
data->wContextStaticsSize = 0;
data->wContextStaticOffset = 0;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
{
if (_legacyImpl is not null)
{
DacpMethodTableFieldData mtFieldDataLocal = default;
int hrLocal = _legacyImpl.GetMethodTableFieldData(mt, &mtFieldDataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->wNumInstanceFields == mtFieldDataLocal.wNumInstanceFields);
Debug.Assert(data->wNumStaticFields == mtFieldDataLocal.wNumStaticFields);
Debug.Assert(data->wNumThreadStaticFields == mtFieldDataLocal.wNumThreadStaticFields);
Debug.Assert(data->wContextStaticOffset == mtFieldDataLocal.wContextStaticOffset);
Debug.Assert(data->wContextStaticsSize == mtFieldDataLocal.wContextStaticsSize);
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodTableForEEClass(ClrDataAddress eeClassReallyCanonMT, ClrDataAddress* value)
{
int hr = HResults.S_OK;
try
{
if (eeClassReallyCanonMT == 0 || value == null)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem contract = _target.Contracts.RuntimeTypeSystem;
Contracts.TypeHandle methodTableHandle = contract.GetTypeHandle(eeClassReallyCanonMT.ToTargetPointer(_target));
*value = methodTableHandle.Address.ToClrDataAddress(_target);
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress valueLocal;
int hrLocal = _legacyImpl.GetMethodTableForEEClass(eeClassReallyCanonMT, &valueLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*value == valueLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodTableName(ClrDataAddress mt, uint count, char* mtName, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (mt == 0)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem;
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.TypeHandle methodTableHandle = typeSystemContract.GetTypeHandle(mt.ToTargetPointer(_target, overrideCheck: true));
if (typeSystemContract.IsFreeObjectMethodTable(methodTableHandle))
{
OutputBufferHelpers.CopyStringToBuffer(mtName, count, pNeeded, "Free");
}
else
{
TargetPointer modulePointer = typeSystemContract.GetModule(methodTableHandle);
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer);
if (!loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _))
{
OutputBufferHelpers.CopyStringToBuffer(mtName, count, pNeeded, "<Unloaded Type>");
}
else
{
System.Text.StringBuilder methodTableName = new();
try
{
TypeNameBuilder.AppendType(_target, methodTableName, methodTableHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst);
}
catch
{
string? fallbackName = _target.Contracts.DacStreams.StringFromEEAddress(mt.ToTargetPointer(_target));
if (fallbackName != null)
{
methodTableName.Clear();
methodTableName.Append(fallbackName);
}
}
OutputBufferHelpers.CopyStringToBuffer(mtName, count, pNeeded, methodTableName.ToString());
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] mtNameLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = mtNameLocal)
{
hrLocal = _legacyImpl.GetMethodTableName(mt, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(mtName == null || new ReadOnlySpan<char>(mtNameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(mtName)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodTableSlot(ClrDataAddress mt, uint slot, ClrDataAddress* value)
{
int hr = HResults.S_OK;
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
try
{
if (mt == 0 || value == null)
throw new ArgumentException();
TargetPointer methodTable = mt.ToTargetPointer(_target);
TypeHandle methodTableHandle = rts.GetTypeHandle(methodTable); // validate MT
ushort vtableSlots = rts.GetNumVtableSlots(methodTableHandle);
if (slot < vtableSlots)
{
*value = rts.GetSlot(methodTableHandle, slot).ToClrDataAddress(_target);
if (*value == 0)
{
hr = HResults.S_FALSE;
}
}
else
{
hr = HResults.E_INVALIDARG;
foreach (TargetPointer mdAddr in rts.GetIntroducedMethodDescs(methodTableHandle))
{
MethodDescHandle mdHandle = rts.GetMethodDescHandle(mdAddr);
if (rts.GetSlotNumber(mdHandle) == slot)
{
*value = rts.GetMethodEntryPointIfExists(mdHandle).ToClrDataAddress(_target);
if (*value == 0)
{
hr = HResults.S_FALSE;
}
else
{
hr = HResults.S_OK;
}
break;
}
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal;
ClrDataAddress valueLocal;
hrLocal = _legacyImpl.GetMethodTableSlot(mt, slot, &valueLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*value == valueLocal, $"cDAC: {*value:x}, DAC: {valueLocal:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetMethodTableTransparencyData(ClrDataAddress mt, DacpMethodTableTransparencyData* data)
{
int hr = HResults.S_OK;
try
{
if (mt == 0 || data is null)
throw new ArgumentException();
// Called for validation
_target.Contracts.RuntimeTypeSystem.GetTypeHandle(mt.ToTargetPointer(_target));
// Zero memory
*data = default;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
int ISOSDacInterface.GetModule(ClrDataAddress addr, DacComNullableByRef<IXCLRDataModule> mod)
{
IXCLRDataModule? legacyModule = null;
if (_legacyImpl is not null)
{
DacComNullableByRef<IXCLRDataModule> legacyOut = new(isNullRef: false);
int hr = _legacyImpl.GetModule(addr, legacyOut);
if (hr < 0)
return hr;
legacyModule = legacyOut.Interface;
}
mod.Interface = new ClrDataModule(addr.ToTargetPointer(_target), _target, legacyModule);
return HResults.S_OK;
}
int ISOSDacInterface.GetModuleData(ClrDataAddress moduleAddr, DacpModuleData* data)
{
int hr = HResults.S_OK;
try
{
if (moduleAddr == 0 || data == null)
throw new ArgumentException();
Contracts.ILoader contract = _target.Contracts.Loader;
Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr.ToTargetPointer(_target));
data->Address = moduleAddr;
data->PEAssembly = moduleAddr; // Module address in .NET 9+ - correspondingly, SOS-DAC APIs for PE assemblies expect a module address
data->Assembly = contract.GetAssembly(handle).ToClrDataAddress(_target);
Contracts.ModuleFlags flags = contract.GetFlags(handle);
bool isReflectionEmit = flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit);
data->isReflection = (uint)(isReflectionEmit ? 1 : 0);
data->isPEFile = (uint)(isReflectionEmit ? 0 : 1); // ReflectionEmit module means it is not a PE file
data->dwTransientFlags = (uint)(flags & Contracts.ModuleFlags.EditAndContinue) != 0 ? (uint)DacpModuleData.TransientFlags.IsEditAndContinue : 0;
data->ilBase = contract.GetILBase(handle).ToClrDataAddress(_target);
try
{
TargetSpan readOnlyMetadata = _target.Contracts.EcmaMetadata.GetReadOnlyMetadataAddress(handle);
data->metadataStart = readOnlyMetadata.Address.Value;
data->metadataSize = readOnlyMetadata.Size;
}
catch (System.Exception)
{
// if we are unable to read the metadata, to match the DAC behavior
// set metadataStart and metadataSize to 0
data->metadataStart = 0;
data->metadataSize = 0;
}
data->LoaderAllocator = contract.GetLoaderAllocator(handle).ToClrDataAddress(_target);
Target.TypeInfo lookupMapTypeInfo = _target.GetTypeInfo(DataType.ModuleLookupMap);
ulong tableDataOffset = (ulong)lookupMapTypeInfo.Fields[Constants.FieldNames.ModuleLookupMap.TableData].Offset;
Contracts.ModuleLookupTables tables = contract.GetLookupTables(handle);
data->FieldDefToDescMap = _target.ReadPointer(tables.FieldDefToDesc + tableDataOffset).ToClrDataAddress(_target);
data->ManifestModuleReferencesMap = _target.ReadPointer(tables.ManifestModuleReferences + tableDataOffset).ToClrDataAddress(_target);
data->MemberRefToDescMap = _target.ReadPointer(tables.MemberRefToDesc + tableDataOffset).ToClrDataAddress(_target);
data->MethodDefToDescMap = _target.ReadPointer(tables.MethodDefToDesc + tableDataOffset).ToClrDataAddress(_target);
data->TypeDefToMethodTableMap = _target.ReadPointer(tables.TypeDefToMethodTable + tableDataOffset).ToClrDataAddress(_target);
data->TypeRefToMethodTableMap = _target.ReadPointer(tables.TypeRefToMethodTable + tableDataOffset).ToClrDataAddress(_target);
// Always 0 - .NET no longer has these concepts
data->dwModuleID = 0;
data->dwBaseClassIndex = 0;
data->dwModuleIndex = 0;
data->ThunkHeap = 0;
}
catch (global::System.Exception e)
{
hr = e.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpModuleData dataLocal;
int hrLocal = _legacyImpl.GetModuleData(moduleAddr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->Address == dataLocal.Address);
Debug.Assert(data->PEAssembly == dataLocal.PEAssembly);
Debug.Assert(data->Assembly == dataLocal.Assembly);
Debug.Assert(data->isReflection == dataLocal.isReflection);
Debug.Assert(data->isPEFile == dataLocal.isPEFile);
Debug.Assert(data->dwTransientFlags == dataLocal.dwTransientFlags);
Debug.Assert(data->ilBase == dataLocal.ilBase);
Debug.Assert(data->metadataStart == dataLocal.metadataStart);
Debug.Assert(data->metadataSize == dataLocal.metadataSize);
Debug.Assert(data->LoaderAllocator == dataLocal.LoaderAllocator);
Debug.Assert(data->ThunkHeap == dataLocal.ThunkHeap);
Debug.Assert(data->FieldDefToDescMap == dataLocal.FieldDefToDescMap);
Debug.Assert(data->ManifestModuleReferencesMap == dataLocal.ManifestModuleReferencesMap);
Debug.Assert(data->MemberRefToDescMap == dataLocal.MemberRefToDescMap);
Debug.Assert(data->MethodDefToDescMap == dataLocal.MethodDefToDescMap);
Debug.Assert(data->TypeDefToMethodTableMap == dataLocal.TypeDefToMethodTableMap);
Debug.Assert(data->TypeRefToMethodTableMap == dataLocal.TypeRefToMethodTableMap);
Debug.Assert(data->dwModuleID == dataLocal.dwModuleID);
Debug.Assert(data->dwBaseClassIndex == dataLocal.dwBaseClassIndex);
Debug.Assert(data->dwModuleIndex == dataLocal.dwModuleIndex);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetNestedExceptionData(ClrDataAddress exception, ClrDataAddress* exceptionObject, ClrDataAddress* nextNestedException)
{
int hr = HResults.S_OK;
try
{
if (exception == 0 || exceptionObject == null || nextNestedException == null)
throw new ArgumentException();
Contracts.IException contract = _target.Contracts.Exception;
TargetPointer exceptionObjectLocal = contract.GetNestedExceptionInfo(
exception.ToTargetPointer(_target),
out TargetPointer nextNestedExceptionLocal,
out _);
*exceptionObject = exceptionObjectLocal.ToClrDataAddress(_target);
*nextNestedException = nextNestedExceptionLocal.Value;
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress exceptionObjectLocal;
ClrDataAddress nextNestedExceptionLocal;
int hrLocal = _legacyImpl.GetNestedExceptionData(exception, &exceptionObjectLocal, &nextNestedExceptionLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*exceptionObject == exceptionObjectLocal);
Debug.Assert(*nextNestedException == nextNestedExceptionLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetObjectClassName(ClrDataAddress obj, uint count, char* className, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (obj == 0)
throw new ArgumentException();
Contracts.IObject objectContract = _target.Contracts.Object;
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer mt = objectContract.GetMethodTableAddress(obj.ToTargetPointer(_target));
Contracts.TypeHandle typeHandle = rts.GetTypeHandle(mt);
TargetPointer modulePointer = rts.GetModule(typeHandle);
if (modulePointer == TargetPointer.Null)
{
OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, "<Unloaded Type>");
}
else
{
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer);
if (!loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _))
{
OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, "<Unloaded Type>");
}
else
{
StringBuilder classNameBuilder = new();
try
{
TypeNameBuilder.AppendType(_target, classNameBuilder, typeHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst);
}
catch
{
string? fallbackName = _target.Contracts.DacStreams.StringFromEEAddress(mt);
if (fallbackName != null)
{
classNameBuilder.Clear();
classNameBuilder.Append(fallbackName);
}
}
OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, classNameBuilder.ToString());
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] classNameLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = classNameLocal)
{
hrLocal = _legacyImpl.GetObjectClassName(obj, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(className == null || new ReadOnlySpan<char>(classNameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(className)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetObjectData(ClrDataAddress objAddr, DacpObjectData* data)
{
int hr = HResults.S_OK;
try
{
if (objAddr == 0 || data == null)
throw new ArgumentException();
Contracts.IObject objectContract = _target.Contracts.Object;
Contracts.IRuntimeTypeSystem runtimeTypeSystemContract = _target.Contracts.RuntimeTypeSystem;
TargetPointer objPtr = objAddr.ToTargetPointer(_target);
TargetPointer mt = objectContract.GetMethodTableAddress(objPtr);
TypeHandle handle = runtimeTypeSystemContract.GetTypeHandle(mt);
data->MethodTable = mt.ToClrDataAddress(_target);
data->Size = runtimeTypeSystemContract.GetBaseSize(handle);
data->dwComponentSize = runtimeTypeSystemContract.GetComponentSize(handle);
if (runtimeTypeSystemContract.IsFreeObjectMethodTable(handle))
{
data->ObjectType = DacpObjectType.OBJ_FREE;
// Free objects have their component count explicitly set at the same offset as that for arrays
// Update the size to include those components
Target.TypeInfo arrayTypeInfo = _target.GetTypeInfo(DataType.Array);
ulong numComponentsOffset = (ulong)_target.GetTypeInfo(DataType.Array).Fields[Constants.FieldNames.Array.NumComponents].Offset;
data->Size += _target.Read<uint>(objAddr + numComponentsOffset) * data->dwComponentSize;
}
else if (mt == _stringMethodTable.Value)
{
data->ObjectType = DacpObjectType.OBJ_STRING;
// Update the size to include the string character components
data->Size += (uint)objectContract.GetStringValue(objPtr).Length * data->dwComponentSize;
}
else if (mt == _objectMethodTable.Value)
{
data->ObjectType = DacpObjectType.OBJ_OBJECT;
}
else if (runtimeTypeSystemContract.IsArray(handle, out uint rank))
{
data->ObjectType = DacpObjectType.OBJ_ARRAY;
data->dwRank = rank;
TargetPointer arrayData = objectContract.GetArrayData(objPtr, out uint numComponents, out TargetPointer boundsStart, out TargetPointer lowerBounds);
data->ArrayDataPtr = arrayData.ToClrDataAddress(_target);
data->dwNumComponents = numComponents;
data->ArrayBoundsPtr = boundsStart.ToClrDataAddress(_target);
data->ArrayLowerBoundsPtr = lowerBounds.ToClrDataAddress(_target);
// Update the size to include the array components
data->Size += numComponents * data->dwComponentSize;
// Get the type of the array elements
TypeHandle element = runtimeTypeSystemContract.GetTypeParam(handle);
data->ElementTypeHandle = element.Address.Value;
data->ElementType = (uint)runtimeTypeSystemContract.GetSignatureCorElementType(element);
// Validate the element type handles for arrays of arrays
while (runtimeTypeSystemContract.IsArray(element, out _))
{
element = runtimeTypeSystemContract.GetTypeParam(element);
}
}
else
{
data->ObjectType = DacpObjectType.OBJ_OTHER;
}
// Populate COM data if this is a COM object
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) != 0
&& objectContract.GetBuiltInComData(objPtr, out TargetPointer rcw, out TargetPointer ccw, out _))
{
data->RCW = rcw & ~(_rcwMask);
data->CCW = ccw;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpObjectData dataLocal;
int hrLocal = _legacyImpl.GetObjectData(objAddr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->MethodTable == dataLocal.MethodTable);
Debug.Assert(data->ObjectType == dataLocal.ObjectType);
Debug.Assert(data->Size == dataLocal.Size);
Debug.Assert(data->ElementTypeHandle == dataLocal.ElementTypeHandle);
Debug.Assert(data->ElementType == dataLocal.ElementType);
Debug.Assert(data->dwRank == dataLocal.dwRank);
Debug.Assert(data->dwNumComponents == dataLocal.dwNumComponents);
Debug.Assert(data->dwComponentSize == dataLocal.dwComponentSize);
Debug.Assert(data->ArrayDataPtr == dataLocal.ArrayDataPtr);
Debug.Assert(data->ArrayBoundsPtr == dataLocal.ArrayBoundsPtr);
Debug.Assert(data->ArrayLowerBoundsPtr == dataLocal.ArrayLowerBoundsPtr);
Debug.Assert(data->RCW == dataLocal.RCW);
Debug.Assert(data->CCW == dataLocal.CCW);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetObjectStringData(ClrDataAddress obj, uint count, char* stringData, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (obj == 0 || (stringData == null && pNeeded == null) || (stringData is not null && count <= 0))
throw new ArgumentException();
Contracts.IObject contract = _target.Contracts.Object;
string str = contract.GetStringValue(obj.ToTargetPointer(_target));
OutputBufferHelpers.CopyStringToBuffer(stringData, count, pNeeded, str);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] stringDataLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = stringDataLocal)
{
hrLocal = _legacyImpl.GetObjectStringData(obj, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(stringData == null || new ReadOnlySpan<char>(stringDataLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(stringData)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetOOMData(ClrDataAddress oomAddr, DacpOomData* data)
{
int hr = HResults.S_OK;
try
{
if (oomAddr == 0 || data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// This method is only valid for server GC mode
if (!gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCOomData oomData = gc.GetOomData(oomAddr.ToTargetPointer(_target));
data->reason = oomData.Reason;
data->alloc_size = oomData.AllocSize.Value;
data->available_pagefile_mb = oomData.AvailablePagefileMB.Value;
data->gc_index = oomData.GCIndex.Value;
data->fgm = oomData.Fgm;
data->size = oomData.Size.Value;
data->loh_p = oomData.LohP ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpOomData dataLocal;
int hrLocal = _legacyImpl.GetOOMData(oomAddr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->reason == dataLocal.reason, $"cDAC: {data->reason}, DAC: {dataLocal.reason}");
Debug.Assert(data->alloc_size == dataLocal.alloc_size, $"cDAC: {data->alloc_size}, DAC: {dataLocal.alloc_size}");
Debug.Assert(data->available_pagefile_mb == dataLocal.available_pagefile_mb, $"cDAC: {data->available_pagefile_mb}, DAC: {dataLocal.available_pagefile_mb}");
Debug.Assert(data->gc_index == dataLocal.gc_index, $"cDAC: {data->gc_index}, DAC: {dataLocal.gc_index}");
Debug.Assert(data->fgm == dataLocal.fgm, $"cDAC: {data->fgm}, DAC: {dataLocal.fgm}");
Debug.Assert(data->size == dataLocal.size, $"cDAC: {data->size}, DAC: {dataLocal.size}");
Debug.Assert(data->loh_p == dataLocal.loh_p, $"cDAC: {data->loh_p}, DAC: {dataLocal.loh_p}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetOOMStaticData(DacpOomData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// This method is only valid for workstation GC mode
if (!gcIdentifiers.Contains(GCIdentifiers.Workstation))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCOomData oomData = gc.GetOomData();
data->reason = oomData.Reason;
data->alloc_size = oomData.AllocSize.Value;
data->available_pagefile_mb = oomData.AvailablePagefileMB.Value;
data->gc_index = oomData.GCIndex.Value;
data->fgm = oomData.Fgm;
data->size = oomData.Size.Value;
data->loh_p = oomData.LohP ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpOomData dataLocal;
int hrLocal = _legacyImpl.GetOOMStaticData(&dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->reason == dataLocal.reason, $"cDAC: {data->reason}, DAC: {dataLocal.reason}");
Debug.Assert(data->alloc_size == dataLocal.alloc_size, $"cDAC: {data->alloc_size}, DAC: {dataLocal.alloc_size}");
Debug.Assert(data->available_pagefile_mb == dataLocal.available_pagefile_mb, $"cDAC: {data->available_pagefile_mb}, DAC: {dataLocal.available_pagefile_mb}");
Debug.Assert(data->gc_index == dataLocal.gc_index, $"cDAC: {data->gc_index}, DAC: {dataLocal.gc_index}");
Debug.Assert(data->fgm == dataLocal.fgm, $"cDAC: {data->fgm}, DAC: {dataLocal.fgm}");
Debug.Assert(data->size == dataLocal.size, $"cDAC: {data->size}, DAC: {dataLocal.size}");
Debug.Assert(data->loh_p == dataLocal.loh_p, $"cDAC: {data->loh_p}, DAC: {dataLocal.loh_p}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetPEFileBase(ClrDataAddress addr, ClrDataAddress* peBase)
{
int hr = HResults.S_OK;
try
{
if (addr == 0 || peBase == null)
throw new ArgumentException();
Contracts.ILoader contract = _target.Contracts.Loader;
Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(addr.ToTargetPointer(_target));
Contracts.ModuleFlags flags = contract.GetFlags(handle);
if (!flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit))
{
*peBase = contract.GetILBase(handle).ToClrDataAddress(_target);
}
else
{
*peBase = 0;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress peBaseLocal;
int hrLocal = _legacyImpl.GetPEFileBase(addr, &peBaseLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*peBase == peBaseLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetPEFileName(ClrDataAddress addr, uint count, char* fileName, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (addr == 0 || (fileName == null && pNeeded == null) || (fileName is not null && count <= 0))
throw new ArgumentException();
Contracts.ILoader contract = _target.Contracts.Loader;
Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(addr.ToTargetPointer(_target));
string path = contract.GetPath(handle);
// Return not implemented for empty paths for non-reflection emit assemblies (for example, loaded from memory)
if (string.IsNullOrEmpty(path))
{
Contracts.ModuleFlags flags = contract.GetFlags(handle);
if (!flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit))
throw new NotImplementedException();
}
OutputBufferHelpers.CopyStringToBuffer(fileName, count, pNeeded, path);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] fileNameLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = fileNameLocal)
{
hrLocal = _legacyImpl.GetPEFileName(addr, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pNeeded == null || *pNeeded == neededLocal);
Debug.Assert(fileName == null || new ReadOnlySpan<char>(fileNameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(fileName)));
}
}
#endif
return hr;
}
int ISOSDacInterface.GetPrivateBinPaths(ClrDataAddress appDomain, int count, char* paths, uint* pNeeded)
{
// Method is not supported on CoreCLR
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetPrivateBinPaths(appDomain, count, paths, pNeeded);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetRCWData(ClrDataAddress addr, DacpRCWData* data)
{
int hr = HResults.S_OK;
try
{
if (addr == 0 || data is null)
throw new ArgumentException();
IBuiltInCOM builtInCom = _target.Contracts.BuiltInCOM; // E_NOTIMPL if not defined (non-Windows)
*data = default;
TargetPointer rcwPtr = addr.ToTargetPointer(_target);
Contracts.RCWData rcwData = builtInCom.GetRCWData(rcwPtr);
data->identityPointer = rcwData.IdentityPointer.ToClrDataAddress(_target);
data->unknownPointer = rcwData.UnknownPointer.ToClrDataAddress(_target);
data->managedObject = rcwData.ManagedObject.ToClrDataAddress(_target);
data->vtablePtr = rcwData.VTablePtr.ToClrDataAddress(_target);
data->creatorThread = rcwData.CreatorThread.ToClrDataAddress(_target);
data->ctxCookie = rcwData.CtxCookie.ToClrDataAddress(_target);
data->refCount = (int)rcwData.RefCount;
data->interfaceCount = builtInCom.GetRCWInterfaces(rcwPtr).Count();
data->isAggregated = rcwData.IsAggregated ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
data->isContained = rcwData.IsContained ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
data->isFreeThreaded = rcwData.IsFreeThreaded ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
data->isDisconnected = rcwData.IsDisconnected ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpRCWData dataLocal;
int hrLocal = _legacyImpl.GetRCWData(addr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->identityPointer == dataLocal.identityPointer, $"cDAC: {data->identityPointer:x}, DAC: {dataLocal.identityPointer:x}");
Debug.Assert(data->unknownPointer == dataLocal.unknownPointer, $"cDAC: {data->unknownPointer:x}, DAC: {dataLocal.unknownPointer:x}");
Debug.Assert(data->managedObject == dataLocal.managedObject, $"cDAC: {data->managedObject:x}, DAC: {dataLocal.managedObject:x}");
Debug.Assert(data->vtablePtr == dataLocal.vtablePtr, $"cDAC: {data->vtablePtr:x}, DAC: {dataLocal.vtablePtr:x}");
Debug.Assert(data->creatorThread == dataLocal.creatorThread, $"cDAC: {data->creatorThread:x}, DAC: {dataLocal.creatorThread:x}");
Debug.Assert(data->ctxCookie == dataLocal.ctxCookie, $"cDAC: {data->ctxCookie:x}, DAC: {dataLocal.ctxCookie:x}");
Debug.Assert(data->refCount == dataLocal.refCount, $"cDAC: {data->refCount}, DAC: {dataLocal.refCount}");
Debug.Assert(data->interfaceCount == dataLocal.interfaceCount, $"cDAC: {data->interfaceCount}, DAC: {dataLocal.interfaceCount}");
Debug.Assert(data->isAggregated == dataLocal.isAggregated, $"cDAC: {data->isAggregated}, DAC: {dataLocal.isAggregated}");
Debug.Assert(data->isContained == dataLocal.isContained, $"cDAC: {data->isContained}, DAC: {dataLocal.isContained}");
Debug.Assert(data->isFreeThreaded == dataLocal.isFreeThreaded, $"cDAC: {data->isFreeThreaded}, DAC: {dataLocal.isFreeThreaded}");
Debug.Assert(data->isDisconnected == dataLocal.isDisconnected, $"cDAC: {data->isDisconnected}, DAC: {dataLocal.isDisconnected}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetRCWInterfaces(ClrDataAddress rcw, uint count, [In, MarshalUsing(CountElementName = nameof(count)), Out] DacpCOMInterfacePointerData[]? interfaces, uint* pNeeded)
{
int hr = HResults.S_OK;
#if DEBUG
int numWritten = 0;
#endif
try
{
if (rcw == 0)
throw new ArgumentException();
TargetPointer rcwPtr = rcw.ToTargetPointer(_target);
IBuiltInCOM builtInCom = _target.Contracts.BuiltInCOM; // E_NOTIMPL if not defined (non-Windows)
IEnumerable<(TargetPointer MethodTable, TargetPointer Unknown)> entries = builtInCom.GetRCWInterfaces(rcwPtr);
if (interfaces == null)
{
if (pNeeded == null)
{
throw new ArgumentException();
}
else
{
*pNeeded = (uint)entries.Count();
#if DEBUG
numWritten = (int)*pNeeded;
#endif
}
}
else
{
TargetPointer ctxCookie = builtInCom.GetRCWContext(rcwPtr);
uint itemIndex = 0;
foreach (var (methodTable, unknown) in entries)
{
if (itemIndex >= count)
{
#if DEBUG
numWritten = (int)itemIndex;
#endif
throw new ArgumentException();
}
interfaces[itemIndex].methodTable = methodTable.ToClrDataAddress(_target);
interfaces[itemIndex].interfacePtr = unknown.ToClrDataAddress(_target);
interfaces[itemIndex].comContext = ctxCookie.ToClrDataAddress(_target);
itemIndex++;
}
if (pNeeded != null)
*pNeeded = itemIndex;
#if DEBUG
numWritten = (int)itemIndex;
#endif
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint pNeededLocal = 0;
int hrLocal;
DacpCOMInterfacePointerData[]? interfacesLocal = interfaces != null ? new DacpCOMInterfacePointerData[count] : null;
hrLocal = _legacyImpl.GetRCWInterfaces(rcw, count, interfacesLocal, pNeeded == null && interfacesLocal == null ? null : &pNeededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(numWritten == pNeededLocal, $"cDAC: {numWritten}, DAC: {pNeededLocal}");
if (interfacesLocal is not null && interfaces is not null)
{
for (int i = 0; i < (int)pNeededLocal; i++)
{
Debug.Assert(interfaces[i].methodTable == interfacesLocal![i].methodTable, $"cDAC: {interfaces[i].methodTable:x}, DAC: {interfacesLocal[i].methodTable:x}");
Debug.Assert(interfaces[i].interfacePtr == interfacesLocal![i].interfacePtr, $"cDAC: {interfaces[i].interfacePtr:x}, DAC: {interfacesLocal[i].interfacePtr:x}");
Debug.Assert(interfaces[i].comContext == interfacesLocal![i].comContext, $"cDAC: {interfaces[i].comContext:x}, DAC: {interfacesLocal[i].comContext:x}");
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface.GetRegisterName(int regName, uint count, char* buffer, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (buffer is null && pNeeded is null)
throw new NullReferenceException();
string[] regs = _target.Contracts.RuntimeInfo.GetTargetArchitecture() switch
{
RuntimeInfoArchitecture.X64 => s_amd64Registers,
RuntimeInfoArchitecture.Arm => s_armRegisters,
RuntimeInfoArchitecture.Arm64 => s_arm64Registers,
RuntimeInfoArchitecture.X86 => s_x86Registers,
RuntimeInfoArchitecture.LoongArch64 => s_loongArch64Registers,
RuntimeInfoArchitecture.RiscV64 => s_riscV64Registers,
_ => throw new InvalidOperationException(),
};
// Caller frame registers are encoded as "-(reg+1)".
bool callerFrame = regName < 0;
int regIndex = callerFrame ? -regName - 1 : regName;
if ((uint)regIndex >= (uint)regs.Length)
return unchecked((int)0x8000FFFF); // E_UNEXPECTED
string name = callerFrame ? $"caller.{regs[regIndex]}" : regs[regIndex];
uint needed = (uint)(name.Length + 1);
if (pNeeded is not null)
*pNeeded = needed;
if (buffer is not null)
{
OutputBufferHelpers.CopyStringToBuffer(buffer, count, neededBufferSize: null, name);
if (count < needed)
hr = HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
char[] bufferLocal = new char[count];
uint neededLocal;
int hrLocal;
fixed (char* ptr = bufferLocal)
{
hrLocal = _legacyImpl.GetRegisterName(regName, count, ptr, &neededLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(pNeeded is null || *pNeeded == neededLocal);
Debug.Assert(buffer is null || new ReadOnlySpan<char>(bufferLocal, 0, (int)Math.Min(count, neededLocal)).SequenceEqual(new ReadOnlySpan<char>(buffer, (int)Math.Min(count, neededLocal))));
}
}
#endif
return hr;
}
private static readonly string[] s_amd64Registers =
[
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
];
private static readonly string[] s_armRegisters =
[
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11", "r12", "sp", "lr",
];
private static readonly string[] s_arm64Registers =
[
"X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
"X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15", "X16", "X17",
"X18", "X19", "X20", "X21", "X22", "X23", "X24", "X25", "X26", "X27",
"X28", "Fp", "Lr", "Sp",
];
private static readonly string[] s_x86Registers =
[
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
];
private static readonly string[] s_loongArch64Registers =
[
"R0", "RA", "TP", "SP",
"A0", "A1", "A2", "A3",
"A4", "A5", "A6", "A7",
"T0", "T1", "T2", "T3",
"T4", "T5", "T6", "T7",
"T8", "R21", "FP", "S0",
"S1", "S2", "S3", "S4",
"S5", "S6", "S7", "S8",
];
private static readonly string[] s_riscV64Registers =
[
"zero", "RA", "SP", "GP",
"TP", "T0", "T1", "T2",
"FP", "S1", "A0", "A1",
"A2", "A3", "A4", "A5",
"A6", "A7", "S2", "S3",
"S4", "S5", "S6", "S7",
"S8", "S9", "S10", "S11",
"T3", "T4", "T5", "T6",
];
int ISOSDacInterface.GetStackLimits(ClrDataAddress threadPtr, ClrDataAddress* lower, ClrDataAddress* upper, ClrDataAddress* fp)
{
int hr = HResults.S_OK;
try
{
if (threadPtr == 0 || (lower == null && upper == null && fp == null))
throw new ArgumentException();
Contracts.IThread contract = _target.Contracts.Thread;
TargetPointer stackBase, stackLimit, frameAddress;
contract.GetStackLimitData(threadPtr.ToTargetPointer(_target), out stackBase, out stackLimit, out frameAddress);
if (lower != null)
*lower = stackBase.ToClrDataAddress(_target);
if (upper != null)
*upper = stackLimit.ToClrDataAddress(_target);
if (fp != null)
*fp = frameAddress.ToClrDataAddress(_target);
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress lowerLocal, upperLocal, fpLocal;
int hrLocal = _legacyImpl.GetStackLimits(threadPtr, &lowerLocal, &upperLocal, &fpLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(lower == null || *lower == lowerLocal, $"cDAC: {*lower:x}, DAC: {lowerLocal:x}");
Debug.Assert(upper == null || *upper == upperLocal, $"cDAC: {*upper:x}, DAC: {upperLocal:x}");
Debug.Assert(fp == null || *fp == fpLocal, $"cDAC: {*fp:x}, DAC: {fpLocal:x}");
}
}
#endif
return hr;
}
[GeneratedComClass]
internal sealed unsafe partial class SOSStackRefEnum : ISOSStackRefEnum
{
private readonly SOSStackRefData[] _refs;
private uint _index;
public SOSStackRefEnum(SOSStackRefData[] refs)
{
_refs = refs;
}
int ISOSStackRefEnum.Next(uint count, SOSStackRefData[] refs, uint* pFetched)
{
int hr = HResults.S_OK;
try
{
if (pFetched is null || refs is null)
throw new NullReferenceException();
count = Math.Min(count, (uint)refs.Length);
uint written = 0;
while (written < count && _index < _refs.Length)
refs[written++] = _refs[(int)_index++];
*pFetched = written;
// COMPAT: S_FALSE means more items remain, S_OK means enumeration is complete.
// This is the inverse of the standard COM IEnumXxx convention, but matches
// the legacy DAC behavior (see SOSHandleEnum.Next).
hr = _index < _refs.Length ? HResults.S_FALSE : HResults.S_OK;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
int ISOSStackRefEnum.EnumerateErrors(DacComNullableByRef<ISOSStackRefErrorEnum> ppEnum)
{
return HResults.E_NOTIMPL;
}
int ISOSEnum.Skip(uint count)
{
_index = Math.Min(_index + count, (uint)_refs.Length);
return HResults.S_OK;
}
int ISOSEnum.Reset()
{
_index = 0;
return HResults.S_OK;
}
int ISOSEnum.GetCount(uint* pCount)
{
if (pCount is null) return HResults.E_POINTER;
*pCount = (uint)_refs.Length;
return HResults.S_OK;
}
}
int ISOSDacInterface.GetStackReferences(int osThreadID, DacComNullableByRef<ISOSStackRefEnum> ppEnum)
{
int hr = HResults.S_OK;
try
{
IThread threadContract = _target.Contracts.Thread;
IStackWalk stackWalkContract = _target.Contracts.StackWalk;
ThreadData? matchingThread = null;
ThreadStoreData threadStore = threadContract.GetThreadStoreData();
TargetPointer threadAddr = threadStore.FirstThread;
while (threadAddr != TargetPointer.Null)
{
ThreadData td = threadContract.GetThreadData(threadAddr);
if (td.OSId.Value == (ulong)osThreadID)
{
matchingThread = td;
break;
}
threadAddr = td.NextThread;
}
if (matchingThread is null)
{
throw new ArgumentException($"No thread with OS ID {osThreadID} was found.");
}
IReadOnlyList<StackReferenceData> refs = stackWalkContract.WalkStackReferences(matchingThread.Value);
SOSStackRefData[] sosRefs = new SOSStackRefData[refs.Count];
for (int i = 0; i < refs.Count; i++)
{
sosRefs[i] = new SOSStackRefData
{
HasRegisterInformation = refs[i].HasRegisterInformation ? 1 : 0,
Register = refs[i].Register,
Offset = refs[i].Offset,
Address = refs[i].Address.Value,
Object = refs[i].Object.Value,
Flags = refs[i].Flags,
Source = refs[i].Source.Value,
SourceType = refs[i].IsStackSourceFrame
? SOSStackSourceType.SOS_StackSourceFrame
: SOSStackSourceType.SOS_StackSourceIP,
StackPointer = refs[i].StackPointer.Value,
};
}
ppEnum.Interface = new SOSStackRefEnum(sosRefs);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
// Validate that the legacy DAC produces the same HResult.
// We pass isNullRef: false to request actual enumeration, but we don't
// compare individual refs — that's done by cdacstress.cpp at runtime.
int hrLocal = _legacyImpl.GetStackReferences(osThreadID, new DacComNullableByRef<ISOSStackRefEnum>(isNullRef: false));
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetStressLogAddress(ClrDataAddress* stressLog)
{
ulong stressLogAddress = _target.ReadGlobalPointer(Constants.Globals.StressLog);
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress legacyStressLog;
Debug.Assert(HResults.S_OK == _legacyImpl.GetStressLogAddress(&legacyStressLog));
Debug.Assert(legacyStressLog == stressLogAddress);
}
#endif
*stressLog = stressLogAddress;
return HResults.S_OK;
}
int ISOSDacInterface.GetSyncBlockCleanupData(ClrDataAddress addr, DacpSyncBlockCleanupData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
*data = default;
ISyncBlock syncBlockContract = _target.Contracts.SyncBlock;
TargetPointer syncBlockPtr;
if (addr == 0)
{
syncBlockPtr = syncBlockContract.GetSyncBlockFromCleanupList();
}
else
{
syncBlockPtr = addr.ToTargetPointer(_target);
}
if (syncBlockPtr != TargetPointer.Null)
{
data->SyncBlockPointer = syncBlockPtr.ToClrDataAddress(_target);
data->nextSyncBlock = syncBlockContract.GetNextSyncBlock(syncBlockPtr).ToClrDataAddress(_target);
if (syncBlockContract.GetBuiltInComData(syncBlockPtr, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf))
{
data->blockRCW = rcw.ToClrDataAddress(_target);
data->blockClassFactory = ccf.ToClrDataAddress(_target);
data->blockCCW = ccw.ToClrDataAddress(_target);
}
}
// Maintain backwards compatibility with old versions of CLRMD. They will not properly iterate, but at least it will not infinite loop.
if (addr == 0)
throw new ArgumentException();
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpSyncBlockCleanupData dataLocal;
int hrLocal = _legacyImpl.GetSyncBlockCleanupData(addr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->SyncBlockPointer == dataLocal.SyncBlockPointer, $"cDAC: {data->SyncBlockPointer:x}, DAC: {dataLocal.SyncBlockPointer:x}");
Debug.Assert(data->nextSyncBlock == dataLocal.nextSyncBlock, $"cDAC: {data->nextSyncBlock:x}, DAC: {dataLocal.nextSyncBlock:x}");
Debug.Assert(data->blockRCW == dataLocal.blockRCW, $"cDAC: {data->blockRCW:x}, DAC: {dataLocal.blockRCW:x}");
Debug.Assert(data->blockClassFactory == dataLocal.blockClassFactory, $"cDAC: {data->blockClassFactory:x}, DAC: {dataLocal.blockClassFactory:x}");
Debug.Assert(data->blockCCW == dataLocal.blockCCW, $"cDAC: {data->blockCCW:x}, DAC: {dataLocal.blockCCW:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetSyncBlockData(uint number, DacpSyncBlockData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
*data = default;
data->bFree = Interop.BOOL.TRUE;
ISyncBlock syncBlock = _target.Contracts.SyncBlock;
uint syncBlockCount = syncBlock.GetSyncBlockCount();
data->SyncBlockCount = syncBlockCount;
if (syncBlockCount > 0 && number <= syncBlockCount)
{
data->bFree = syncBlock.IsSyncBlockFree(number) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
if (data->bFree == Interop.BOOL.FALSE)
{
TargetPointer obj = syncBlock.GetSyncBlockObject(number);
data->Object = obj.ToClrDataAddress(_target);
if (syncBlock.GetSyncBlock(number) is TargetPointer syncBlockAddr && syncBlockAddr != TargetPointer.Null)
{
data->SyncBlockPointer = syncBlockAddr.ToClrDataAddress(_target);
if (syncBlock.GetBuiltInComData(syncBlockAddr, out TargetPointer rcw, out TargetPointer ccw, out TargetPointer ccf))
{
data->COMFlags = (rcw & ~(_rcwMask)) != TargetPointer.Null ? (uint)DacpSyncBlockData.COMFlagsEnum.HasRCW : 0;
data->COMFlags |= ccw != TargetPointer.Null ? (uint)DacpSyncBlockData.COMFlagsEnum.HasCCW : 0;
data->COMFlags |= ccf != TargetPointer.Null ? (uint)DacpSyncBlockData.COMFlagsEnum.HasCCF : 0;
}
bool monitorHeld = syncBlock.TryGetLockInfo(syncBlockAddr, out uint owningThreadId, out uint recursion);
data->MonitorHeld = monitorHeld ? (uint)1 : 0;
if (monitorHeld)
{
data->Recursion = recursion + 1;
IThread thread = _target.Contracts.Thread;
TargetPointer threadPtr = thread.IdToThread(owningThreadId);
data->HoldingThread = threadPtr.ToClrDataAddress(_target);
}
TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomain = _target.ReadPointer(appDomainPointer);
data->appDomainPtr = appDomain.ToClrDataAddress(_target);
data->AdditionalThreadCount = syncBlock.GetAdditionalThreadCount(syncBlockAddr);
}
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpSyncBlockData dataLocal;
int hrLocal = _legacyImpl.GetSyncBlockData(number, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->Object == dataLocal.Object, $"cDAC: {data->Object:x}, DAC: {dataLocal.Object:x}");
Debug.Assert(data->bFree == dataLocal.bFree, $"cDAC: {data->bFree}, DAC: {dataLocal.bFree}");
Debug.Assert(data->SyncBlockPointer == dataLocal.SyncBlockPointer, $"cDAC: {data->SyncBlockPointer:x}, DAC: {dataLocal.SyncBlockPointer:x}");
Debug.Assert(data->COMFlags == dataLocal.COMFlags, $"cDAC: {data->COMFlags}, DAC: {dataLocal.COMFlags}");
Debug.Assert(data->MonitorHeld == dataLocal.MonitorHeld, $"cDAC: {data->MonitorHeld}, DAC: {dataLocal.MonitorHeld}");
if (data->MonitorHeld != 0)
{
Debug.Assert(data->Recursion == dataLocal.Recursion, $"cDAC: {data->Recursion}, DAC: {dataLocal.Recursion}");
Debug.Assert(data->HoldingThread == dataLocal.HoldingThread, $"cDAC: {data->HoldingThread:x}, DAC: {dataLocal.HoldingThread:x}");
}
Debug.Assert(data->AdditionalThreadCount == dataLocal.AdditionalThreadCount, $"cDAC: {data->AdditionalThreadCount}, DAC: {dataLocal.AdditionalThreadCount}");
Debug.Assert(data->appDomainPtr == dataLocal.appDomainPtr, $"cDAC: {data->appDomainPtr:x}, DAC: {dataLocal.appDomainPtr:x}");
Debug.Assert(data->SyncBlockCount == dataLocal.SyncBlockCount, $"cDAC: {data->SyncBlockCount}, DAC: {dataLocal.SyncBlockCount}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, DacpAllocData* data)
{
int hr = HResults.S_OK;
try
{
if (thread == 0)
throw new ArgumentException();
if (data is null)
throw new NullReferenceException();
Contracts.IThread contract = _target.Contracts.Thread;
contract.GetThreadAllocContext(thread.ToTargetPointer(_target), out long allocBytes, out long allocBytesLoh);
data->allocBytes = (ClrDataAddress)(ulong)allocBytes;
data->allocBytesLoh = (ClrDataAddress)(ulong)allocBytesLoh;
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpAllocData dataLocal = default;
int hrLocal = _legacyImpl.GetThreadAllocData(thread, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->allocBytes == dataLocal.allocBytes, $"cDAC: {data->allocBytes:x}, DAC: {dataLocal.allocBytes:x}");
Debug.Assert(data->allocBytesLoh == dataLocal.allocBytesLoh, $"cDAC: {data->allocBytesLoh:x}, DAC: {dataLocal.allocBytesLoh:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetThreadData(ClrDataAddress thread, DacpThreadData* data)
{
int hr = HResults.S_OK;
try
{
if (thread == 0 || data == null)
throw new ArgumentException();
Contracts.IThread contract = _target.Contracts.Thread;
Contracts.ThreadData threadData = contract.GetThreadData(thread.ToTargetPointer(_target));
data->corThreadId = (int)threadData.Id;
data->osThreadId = (int)threadData.OSId.Value;
data->state = 0; // Set to 0, nobody uses this
data->preemptiveGCDisabled = (uint)(threadData.PreemptiveGCDisabled ? 1 : 0);
data->allocContextPtr = threadData.AllocContextPointer.ToClrDataAddress(_target);
data->allocContextLimit = threadData.AllocContextLimit.ToClrDataAddress(_target);
data->fiberData = 0; // Always set to 0 - fibers are no longer supported
TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomain = _target.ReadPointer(appDomainPointer);
data->context = appDomain.ToClrDataAddress(_target);
data->domain = appDomain.ToClrDataAddress(_target);
data->lockCount = -1; // Always set to -1 - lock count was .NET Framework and no longer needed
data->pFrame = threadData.Frame.ToClrDataAddress(_target);
data->firstNestedException = threadData.FirstNestedException.ToClrDataAddress(_target);
data->teb = 0; // TEB is no longer provided by the DAC - consumers should look it up from the OS thread ID
data->lastThrownObjectHandle = threadData.LastThrownObjectHandle.ToClrDataAddress(_target);
data->nextThread = threadData.NextThread.ToClrDataAddress(_target);
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpThreadData dataLocal;
int hrLocal = _legacyImpl.GetThreadData(thread, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->corThreadId == dataLocal.corThreadId, $"cDAC: {data->corThreadId}, DAC: {dataLocal.corThreadId}");
Debug.Assert(data->osThreadId == dataLocal.osThreadId, $"cDAC: {data->osThreadId}, DAC: {dataLocal.osThreadId}");
Debug.Assert(data->state == dataLocal.state, $"cDAC: {data->state}, DAC: {dataLocal.state}");
Debug.Assert(data->preemptiveGCDisabled == dataLocal.preemptiveGCDisabled, $"cDAC: {data->preemptiveGCDisabled}, DAC: {dataLocal.preemptiveGCDisabled}");
Debug.Assert(data->allocContextPtr == dataLocal.allocContextPtr, $"cDAC: {data->allocContextPtr:x}, DAC: {dataLocal.allocContextPtr:x}");
Debug.Assert(data->allocContextLimit == dataLocal.allocContextLimit, $"cDAC: {data->allocContextLimit:x}, DAC: {dataLocal.allocContextLimit:x}");
Debug.Assert(data->fiberData == dataLocal.fiberData, $"cDAC: {data->fiberData:x}, DAC: {dataLocal.fiberData:x}");
Debug.Assert(data->context == dataLocal.context, $"cDAC: {data->context:x}, DAC: {dataLocal.context:x}");
Debug.Assert(data->domain == dataLocal.domain, $"cDAC: {data->domain:x}, DAC: {dataLocal.domain:x}");
Debug.Assert(data->lockCount == dataLocal.lockCount, $"cDAC: {data->lockCount}, DAC: {dataLocal.lockCount}");
Debug.Assert(data->pFrame == dataLocal.pFrame, $"cDAC: {data->pFrame:x}, DAC: {dataLocal.pFrame:x}");
Debug.Assert(data->firstNestedException == dataLocal.firstNestedException, $"cDAC: {data->firstNestedException:x}, DAC: {dataLocal.firstNestedException:x}");
Debug.Assert(data->lastThrownObjectHandle == dataLocal.lastThrownObjectHandle, $"cDAC: {data->lastThrownObjectHandle:x}, DAC: {dataLocal.lastThrownObjectHandle:x}");
Debug.Assert(data->nextThread == dataLocal.nextThread, $"cDAC: {data->nextThread:x}, DAC: {dataLocal.nextThread:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface.GetThreadFromThinlockID(uint thinLockId, ClrDataAddress* pThread)
{
int hr = HResults.S_OK;
try
{
if (pThread == null)
throw new ArgumentException();
TargetPointer threadPtr = _target.Contracts.Thread.IdToThread(thinLockId);
*pThread = threadPtr.ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
ClrDataAddress pThreadLocal;
int hrLocal = _legacyImpl.GetThreadFromThinlockID(thinLockId, &pThreadLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*pThread == pThreadLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetThreadLocalModuleData(ClrDataAddress thread, uint index, void* data)
{
// CoreCLR does not use thread local modules anymore
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetThreadLocalModuleData(thread, index, data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetThreadpoolData(void* data)
{
// This API is not implemented by the legacy DAC
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetThreadpoolData(data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
int ISOSDacInterface.GetThreadStoreData(DacpThreadStoreData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
Contracts.IThread thread = _target.Contracts.Thread;
Contracts.ThreadStoreData threadStoreData = thread.GetThreadStoreData();
data->threadCount = threadStoreData.ThreadCount;
data->firstThread = threadStoreData.FirstThread.ToClrDataAddress(_target);
data->finalizerThread = threadStoreData.FinalizerThread.ToClrDataAddress(_target);
data->gcThread = threadStoreData.GCThread.ToClrDataAddress(_target);
Contracts.ThreadStoreCounts threadCounts = thread.GetThreadCounts();
data->unstartedThreadCount = threadCounts.UnstartedThreadCount;
data->backgroundThreadCount = threadCounts.BackgroundThreadCount;
data->pendingThreadCount = threadCounts.PendingThreadCount;
data->deadThreadCount = threadCounts.DeadThreadCount;
data->fHostConfig = 0; // Always 0 for non-Framework
}
catch (global::System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpThreadStoreData dataLocal;
int hrLocal = _legacyImpl.GetThreadStoreData(&dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(data->threadCount == dataLocal.threadCount);
Debug.Assert(data->firstThread == dataLocal.firstThread);
Debug.Assert(data->finalizerThread == dataLocal.finalizerThread);
Debug.Assert(data->gcThread == dataLocal.gcThread);
Debug.Assert(data->unstartedThreadCount == dataLocal.unstartedThreadCount);
Debug.Assert(data->backgroundThreadCount == dataLocal.backgroundThreadCount);
Debug.Assert(data->pendingThreadCount == dataLocal.pendingThreadCount);
Debug.Assert(data->deadThreadCount == dataLocal.deadThreadCount);
Debug.Assert(data->fHostConfig == dataLocal.fHostConfig);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetTLSIndex(uint* pIndex)
{
int hr = HResults.S_OK;
try
{
if (pIndex == null)
throw new ArgumentException();
uint TlsIndexBase = _target.Read<uint>(_target.ReadGlobalPointer(Constants.Globals.TlsIndexBase));
uint OffsetOfCurrentThreadInfo = _target.Read<uint>(_target.ReadGlobalPointer(Constants.Globals.OffsetOfCurrentThreadInfo));
uint CombinedTlsIndex = TlsIndexBase + (OffsetOfCurrentThreadInfo << 16) + 0x80000000;
*pIndex = CombinedTlsIndex;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
uint indexLocal;
int hrLocal = _legacyImpl.GetTLSIndex(&indexLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*pIndex == indexLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetUsefulGlobals(DacpUsefulGlobalsData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
data->ArrayMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.ObjectArrayMethodTable))
.ToClrDataAddress(_target);
data->StringMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.StringMethodTable))
.ToClrDataAddress(_target);
data->ObjectMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.ObjectMethodTable))
.ToClrDataAddress(_target);
data->ExceptionMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.ExceptionMethodTable))
.ToClrDataAddress(_target);
data->FreeMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable))
.ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
// There are some scenarios where SOS can call GetUsefulGlobals before the globals are initialized,
// in these cases set the method table pointers to 0 and assert that the legacy DAC returns the same
// uninitialized values.
data->ArrayMethodTable = 0;
data->StringMethodTable = 0;
data->ObjectMethodTable = 0;
data->ExceptionMethodTable = 0;
data->FreeMethodTable = 0;
}
#if DEBUG
if (_legacyImpl is not null)
{
DacpUsefulGlobalsData dataLocal;
int hrLocal = _legacyImpl.GetUsefulGlobals(&dataLocal);
// SOS can call GetUsefulGlobals before the global pointers are initialized.
// In the DAC, this behavior depends on the compiler.
// MSVC builds: the DAC global table is a compile time constant and the DAC will return successfully.
// Clang builds: the DAC global table is constructed at runtime and the DAC will fail.
// Because of this variation, we cannot match the DAC behavior exactly.
// As long as the returned data matches, it should be fine.
if (hr == HResults.S_OK || hrLocal == HResults.S_OK)
{
Debug.Assert(data->ArrayMethodTable == dataLocal.ArrayMethodTable);
Debug.Assert(data->StringMethodTable == dataLocal.StringMethodTable);
Debug.Assert(data->ObjectMethodTable == dataLocal.ObjectMethodTable);
Debug.Assert(data->ExceptionMethodTable == dataLocal.ExceptionMethodTable);
Debug.Assert(data->FreeMethodTable == dataLocal.FreeMethodTable);
}
}
#endif
return hr;
}
int ISOSDacInterface.GetWorkRequestData(ClrDataAddress addrWorkRequest, void* data)
{
// This API is not implemented by the legacy DAC
int hr = HResults.E_NOTIMPL;
#if DEBUG
if (_legacyImpl is not null)
{
int hrLocal = _legacyImpl.GetWorkRequestData(addrWorkRequest, data);
Debug.ValidateHResult(hr, hrLocal);
}
#endif
return hr;
}
#if DEBUG
internal sealed class TraverseEhInfoExpected
{
public TraverseEhInfoExpected(List<DACEHInfo> elements, bool expectAbort, uint? abortIndex = null)
{
Elements = elements;
ExpectAbort = expectAbort;
AbortIndex = abortIndex;
}
public List<DACEHInfo> Elements { get; }
public bool ExpectAbort { get; }
public uint? AbortIndex { get; }
public int CallbackCount { get; set; }
}
[UnmanagedCallersOnly]
private static int TraverseEHInfoCallback(uint clauseIndex, uint totalClauses, DACEHInfo* pEHInfo, void* expectedEhInfo)
{
var expected = (TraverseEhInfoExpected)GCHandle.FromIntPtr((nint)expectedEhInfo).Target!;
Debug.Assert(clauseIndex < totalClauses, $"Invalid clause index {clauseIndex} of {totalClauses}");
if (clauseIndex < expected.Elements.Count)
{
DACEHInfo expectedEhClause = expected.Elements[(int)clauseIndex];
Debug.Assert(pEHInfo->clauseType == expectedEhClause.clauseType, $"cDAC: {expectedEhClause.clauseType}, DAC: {pEHInfo->clauseType}");
Debug.Assert(pEHInfo->tryStartOffset == expectedEhClause.tryStartOffset, $"cDAC: {expectedEhClause.tryStartOffset:x}, DAC: {pEHInfo->tryStartOffset:x}");
Debug.Assert(pEHInfo->tryEndOffset == expectedEhClause.tryEndOffset, $"cDAC: {expectedEhClause.tryEndOffset:x}, DAC: {pEHInfo->tryEndOffset:x}");
Debug.Assert(pEHInfo->handlerStartOffset == expectedEhClause.handlerStartOffset, $"cDAC: {expectedEhClause.handlerStartOffset:x}, DAC: {pEHInfo->handlerStartOffset:x}");
Debug.Assert(pEHInfo->handlerEndOffset == expectedEhClause.handlerEndOffset, $"cDAC: {expectedEhClause.handlerEndOffset:x}, DAC: {pEHInfo->handlerEndOffset:x}");
Debug.Assert(pEHInfo->isDuplicateClause == expectedEhClause.isDuplicateClause, $"cDAC: {expectedEhClause.isDuplicateClause}, DAC: {pEHInfo->isDuplicateClause}");
Debug.Assert(pEHInfo->filterOffset == expectedEhClause.filterOffset, $"cDAC: {expectedEhClause.filterOffset:x}, DAC: {pEHInfo->filterOffset:x}");
Debug.Assert(pEHInfo->isCatchAllHandler == expectedEhClause.isCatchAllHandler, $"cDAC: {expectedEhClause.isCatchAllHandler}, DAC: {pEHInfo->isCatchAllHandler}");
Debug.Assert(pEHInfo->moduleAddr == expectedEhClause.moduleAddr, $"cDAC: {expectedEhClause.moduleAddr:x}, DAC: {pEHInfo->moduleAddr:x}");
Debug.Assert(pEHInfo->mtCatch == expectedEhClause.mtCatch, $"cDAC: {expectedEhClause.mtCatch:x}, DAC: {pEHInfo->mtCatch:x}");
Debug.Assert(pEHInfo->tokCatch == expectedEhClause.tokCatch, $"cDAC: {expectedEhClause.tokCatch:x}, DAC: {pEHInfo->tokCatch:x}");
}
else
{
Debug.Fail($"Received unexpected clause index {clauseIndex} of {totalClauses}");
}
expected.CallbackCount++;
if (expected.ExpectAbort && expected.AbortIndex == clauseIndex)
{
return 0; // Return 0 to trigger E_ABORT and stop enumeration
}
return 1; // Return non-zero to continue enumeration
}
#endif
int ISOSDacInterface.TraverseEHInfo(ClrDataAddress ip, delegate* unmanaged<uint, uint, DACEHInfo*, void*, int> pCallback, void* token)
{
int hr = HResults.S_OK;
#if DEBUG
List<DACEHInfo> clausesLocal = new();
#endif
int E_ABORT = unchecked((int)0x80004004);
uint lastIndex = 0;
try
{
if (ip == 0 || pCallback == null)
{
throw new ArgumentException();
}
IExecutionManager executionManager = _target.Contracts.ExecutionManager;
CodeBlockHandle? handle = executionManager.GetCodeBlockHandle(ip.ToTargetCodePointer(_target));
if (handle is not CodeBlockHandle codeBlockHandle)
{
throw new ArgumentException();
}
List<ExceptionClauseInfo> exceptionClauses = executionManager.GetExceptionClauses(codeBlockHandle);
uint numClauses = (uint)exceptionClauses.Count;
for (uint i = 0; i < numClauses; i++)
{
ExceptionClauseInfo clause = exceptionClauses[(int)i];
DACEHInfo ehInfo = default;
ehInfo.clauseType = clause.ClauseType switch
{
ExceptionClauseInfo.ExceptionClauseFlags.Fault => DACEHInfo.EHClauseType.EHFault,
ExceptionClauseInfo.ExceptionClauseFlags.Finally => DACEHInfo.EHClauseType.EHFinally,
ExceptionClauseInfo.ExceptionClauseFlags.Filter => DACEHInfo.EHClauseType.EHFilter,
ExceptionClauseInfo.ExceptionClauseFlags.Typed => DACEHInfo.EHClauseType.EHTyped,
_ => DACEHInfo.EHClauseType.EHUnknown,
};
ehInfo.filterOffset = clause.FilterOffset is uint filterOffset ? (ulong)filterOffset : 0;
ehInfo.isCatchAllHandler = clause.IsCatchAllHandler is true ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
ehInfo.tokCatch = clause.ClassToken is uint classToken ? classToken : 0;
ehInfo.moduleAddr = clause.ModuleAddr is TargetPointer moduleAddr ? moduleAddr.ToClrDataAddress(_target) : 0;
ehInfo.mtCatch = clause.TypeHandle is TargetNUInt th ? new TargetPointer(th.Value).ToClrDataAddress(_target) : 0;
ehInfo.tryStartOffset = (ulong)clause.TryStartPC;
ehInfo.tryEndOffset = (ulong)clause.TryEndPC;
ehInfo.handlerStartOffset = (ulong)clause.HandlerStartPC;
ehInfo.handlerEndOffset = (ulong)clause.HandlerEndPC;
#if DEBUG
clausesLocal.Add(ehInfo);
#endif
if (pCallback(i, numClauses, &ehInfo, token) == 0)
{
lastIndex = i;
throw Marshal.GetExceptionForHR(E_ABORT)!;
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
TraverseEhInfoExpected expected = new(clausesLocal, hr == E_ABORT, hr == E_ABORT ? lastIndex : null);
GCHandle expectedHandle = GCHandle.Alloc(expected);
try
{
void* tokenDebug = GCHandle.ToIntPtr(expectedHandle).ToPointer();
delegate* unmanaged<uint, uint, DACEHInfo*, void*, int> callbackDebugPtr = &TraverseEHInfoCallback;
int hrLocal = _legacyImpl.TraverseEHInfo(ip, callbackDebugPtr, tokenDebug);
Debug.ValidateHResult(hr, hrLocal, HResultValidationMode.Exact);
}
finally
{
expectedHandle.Free();
}
}
#endif
return hr;
}
#if DEBUG
[ThreadStatic]
private static List<(ulong VirtualAddress, nuint VirtualSize)>? _debugTraverseLoaderHeapBlocks;
[ThreadStatic]
private static uint _debugTraverseLoaderDebugCount;
private static List<(ulong VirtualAddress, nuint VirtualSize)> DebugTraverseLoaderHeapBlocks
=> _debugTraverseLoaderHeapBlocks ??= new();
[UnmanagedCallersOnly]
private static void TraverseLoaderHeapDebugCallback(ulong virtualAddress, nuint virtualSize, Interop.BOOL _)
{
List<(ulong VirtualAddress, nuint VirtualSize)> expected = DebugTraverseLoaderHeapBlocks;
bool found = expected.Remove((virtualAddress, virtualSize));
_debugTraverseLoaderDebugCount++;
Debug.Assert(found, $"Unexpected loader heap block: address={virtualAddress:x}, size={virtualSize:x}");
}
#endif
private int TraverseLoaderHeapCore(TargetPointer loaderHeapAddr, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = HResults.S_OK;
#if DEBUG
DebugTraverseLoaderHeapBlocks.Clear();
_debugTraverseLoaderDebugCount = 0;
#endif
try
{
if (loaderHeapAddr == TargetPointer.Null || pCallback is null)
throw new ArgumentException();
int iterationMax = 8192;
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer block = loader.GetFirstLoaderHeapBlock(loaderHeapAddr);
TargetPointer firstBlock = block;
int i = 0;
while (block != TargetPointer.Null && i++ < iterationMax)
{
Contracts.LoaderHeapBlockData blockData;
try
{
blockData = loader.GetLoaderHeapBlockData(block);
}
catch (VirtualReadException)
{
throw new NullReferenceException();
}
pCallback(blockData.Address.Value, (nuint)blockData.Size.Value, block == firstBlock ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);
#if DEBUG
DebugTraverseLoaderHeapBlocks.Add((blockData.Address.Value, (nuint)blockData.Size.Value));
#endif
block = blockData.NextBlock;
if (block == firstBlock)
throw new NullReferenceException();
}
if (i >= iterationMax)
hr = HResults.S_FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
int ISOSDacInterface.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = TraverseLoaderHeapCore(loaderHeapAddr.ToTargetPointer(_target), pCallback);
#if DEBUG
if (_legacyImpl is not null)
{
int cdacCount = DebugTraverseLoaderHeapBlocks.Count;
delegate* unmanaged<ulong, nuint, Interop.BOOL, void> debugCallbackPtr = &TraverseLoaderHeapDebugCallback;
int hrLocal = _legacyImpl.TraverseLoaderHeap(loaderHeapAddr, debugCallbackPtr);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(DebugTraverseLoaderHeapBlocks.Count == 0,
$"cDAC found {cdacCount} blocks, DAC matched {_debugTraverseLoaderDebugCount}, {DebugTraverseLoaderHeapBlocks.Count} unmatched");
Debug.Assert(_debugTraverseLoaderDebugCount == (uint)cdacCount,
$"cDAC: {cdacCount} blocks, DAC: {_debugTraverseLoaderDebugCount} blocks");
}
}
#endif
return hr;
}
#if DEBUG
[UnmanagedCallersOnly]
private static void TraverseModuleMapCallback(uint index, ulong moduleAddr, void* expectedElements)
{
var expectedElementsDict = (Dictionary<ulong, uint>)GCHandle.FromIntPtr((nint)expectedElements).Target!;
if (expectedElementsDict.TryGetValue(moduleAddr, out uint expectedIndex) && expectedIndex == index)
{
expectedElementsDict[default]++; // Increment the count for verification
}
else
{
Debug.Assert(false, $"Unexpected module address {moduleAddr:x} at index {index}");
}
}
#endif
int ISOSDacInterface.TraverseModuleMap(ModuleMapType mmt, ClrDataAddress moduleAddr, delegate* unmanaged<uint, ulong, void*, void> pCallback, void* token)
{
int hr = HResults.S_OK;
IEnumerable<(TargetPointer Address, uint Index)> elements = Enumerable.Empty<(TargetPointer, uint)>();
try
{
if (moduleAddr == 0)
throw new ArgumentException();
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer moduleAddrPtr = moduleAddr.ToTargetPointer(_target);
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(moduleAddrPtr);
Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle);
switch (mmt)
{
case ModuleMapType.TYPEDEFTOMETHODTABLE:
elements = loader.EnumerateModuleLookupMap(lookupTables.TypeDefToMethodTable);
break;
case ModuleMapType.TYPEREFTOMETHODTABLE:
elements = loader.EnumerateModuleLookupMap(lookupTables.TypeRefToMethodTable);
break;
default:
throw new ArgumentException();
}
foreach ((TargetPointer element, uint index) in elements)
{
// Call the callback with each element
pCallback(index, element.ToClrDataAddress(_target).Value, token);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
Dictionary<ulong, uint> expectedElements = elements.ToDictionary(tuple => tuple.Address.ToClrDataAddress(_target).Value, tuple => tuple.Index);
expectedElements.Add(default, 0);
void* tokenDebug = GCHandle.ToIntPtr(GCHandle.Alloc(expectedElements)).ToPointer();
delegate* unmanaged<uint, ulong, void*, void> callbackDebugPtr = &TraverseModuleMapCallback;
int hrLocal = _legacyImpl.TraverseModuleMap(mmt, moduleAddr, callbackDebugPtr, tokenDebug);
Debug.ValidateHResult(hr, hrLocal);
Debug.Assert(expectedElements[default] == elements.Count(), $"cDAC: {elements.Count()} elements, DAC: {expectedElements[default]} elements");
GCHandle.FromIntPtr((nint)tokenDebug).Free();
}
#endif
return hr;
}
#if DEBUG
[UnmanagedCallersOnly]
private static Interop.BOOL TraverseRCWCleanupListCallback(ulong rcwAddr, ulong ctx, ulong staThread, Interop.BOOL isFreeThreaded, void* expectedElements)
{
var expectedElementsDict = (Dictionary<ulong, ulong>)GCHandle.FromIntPtr((nint)expectedElements).Target!;
if (expectedElementsDict.TryGetValue(rcwAddr, out ulong expectedCtx) && expectedCtx == ctx)
{
expectedElementsDict[default]++; // Increment the count for verification
}
else
{
Debug.Fail($"Unexpected RCW address {rcwAddr:x} or context {ctx:x}");
}
return Interop.BOOL.TRUE;
}
#endif
int ISOSDacInterface.TraverseRCWCleanupList(ClrDataAddress cleanupListPtr, delegate* unmanaged<ulong, ulong, ulong, Interop.BOOL, void*, Interop.BOOL> pCallback, void* token)
{
int hr = HResults.S_OK;
IEnumerable<Contracts.RCWCleanupInfo> cleanupInfos = Enumerable.Empty<Contracts.RCWCleanupInfo>();
try
{
if (pCallback is null)
throw new ArgumentException();
Contracts.IBuiltInCOM contract = _target.Contracts.BuiltInCOM; // E_NOTIMPL if not defined (non-Windows)
TargetPointer listPtr = cleanupListPtr.ToTargetPointer(_target);
cleanupInfos = contract.GetRCWCleanupList(listPtr);
foreach (Contracts.RCWCleanupInfo info in cleanupInfos)
{
pCallback(
info.RCW.ToClrDataAddress(_target).Value,
info.Context.ToClrDataAddress(_target).Value,
info.STAThread.ToClrDataAddress(_target).Value,
info.IsFreeThreaded ? Interop.BOOL.TRUE : Interop.BOOL.FALSE,
token);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
Dictionary<ulong, ulong> expectedElements = cleanupInfos.ToDictionary(info => info.RCW.ToClrDataAddress(_target).Value, info => info.Context.ToClrDataAddress(_target).Value);
expectedElements.Add(default, 0);
GCHandle expectedElementsHandle = GCHandle.Alloc(expectedElements);
void* tokenDebug = GCHandle.ToIntPtr(expectedElementsHandle).ToPointer();
delegate* unmanaged<ulong, ulong, ulong, Interop.BOOL, void*, Interop.BOOL> callbackDebugPtr = &TraverseRCWCleanupListCallback;
int hrLocal = _legacyImpl.TraverseRCWCleanupList(cleanupListPtr, callbackDebugPtr, tokenDebug);
Debug.ValidateHResult(hr, hrLocal);
Debug.Assert(expectedElements[default] == (ulong)cleanupInfos.Count(), $"cDAC: {cleanupInfos.Count()} elements, DAC: {expectedElements[default]} elements");
expectedElementsHandle.Free();
}
#endif
return hr;
}
int ISOSDacInterface.TraverseVirtCallStubHeap(ClrDataAddress pAppDomain, VCSHeapType heaptype, delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = HResults.S_OK;
try
{
// Native DAC only validates pAppDomain here; traversal always uses the global loader allocator.
if (pAppDomain == 0 || pCallback is null)
throw new ArgumentException();
Contracts.ILoader loader = _target.Contracts.Loader;
TargetPointer globalLoaderAllocator = loader.GetGlobalLoaderAllocator();
IReadOnlyDictionary<Contracts.LoaderAllocatorHeapType, TargetPointer> heaps = loader.GetLoaderAllocatorHeaps(globalLoaderAllocator);
if (!heaps.ContainsKey(Contracts.LoaderAllocatorHeapType.IndcellHeap))
throw new NullReferenceException();
Contracts.LoaderAllocatorHeapType heapKey = heaptype switch
{
VCSHeapType.IndcellHeap => Contracts.LoaderAllocatorHeapType.IndcellHeap,
VCSHeapType.CacheEntryHeap => Contracts.LoaderAllocatorHeapType.CacheEntryHeap,
_ => throw new ArgumentException(),
};
if (heaps.TryGetValue(heapKey, out TargetPointer heap) && heap != TargetPointer.Null)
{
hr = TraverseLoaderHeapCore(heap, pCallback);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl is not null)
{
int cdacCount = DebugTraverseLoaderHeapBlocks.Count;
delegate* unmanaged<ulong, nuint, Interop.BOOL, void> debugCallbackPtr = &TraverseLoaderHeapDebugCallback;
int hrLocal = _legacyImpl.TraverseVirtCallStubHeap(pAppDomain, heaptype, debugCallbackPtr);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(DebugTraverseLoaderHeapBlocks.Count == 0,
$"cDAC found {cdacCount} blocks, DAC matched {_debugTraverseLoaderDebugCount}, {DebugTraverseLoaderHeapBlocks.Count} unmatched");
Debug.Assert(_debugTraverseLoaderDebugCount == (uint)cdacCount,
$"cDAC: {cdacCount} blocks, DAC: {_debugTraverseLoaderDebugCount} blocks");
}
}
#endif
return hr;
}
#endregion ISOSDacInterface
#region ISOSDacInterface2
int ISOSDacInterface2.GetObjectExceptionData(ClrDataAddress objectAddress, DacpExceptionObjectData* data)
{
try
{
Contracts.IException contract = _target.Contracts.Exception;
Contracts.ExceptionData exceptionData = contract.GetExceptionData(objectAddress.ToTargetPointer(_target));
data->Message = exceptionData.Message.ToClrDataAddress(_target);
data->InnerException = exceptionData.InnerException.ToClrDataAddress(_target);
data->StackTrace = exceptionData.StackTrace.ToClrDataAddress(_target);
data->WatsonBuckets = exceptionData.WatsonBuckets.ToClrDataAddress(_target);
data->StackTraceString = exceptionData.StackTraceString.ToClrDataAddress(_target);
data->RemoteStackTraceString = exceptionData.RemoteStackTraceString.ToClrDataAddress(_target);
data->HResult = exceptionData.HResult;
data->XCode = exceptionData.XCode;
}
catch (System.Exception ex)
{
return ex.HResult;
}
return HResults.S_OK;
}
int ISOSDacInterface2.IsRCWDCOMProxy(ClrDataAddress rcwAddress, int* inDCOMProxy)
{
int hr = HResults.S_OK;
try
{
if (inDCOMProxy == null)
throw new NullReferenceException(); // HResults.E_POINTER;
*inDCOMProxy = (int)Interop.BOOL.FALSE;
if (_target.ReadGlobal<byte>(Constants.Globals.FeatureCOMInterop) == 0)
{
hr = HResults.E_NOTIMPL;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl2 is not null)
{
int inDCOMProxyLocal;
int hrLocal = _legacyImpl2.IsRCWDCOMProxy(rcwAddress, &inDCOMProxyLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*inDCOMProxy == inDCOMProxyLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface2
#region ISOSDacInterface3
int ISOSDacInterface3.GetGCInterestingInfoData(ClrDataAddress interestingInfoAddr, DacpGCInterestingInfoData* data)
{
int hr = HResults.S_OK;
try
{
if (interestingInfoAddr == 0 || data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// doesn't make sense to call this on WKS mode
if (!gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
// For server GC, use GetHeapData(TargetPointer heap)
GCHeapData heapData = gc.GetHeapData(interestingInfoAddr.ToTargetPointer(_target));
PopulateGCInterestingInfoData(heapData, data);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl3 is not null)
{
DacpGCInterestingInfoData dataLocal = default;
int hrLocal = _legacyImpl3.GetGCInterestingInfoData(interestingInfoAddr, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
VerifyGCInterestingInfoData(data, &dataLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface3.GetGCInterestingInfoStaticData(DacpGCInterestingInfoData* data)
{
int hr = HResults.S_OK;
try
{
if (data == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
// doesn't make sense to call this on SVR mode
if (!gcIdentifiers.Contains(GCIdentifiers.Workstation))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
// For workstation GC, use GetHeapData()
GCHeapData heapData = gc.GetHeapData();
PopulateGCInterestingInfoData(heapData, data);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl3 is not null)
{
DacpGCInterestingInfoData dataLocal = default;
int hrLocal = _legacyImpl3.GetGCInterestingInfoStaticData(&dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
VerifyGCInterestingInfoData(data, &dataLocal);
}
}
#endif
return hr;
}
private static void PopulateGCInterestingInfoData(GCHeapData heapData, DacpGCInterestingInfoData* data)
{
*data = default;
// The DacpGCInterestingInfoData struct hardcodes platform sized ints.
// This is problematic for new cross-bit scenarios.
// If the target platform is 64-bits but the cDAC/SOS is 32-bits, the values will be truncated.
// Copy interesting data points
for (int i = 0; i < Math.Min(GCConstants.DAC_NUM_GC_DATA_POINTS, heapData.InterestingData.Count); i++)
data->interestingDataPoints[i] = (nuint)heapData.InterestingData[i].Value;
// Copy compact reasons
for (int i = 0; i < Math.Min(GCConstants.DAC_MAX_COMPACT_REASONS_COUNT, heapData.CompactReasons.Count); i++)
data->compactReasons[i] = (nuint)heapData.CompactReasons[i].Value;
// Copy expand mechanisms
for (int i = 0; i < Math.Min(GCConstants.DAC_MAX_EXPAND_MECHANISMS_COUNT, heapData.ExpandMechanisms.Count); i++)
data->expandMechanisms[i] = (nuint)heapData.ExpandMechanisms[i].Value;
// Copy interesting mechanism bits
for (int i = 0; i < Math.Min(GCConstants.DAC_MAX_GC_MECHANISM_BITS_COUNT, heapData.InterestingMechanismBits.Count); i++)
data->bitMechanisms[i] = (nuint)heapData.InterestingMechanismBits[i].Value;
}
#if DEBUG
private static void VerifyGCInterestingInfoData(DacpGCInterestingInfoData* cdacData, DacpGCInterestingInfoData* legacyData)
{
// Compare interesting data points array
for (int i = 0; i < GCConstants.DAC_NUM_GC_DATA_POINTS; i++)
{
Debug.Assert(cdacData->interestingDataPoints[i] == legacyData->interestingDataPoints[i],
$"interestingDataPoints[{i}] - cDAC: {cdacData->interestingDataPoints[i]}, DAC: {legacyData->interestingDataPoints[i]}");
}
// Compare compact reasons array
for (int i = 0; i < GCConstants.DAC_MAX_COMPACT_REASONS_COUNT; i++)
{
Debug.Assert(cdacData->compactReasons[i] == legacyData->compactReasons[i],
$"compactReasons[{i}] - cDAC: {cdacData->compactReasons[i]}, DAC: {legacyData->compactReasons[i]}");
}
// Compare expand mechanisms array
for (int i = 0; i < GCConstants.DAC_MAX_EXPAND_MECHANISMS_COUNT; i++)
{
Debug.Assert(cdacData->expandMechanisms[i] == legacyData->expandMechanisms[i],
$"expandMechanisms[{i}] - cDAC: {cdacData->expandMechanisms[i]}, DAC: {legacyData->expandMechanisms[i]}");
}
// Compare bit mechanisms array
for (int i = 0; i < GCConstants.DAC_MAX_GC_MECHANISM_BITS_COUNT; i++)
{
Debug.Assert(cdacData->bitMechanisms[i] == legacyData->bitMechanisms[i],
$"bitMechanisms[{i}] - cDAC: {cdacData->bitMechanisms[i]}, DAC: {legacyData->bitMechanisms[i]}");
}
// Compare global mechanisms array
for (int i = 0; i < GCConstants.DAC_MAX_GLOBAL_GC_MECHANISMS_COUNT; i++)
{
Debug.Assert(cdacData->globalMechanisms[i] == legacyData->globalMechanisms[i],
$"globalMechanisms[{i}] - cDAC: {cdacData->globalMechanisms[i]}, DAC: {legacyData->globalMechanisms[i]}");
}
}
#endif
int ISOSDacInterface3.GetGCGlobalMechanisms(nuint* globalMechanisms)
{
int hr = HResults.S_OK;
try
{
if (globalMechanisms == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
IReadOnlyList<TargetNUInt> globalMechanismsData = gc.GetGlobalMechanisms();
// Clear the array
for (int i = 0; i < GCConstants.DAC_MAX_GLOBAL_GC_MECHANISMS_COUNT; i++)
globalMechanisms[i] = 0;
// Copy global mechanisms data
for (int i = 0; i < Math.Min(GCConstants.DAC_MAX_GLOBAL_GC_MECHANISMS_COUNT, globalMechanismsData.Count); i++)
{
// This API hardcodes platform sized ints in the struct
// This is problematic for new cross-bit scenarios.
// If the target platform is 64-bits but the cDAC/SOS is 32-bits, the values will be truncated.
globalMechanisms[i] = (nuint)globalMechanismsData[i].Value;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl3 is not null)
{
nuint[] globalMechanismsLocal = new nuint[GCConstants.DAC_MAX_GLOBAL_GC_MECHANISMS_COUNT];
fixed (nuint* pLocal = globalMechanismsLocal)
{
int hrLocal = _legacyImpl3.GetGCGlobalMechanisms(pLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
for (int i = 0; i < GCConstants.DAC_MAX_GLOBAL_GC_MECHANISMS_COUNT; i++)
{
Debug.Assert(globalMechanisms[i] == globalMechanismsLocal[i],
$"globalMechanisms[{i}] - cDAC: {globalMechanisms[i]}, DAC: {globalMechanismsLocal[i]}");
}
}
}
}
#endif
return hr;
}
#endregion ISOSDacInterface3
#region ISOSDacInterface4
int ISOSDacInterface4.GetClrNotification(ClrDataAddress[] arguments, int count, int* pNeeded)
{
int hr = HResults.S_OK;
uint MaxClrNotificationArgs = _target.ReadGlobal<uint>(Constants.Globals.MaxClrNotificationArgs);
try
{
*pNeeded = (int)MaxClrNotificationArgs;
TargetPointer basePtr = _target.ReadGlobalPointer(Constants.Globals.ClrNotificationArguments);
if (_target.ReadNUInt(basePtr).Value == 0)
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
for (int i = 0; i < count && i < MaxClrNotificationArgs; i++)
{
arguments[i] = _target.ReadNUInt(basePtr.Value + (ulong)(i * _target.PointerSize)).Value;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl4 is not null)
{
ClrDataAddress[] argumentsLocal = new ClrDataAddress[count];
int neededLocal;
int hrLocal = _legacyImpl4.GetClrNotification(argumentsLocal, count, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*pNeeded == neededLocal);
for (int i = 0; i < count && i < MaxClrNotificationArgs; i++)
{
Debug.Assert(arguments[i] == argumentsLocal[i]);
}
}
}
#endif
return hr;
}
#endregion ISOSDacInterface4
#region ISOSDacInterface5
int ISOSDacInterface5.GetTieredVersions(
ClrDataAddress methodDesc,
int rejitId,
[In, MarshalUsing(CountElementName = nameof(cNativeCodeAddrs)), Out] DacpTieredVersionData[]? nativeCodeAddrs,
int cNativeCodeAddrs,
int* pcNativeCodeAddrs)
{
int hr = HResults.S_OK;
try
{
if (methodDesc == 0 || cNativeCodeAddrs == 0 || pcNativeCodeAddrs == null || nativeCodeAddrs is null)
{
throw new ArgumentException();
}
*pcNativeCodeAddrs = 0;
ILoader loader = _target.Contracts.Loader;
ICodeVersions codeVersions = _target.Contracts.CodeVersions;
IReJIT rejitContract = _target.Contracts.ReJIT;
TargetPointer methodDescPtr = methodDesc.ToTargetPointer(_target);
ILCodeVersionHandle ilCodeVersionHandle = codeVersions.GetILCodeVersions(methodDescPtr)
.FirstOrDefault(ilcode => rejitContract.GetRejitId(ilcode).Value == (ulong)rejitId, ILCodeVersionHandle.Invalid);
if (!ilCodeVersionHandle.IsValid)
throw new ArgumentException();
IRuntimeTypeSystem runtimeTypeSystemContract = _target.Contracts.RuntimeTypeSystem;
MethodDescHandle methodDescHandle = runtimeTypeSystemContract.GetMethodDescHandle(methodDescPtr);
TargetPointer modulePtr = runtimeTypeSystemContract.GetModule(runtimeTypeSystemContract.GetTypeHandle(runtimeTypeSystemContract.GetMethodTable(methodDescHandle)));
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
TargetPointer r2rImageBase = TargetPointer.Null;
TargetPointer r2rImageEnd = TargetPointer.Null;
if (loader.IsReadyToRun(moduleHandle)
&& loader.TryGetLoadedImageContents(moduleHandle, out r2rImageBase, out uint r2rSize, out _))
{
r2rImageEnd = r2rImageBase + r2rSize;
}
bool isEligibleForTieredCompilation = runtimeTypeSystemContract.IsEligibleForTieredCompilation(methodDescHandle);
int count = 0;
foreach (NativeCodeVersionHandle nativeCodeVersionHandle in codeVersions.GetNativeCodeVersions(methodDescPtr, ilCodeVersionHandle))
{
TargetCodePointer nativeCode = _target.Contracts.PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent(codeVersions.GetNativeCode(nativeCodeVersionHandle));
TargetPointer nativeCodeAddr = nativeCode.ToAddress(_target);
nativeCodeAddrs[count].nativeCodeAddr = nativeCodeAddr.ToClrDataAddress(_target);
nativeCodeAddrs[count].nativeCodeVersionNodePtr = nativeCodeVersionHandle.CodeVersionNodeAddress.ToClrDataAddress(_target);
if (r2rImageBase <= nativeCodeAddr && nativeCodeAddr < r2rImageEnd)
{
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.ReadyToRun;
}
else if (isEligibleForTieredCompilation)
{
switch (codeVersions.GetOptimizationTier(nativeCodeVersionHandle))
{
default:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.Unknown;
break;
case OptimizationTier.OptimizationTier0:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.QuickJitted;
break;
case OptimizationTier.OptimizationTier1:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.OptimizedTier1;
break;
case OptimizationTier.OptimizationTier1OSR:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.OptimizedTier1OSR;
break;
case OptimizationTier.OptimizationTierOptimized:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.Optimized;
break;
case OptimizationTier.OptimizationTier0Instrumented:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.QuickJittedInstrumented;
break;
case OptimizationTier.OptimizationTier1Instrumented:
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.OptimizedTier1Instrumented;
break;
}
}
else
{
nativeCodeAddrs[count].optimizationTier = DacpTieredVersionData.OptimizationTier.Unknown;
}
count++;
if (count >= cNativeCodeAddrs)
{
hr = HResults.S_FALSE;
break;
}
}
*pcNativeCodeAddrs = count;
}
catch (NotImplementedException)
{
// ReJIT contract not available — feature not active in the target runtime
return HResults.S_OK;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl5 is not null)
{
var legacyBuffer = new DacpTieredVersionData[cNativeCodeAddrs];
int legacyCount;
int hrLocal = _legacyImpl5.GetTieredVersions(methodDesc, rejitId, legacyBuffer, cNativeCodeAddrs, &legacyCount);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*pcNativeCodeAddrs == legacyCount, $"cDAC count: {*pcNativeCodeAddrs}, DAC count: {legacyCount}");
if (nativeCodeAddrs is not null)
{
for (int i = 0; i < *pcNativeCodeAddrs; i++)
{
Debug.Assert(nativeCodeAddrs[i].nativeCodeAddr == legacyBuffer[i].nativeCodeAddr,
$"[{i}] cDAC nativeCodeAddr: 0x{(ulong)nativeCodeAddrs[i].nativeCodeAddr:x}, DAC: 0x{(ulong)legacyBuffer[i].nativeCodeAddr:x}");
Debug.Assert(nativeCodeAddrs[i].nativeCodeVersionNodePtr == legacyBuffer[i].nativeCodeVersionNodePtr,
$"[{i}] cDAC nodePtr: 0x{(ulong)nativeCodeAddrs[i].nativeCodeVersionNodePtr:x}, DAC: 0x{(ulong)legacyBuffer[i].nativeCodeVersionNodePtr:x}");
Debug.Assert(nativeCodeAddrs[i].optimizationTier == legacyBuffer[i].optimizationTier,
$"[{i}] cDAC tier: {nativeCodeAddrs[i].optimizationTier}, DAC: {legacyBuffer[i].optimizationTier}");
}
}
}
}
#endif // DEBUG
return hr;
}
#endregion ISOSDacInterface5
#region ISOSDacInterface6
int ISOSDacInterface6.GetMethodTableCollectibleData(ClrDataAddress mt, DacpMethodTableCollectibleData* data)
{
int hr = HResults.S_OK;
try
{
if (mt == 0 || data == null)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
ILoader loaderContract = _target.Contracts.Loader;
Contracts.TypeHandle typeHandle = rtsContract.GetTypeHandle(mt.ToTargetPointer(_target));
bool isCollectible = rtsContract.IsCollectible(typeHandle);
if (isCollectible)
{
TargetPointer modulePtr = rtsContract.GetLoaderModule(typeHandle);
Contracts.ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(modulePtr);
TargetPointer loaderAllocator = loaderContract.GetLoaderAllocator(moduleHandle);
TargetPointer loaderAllocatorHandle = loaderContract.GetObjectHandle(loaderAllocator);
data->LoaderAllocatorObjectHandle = loaderAllocatorHandle.ToClrDataAddress(_target);
}
data->bCollectible = isCollectible ? 1 : 0;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl6 is not null)
{
DacpMethodTableCollectibleData dataLocal;
int hrLocal = _legacyImpl6.GetMethodTableCollectibleData(mt, &dataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert((data->bCollectible == 0) == (dataLocal.bCollectible == 0), $"cDAC: {data->bCollectible}, DAC: {dataLocal.bCollectible}");
Debug.Assert(data->LoaderAllocatorObjectHandle == dataLocal.LoaderAllocatorObjectHandle, $"cDAC: {data->LoaderAllocatorObjectHandle:x}, DAC: {dataLocal.LoaderAllocatorObjectHandle:x}");
}
}
#endif
return hr;
}
#endregion ISOSDacInterface6
#region ISOSDacInterface7
int ISOSDacInterface7.GetPendingReJITID(ClrDataAddress methodDesc, int* pRejitId)
{
int hr = HResults.S_OK;
try
{
if (methodDesc == 0 || pRejitId == null)
throw new ArgumentException();
Contracts.IReJIT rejitContract = _target.Contracts.ReJIT;
Contracts.ICodeVersions codeVersionsContract = _target.Contracts.CodeVersions;
TargetPointer methodDescPtr = methodDesc.ToTargetPointer(_target);
Contracts.ILCodeVersionHandle activeILCodeVersion = codeVersionsContract.GetActiveILCodeVersion(methodDescPtr);
if (rejitContract.GetRejitState(activeILCodeVersion) == Contracts.RejitState.Requested)
{
*pRejitId = (int)rejitContract.GetRejitId(activeILCodeVersion).Value;
}
else
{
hr = HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl7 is not null)
{
int rejitIdLocal;
int hrLocal = _legacyImpl7.GetPendingReJITID(methodDesc, &rejitIdLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*pRejitId == rejitIdLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface7.GetReJITInformation(ClrDataAddress methodDesc, int rejitId, DacpReJitData2* pRejitData)
{
int hr = HResults.S_OK;
try
{
if (methodDesc == 0 || pRejitData == null || rejitId < 0)
throw new ArgumentException();
ICodeVersions cv = _target.Contracts.CodeVersions;
IReJIT rejitContract = _target.Contracts.ReJIT;
TargetPointer methodDescPtr = methodDesc.ToTargetPointer(_target);
ILCodeVersionHandle ilCodeVersion = cv.GetILCodeVersions(methodDescPtr)
.FirstOrDefault(ilcode => rejitContract.GetRejitId(ilcode).Value == (ulong)rejitId,
ILCodeVersionHandle.Invalid);
if (!ilCodeVersion.IsValid)
throw new ArgumentException();
else
{
pRejitData->rejitID = (uint)rejitId;
switch (rejitContract.GetRejitState(ilCodeVersion))
{
case RejitState.Requested:
pRejitData->flags = DacpReJitData2.Flags.kRequested;
break;
case RejitState.Active:
pRejitData->flags = DacpReJitData2.Flags.kActive;
break;
default:
Debug.Assert(true, "Unknown SharedRejitInfo state. cDAC should be updated to understand this new state.");
pRejitData->flags = DacpReJitData2.Flags.kUnknown;
break;
}
pRejitData->il = cv.GetIL(ilCodeVersion).ToClrDataAddress(_target);
if (ilCodeVersion.IsExplicit)
pRejitData->ilCodeVersionNodePtr = ilCodeVersion.ILCodeVersionNode.ToClrDataAddress(_target);
else
pRejitData->ilCodeVersionNodePtr = 0;
}
}
catch (System.Exception ex)
{
return ex.HResult;
}
#if DEBUG
if (_legacyImpl7 is not null)
{
DacpReJitData2 rejitDataLocal;
int hrLocal = _legacyImpl7.GetReJITInformation(methodDesc, rejitId, &rejitDataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pRejitData->rejitID == rejitDataLocal.rejitID);
Debug.Assert(pRejitData->il == rejitDataLocal.il);
Debug.Assert(pRejitData->flags == rejitDataLocal.flags);
Debug.Assert(pRejitData->ilCodeVersionNodePtr == rejitDataLocal.ilCodeVersionNodePtr);
}
}
#endif
return hr;
}
int ISOSDacInterface7.GetProfilerModifiedILInformation(ClrDataAddress methodDesc, DacpProfilerILData* pILData)
{
int hr = HResults.S_OK;
try
{
if (methodDesc == 0 || pILData == null)
throw new ArgumentException();
pILData->type = DacpProfilerILData.ModificationType.Unmodified;
pILData->rejitID = 0;
pILData->il = 0;
Contracts.IReJIT rejit = _target.Contracts.ReJIT;
Contracts.ICodeVersions cv = _target.Contracts.CodeVersions;
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TargetPointer methodDescPtr = methodDesc.ToTargetPointer(_target);
// getting the module handle and the token from the method desc
MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
TargetPointer mt = rts.GetMethodTable(mdh);
TypeHandle typeHandle = rts.GetTypeHandle(mt);
TargetPointer modulePtr = rts.GetModule(typeHandle);
uint token = rts.GetMethodToken(mdh);
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
Contracts.ILCodeVersionHandle activeILCodeVersion = cv.GetActiveILCodeVersion(methodDescPtr);
// rejit in progress or rejit applied?
if (rejit.GetRejitState(activeILCodeVersion) != RejitState.Active || !cv.HasDefaultIL(activeILCodeVersion))
{
pILData->type = DacpProfilerILData.ModificationType.ReJITModified;
pILData->rejitID = (uint)rejit.GetRejitId(activeILCodeVersion).Value;
}
TargetPointer il = loader.GetDynamicIL(moduleHandle, token);
if (il != 0)
{
pILData->type = DacpProfilerILData.ModificationType.ILModified;
pILData->il = il.ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl7 is not null)
{
DacpProfilerILData ilDataLocal;
int hrLocal = _legacyImpl7.GetProfilerModifiedILInformation(methodDesc, &ilDataLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(pILData->type == ilDataLocal.type, $"cDAC: {pILData->type}, DAC: {ilDataLocal.type}");
Debug.Assert(pILData->rejitID == ilDataLocal.rejitID, $"cDAC: {pILData->rejitID}, DAC: {ilDataLocal.rejitID}");
Debug.Assert(pILData->il == ilDataLocal.il, $"cDAC: {pILData->il:x}, DAC: {ilDataLocal.il:x}");
}
}
#endif
return hr;
}
int ISOSDacInterface7.GetMethodsWithProfilerModifiedIL(ClrDataAddress mod, ClrDataAddress* methodDescs, int cMethodDescs, int* pcMethodDescs)
{
int hr = HResults.S_OK;
try
{
if (mod == 0 || methodDescs == null || cMethodDescs == 0 || pcMethodDescs == null)
throw new ArgumentException();
*pcMethodDescs = 0;
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
Contracts.IReJIT rejit = _target.Contracts.ReJIT;
Contracts.ICodeVersions cv = _target.Contracts.CodeVersions;
TargetPointer modulePtr = mod.ToTargetPointer(_target);
Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
// iterate through typedef to method table map
foreach ((TargetPointer ptr, _) in loader.EnumerateModuleLookupMap(loader.GetLookupTables(moduleHandle).TypeDefToMethodTable))
{
if (*pcMethodDescs >= cMethodDescs)
break;
TypeHandle typeHandle = rts.GetTypeHandle(ptr);
foreach (TargetPointer md in rts.GetIntroducedMethodDescs(typeHandle))
{
MethodDescHandle mdh = rts.GetMethodDescHandle(md);
uint token = rts.GetMethodToken(mdh);
Contracts.ILCodeVersionHandle activeILCodeVersion = cv.GetActiveILCodeVersion(md);
// first condition: is method in process of being rejitted?
// second condition: has rejit been applied or null default IL been otherwise used for profiler modification (see src/coreclr/vm/codeversion.cpp comment)?
// third condition: has profiler modified IL through ICorProfilerInfo::SetILFunctionBody?
if (rejit.GetRejitState(activeILCodeVersion) != RejitState.Active ||
!cv.HasDefaultIL(activeILCodeVersion) ||
loader.GetDynamicIL(moduleHandle, token) != 0)
{
methodDescs[*pcMethodDescs] = md.ToClrDataAddress(_target);
(*pcMethodDescs)++;
}
if (*pcMethodDescs >= cMethodDescs)
break;
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl7 is not null)
{
ClrDataAddress[] methodDescsLocal = new ClrDataAddress[cMethodDescs];
int pcMethodDescsLocal;
int hrLocal;
fixed (ClrDataAddress* ptr = methodDescsLocal)
{
hrLocal = _legacyImpl7.GetMethodsWithProfilerModifiedIL(mod, ptr, cMethodDescs, &pcMethodDescsLocal);
}
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*pcMethodDescs == pcMethodDescsLocal, $"cDAC: {*pcMethodDescs}, DAC: {pcMethodDescsLocal}");
for (int i = 0; i < *pcMethodDescs; i++)
{
Debug.Assert(methodDescs[i] == methodDescsLocal[i], $"cDAC: {methodDescs[i]:x}, DAC: {methodDescsLocal[i]:x}");
}
}
}
#endif
return hr;
}
#endregion ISOSDacInterface7
#region ISOSDacInterface8
int ISOSDacInterface8.GetNumberGenerations(uint* pGenerations)
{
int hr = HResults.S_OK;
try
{
if (pGenerations is null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
GCHeapData heapData = gcIdentifiers.Contains(GCIdentifiers.Server)
? gc.GetHeapData(gc.GetGCHeaps().First())
: gc.GetHeapData();
*pGenerations = (uint)heapData.GenerationTable.Count;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl8 is not null)
{
uint pGenerationsLocal;
int hrLocal = _legacyImpl8.GetNumberGenerations(&pGenerationsLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*pGenerations == pGenerationsLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface8.GetGenerationTable(uint cGenerations, DacpGenerationData* pGenerationData, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (cGenerations > 0 && pGenerationData is null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
if (gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCHeapData heapData = gc.GetHeapData();
uint totalGenerationCount = (uint)heapData.GenerationTable.Count;
if (pNeeded is not null)
*pNeeded = totalGenerationCount;
if (cGenerations < totalGenerationCount)
{
hr = HResults.S_FALSE;
}
else
{
for (int i = 0; i < (int)totalGenerationCount; i++)
{
GCGenerationData gen = heapData.GenerationTable[i];
pGenerationData[i].start_segment = gen.StartSegment.ToClrDataAddress(_target);
pGenerationData[i].allocation_start = gen.AllocationStart.ToClrDataAddress(_target);
pGenerationData[i].allocContextPtr = gen.AllocationContextPointer.ToClrDataAddress(_target);
pGenerationData[i].allocContextLimit = gen.AllocationContextLimit.ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
DacpGenerationData[]? genDataLocal = cGenerations > 0 ? new DacpGenerationData[cGenerations] : null;
fixed (DacpGenerationData* pGenDataLocal = genDataLocal)
{
int hrLocal = _legacyImpl8.GetGenerationTable(cGenerations, pGenDataLocal, &pNeededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (pNeeded is not null)
{
Debug.Assert(*pNeeded == pNeededLocal);
}
if (hr == HResults.S_OK && pGenerationData is not null)
{
for (int i = 0; i < (int)pNeededLocal; i++)
{
Debug.Assert(pGenDataLocal[i].start_segment == pGenerationData[i].start_segment);
Debug.Assert(pGenDataLocal[i].allocation_start == pGenerationData[i].allocation_start);
Debug.Assert(pGenDataLocal[i].allocContextPtr == pGenerationData[i].allocContextPtr);
Debug.Assert(pGenDataLocal[i].allocContextLimit == pGenerationData[i].allocContextLimit);
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface8.GetFinalizationFillPointers(uint cFillPointers, ClrDataAddress* pFinalizationFillPointers, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (cFillPointers > 0 && pFinalizationFillPointers is null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
if (gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCHeapData heapData = gc.GetHeapData();
uint numFillPointers = (uint)heapData.FillPointers.Count;
if (pNeeded is not null)
*pNeeded = numFillPointers;
if (cFillPointers < numFillPointers)
{
hr = HResults.S_FALSE;
}
else
{
for (int i = 0; i < (int)numFillPointers; i++)
{
pFinalizationFillPointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
ClrDataAddress[]? fillPointersLocal = cFillPointers > 0 ? new ClrDataAddress[cFillPointers] : null;
fixed (ClrDataAddress* pFillPointersLocal = fillPointersLocal)
{
int hrLocal = _legacyImpl8.GetFinalizationFillPointers(cFillPointers, pFillPointersLocal, &pNeededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (pNeeded is not null)
{
Debug.Assert(*pNeeded == pNeededLocal);
}
if (hr == HResults.S_OK && pFinalizationFillPointers is not null)
{
for (int i = 0; i < (int)pNeededLocal; i++)
{
Debug.Assert(pFillPointersLocal[i] == pFinalizationFillPointers[i]);
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface8.GetGenerationTableSvr(ClrDataAddress heapAddr, uint cGenerations, DacpGenerationData* pGenerationData, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (heapAddr == 0 || (cGenerations > 0 && pGenerationData is null))
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
if (!gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCHeapData heapData = gc.GetHeapData(heapAddr.ToTargetPointer(_target));
uint totalGenerationCount = (uint)heapData.GenerationTable.Count;
if (pNeeded is not null)
*pNeeded = totalGenerationCount;
if (cGenerations < totalGenerationCount)
{
hr = HResults.S_FALSE;
}
else
{
for (int i = 0; i < (int)totalGenerationCount; i++)
{
GCGenerationData gen = heapData.GenerationTable[i];
pGenerationData[i].start_segment = gen.StartSegment.ToClrDataAddress(_target);
pGenerationData[i].allocation_start = gen.AllocationStart.ToClrDataAddress(_target);
pGenerationData[i].allocContextPtr = gen.AllocationContextPointer.ToClrDataAddress(_target);
pGenerationData[i].allocContextLimit = gen.AllocationContextLimit.ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
DacpGenerationData[]? genDataLocal = cGenerations > 0 ? new DacpGenerationData[cGenerations] : null;
fixed (DacpGenerationData* pGenDataLocal = genDataLocal)
{
int hrLocal = _legacyImpl8.GetGenerationTableSvr(heapAddr, cGenerations, pGenDataLocal, &pNeededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (pNeeded is not null)
{
Debug.Assert(*pNeeded == pNeededLocal);
}
if (hr == HResults.S_OK && pGenerationData is not null)
{
for (int i = 0; i < (int)pNeededLocal; i++)
{
Debug.Assert(pGenDataLocal[i].start_segment == pGenerationData[i].start_segment);
Debug.Assert(pGenDataLocal[i].allocation_start == pGenerationData[i].allocation_start);
Debug.Assert(pGenDataLocal[i].allocContextPtr == pGenerationData[i].allocContextPtr);
Debug.Assert(pGenDataLocal[i].allocContextLimit == pGenerationData[i].allocContextLimit);
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface8.GetFinalizationFillPointersSvr(ClrDataAddress heapAddr, uint cFillPointers, ClrDataAddress* pFinalizationFillPointers, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (heapAddr == 0 || (cFillPointers > 0 && pFinalizationFillPointers is null))
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
string[] gcIdentifiers = gc.GetGCIdentifiers();
if (!gcIdentifiers.Contains(GCIdentifiers.Server))
throw Marshal.GetExceptionForHR(HResults.E_FAIL)!;
GCHeapData heapData = gc.GetHeapData(heapAddr.ToTargetPointer(_target));
uint numFillPointers = (uint)heapData.FillPointers.Count;
if (pNeeded is not null)
*pNeeded = numFillPointers;
if (cFillPointers < numFillPointers)
{
hr = HResults.S_FALSE;
}
else
{
for (int i = 0; i < (int)numFillPointers; i++)
{
pFinalizationFillPointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
ClrDataAddress[]? fillPointersLocal = cFillPointers > 0 ? new ClrDataAddress[cFillPointers] : null;
fixed (ClrDataAddress* pFillPointersLocal = fillPointersLocal)
{
int hrLocal = _legacyImpl8.GetFinalizationFillPointersSvr(heapAddr, cFillPointers, pFillPointersLocal, &pNeededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (pNeeded is not null)
{
Debug.Assert(*pNeeded == pNeededLocal);
}
if (hr == HResults.S_OK && pFinalizationFillPointers is not null)
{
int fillPointersToCompare = (int)Math.Min(cFillPointers, pNeededLocal);
for (int i = 0; i < fillPointersToCompare; i++)
{
Debug.Assert(pFillPointersLocal[i] == pFinalizationFillPointers[i]);
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface8.GetAssemblyLoadContext(ClrDataAddress methodTable, ClrDataAddress* assemblyLoadContext)
{
int hr = HResults.S_OK;
try
{
if (methodTable == 0 || assemblyLoadContext == null)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
Contracts.ILoader loaderContract = _target.Contracts.Loader;
Contracts.TypeHandle methodTableHandle = rtsContract.GetTypeHandle(methodTable.ToTargetPointer(_target));
Contracts.ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(rtsContract.GetModule(methodTableHandle));
TargetPointer alc = loaderContract.GetAssemblyLoadContext(moduleHandle);
*assemblyLoadContext = alc.ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl8 is not null)
{
ClrDataAddress assemblyLoadContextLocal;
int hrLocal = _legacyImpl8.GetAssemblyLoadContext(methodTable, &assemblyLoadContextLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*assemblyLoadContext == assemblyLoadContextLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface8
#region ISOSDacInterface9
int ISOSDacInterface9.GetBreakingChangeVersion()
{
int version = _target.ReadGlobal<byte>(Constants.Globals.SOSBreakingChangeVersion);
#if DEBUG
if (_legacyImpl9 is not null)
{
Debug.Assert(version == _legacyImpl9.GetBreakingChangeVersion());
}
#endif
return version;
}
#endregion ISOSDacInterface9
#region ISOSDacInterface10
int ISOSDacInterface10.GetObjectComWrappersData(ClrDataAddress objAddr, ClrDataAddress* rcw, uint count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[]? mowList, uint* pNeeded)
{
int hr = HResults.S_FALSE;
try
{
if (objAddr == 0 || (count > 0 && mowList == null))
throw new ArgumentException();
if (pNeeded != null)
*pNeeded = 0;
if (rcw != null)
*rcw = 0;
Contracts.IComWrappers comWrappersContract = _target.Contracts.ComWrappers;
TargetPointer objPtr = objAddr.ToTargetPointer(_target);
TargetPointer rcwObj = comWrappersContract.GetComWrappersRCWForObject(objPtr);
if (rcwObj != TargetPointer.Null)
{
if (rcw != null)
*rcw = rcwObj.ToClrDataAddress(_target) | _rcwMask;
hr = HResults.S_OK;
}
List<TargetPointer> mows = comWrappersContract.GetMOWs(objPtr, out bool hasMOWTable);
if (hasMOWTable)
hr = HResults.S_OK;
if (mows.Count > 0)
{
if (pNeeded != null)
*pNeeded = (uint)mows.Count;
if (count < (uint)mows.Count)
hr = HResults.S_FALSE;
for (int i = 0; i < (int)count && i < mows.Count; i++)
{
TargetPointer comIdentity = comWrappersContract.GetIdentityForMOW(mows[i]);
mowList![i] = comIdentity.ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl10 is not null)
{
ClrDataAddress rcwLocal = 0;
uint neededLocal = 0;
ClrDataAddress[]? mowListLocal = count > 0 ? new ClrDataAddress[count] : null;
int hrLocal = _legacyImpl10.GetObjectComWrappersData(objAddr, rcw == null ? null : &rcwLocal, count, mowListLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
if (rcw != null)
Debug.Assert(*rcw == rcwLocal);
if (pNeeded != null)
Debug.Assert(*pNeeded == neededLocal);
if (mowList != null)
{
for (int i = 0; i < (int)neededLocal && i < count; i++)
{
Debug.Assert(mowList[i] == mowListLocal![i]);
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface10.IsComWrappersCCW(ClrDataAddress ccw, Interop.BOOL* isComWrappersCCW)
{
int hr = HResults.S_OK;
try
{
Contracts.IComWrappers comWrappersContract = _target.Contracts.ComWrappers;
if (ccw == 0)
throw new ArgumentException();
if (isComWrappersCCW != null)
{
TargetPointer ccwPtr = comWrappersContract.GetManagedObjectWrapperFromCCW(ccw.ToTargetPointer(_target));
*isComWrappersCCW = (ccwPtr != TargetPointer.Null) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
hr = (ccwPtr != TargetPointer.Null) ? HResults.S_OK : HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl10 is not null)
{
Interop.BOOL isComWrappersCCWLocal;
int hrLocal = _legacyImpl10.IsComWrappersCCW(ccw, &isComWrappersCCWLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*isComWrappersCCW == isComWrappersCCWLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface10.GetComWrappersCCWData(ClrDataAddress ccw, ClrDataAddress* managedObject, int* refCount)
{
int hr = HResults.S_OK;
try
{
if (ccw == 0)
throw new ArgumentException();
TargetPointer ccwPtr = ccw.ToTargetPointer(_target);
Contracts.IComWrappers comWrappersContract = _target.Contracts.ComWrappers;
TargetPointer managedObjectPtr = comWrappersContract.GetManagedObjectWrapperFromCCW(ccwPtr);
if (managedObjectPtr == TargetPointer.Null)
throw new ArgumentException();
if (managedObject != null)
{
*managedObject = 0;
*managedObject = comWrappersContract.GetComWrappersObjectFromMOW(managedObjectPtr).ToClrDataAddress(_target);
}
if (refCount != null)
*refCount = (int)comWrappersContract.GetMOWReferenceCount(managedObjectPtr);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl10 is not null)
{
ClrDataAddress managedObjectLocal;
int refCountLocal;
int hrLocal = _legacyImpl10.GetComWrappersCCWData(ccw, &managedObjectLocal, &refCountLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
if (managedObject != null)
Debug.Assert(*managedObject == managedObjectLocal);
if (refCount != null)
Debug.Assert(*refCount == refCountLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface10.IsComWrappersRCW(ClrDataAddress rcw, Interop.BOOL* isComWrappersRCW)
{
int hr = HResults.S_OK;
try
{
Contracts.IComWrappers comWrappersContract = _target.Contracts.ComWrappers;
if (rcw == 0)
throw new ArgumentException();
else if (isComWrappersRCW != null)
{
if ((rcw & _rcwMask) == 0)
*isComWrappersRCW = Interop.BOOL.FALSE;
else
{
TargetPointer rcwPtr = rcw.ToTargetPointer(_target) & ~_rcwMask;
*isComWrappersRCW = comWrappersContract.IsComWrappersRCW(rcwPtr) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE;
}
hr = (*isComWrappersRCW != Interop.BOOL.FALSE) ? HResults.S_OK : HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl10 is not null)
{
Interop.BOOL isComWrappersRCWLocal;
int hrLocal = _legacyImpl10.IsComWrappersRCW(rcw, &isComWrappersRCWLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*isComWrappersRCW == isComWrappersRCWLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface10.GetComWrappersRCWData(ClrDataAddress rcw, ClrDataAddress* identity)
{
int hr = HResults.S_OK;
try
{
Contracts.IComWrappers comWrappersContract = _target.Contracts.ComWrappers;
if (rcw == 0 || identity == null)
throw new ArgumentException();
else if ((rcw & _rcwMask) == 0)
*identity = 0;
else
{
TargetPointer identityPtr = comWrappersContract.GetComWrappersIdentity(rcw.ToTargetPointer(_target) & ~_rcwMask);
*identity = identityPtr.ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl10 is not null)
{
ClrDataAddress identityLocal;
int hrLocal = _legacyImpl10.GetComWrappersRCWData(rcw, &identityLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*identity == identityLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface10
#region ISOSDacInterface11
int ISOSDacInterface11.IsTrackedType(ClrDataAddress objAddr, Interop.BOOL* isTrackedType, Interop.BOOL* hasTaggedMemory)
{
int hr = HResults.S_OK;
try
{
if (objAddr == 0 || isTrackedType == null || hasTaggedMemory == null)
throw new ArgumentException();
*isTrackedType = Interop.BOOL.FALSE;
*hasTaggedMemory = Interop.BOOL.FALSE;
TargetPointer objPtr = objAddr.ToTargetPointer(_target);
Contracts.IObject objectContract = _target.Contracts.Object;
TargetPointer mt = objectContract.GetMethodTableAddress(objPtr);
if (mt == TargetPointer.Null)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
TypeHandle mtHandle = rtsContract.GetTypeHandle(mt);
if (rtsContract.IsTrackedReferenceWithFinalizer(mtHandle))
*isTrackedType = Interop.BOOL.TRUE;
hr = (*isTrackedType == Interop.BOOL.TRUE) ? HResults.S_OK : HResults.S_FALSE;
if (_target.Contracts.TryGetContract<IObjectiveCMarshal>(out IObjectiveCMarshal? objcContract))
{
TargetPointer taggedMemoryPtr = objcContract.GetTaggedMemory(objPtr, out _);
if (taggedMemoryPtr != TargetPointer.Null)
*hasTaggedMemory = Interop.BOOL.TRUE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl11 is not null)
{
Interop.BOOL isTrackedTypeLocal;
Interop.BOOL hasTaggedMemoryLocal;
int hrLocal = _legacyImpl11.IsTrackedType(objAddr, &isTrackedTypeLocal, &hasTaggedMemoryLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*isTrackedType == isTrackedTypeLocal);
Debug.Assert(*hasTaggedMemory == hasTaggedMemoryLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface11.GetTaggedMemory(ClrDataAddress objAddr, ClrDataAddress* taggedMemory, nuint* taggedMemorySizeInBytes)
{
int hr = HResults.S_FALSE;
try
{
if (objAddr == 0 || taggedMemory == null || taggedMemorySizeInBytes == null)
throw new ArgumentException();
*taggedMemory = 0;
*taggedMemorySizeInBytes = 0;
TargetPointer objPtr = objAddr.ToTargetPointer(_target);
if (_target.Contracts.TryGetContract<IObjectiveCMarshal>(out IObjectiveCMarshal? objcContract))
{
TargetPointer taggedMemoryPtr = objcContract.GetTaggedMemory(objPtr, out TargetNUInt taggedMemorySizeNUInt);
if (taggedMemoryPtr != TargetPointer.Null)
{
*taggedMemory = taggedMemoryPtr.ToClrDataAddress(_target);
*taggedMemorySizeInBytes = (nuint)taggedMemorySizeNUInt.Value;
hr = HResults.S_OK;
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl11 is not null)
{
ClrDataAddress taggedMemoryLocal;
nuint taggedMemorySizeInBytesLocal;
int hrLocal = _legacyImpl11.GetTaggedMemory(objAddr, &taggedMemoryLocal, &taggedMemorySizeInBytesLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*taggedMemory == taggedMemoryLocal);
Debug.Assert(*taggedMemorySizeInBytes == taggedMemorySizeInBytesLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface11
#region ISOSDacInterface12
int ISOSDacInterface12.GetGlobalAllocationContext(ClrDataAddress* allocPtr, ClrDataAddress* allocLimit)
{
int hr = HResults.S_OK;
try
{
if (allocPtr == null || allocLimit == null)
throw new ArgumentException();
Contracts.IGC gcContract = _target.Contracts.GC;
gcContract.GetGlobalAllocationContext(out TargetPointer pointer, out TargetPointer limit);
*allocPtr = pointer.ToClrDataAddress(_target);
*allocLimit = limit.ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl12 is not null)
{
ClrDataAddress allocPtrLocal = default;
ClrDataAddress allocLimitLocal = default;
int hrLocal = _legacyImpl12.GetGlobalAllocationContext(&allocPtrLocal, &allocLimitLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*allocPtr == allocPtrLocal);
Debug.Assert(*allocLimit == allocLimitLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface12
#region ISOSDacInterface13
int ISOSDacInterface13.TraverseLoaderHeap(ClrDataAddress loaderHeapAddr, /*LoaderHeapKind*/ int kind, /*VISITHEAP*/ delegate* unmanaged<ulong, nuint, Interop.BOOL, void> pCallback)
{
int hr = TraverseLoaderHeapCore(loaderHeapAddr.ToTargetPointer(_target), pCallback);
#if DEBUG
if (_legacyImpl13 is not null)
{
int cdacCount = DebugTraverseLoaderHeapBlocks.Count;
delegate* unmanaged<ulong, nuint, Interop.BOOL, void> debugCallbackPtr = &TraverseLoaderHeapDebugCallback;
int hrLocal = _legacyImpl13.TraverseLoaderHeap(loaderHeapAddr, kind, debugCallbackPtr);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(DebugTraverseLoaderHeapBlocks.Count == 0,
$"cDAC found {cdacCount} blocks, DAC matched {_debugTraverseLoaderDebugCount}, {DebugTraverseLoaderHeapBlocks.Count} unmatched");
Debug.Assert(_debugTraverseLoaderDebugCount == (uint)cdacCount,
$"cDAC: {cdacCount} blocks, DAC: {_debugTraverseLoaderDebugCount} blocks");
}
}
#endif
return hr;
}
int ISOSDacInterface13.GetDomainLoaderAllocator(ClrDataAddress domainAddress, ClrDataAddress* pLoaderAllocator)
{
int hr = HResults.S_OK;
try
{
if (pLoaderAllocator == null)
throw new ArgumentException();
if (domainAddress == 0)
{
*pLoaderAllocator = 0;
hr = HResults.S_FALSE;
}
else
{
// The one and only app domain uses the global loader allocator
Contracts.ILoader contract = _target.Contracts.Loader;
TargetPointer globalLoaderAllocator = contract.GetGlobalLoaderAllocator();
*pLoaderAllocator = globalLoaderAllocator.ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl13 is not null)
{
ClrDataAddress pLoaderAllocatorLocal;
int hrLocal = _legacyImpl13.GetDomainLoaderAllocator(domainAddress, &pLoaderAllocatorLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*pLoaderAllocator == pLoaderAllocatorLocal);
}
}
#endif
return hr;
}
// Static ANSI string pointers for all known heap names, in the canonical order matching
// LoaderAllocatorLoaderHeapNames in request.cpp. These are process-lifetime allocations,
// equivalent to static const char* literals in C++.
private static readonly (LoaderAllocatorHeapType HeapType, nint AnsiPtr)[] s_heapNameEntries = InitializeHeapNameEntries();
private (LoaderAllocatorHeapType HeapType, nint AnsiPtr)[]? _filteredHeapNameEntries;
private static (LoaderAllocatorHeapType heapType, nint AnsiPtr)[] InitializeHeapNameEntries()
{
// Order must match LoaderAllocatorLoaderHeapNames in src/coreclr/debug/daccess/request.cpp
LoaderAllocatorHeapType[] heapTypes =
[
LoaderAllocatorHeapType.LowFrequencyHeap,
LoaderAllocatorHeapType.HighFrequencyHeap,
LoaderAllocatorHeapType.StaticsHeap,
LoaderAllocatorHeapType.StubHeap,
LoaderAllocatorHeapType.ExecutableHeap,
LoaderAllocatorHeapType.FixupPrecodeHeap,
LoaderAllocatorHeapType.NewStubPrecodeHeap,
LoaderAllocatorHeapType.DynamicHelpersStubHeap,
LoaderAllocatorHeapType.IndcellHeap,
LoaderAllocatorHeapType.CacheEntryHeap
];
var entries = new (LoaderAllocatorHeapType, nint)[heapTypes.Length];
for (int i = 0; i < heapTypes.Length; i++)
entries[i] = (heapTypes[i], Marshal.StringToHGlobalAnsi(heapTypes[i].ToString()));
return entries;
}
// Returns the set of heap name entries for this target, filtered by which
// data descriptor fields exist. This mirrors the DAC's compile-time
// LoaderAllocatorLoaderHeapNames array and ensures a fixed count/ordering
// regardless of per-loader-allocator runtime state (e.g. VCS manager being null).
private (LoaderAllocatorHeapType HeapType, nint AnsiPtr)[] GetFilteredHeapNameEntries()
{
if (_filteredHeapNameEntries is not null)
return _filteredHeapNameEntries;
Target.TypeInfo laType = _target.GetTypeInfo(DataType.LoaderAllocator);
Target.TypeInfo vcsType = _target.GetTypeInfo(DataType.VirtualCallStubManager);
var entries = new List<(LoaderAllocatorHeapType HeapType, nint AnsiPtr)>();
foreach (var entry in s_heapNameEntries)
{
bool include = entry.HeapType is LoaderAllocatorHeapType.IndcellHeap or LoaderAllocatorHeapType.CacheEntryHeap
? vcsType.Fields.ContainsKey(entry.HeapType.ToString())
: laType.Fields.ContainsKey(entry.HeapType.ToString());
if (include)
entries.Add(entry);
}
_filteredHeapNameEntries = entries.ToArray();
return _filteredHeapNameEntries;
}
int ISOSDacInterface13.GetLoaderAllocatorHeapNames(int count, char** ppNames, int* pNeeded)
{
int hr = HResults.S_OK;
try
{
var filteredEntries = GetFilteredHeapNameEntries();
int loaderHeapCount = filteredEntries.Length;
if (pNeeded != null)
*pNeeded = loaderHeapCount;
if (ppNames != null)
{
int fillCount = Math.Min(count, loaderHeapCount);
for (int i = 0; i < fillCount; i++)
ppNames[i] = (char*)filteredEntries[i].AnsiPtr;
}
if (count < loaderHeapCount)
hr = HResults.S_FALSE;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl13 is not null)
{
int pNeededLocal;
_legacyImpl13.GetLoaderAllocatorHeapNames(0, null, &pNeededLocal);
Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC needed: {(pNeeded != null ? *pNeeded : -1)}, DAC needed: {pNeededLocal}");
if (hr >= 0 && ppNames != null && pNeededLocal > 0)
{
char** ppNamesLocal = stackalloc char*[pNeededLocal];
_legacyImpl13.GetLoaderAllocatorHeapNames(pNeededLocal, ppNamesLocal, null);
int compareCount = Math.Min(count, pNeededLocal);
for (int i = 0; i < compareCount; i++)
{
string cdacName = Marshal.PtrToStringAnsi((nint)ppNames[i])!;
string dacName = Marshal.PtrToStringAnsi((nint)ppNamesLocal[i])!;
Debug.Assert(cdacName == dacName, $"HeapName[{i}] - cDAC: {cdacName}, DAC: {dacName}");
}
}
}
#endif
return hr;
}
int ISOSDacInterface13.GetLoaderAllocatorHeaps(ClrDataAddress loaderAllocator, int count, ClrDataAddress* pLoaderHeaps, /*LoaderHeapKind*/ int* pKinds, int* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (loaderAllocator == 0)
throw new ArgumentException("loaderAllocator cannot be zero.", nameof(loaderAllocator));
Contracts.ILoader contract = _target.Contracts.Loader;
IReadOnlyDictionary<LoaderAllocatorHeapType, TargetPointer> heaps = contract.GetLoaderAllocatorHeaps(loaderAllocator.ToTargetPointer(_target));
var filteredEntries = GetFilteredHeapNameEntries();
int loaderHeapCount = filteredEntries.Length;
if (pNeeded != null)
*pNeeded = loaderHeapCount;
if (pLoaderHeaps != null)
{
if (count < loaderHeapCount)
{
throw new ArgumentException($"The count parameter ({count}) is less than the number of loader heaps ({loaderHeapCount}).", nameof(count));
}
for (int i = 0; i < loaderHeapCount; i++)
{
pLoaderHeaps[i] = heaps.TryGetValue(filteredEntries[i].HeapType, out TargetPointer heapAddr)
? heapAddr.ToClrDataAddress(_target)
: 0;
pKinds[i] = 0; // LoaderHeapKindNormal
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl13 is not null)
{
int pNeededLocal;
int hrLocal = _legacyImpl13.GetLoaderAllocatorHeaps(loaderAllocator, 0, null, null, &pNeededLocal);
Debug.Assert(pNeeded is null || *pNeeded == pNeededLocal, $"cDAC needed: {(pNeeded != null ? *pNeeded : -1)}, DAC needed: {pNeededLocal}");
if (hr >= 0 && pLoaderHeaps != null && pNeededLocal > 0)
{
ClrDataAddress* pLoaderHeapsLocal = stackalloc ClrDataAddress[pNeededLocal];
int* pKindsLocal = stackalloc int[pNeededLocal];
hrLocal = _legacyImpl13.GetLoaderAllocatorHeaps(loaderAllocator, pNeededLocal, pLoaderHeapsLocal, pKindsLocal, null);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
if (hrLocal >= 0)
{
for (int i = 0; i < pNeededLocal; i++)
{
Debug.Assert(pLoaderHeaps[i] == pLoaderHeapsLocal[i], $"Heap[{i}] - cDAC: {pLoaderHeaps[i]:x}, DAC: {pLoaderHeapsLocal[i]:x}");
Debug.Assert(pKinds[i] == pKindsLocal[i], $"Kind[{i}] - cDAC: {pKinds[i]}, DAC: {pKindsLocal[i]}");
}
}
}
}
#endif
return hr;
}
int ISOSDacInterface13.GetHandleTableMemoryRegions(DacComNullableByRef<ISOSMemoryEnum> ppEnum)
{
int hr = HResults.S_OK;
try
{
IReadOnlyList<GCMemoryRegionData> regions = _target.Contracts.GC.GetHandleTableMemoryRegions();
ISOSMemoryEnum? legacyMemoryEnum = null;
#if DEBUG
if (_legacyImpl13 is not null)
{
DacComNullableByRef<ISOSMemoryEnum> legacyOut = new(isNullRef: false);
int hrLocal = _legacyImpl13.GetHandleTableMemoryRegions(legacyOut);
Debug.ValidateHResult(hr, hrLocal);
legacyMemoryEnum = legacyOut.Interface;
}
#endif
ppEnum.Interface = new SOSMemoryEnum(_target, regions, legacyMemoryEnum);
}
catch (System.Exception e)
{
hr = e.HResult;
}
return hr;
}
int ISOSDacInterface13.GetGCBookkeepingMemoryRegions(DacComNullableByRef<ISOSMemoryEnum> ppEnum)
{
int hr = HResults.S_OK;
try
{
IReadOnlyList<GCMemoryRegionData> regions = _target.Contracts.GC.GetGCBookkeepingMemoryRegions();
ISOSMemoryEnum? legacyMemoryEnum = null;
#if DEBUG
if (_legacyImpl13 is not null)
{
DacComNullableByRef<ISOSMemoryEnum> legacyOut = new(isNullRef: false);
int hrLocal = _legacyImpl13.GetGCBookkeepingMemoryRegions(legacyOut);
Debug.ValidateHResult(hr, hrLocal);
legacyMemoryEnum = legacyOut.Interface;
}
#endif
ppEnum.Interface = new SOSMemoryEnum(_target, regions, legacyMemoryEnum);
}
catch (System.Exception e)
{
hr = e.HResult;
}
return hr;
}
int ISOSDacInterface13.GetGCFreeRegions(DacComNullableByRef<ISOSMemoryEnum> ppEnum)
{
int hr = HResults.S_OK;
try
{
IReadOnlyList<GCMemoryRegionData> regions = _target.Contracts.GC.GetGCFreeRegions();
ISOSMemoryEnum? legacyMemoryEnum = null;
#if DEBUG
if (_legacyImpl13 is not null)
{
DacComNullableByRef<ISOSMemoryEnum> legacyOut = new(isNullRef: false);
int hrLocal = _legacyImpl13.GetGCFreeRegions(legacyOut);
Debug.ValidateHResult(hr, hrLocal);
legacyMemoryEnum = legacyOut.Interface;
}
#endif
ppEnum.Interface = new SOSMemoryEnum(_target, regions, legacyMemoryEnum);
}
catch (System.Exception e)
{
hr = e.HResult;
}
return hr;
}
int ISOSDacInterface13.LockedFlush()
{
_target.Flush();
// As long as any part of cDAC falls back to the legacy DAC, we need to propagate the Flush call
if (_legacyImpl13 is not null)
return _legacyImpl13.LockedFlush();
return HResults.S_OK;
}
#endregion ISOSDacInterface13
#region ISOSDacInterface14
int ISOSDacInterface14.GetStaticBaseAddress(ClrDataAddress methodTable, ClrDataAddress* nonGCStaticsAddress, ClrDataAddress* GCStaticsAddress)
{
int hr = HResults.S_OK;
try
{
if (nonGCStaticsAddress == null && GCStaticsAddress == null)
throw new NullReferenceException();
if (methodTable == 0)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
Contracts.TypeHandle typeHandle = rtsContract.GetTypeHandle(methodTable.ToTargetPointer(_target));
if (GCStaticsAddress != null)
*GCStaticsAddress = rtsContract.GetGCStaticsBasePointer(typeHandle).ToClrDataAddress(_target);
if (nonGCStaticsAddress != null)
*nonGCStaticsAddress = rtsContract.GetNonGCStaticsBasePointer(typeHandle).ToClrDataAddress(_target);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl14 is not null)
{
ClrDataAddress nonGCStaticsAddressLocal;
ClrDataAddress GCStaticsAddressLocal;
int hrLocal = _legacyImpl14.GetStaticBaseAddress(methodTable, &nonGCStaticsAddressLocal, &GCStaticsAddressLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
if (GCStaticsAddress != null)
Debug.Assert(*GCStaticsAddress == GCStaticsAddressLocal);
if (nonGCStaticsAddress != null)
Debug.Assert(*nonGCStaticsAddress == nonGCStaticsAddressLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface14.GetThreadStaticBaseAddress(ClrDataAddress methodTable, ClrDataAddress thread, ClrDataAddress* nonGCStaticsAddress, ClrDataAddress* GCStaticsAddress)
{
int hr = HResults.S_OK;
try
{
if (nonGCStaticsAddress == null && GCStaticsAddress == null)
throw new NullReferenceException();
if (methodTable == 0 || thread == 0)
throw new ArgumentException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
TargetPointer methodTablePtr = methodTable.ToTargetPointer(_target);
TargetPointer threadPtr = thread.ToTargetPointer(_target);
Contracts.TypeHandle typeHandle = rtsContract.GetTypeHandle(methodTablePtr);
ushort numThreadStaticFields = rtsContract.GetNumThreadStaticFields(typeHandle);
if (numThreadStaticFields == 0)
{
if (GCStaticsAddress != null)
*GCStaticsAddress = 0;
if (nonGCStaticsAddress != null)
*nonGCStaticsAddress = 0;
}
else
{
if (GCStaticsAddress != null)
*GCStaticsAddress = rtsContract.GetGCThreadStaticsBasePointer(typeHandle, threadPtr).ToClrDataAddress(_target);
if (nonGCStaticsAddress != null)
*nonGCStaticsAddress = rtsContract.GetNonGCThreadStaticsBasePointer(typeHandle, threadPtr).ToClrDataAddress(_target);
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl14 is not null)
{
ClrDataAddress nonGCStaticsAddressLocal = default;
ClrDataAddress GCStaticsAddressLocal = default;
ClrDataAddress* nonGCStaticsAddressOrNull = nonGCStaticsAddress != null ? &nonGCStaticsAddressLocal : null;
ClrDataAddress* gcStaticsAddressOrNull = GCStaticsAddress != null ? &GCStaticsAddressLocal : null;
int hrLocal = _legacyImpl14.GetThreadStaticBaseAddress(methodTable, thread, nonGCStaticsAddressOrNull, gcStaticsAddressOrNull);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
if (nonGCStaticsAddress != null)
Debug.Assert(*nonGCStaticsAddress == nonGCStaticsAddressLocal);
if (GCStaticsAddress != null)
Debug.Assert(*GCStaticsAddress == GCStaticsAddressLocal);
}
}
#endif
return hr;
}
int ISOSDacInterface14.GetMethodTableInitializationFlags(ClrDataAddress methodTable, MethodTableInitializationFlags* initializationStatus)
{
int hr = HResults.S_OK;
try
{
if (methodTable == 0)
throw new ArgumentException();
if (initializationStatus == null)
throw new NullReferenceException();
Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem;
Contracts.TypeHandle methodTableHandle = rtsContract.GetTypeHandle(methodTable.ToTargetPointer(_target));
*initializationStatus = (MethodTableInitializationFlags)0;
if (rtsContract.IsClassInited(methodTableHandle))
*initializationStatus = MethodTableInitializationFlags.MethodTableInitialized;
if (rtsContract.IsInitError(methodTableHandle))
*initializationStatus |= MethodTableInitializationFlags.MethodTableInitializationFailed;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl14 is not null)
{
MethodTableInitializationFlags initializationStatusLocal;
int hrLocal = _legacyImpl14.GetMethodTableInitializationFlags(methodTable, &initializationStatusLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*initializationStatus == initializationStatusLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface14
#region ISOSDacInterface15
[GeneratedComClass]
internal sealed unsafe partial class SOSMethodEnum : ISOSMethodEnum
{
private readonly Target _target;
private readonly IRuntimeTypeSystem _rts;
private readonly TypeHandle _methodTable;
private readonly ISOSMethodEnum? _legacyMethodEnum;
private uint _iteratorIndex;
private List<SOSMethodData> _methods = [];
public SOSMethodEnum(Target target, TypeHandle methodTable, ISOSMethodEnum? legacyMethodEnum)
{
_target = target;
_rts = _target.Contracts.RuntimeTypeSystem;
_methodTable = methodTable;
_legacyMethodEnum = legacyMethodEnum;
PopulateMethods();
}
private void PopulateMethods()
{
ushort numVtableSlots = _rts.GetNumVtableSlots(_methodTable);
for (ushort i = 0; i < numVtableSlots; i++)
{
SOSMethodData methodData = default;
TargetPointer mdAddr = TargetPointer.Null;
try
{
mdAddr = _rts.GetMethodDescForSlot(_methodTable, i);
}
catch (System.Exception)
{
// Ignore exceptions reading method data
}
if (mdAddr != TargetPointer.Null)
{
MethodDescHandle mdh = _rts.GetMethodDescHandle(mdAddr);
methodData.MethodDesc = mdAddr.ToClrDataAddress(_target);
TargetPointer mtAddr = _rts.GetMethodTable(mdh);
methodData.DefiningMethodTable = mtAddr.ToClrDataAddress(_target);
TypeHandle typeHandle = _rts.GetTypeHandle(mtAddr);
methodData.DefiningModule = _rts.GetModule(typeHandle).ToClrDataAddress(_target);
methodData.Token = _rts.GetMethodToken(mdh);
}
methodData.Entrypoint = _rts.GetSlot(_methodTable, i).ToClrDataAddress(_target);
methodData.Slot = i;
_methods.Add(methodData);
}
foreach (TargetPointer mdAddr in _rts.GetIntroducedMethodDescs(_methodTable))
{
MethodDescHandle mdh = _rts.GetMethodDescHandle(mdAddr);
ushort slot = _rts.GetSlotNumber(mdh);
if (slot >= numVtableSlots)
{
SOSMethodData methodData = default;
methodData.MethodDesc = mdAddr.ToClrDataAddress(_target);
methodData.Entrypoint = _rts.GetMethodEntryPointIfExists(mdh).ToClrDataAddress(_target);
TargetPointer mtAddr = _rts.GetMethodTable(mdh);
methodData.DefiningMethodTable = mtAddr.ToClrDataAddress(_target);
TypeHandle typeHandle = _rts.GetTypeHandle(mtAddr);
methodData.DefiningModule = _rts.GetModule(typeHandle).ToClrDataAddress(_target);
methodData.Token = _rts.GetMethodToken(mdh);
if (slot == ushort.MaxValue)
methodData.Slot = uint.MaxValue;
else
methodData.Slot = slot;
_methods.Add(methodData);
}
}
}
int ISOSMethodEnum.Next(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] SOSMethodData[] values, uint* pNeeded)
{
int hr = HResults.S_OK;
try
{
if (pNeeded is null)
throw new NullReferenceException();
if (values is null)
throw new NullReferenceException();
uint i = 0;
while (i < count && _iteratorIndex < _methods.Count)
{
values[i++] = _methods[(int)_iteratorIndex++];
}
*pNeeded = i;
hr = i < count ? HResults.S_FALSE : HResults.S_OK;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyMethodEnum is not null)
{
SOSMethodData[] valuesLocal = new SOSMethodData[count];
uint neededLocal;
int hrLocal = _legacyMethodEnum.Next(count, valuesLocal, &neededLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(*pNeeded == neededLocal, $"cDAC: {*pNeeded}, DAC: {neededLocal}");
for (uint i = 0; i < *pNeeded; i++)
{
Debug.Assert(values[i].MethodDesc == valuesLocal[i].MethodDesc, $"cDAC: {values[i].MethodDesc:x}, DAC: {valuesLocal[i].MethodDesc:x}");
Debug.Assert(values[i].DefiningMethodTable == valuesLocal[i].DefiningMethodTable, $"cDAC: {values[i].DefiningMethodTable:x}, DAC: {valuesLocal[i].DefiningMethodTable:x}");
Debug.Assert(values[i].DefiningModule == valuesLocal[i].DefiningModule, $"cDAC: {values[i].DefiningModule:x}, DAC: {valuesLocal[i].DefiningModule:x}");
Debug.Assert(values[i].Token == valuesLocal[i].Token, $"cDAC: {values[i].Token}, DAC: {valuesLocal[i].Token}");
Debug.Assert(values[i].Entrypoint == valuesLocal[i].Entrypoint, $"cDAC: {values[i].Entrypoint:x}, DAC: {valuesLocal[i].Entrypoint:x}");
Debug.Assert(values[i].Slot == valuesLocal[i].Slot, $"cDAC: {values[i].Slot}, DAC: {valuesLocal[i].Slot}");
}
}
}
#endif
return hr;
}
int ISOSEnum.Skip(uint count)
{
_iteratorIndex += count;
#if DEBUG
_legacyMethodEnum?.Skip(count);
#endif
return HResults.S_OK;
}
int ISOSEnum.Reset()
{
_iteratorIndex = 0;
#if DEBUG
_legacyMethodEnum?.Reset();
#endif
return HResults.S_OK;
}
int ISOSEnum.GetCount(uint* pCount)
{
int hr = HResults.S_OK;
try
{
if (pCount == null)
throw new NullReferenceException();
#if DEBUG
if (_legacyMethodEnum is not null)
{
uint countLocal;
_legacyMethodEnum.GetCount(&countLocal);
Debug.Assert(countLocal == (uint)_methods.Count);
}
#endif
*pCount = (uint)_methods.Count;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
}
int ISOSDacInterface15.GetMethodTableSlotEnumerator(ClrDataAddress mt, DacComNullableByRef<ISOSMethodEnum> enumerator)
{
int hr = HResults.S_OK;
try
{
if (mt == 0)
throw new ArgumentException();
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
TypeHandle methodTableHandle = rts.GetTypeHandle(mt.ToTargetPointer(_target));
ISOSMethodEnum? legacyMethodEnum = null;
#if DEBUG
if (_legacyImpl15 is not null)
{
DacComNullableByRef<ISOSMethodEnum> legacyOut = new(isNullRef: false);
int hrLocal = _legacyImpl15.GetMethodTableSlotEnumerator(mt, legacyOut);
Debug.ValidateHResult(hr, hrLocal);
legacyMethodEnum = legacyOut.Interface;
}
#endif
enumerator.Interface = new SOSMethodEnum(_target, methodTableHandle, legacyMethodEnum);
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
return hr;
}
#endregion ISOSDacInterface15
#region ISOSDacInterface16
int ISOSDacInterface16.GetGCDynamicAdaptationMode(int* pDynamicAdaptationMode)
{
int hr = HResults.S_OK;
try
{
if (pDynamicAdaptationMode == null)
throw new ArgumentException();
IGC gc = _target.Contracts.GC;
if (gc.TryGetGCDynamicAdaptationMode(out int mode))
{
*pDynamicAdaptationMode = mode;
}
else
{
*pDynamicAdaptationMode = -1;
hr = HResults.S_FALSE;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacyImpl16 is not null)
{
int dynamicAdaptationModeLocal;
int hrLocal = _legacyImpl16.GetGCDynamicAdaptationMode(&dynamicAdaptationModeLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK || hr == HResults.S_FALSE)
{
Debug.Assert(pDynamicAdaptationMode == null || *pDynamicAdaptationMode == dynamicAdaptationModeLocal);
}
}
#endif
return hr;
}
#endregion ISOSDacInterface16
}
|