diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 6d74b30cc4a346..6070834e1b285d 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -15,9 +15,12 @@ readonly struct ModuleHandle [Flags] enum ModuleFlags { - Tenured = 0x00000001, // Set once we know for sure the Module will not be freed until the appdomain itself exits - EditAndContinue = 0x00000008, // Edit and Continue is enabled for this module - ReflectionEmit = 0x00000040, // Reflection.Emit was used to create this module + Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits + JitOptimizationDisabled = 0x2, // Cached flag: JIT optimizations are disabled + EditAndContinue = 0x8, // Edit and Continue is enabled for this module + ReflectionEmit = 0x40, // Reflection.Emit was used to create this module + ProfDisableOptimizations = 0x80, // Profiler disabled JIT optimizations + EncCapable = 0x200, // Cached flag: module is Edit and Continue capable } [Flags] @@ -90,6 +93,31 @@ TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer); TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer); + +DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle); +void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits); +``` + +The `DebuggerAssemblyControlFlags` enum is defined as: +```csharp +[Flags] +enum DebuggerAssemblyControlFlags : uint +{ + DACF_NONE = 0x00, + DACF_ALLOW_JIT_OPTS = 0x02, + DACF_ENC_ENABLED = 0x08, + DACF_CONTROL_FLAGS_MASK = 0x2E, +} +``` + +The `ClrModifiableAssemblies` enum (from `EEConfig::ModifiableAssemblies`) is defined as: +```csharp +enum ClrModifiableAssemblies : uint +{ + Unset = 0, + None = 1, + Debug = 2, +} ``` ## Version 1 @@ -173,6 +201,7 @@ IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer | `DynamicILBlobTable` | `EntrySize` | Size of each table entry | | `DynamicILBlobTable` | `EntryMethodToken` | Offset of each entry method token from entry address | | `DynamicILBlobTable` | `EntryIL` | Offset of each entry IL from entry address | +| `EEConfig` | `ModifiableAssemblies` | Controls Edit and Continue support (ClrModifiableAssemblies enum) | @@ -181,6 +210,7 @@ IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer | --- | --- | --- | | `AppDomain` | TargetPointer | Pointer to the global AppDomain | | `SystemDomain` | TargetPointer | Pointer to the global SystemDomain | +| `EEConfig` | TargetPointer | Pointer to the global EEConfig (runtime configuration) | ### Contract Constants: @@ -189,6 +219,9 @@ IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer | `ASSEMBLY_NOTIFYFLAGS_PROFILER_NOTIFIED` | uint | Flag in Assembly NotifyFlags indicating the Assembly will notify profilers. | `0x1` | | `DefaultDomainFriendlyName` | string | Friendly name returned when `AppDomain.FriendlyName` is null (matches native `DEFAULT_DOMAIN_FRIENDLY_NAME`) | `"DefaultDomain"` | | `MaxWebcilSections` | ushort | Maximum number of COFF sections supported in a Webcil image (must stay in sync with native `WEBCIL_MAX_SECTIONS`) | `16` | +| `DebuggerInfoMask` | uint | Mask for the debugger info bits within the Module's transient flags | `0x0000FC00` | +| `DebuggerInfoShift` | int | Bit shift for the debugger info bits within the Module's transient flags | `10` | +| `DEBUGGER_ALLOW_JIT_OPTS_PRIV` | uint | Debugger allows JIT optimizations (shifted in transient flags) | `0x00000800` | Contracts used: | Contract Name | @@ -817,6 +850,36 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token) Data.DynamicILBlobEntry blobEntry = shashContract.LookupSHash(shash, token); return /* blob entry IL address */ } + +DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) +{ + uint flags = // read Module::Flags at handle.Address + Flags offset + return (DebuggerAssemblyControlFlags)((flags & DebuggerInfoMask) >> DebuggerInfoShift); +} + +void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) +{ + uint currentFlags = // read Module::Flags at handle.Address + Flags offset + uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; + uint updated = (currentFlags & ~DebuggerInfoMask) | (((uint)newBits & debuggerInfoBitsMask) << DebuggerInfoShift); + + bool jitOptDisabled = (updated & DEBUGGER_ALLOW_JIT_OPTS_PRIV) == 0 || (updated & PROF_DISABLE_OPTIMIZATIONS) != 0; + // Set or clear IS_JIT_OPTIMIZATION_DISABLED accordingly. + + if ((updated & IS_ENC_CAPABLE) != 0) + { + ClrModifiableAssemblies modifiable = // read EEConfig::ModifiableAssemblies from g_pConfig + if (modifiable != None) + { + bool encRequested = (newBits & DACF_ENC_ENABLED) != 0; + bool setEnC = encRequested || (modifiable == Debug && jitOptDisabled); + if (setEnC) + updated |= IS_EDIT_AND_CONTINUE; + } + } + + // Write updated flags back to handle.Address + Flags offset +} ``` ### DacEnumerableHash (EETypeHashTable and InstMethodHashTable) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 7cdb3d4af1a08f..b0bd544472fbf6 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -879,8 +879,6 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::SetCompilerFlags(VMPTR_DomainAsse hr = CORDBG_S_NOT_ALL_BITS_SET; } } - // Settings from the debugger take precedence over all other settings. - dwBits |= DACF_USER_OVERRIDE; // set flags. This will write back to the target pModule->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits); diff --git a/src/coreclr/debug/daccess/task.cpp b/src/coreclr/debug/daccess/task.cpp index 651285ad5e8f3f..c8ed4df807d135 100644 --- a/src/coreclr/debug/daccess/task.cpp +++ b/src/coreclr/debug/daccess/task.cpp @@ -2798,9 +2798,6 @@ ClrDataModule::SetJITCompilerFlags( dwBits |= DACF_ALLOW_JIT_OPTS; } - // Settings from the debugger take precedence over all other settings. - dwBits |= DACF_USER_OVERRIDE; - // set flags. This will write back to the target m_module->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits); diff --git a/src/coreclr/inc/cordbpriv.h b/src/coreclr/inc/cordbpriv.h index 32a85c28b07401..e323937df63ce2 100644 --- a/src/coreclr/inc/cordbpriv.h +++ b/src/coreclr/inc/cordbpriv.h @@ -36,7 +36,6 @@ enum DebuggerControlFlag DBCF_USER_MASK = 0x00FF, DBCF_GENERATE_DEBUG_CODE = 0x0001, - DBCF_ALLOW_JIT_OPT = 0x0008, DBCF_PROFILER_ENABLED = 0x0020, // DBCF_ACTIVATE_REMOTE_DEBUGGING = 0x0040, Deprecated. DO NOT USE @@ -50,15 +49,15 @@ enum DebuggerControlFlag // Flags used to control the debuggable state of modules and // assemblies. // +// [cDAC] [Loader]: Contract depends on DACF_NONE, DACF_ALLOW_JIT_OPTS, DACF_ENC_ENABLED, DACF_CONTROL_FLAGS_MASK. enum DebuggerAssemblyControlFlags { DACF_NONE = 0x00, - DACF_USER_OVERRIDE = 0x01, DACF_ALLOW_JIT_OPTS = 0x02, DACF_OBSOLETE_TRACK_JIT_INFO = 0x04, // obsolete in V2.0, we're always tracking. DACF_ENC_ENABLED = 0x08, DACF_IGNORE_PDBS = 0x20, - DACF_CONTROL_FLAGS_MASK = 0x2F, + DACF_CONTROL_FLAGS_MASK = 0x2E, DACF_PDBS_COPIED = 0x10, DACF_MISC_FLAGS_MASK = 0x10, diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index cb08ac75c78d95..250b039cc1d09a 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -516,6 +516,8 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_dwTransientFlags = m_dwTransientFlags | PROF_DISABLE_OPTIMIZATIONS; } + UpdateJitOptimizationDisabledState(); + m_pJitInlinerTrackingMap = NULL; if (ReJitManager::IsReJITInlineTrackingEnabled()) { @@ -538,6 +540,7 @@ void Module::SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits) ~DEBUGGER_INFO_MASK_PRIV) == 0); SetTransientFlagInterlockedWithMask(newBits << DEBUGGER_INFO_SHIFT_PRIV, DEBUGGER_INFO_MASK_PRIV); + UpdateJitOptimizationDisabledState(); #ifdef DEBUGGING_SUPPORTED if (IsEditAndContinueCapable()) @@ -611,6 +614,7 @@ Module *Module::Create(Assembly *pAssembly, PEAssembly *pPEAssembly, AllocMemTra void* pMemory = pamTracker->Track(pAssembly->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(EditAndContinueModule)))); pModule = new (pMemory) EditAndContinueModule(pAssembly, pPEAssembly); + pModule->SetTransientFlagInterlocked(IS_ENC_CAPABLE); } else #endif // FEATURE_METADATA_UPDATER diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 9d39750334544e..0f12d7e348bdb0 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -618,10 +618,10 @@ class Module : public ModuleBase enum { // These are the values set in m_dwTransientFlags. - // [cDAC] [Loader]: Contract depends on the values of MODULE_IS_TENURED, IS_EDIT_AND_CONTINUE, and IS_REFLECTION_EMIT. + // [cDAC] [Loader]: Contract depends on the values of MODULE_IS_TENURED, IS_EDIT_AND_CONTINUE, IS_REFLECTION_EMIT, IS_JIT_OPTIMIZATION_DISABLED, IS_ENC_CAPABLE, PROF_DISABLE_OPTIMIZATIONS, DEBUGGER_INFO_MASK_PRIV, DEBUGGER_INFO_SHIFT_PRIV. MODULE_IS_TENURED = 0x00000001, // Set once we know for sure the Module will not be freed until the appdomain itself exits - // unused = 0x00000002, + IS_JIT_OPTIMIZATION_DISABLED= 0x00000002, // Cached result: JIT optimizations are disabled for this module (by debugger or profiler) CLASSES_FREED = 0x00000004, IS_EDIT_AND_CONTINUE = 0x00000008, // is EnC Enabled for this module @@ -632,12 +632,14 @@ class Module : public ModuleBase PROF_DISABLE_OPTIMIZATIONS = 0x00000080, // indicates if Profiler disabled JIT optimization event mask was set when loaded PROF_DISABLE_INLINING = 0x00000100, // indicates if Profiler disabled JIT Inlining event mask was set when loaded + IS_ENC_CAPABLE = 0x00000200, // Cached result of IsEditAndContinueCapable() at Module creation + // // Note: The values below must match the ones defined in // cordbpriv.h for DebuggerAssemblyControlFlags when shifted // right DEBUGGER_INFO_SHIFT bits. // - DEBUGGER_USER_OVERRIDE_PRIV = 0x00000400, + // DEBUGGER_USER_OVERRIDE_PRIV was 0x00000400. Deprecated. DEBUGGER_ALLOW_JIT_OPTS_PRIV= 0x00000800, DEBUGGER_TRACK_JIT_INFO_PRIV= 0x00001000, DEBUGGER_ENC_ENABLED_PRIV = 0x00002000, // this is what was attempted to be set. IS_EDIT_AND_CONTINUE is actual result. @@ -651,7 +653,6 @@ class Module : public ModuleBase IS_BEING_UNLOADED = 0x00100000, }; - static_assert(DEBUGGER_USER_OVERRIDE_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_USER_OVERRIDE); static_assert(DEBUGGER_ALLOW_JIT_OPTS_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_ALLOW_JIT_OPTS); static_assert(DEBUGGER_TRACK_JIT_INFO_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_OBSOLETE_TRACK_JIT_INFO); static_assert(DEBUGGER_ENC_ENABLED_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_ENC_ENABLED); @@ -938,27 +939,10 @@ class Module : public ModuleBase BOOL AreJITOptimizationsDisabled() const { - WRAPPER_NO_CONTRACT; + LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; -#ifdef DEBUGGING_SUPPORTED - // check if debugger has disallowed JIT optimizations - auto dwDebuggerBits = GetDebuggerInfoBits(); - if (!CORDebuggerAllowJITOpts(dwDebuggerBits)) - { - return TRUE; - } -#endif // DEBUGGING_SUPPORTED - -#if defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) - // check if profiler had disabled JIT optimizations when module was loaded - if (m_dwTransientFlags & PROF_DISABLE_OPTIMIZATIONS) - { - return TRUE; - } -#endif // defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) - - return FALSE; + return (m_dwTransientFlags & IS_JIT_OPTIMIZATION_DISABLED) != 0; } #ifdef FEATURE_METADATA_UPDATER @@ -976,6 +960,17 @@ class Module : public ModuleBase SetTransientFlagInterlocked(IS_EDIT_AND_CONTINUE); } + // Recompute and cache the IS_JIT_OPTIMIZATION_DISABLED bit from the debugger and profiler source bits. + // Must be called after any change to DEBUGGER_ALLOW_JIT_OPTS_PRIV or PROF_DISABLE_OPTIMIZATIONS. + void UpdateJitOptimizationDisabledState() + { + LIMITED_METHOD_CONTRACT; + + DWORD flags = m_dwTransientFlags; + bool disabled = !(flags & DEBUGGER_ALLOW_JIT_OPTS_PRIV) || (flags & PROF_DISABLE_OPTIMIZATIONS); + SetTransientFlagInterlockedWithMask(disabled ? IS_JIT_OPTIMIZATION_DISABLED : 0, IS_JIT_OPTIMIZATION_DISABLED); + } + public: BOOL IsTenured() { diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 497b2f7f3988c3..dd3da1afa9ee94 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1278,6 +1278,11 @@ CDAC_TYPE_FIELD(AuxiliarySymbolInfo, T_POINTER, Address, offsetof(VMAUXILIARYSYM CDAC_TYPE_FIELD(AuxiliarySymbolInfo, T_POINTER, Name, offsetof(VMAUXILIARYSYMBOLDEF, name)) CDAC_TYPE_END(AuxiliarySymbolInfo) +CDAC_TYPE_BEGIN(EEConfig) +CDAC_TYPE_INDETERMINATE(EEConfig) +CDAC_TYPE_FIELD(EEConfig, T_UINT32, ModifiableAssemblies, cdac_data::ModifiableAssemblies) +CDAC_TYPE_END(EEConfig) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -1328,6 +1333,7 @@ CDAC_GLOBAL_POINTER(SystemDomain, cdac_data::SystemDomainPtr) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) +CDAC_GLOBAL_POINTER(EEConfig, &::g_pConfig) #if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM) CDAC_GLOBAL_POINTER(Debugger, &::g_pDebugger) CDAC_GLOBAL_POINTER(CLRJitAttachState, &::CLRJitAttachState) diff --git a/src/coreclr/vm/eeconfig.h b/src/coreclr/vm/eeconfig.h index 7319b85991c1bd..b21a2018a050f2 100644 --- a/src/coreclr/vm/eeconfig.h +++ b/src/coreclr/vm/eeconfig.h @@ -43,6 +43,7 @@ enum { OPT_BLENDED, OPT_RANDOM, OPT_DEFAULT = OPT_BLENDED }; +// [cDAC] [Loader]: Contract depends on these values. enum ClrModifiableAssemblies { /* modifiable assemblies are implicitly disabled */ MODIFIABLE_ASSM_UNSET = 0, @@ -59,6 +60,7 @@ enum ParseCtl { class EEConfig { + friend struct ::cdac_data; public: static HRESULT Setup(); @@ -715,6 +717,11 @@ class EEConfig { return dwSleepOnExit; } }; +template<> +struct cdac_data +{ + static constexpr size_t ModifiableAssemblies = offsetof(EEConfig, modifiableAssemblies); +}; #ifdef _DEBUG_IMPL diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index c249db40571b90..acae3ae57e7817 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -546,14 +546,8 @@ inline bool CORDebuggerAttached() return (g_CORDebuggerControlFlags & DBCF_ATTACHED) && !IsAtProcessExit(); } -// This only check debugger bits. However JIT optimizations can be disabled by other ways on a module -// In most cases Module::AreJITOptimizationsDisabled() should be the prefered for checking if JIT optimizations -// are disabled for a module (it does check both debugger bits and profiler jit deoptimization flag) #define CORDebuggerAllowJITOpts(dwDebuggerBits) \ - (((dwDebuggerBits) & DACF_ALLOW_JIT_OPTS) \ - || \ - ((g_CORDebuggerControlFlags & DBCF_ALLOW_JIT_OPT) && \ - !((dwDebuggerBits) & DACF_USER_OVERRIDE))) + (((dwDebuggerBits) & DACF_ALLOW_JIT_OPTS) != 0) #define CORDebuggerEnCMode(dwDebuggerBits) \ ((dwDebuggerBits) & DACF_ENC_ENABLED) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 1a3828bb3de62c..0b1a63db75ea96 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -19,9 +19,28 @@ public ModuleHandle(TargetPointer address) [Flags] public enum ModuleFlags { - Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits - EditAndContinue = 0x8, // Edit and Continue is enabled for this module - ReflectionEmit = 0x40, // Reflection.Emit was used to create this module + Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits + JitOptimizationDisabled = 0x2, // Cached flag: JIT optimizations are disabled + EditAndContinue = 0x8, // Edit and Continue is enabled for this module + ReflectionEmit = 0x40, // Reflection.Emit was used to create this module + ProfDisableOptimizations = 0x80, // Profiler disabled JIT optimizations + EncCapable = 0x200, // Cached flag: module is Edit and Continue capable +} + +[Flags] +public enum DebuggerAssemblyControlFlags : uint +{ + DACF_NONE = 0x00, + DACF_ALLOW_JIT_OPTS = 0x02, + DACF_ENC_ENABLED = 0x08, + DACF_CONTROL_FLAGS_MASK = 0x2E, +} + +public enum ClrModifiableAssemblies : uint +{ + Unset = 0, + None = 1, + Debug = 2, } [Flags] @@ -98,6 +117,9 @@ public interface ILoader : IContract TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); TargetPointer GetDynamicIL(ModuleHandle handle, uint token) => throw new NotImplementedException(); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); + + DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException(); + void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) => throw new NotImplementedException(); } public readonly struct Loader : ILoader diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 307956def85147..0b3cf9e833539c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -139,6 +139,7 @@ public enum DataType PatchpointInfo, PortableEntryPoint, VirtualCallStubManager, + EEConfig, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index c2cc5f234141a1..41eccea4c65d96 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -16,6 +16,7 @@ public static class Globals public const string Debugger = nameof(Debugger); public const string CLRJitAttachState = nameof(CLRJitAttachState); public const string MetadataUpdatesApplied = nameof(MetadataUpdatesApplied); + public const string EEConfig = nameof(EEConfig); public const string FeatureCOMInterop = nameof(FeatureCOMInterop); public const string FeatureComWrappers = nameof(FeatureComWrappers); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 27451291e8b0fe..08584f9c097cb0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -23,6 +23,11 @@ private enum ModuleFlags_1 : uint ReflectionEmit = 0x40, // Reflection.Emit was used to create this module } + private const uint DebuggerInfoMask = 0x0000FC00; + private const int DebuggerInfoShift = 10; + + private const uint DEBUGGER_ALLOW_JIT_OPTS_PRIV = 0x00000800; + private enum PEImageFlags : uint { FLAG_MAPPED = 0x01, // the file is mapped/hydrated (vs. the raw disk layout) @@ -376,6 +381,45 @@ ModuleFlags ILoader.GetFlags(ModuleHandle handle) return GetFlags(module); } + DebuggerAssemblyControlFlags ILoader.GetDebuggerInfoBits(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + return (DebuggerAssemblyControlFlags)((module.Flags & DebuggerInfoMask) >> DebuggerInfoShift); + } + + void ILoader.SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + uint currentFlags = module.Flags; + uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; + uint updatedFlags = (currentFlags & ~DebuggerInfoMask) | (((uint)newBits & debuggerInfoBitsMask) << DebuggerInfoShift); + + bool jitOptDisabled = (updatedFlags & DEBUGGER_ALLOW_JIT_OPTS_PRIV) == 0 || (updatedFlags & (uint)ModuleFlags.ProfDisableOptimizations) != 0; + if (jitOptDisabled) + updatedFlags |= (uint)ModuleFlags.JitOptimizationDisabled; + else + updatedFlags &= ~(uint)ModuleFlags.JitOptimizationDisabled; + + if ((updatedFlags & (uint)ModuleFlags.EncCapable) != 0) + { + TargetPointer configPtr = _target.ReadGlobalPointer(Constants.Globals.EEConfig); + Data.EEConfig config = _target.ProcessedData.GetOrAdd(configPtr); + ClrModifiableAssemblies modifiableAssemblies = (ClrModifiableAssemblies)config.ModifiableAssemblies; + + if (modifiableAssemblies != ClrModifiableAssemblies.None) + { + bool encRequested = (newBits & DebuggerAssemblyControlFlags.DACF_ENC_ENABLED) != 0; + bool jitOptsDisabledForEnc = (updatedFlags & (uint)ModuleFlags.JitOptimizationDisabled) != 0; + bool setEnC = encRequested || (modifiableAssemblies == ClrModifiableAssemblies.Debug && jitOptsDisabledForEnc); + + if (setEnC) + updatedFlags |= (uint)ModuleFlags.EditAndContinue; + } + } + + module.WriteFlags(_target, updatedFlags); + } + bool ILoader.IsReadyToRun(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs new file mode 100644 index 00000000000000..dd318f16da0e44 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class EEConfig : IData +{ + static EEConfig IData.Create(Target target, TargetPointer address) + => new EEConfig(target, address); + + public EEConfig(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EEConfig); + + ModifiableAssemblies = target.ReadField(address, type, nameof(ModifiableAssemblies)); + } + + public uint ModifiableAssemblies { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 3871489d556fab..8e16e53b190183 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -14,6 +14,7 @@ public Module(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.Module); + _address = address; Flags = target.ReadField(address, type, nameof(Flags)); Assembly = target.ReadPointerField(address, type, nameof(Assembly)); PEAssembly = target.ReadPointerField(address, type, nameof(PEAssembly)); @@ -38,9 +39,19 @@ public Module(Target target, TargetPointer address) DynamicILBlobTable = target.ReadPointerField(address, type, nameof(DynamicILBlobTable)); } + private readonly TargetPointer _address; + + public void WriteFlags(Target target, uint flags) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Module); + ulong flagsAddr = _address + (ulong)type.Fields[nameof(Flags)].Offset; + target.Write(flagsAddr, flags); + Flags = flags; + } + public TargetPointer Assembly { get; init; } public TargetPointer PEAssembly { get; init; } - public uint Flags { get; init; } + public uint Flags { get; private set; } public TargetPointer Base { get; init; } public TargetPointer LoaderAllocator { get; init; } public TargetPointer DynamicMetadata { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index bfc3c9ba288b88..6831c351847283 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -47,6 +47,8 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega } } + private const uint CORDEBUG_JIT_DEFAULT = 0x1; + private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) @@ -717,5 +719,41 @@ int IXCLRDataModule.GetVersionId(Guid* vid) => _legacyModule is not null ? _legacyModule.GetVersionId(vid) : HResults.E_NOTIMPL; int IXCLRDataModule2.SetJITCompilerFlags(uint flags) - => _legacyModule2 is not null ? _legacyModule2.SetJITCompilerFlags(flags) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if ((flags != CORDEBUG_JIT_DEFAULT) && (flags != CORDEBUG_JIT_DISABLE_OPTIMIZATION)) + throw new ArgumentException(); + + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(_address); + + bool allowJitOpts = (flags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION; + DebuggerAssemblyControlFlags bits = loader.GetDebuggerInfoBits(handle) + & ~(DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + bits &= DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; + + if (allowJitOpts) + { + bits |= DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; + } + + loader.SetDebuggerInfoBits(handle, bits); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyModule2 is not null) + { + int hrLocal = _legacyModule2.SetJITCompilerFlags(flags); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + + return hr; + } } diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index e0cfa884f5aaa3..c415540d2656dd 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -15,11 +15,18 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public unsafe class LoaderTests { + private const uint IsJitOptimizationDisabled = 0x00000002; + private const uint IsEditAndContinue = 0x00000008; + private const uint IsEncCapable = 0x00000200; + private const uint DebuggerAllowJitOptsPriv = 0x00000800; + private const uint DebuggerEncEnabledPriv = 0x00002000; + internal static Dictionary CreateContractTypes(MockLoaderBuilder loader) => new() { [DataType.Module] = TargetTestHelpers.CreateTypeInfo(loader.ModuleLayout), [DataType.Assembly] = TargetTestHelpers.CreateTypeInfo(loader.AssemblyLayout), + [DataType.EEConfig] = TargetTestHelpers.CreateTypeInfo(loader.EEConfigLayout), }; private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action configure) @@ -36,6 +43,21 @@ private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action return target.Contracts.Loader; } + private static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderContractWithTarget( + MockTarget.Architecture arch, + Action configure) + { + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); + + configure(loader, targetBuilder); + + targetBuilder.AddTypes(CreateContractTypes(loader)); + targetBuilder.AddContract(version: 1); + var target = targetBuilder.Build(); + return (target.Contracts.Loader, target); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetPath(MockTarget.Architecture arch) @@ -508,4 +530,185 @@ public void GetILAddr_WebcilV1RvaToOffset(MockTarget.Architecture arch) // RVA in second section: offset = (0x4500 - 0x4000) + 0x2200 = 0x2700 Assert.Equal((TargetPointer)(imageBase + 0x2700u), contract.GetILAddr(peAssemblyAddr, 0x4500)); } + + public static IEnumerable GetDebuggerInfoBitsData() + { + foreach (var arch in new MockTarget.StdArch()) + { + yield return [0u, DebuggerAssemblyControlFlags.DACF_NONE, arch[0]]; + yield return [DebuggerAllowJitOptsPriv, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS, arch[0]]; + yield return [DebuggerEncEnabledPriv, DebuggerAssemblyControlFlags.DACF_ENC_ENABLED, arch[0]]; + } + } + + [Theory] + [MemberData(nameof(GetDebuggerInfoBitsData))] + public void GetDebuggerInfoBits(uint rawFlags, DebuggerAssemblyControlFlags expectedBits, MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + + ILoader contract = CreateLoaderContract(arch, loader => + { + moduleAddr = loader.AddModule(flags: rawFlags).Address; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + DebuggerAssemblyControlFlags actual = contract.GetDebuggerInfoBits(handle); + Assert.Equal(expectedBits, actual); + } + + public static IEnumerable SetDebuggerInfoBitsData() + { + foreach (var arch in new MockTarget.StdArch()) + { + // IS_JIT_OPTIMIZATION_DISABLED is set when DACF_ALLOW_JIT_OPTS is absent + yield return [DebuggerAssemblyControlFlags.DACF_NONE, IsJitOptimizationDisabled, arch[0]]; + yield return [DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS, DebuggerAllowJitOptsPriv, arch[0]]; + yield return [DebuggerAssemblyControlFlags.DACF_ENC_ENABLED, DebuggerEncEnabledPriv | IsJitOptimizationDisabled, arch[0]]; + } + } + + [Theory] + [MemberData(nameof(SetDebuggerInfoBitsData))] + public void SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits, uint expectedRawFlags, MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule().Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + contract.SetDebuggerInfoBits(handle, newBits); + + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.Equal(expectedRawFlags, rawFlags); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_PreservesOtherFlags(MockTarget.Architecture arch) + { + uint initialFlags = (uint)(ModuleFlags.Tenured | ModuleFlags.ReflectionEmit); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule(flags: initialFlags).Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + DebuggerAssemblyControlFlags debuggerBits = DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; + int debuggerInfoShift = 10; + contract.SetDebuggerInfoBits(handle, debuggerBits); + + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.Equal(initialFlags | ((uint)debuggerBits << debuggerInfoShift), rawFlags); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_UpdatesJitOptimizationDisabledState(MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule().Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + // Setting debugger bits without DACF_ALLOW_JIT_OPTS should set IS_JIT_OPTIMIZATION_DISABLED + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); + + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.True((rawFlags & IsJitOptimizationDisabled) != 0, "IS_JIT_OPTIMIZATION_DISABLED should be set when DACF_ALLOW_JIT_OPTS is not set"); + + // Setting debugger bits WITH DACF_ALLOW_JIT_OPTS should clear IS_JIT_OPTIMIZATION_DISABLED + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS); + + rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.True((rawFlags & IsJitOptimizationDisabled) == 0, "IS_JIT_OPTIMIZATION_DISABLED should be cleared when DACF_ALLOW_JIT_OPTS is set"); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_DoesNotEnableEnC(MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule().Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); + + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.True((rawFlags & IsEditAndContinue) == 0, "IS_EDIT_AND_CONTINUE should NOT be set when module is not EnC-capable"); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_EnablesEnC_DisabledJitOpts(MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule(flags: IsEncCapable).Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); + + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when module is EnC-capable, config is Debug, and JIT opts are disabled"); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_EnablesEnC_ExplicitFlag(MockTarget.Architecture arch) + { + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule(flags: IsEncCapable).Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when DACF_ENC_ENABLED is explicitly requested"); + } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs index 98589ae99120b7..b1ad52a8cd197a 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -80,6 +80,12 @@ public ulong FileName set => WritePointerField(FileNameFieldName, value); } + public uint Flags + { + get => ReadUInt32Field(FlagsFieldName); + set => WriteUInt32Field(FlagsFieldName, value); + } + public ulong ReadyToRunInfo { get => ReadPointerField(ReadyToRunInfoFieldName); @@ -113,6 +119,22 @@ public ulong Module } } +internal sealed class MockEEConfig : TypedView +{ + private const string ModifiableAssembliesFieldName = "ModifiableAssemblies"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("EEConfig", architecture) + .AddUInt32Field(ModifiableAssembliesFieldName) + .Build(); + + public uint ModifiableAssemblies + { + get => ReadUInt32Field(ModifiableAssembliesFieldName); + set => WriteUInt32Field(ModifiableAssembliesFieldName, value); + } +} + internal sealed class MockLoaderBuilder { private const ulong DefaultAllocationRangeStart = 0x0001_0000; @@ -121,6 +143,7 @@ internal sealed class MockLoaderBuilder internal MockMemorySpace.Builder Builder { get; } internal Layout ModuleLayout { get; } internal Layout AssemblyLayout { get; } + internal Layout EEConfigLayout { get; } private readonly MockMemorySpace.BumpAllocator _allocator; @@ -138,16 +161,23 @@ public MockLoaderBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong En ModuleLayout = MockLoaderModule.CreateLayout(builder.TargetTestHelpers.Arch); AssemblyLayout = MockLoaderAssembly.CreateLayout(builder.TargetTestHelpers.Arch); + EEConfigLayout = MockEEConfig.CreateLayout(builder.TargetTestHelpers.Arch); } internal MockLoaderModule AddModule( string? path = null, string? fileName = null, string? simpleName = null, - byte[]? simpleNameBytes = null) + byte[]? simpleNameBytes = null, + uint flags = 0) { MockLoaderModule module = ModuleLayout.Create(_allocator.Allocate((ulong)ModuleLayout.Size, "Module")); + if (flags != 0) + { + module.Flags = flags; + } + byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes; if (rawSimpleName is not null) { @@ -170,6 +200,13 @@ internal MockLoaderModule AddModule( return module; } + internal MockEEConfig AddEEConfig(uint modifiableAssemblies) + { + MockEEConfig config = EEConfigLayout.Create(_allocator.Allocate((ulong)EEConfigLayout.Size, "EEConfig")); + config.ModifiableAssemblies = modifiableAssemblies; + return config; + } + private ulong AddNullTerminatedUtf8(ReadOnlySpan bytes, string name) { MockMemorySpace.HeapFragment fragment = _allocator.Allocate((ulong)bytes.Length + 1, name); diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index 8e9fc73072acaf..a63379f384f9e1 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -24,12 +24,14 @@ internal class TestPlaceholderTarget : Target private readonly (string Name, string Value)[] _globalStrings; internal delegate int ReadFromTargetDelegate(ulong address, Span buffer); + internal delegate int WriteToTargetDelegate(ulong address, Span buffer); private readonly ReadFromTargetDelegate _dataReader; + private readonly WriteToTargetDelegate? _dataWriter; private static readonly UTF8Encoding strictUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly UTF8Encoding looseUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false); - public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null) + public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null, WriteToTargetDelegate? writer = null) { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; @@ -37,6 +39,7 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat _dataCache = new DefaultDataCache(this); _typeInfoCache = types ?? []; _dataReader = reader; + _dataWriter = writer; _globals = globals ?? []; _globalStrings = globalStrings ?? []; } @@ -137,12 +140,14 @@ public Builder AddMockContract(Mock mock) where TContract public TestPlaceholderTarget Build() { + var memoryContext = _memBuilder.GetMemoryContext(); var target = new TestPlaceholderTarget( _arch, - _readerOverride ?? _memBuilder.GetMemoryContext().ReadFromTarget, + _readerOverride ?? memoryContext.ReadFromTarget, _types, _globals.ToArray(), - _globalStrings.ToArray()); + _globalStrings.ToArray(), + memoryContext.WriteToTarget); var registry = new TestContractRegistry(); registry.SetTarget(target); @@ -206,7 +211,13 @@ public override void ReadBuffer(ulong address, Span buffer) if (_dataReader(address, buffer) < 0) throw new VirtualReadException($"Failed to read {buffer.Length} bytes at 0x{address:x8}."); } - public override void WriteBuffer(ulong address, Span buffer) => throw new NotImplementedException(); + public override void WriteBuffer(ulong address, Span buffer) + { + if (_dataWriter is null) + throw new NotImplementedException(); + if (_dataWriter(address, buffer) < 0) + throw new InvalidOperationException($"Failed to write {buffer.Length} bytes at 0x{address:x8}."); + } public override string ReadUtf8String(ulong address, bool strict = false) { @@ -325,7 +336,19 @@ public override bool TryRead(ulong address, out T value) return true; } - public override void Write(ulong address, T value) => throw new NotImplementedException(); + public override void Write(ulong address, T value) + { + if (_dataWriter is null) + throw new NotImplementedException(); + Span buffer = stackalloc byte[Unsafe.SizeOf()]; + bool success = IsLittleEndian + ? value.TryWriteLittleEndian(buffer, out int bytesWritten) + : value.TryWriteBigEndian(buffer, out bytesWritten); + + if (!success || bytesWritten != buffer.Length) + throw new InvalidOperationException($"Failed to write {typeof(T)} to buffer."); + WriteBuffer(address, buffer); + } #region subclass reader helpers