diff --git a/docs/design/datacontracts/AuxiliarySymbols.md b/docs/design/datacontracts/AuxiliarySymbols.md new file mode 100644 index 00000000000000..69cc4f7d35a53f --- /dev/null +++ b/docs/design/datacontracts/AuxiliarySymbols.md @@ -0,0 +1,58 @@ +# Contract AuxiliarySymbols + +This contract provides name resolution for helper functions whose executing code +resides at dynamically-determined addresses. + +## APIs of contract + +``` csharp +// Attempts to resolve a code address to a helper function name. +// Returns true if the address matches a known helper, with the name in symbolName. +// Returns false if the address does not match any known helper. +bool TryGetAuxiliarySymbolName(TargetPointer ip, out string symbolName); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `AuxiliarySymbolInfo` | `Address` | Code pointer to the dynamically-located helper function | +| `AuxiliarySymbolInfo` | `Name` | Pointer to a null-terminated char string with the helper name | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `AuxiliarySymbols` | TargetPointer | Pointer to an array of `AuxiliarySymbolInfo` entries | +| `AuxiliarySymbolCount` | TargetPointer | Pointer to the count of populated entries in the array | + +Contracts used: none + +``` csharp +bool TryGetAuxiliarySymbolName(TargetPointer ip, out string? symbolName) +{ + symbolName = null; + + TargetCodePointer codePointer = CodePointerFromAddress(ip); + + TargetPointer helperArray = target.ReadGlobalPointer("AuxiliarySymbols"); + uint count = target.Read(target.ReadGlobalPointer("AuxiliarySymbolCount")); + + uint entrySize = /* AuxiliarySymbolInfo size */; + + for (uint i = 0; i < count; i++) + { + TargetPointer entryAddr = helperArray + (i * entrySize); + TargetCodePointer address = target.ReadCodePointer(entryAddr + /* AuxiliarySymbolInfo::Address offset */); + TargetPointer namePointer = target.ReadPointer(entryAddr + /* AuxiliarySymbolInfo::Name offset */); + + if (address == codePointer && namePointer != TargetPointer.Null) + { + symbolName = target.ReadUtf8String(namePointer); + return true; + } + } + + return false; +} +``` diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 93062186b89fc9..661648e67afe50 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -5295,50 +5295,14 @@ ClrDataAccess::GetFullMethodName( } PCSTR -ClrDataAccess::GetJitHelperName( - IN TADDR address, - IN bool dynamicHelpersOnly /*=false*/ - ) +ClrDataAccess::GetJitHelperName(IN TADDR address) { - const static PCSTR s_rgHelperNames[] = { -#define JITHELPER(code,fn,sig) #code, -#include - }; - static_assert(ARRAY_SIZE(s_rgHelperNames) == CORINFO_HELP_COUNT); - -#ifdef TARGET_UNIX - if (!dynamicHelpersOnly) -#else - if (!dynamicHelpersOnly && g_runtimeLoadedBaseAddress <= address && - address < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize) -#endif // TARGET_UNIX - { - // Read the whole table from the target in one shot for better performance - VMHELPDEF * pTable = static_cast( - PTR_READ(dac_cast(&hlpFuncTable), CORINFO_HELP_COUNT * sizeof(VMHELPDEF))); - - for (int i = 0; i < CORINFO_HELP_COUNT; i++) - { - if (address == pTable[i].pfnHelper) - return s_rgHelperNames[i]; - } - } - - // Check if its a dynamically generated JIT helper - const static CorInfoHelpFunc s_rgDynamicHCallIds[] = { -#define DYNAMICJITHELPER(code, fn, binderId) code, -#define JITHELPER(code, fn, binderId) -#include - }; - - // Read the whole table from the target in one shot for better performance - VMHELPDEF * pDynamicTable = static_cast( - PTR_READ(dac_cast(&hlpDynamicFuncTable), DYNAMIC_CORINFO_HELP_COUNT * sizeof(VMHELPDEF))); - for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++) + PCODE pCode = PINSTRToPCODE(address); + for (unsigned i = 0; i < g_auxiliarySymbolCount; i++) { - if (address == pDynamicTable[d].pfnHelper) + if (pCode == hlpAuxiliarySymbolTable[i].pfnAuxiliarySymbol) { - return s_rgHelperNames[s_rgDynamicHCallIds[d]]; + return hlpAuxiliarySymbolTable[i].name; } } @@ -5565,7 +5529,7 @@ ClrDataAccess::RawGetMethodName( // Do not waste time looking up name for static helper. Debugger can get the actual name from .pdb. PCSTR pHelperName; - pHelperName = GetJitHelperName(TO_TADDR(address), true /* dynamicHelpersOnly */); + pHelperName = GetJitHelperName(TO_TADDR(address)); if (pHelperName != NULL) { if (displacement) diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index e3b08fe128313c..d1c03c7df951f5 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1205,8 +1205,7 @@ class ClrDataAccess Thread* FindClrThreadByTaskId(ULONG64 taskId); HRESULT IsPossibleCodeAddress(IN TADDR address); - PCSTR GetJitHelperName(IN TADDR address, - IN bool dynamicHelpersOnly = false); + PCSTR GetJitHelperName(IN TADDR address); HRESULT GetFullMethodName(IN MethodDesc* methodDesc, IN ULONG32 symbolChars, IN ULONG32* symbolLen, diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 80d4e0d5506cf2..5c59425117ee75 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -92,6 +92,8 @@ DEFINE_DACVAR(PTR_InterpreterCodeManager, ExecutionManager__m_pInterpreterCodeMa DEFINE_DACVAR_NO_DUMP(VMHELPDEF *, dac__hlpFuncTable, ::hlpFuncTable) DEFINE_DACVAR(VMHELPDEF *, dac__hlpDynamicFuncTable, ::hlpDynamicFuncTable) +DEFINE_DACVAR(VMAUXILIARYSYMBOLDEF *, dac__hlpAuxiliarySymbolTable, ::hlpAuxiliarySymbolTable) +DEFINE_DACVAR(DWORD, dac__g_auxiliarySymbolCount, ::g_auxiliarySymbolCount) DEFINE_DACVAR(PTR_StubManager, StubManager__g_pFirstManager, StubManager::g_pFirstManager) DEFINE_DACVAR(PTR_PrecodeStubManager, PrecodeStubManager__g_pManager, PrecodeStubManager::g_pManager) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 3e7993b5dbb078..caeeded4fbb82d 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1240,6 +1240,12 @@ CDAC_TYPE_FIELD(WebcilSectionHeader, /*uint32*/, PointerToRawData, offsetof(Webc CDAC_TYPE_END(WebcilSectionHeader) #endif +CDAC_TYPE_BEGIN(AuxiliarySymbolInfo) +CDAC_TYPE_SIZE(sizeof(VMAUXILIARYSYMBOLDEF)) +CDAC_TYPE_FIELD(AuxiliarySymbolInfo, /*pointer*/, Address, offsetof(VMAUXILIARYSYMBOLDEF, pfnAuxiliarySymbol)) +CDAC_TYPE_FIELD(AuxiliarySymbolInfo, /*pointer*/, Name, offsetof(VMAUXILIARYSYMBOLDEF, name)) +CDAC_TYPE_END(AuxiliarySymbolInfo) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -1409,6 +1415,8 @@ CDAC_GLOBAL(SyncBlockHashCodeMask, uint32, MASK_HASHCODE) CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address) CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) +CDAC_GLOBAL_POINTER(AuxiliarySymbols, &::hlpAuxiliarySymbolTable) +CDAC_GLOBAL_POINTER(AuxiliarySymbolCount, &::g_auxiliarySymbolCount) #if FEATURE_COMINTEROP CDAC_GLOBAL(CCWNumInterfaces, uint32, cdac_data::NumInterfaces) CDAC_GLOBAL(CCWThisMask, nuint, cdac_data::ThisMask) @@ -1426,6 +1434,7 @@ CDAC_GLOBAL(RCWInterfaceCacheSize, uint32, INTERFACE_ENTRY_CACHE_SIZE) // When adding a new subdescriptor, EnumMemDescriptors must be updated appropriately. CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor)) +CDAC_GLOBAL_CONTRACT(AuxiliarySymbols, 1) #if FEATURE_COMINTEROP CDAC_GLOBAL_CONTRACT(BuiltInCOM, 1) #endif // FEATURE_COMINTEROP @@ -1433,6 +1442,7 @@ CDAC_GLOBAL_CONTRACT(CodeVersions, 1) #ifdef FEATURE_COMWRAPPERS CDAC_GLOBAL_CONTRACT(ComWrappers, 1) #endif // FEATURE_COMWRAPPERS +CDAC_GLOBAL_CONTRACT(ConditionalWeakTable, 1) CDAC_GLOBAL_CONTRACT(DacStreams, 1) CDAC_GLOBAL_CONTRACT(DebugInfo, 2) CDAC_GLOBAL_CONTRACT(EcmaMetadata, 1) @@ -1449,10 +1459,9 @@ CDAC_GLOBAL_CONTRACT(RuntimeInfo, 1) CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1) CDAC_GLOBAL_CONTRACT(SHash, 1) CDAC_GLOBAL_CONTRACT(SignatureDecoder, 1) -CDAC_GLOBAL_CONTRACT(ConditionalWeakTable, 1) -CDAC_GLOBAL_CONTRACT(SyncBlock, 1) CDAC_GLOBAL_CONTRACT(StackWalk, 1) CDAC_GLOBAL_CONTRACT(StressLog, 2) +CDAC_GLOBAL_CONTRACT(SyncBlock, 1) CDAC_GLOBAL_CONTRACT(Thread, 1) CDAC_GLOBALS_END() diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index c16e2513f9972e..af477ea52524dc 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2506,6 +2506,24 @@ void _SetJitHelperFunction(DynamicCorInfoHelpFunc ftnNum, void * pFunc) hlpDynamicFuncTable[ftnNum].pfnHelper = (PCODE)pFunc; } +VMAUXILIARYSYMBOLDEF hlpAuxiliarySymbolTable[MAX_AUXILIARY_SYMBOLS]; +DWORD g_auxiliarySymbolCount = 0; + +void SetAuxiliarySymbol(void* pFunc, const char* name) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(g_auxiliarySymbolCount < MAX_AUXILIARY_SYMBOLS); + hlpAuxiliarySymbolTable[g_auxiliarySymbolCount].pfnAuxiliarySymbol = (PCODE)pFunc; + hlpAuxiliarySymbolTable[g_auxiliarySymbolCount].name = name; + g_auxiliarySymbolCount++; +} + PCODE LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index f1af7efa20c416..32a3aa33cec73d 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -81,6 +81,8 @@ // Hence, we add them here. GARY_IMPL(VMHELPDEF, hlpFuncTable, CORINFO_HELP_COUNT); GARY_IMPL(VMHELPDEF, hlpDynamicFuncTable, DYNAMIC_CORINFO_HELP_COUNT); +GARY_IMPL(VMAUXILIARYSYMBOLDEF, hlpAuxiliarySymbolTable, MAX_AUXILIARY_SYMBOLS); +GVAL_IMPL_INIT(DWORD, g_auxiliarySymbolCount, 0); #else // DACCESS_COMPILE diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index ed9d728ee88748..fed6eb751560ff 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -1005,13 +1005,25 @@ struct VMHELPDEF bool IsDynamicHelper(DynamicCorInfoHelpFunc* dynamicFtnNum) const; }; +struct VMAUXILIARYSYMBOLDEF +{ + PCODE pfnAuxiliarySymbol; + PTR_CSTR name; +}; + +#define MAX_AUXILIARY_SYMBOLS 7 + #if defined(DACCESS_COMPILE) GARY_DECL(VMHELPDEF, hlpFuncTable, CORINFO_HELP_COUNT); +GARY_DECL(VMAUXILIARYSYMBOLDEF, hlpAuxiliarySymbolTable, MAX_AUXILIARY_SYMBOLS); +GVAL_DECL(DWORD, g_auxiliarySymbolCount); #else extern "C" const VMHELPDEF hlpFuncTable[CORINFO_HELP_COUNT]; +extern "C" VMAUXILIARYSYMBOLDEF hlpAuxiliarySymbolTable[MAX_AUXILIARY_SYMBOLS]; +extern "C" DWORD g_auxiliarySymbolCount; #ifdef FEATURE_PORTABLE_ENTRYPOINTS extern "C" PCODE hlpFuncEntryPoints[CORINFO_HELP_COUNT]; @@ -1027,6 +1039,7 @@ GARY_DECL(VMHELPDEF, hlpDynamicFuncTable, DYNAMIC_CORINFO_HELP_COUNT); #define SetJitHelperFunction(ftnNum, pFunc) _SetJitHelperFunction(DYNAMIC_##ftnNum, (void*)(pFunc)) void _SetJitHelperFunction(DynamicCorInfoHelpFunc ftnNum, void * pFunc); +void SetAuxiliarySymbol(void* pFunc, const char* name); PCODE LoadDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum); bool HasILBasedDynamicJitHelper(DynamicCorInfoHelpFunc ftnNum); diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index f0cf759e010cbf..ee497a17b0bfe2 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -1082,6 +1082,7 @@ void InitThreadManager() #define X86_WRITE_BARRIER_REGISTER(reg) \ SetJitHelperFunction(CORINFO_HELP_ASSIGN_REF_##reg, GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier##reg)); \ + SetAuxiliarySymbol(GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier##reg), "JIT_WriteBarrier" #reg); \ ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier##reg), W("@WriteBarrier" #reg)); ENUM_X86_WRITE_BARRIER_REGISTERS() @@ -1092,6 +1093,7 @@ void InitThreadManager() JIT_WriteBarrier_Loc = GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier); #endif // TARGET_X86 SetJitHelperFunction(CORINFO_HELP_ASSIGN_REF, GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier)); + SetAuxiliarySymbol(GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier), "JIT_WriteBarrier"); ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier), W("@WriteBarrier")); #if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) @@ -1101,8 +1103,10 @@ void InitThreadManager() #if defined(TARGET_ARM64) || defined(TARGET_ARM) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) SetJitHelperFunction(CORINFO_HELP_CHECKED_ASSIGN_REF, GetWriteBarrierCodeLocation((void*)JIT_CheckedWriteBarrier)); + SetAuxiliarySymbol(GetWriteBarrierCodeLocation((void*)JIT_CheckedWriteBarrier), "JIT_CheckedWriteBarrier"); ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_CheckedWriteBarrier), W("@CheckedWriteBarrier")); SetJitHelperFunction(CORINFO_HELP_ASSIGN_BYREF, GetWriteBarrierCodeLocation((void*)JIT_ByRefWriteBarrier)); + SetAuxiliarySymbol(GetWriteBarrierCodeLocation((void*)JIT_ByRefWriteBarrier), "JIT_ByRefWriteBarrier"); ETW::MethodLog::StubInitialized((ULONGLONG)GetWriteBarrierCodeLocation((void*)JIT_ByRefWriteBarrier), W("@ByRefWriteBarrier")); #endif // TARGET_ARM64 || TARGET_ARM || TARGET_LOONGARCH64 || TARGET_RISCV64 diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 7362458b770241..f47c90249aefa5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -106,6 +106,10 @@ public abstract class ContractRegistry /// Gets an instance of the ConditionalWeakTable contract for the target. /// public virtual IConditionalWeakTable ConditionalWeakTable => GetContract(); + /// + /// Gets an instance of the AuxiliarySymbols contract for the target. + /// + public virtual IAuxiliarySymbols AuxiliarySymbols => GetContract(); public abstract TContract GetContract() where TContract : IContract; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IAuxiliarySymbols.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IAuxiliarySymbols.cs new file mode 100644 index 00000000000000..3f76e84606d0ee --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IAuxiliarySymbols.cs @@ -0,0 +1,18 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IAuxiliarySymbols : IContract +{ + static string IContract.Name { get; } = nameof(AuxiliarySymbols); + bool TryGetAuxiliarySymbolName(TargetPointer ip, [NotNullWhen(true)] out string? symbolName) => throw new NotImplementedException(); +} + +public readonly struct AuxiliarySymbols : IAuxiliarySymbols +{ + // Everything throws NotImplementedException +} 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 804dd066bafa48..45d2f13eb87934 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -161,7 +161,7 @@ public enum DataType InterfaceEntry, ComInterfaceEntry, InternalComInterfaceDispatch, - + AuxiliarySymbolInfo, /* GC Data Types */ 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 53aa3521f6ed20..945bb0d07cc6ac 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -162,6 +162,9 @@ public static class Globals public const string GCHeapFreeableSohSegment = nameof(GCHeapFreeableSohSegment); public const string GCHeapFreeableUohSegment = nameof(GCHeapFreeableUohSegment); public const string GCHeapFreeRegions = nameof(GCHeapFreeRegions); + public const string AuxiliarySymbols = nameof(AuxiliarySymbols); + public const string AuxiliarySymbolCount = nameof(AuxiliarySymbolCount); + } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/AuxiliarySymbolsFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/AuxiliarySymbolsFactory.cs new file mode 100644 index 00000000000000..10faa7c9e7f028 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/AuxiliarySymbolsFactory.cs @@ -0,0 +1,16 @@ +// 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.Contracts; + +public sealed class AuxiliarySymbolsFactory : IContractFactory +{ + IAuxiliarySymbols IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new AuxiliarySymbols_1(target), + _ => default(AuxiliarySymbols), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/AuxiliarySymbols_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/AuxiliarySymbols_1.cs new file mode 100644 index 00000000000000..a84f6f3f800cfd --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/AuxiliarySymbols_1.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct AuxiliarySymbols_1 : IAuxiliarySymbols +{ + private readonly Target _target; + + internal AuxiliarySymbols_1(Target target) + { + _target = target; + } + + bool IAuxiliarySymbols.TryGetAuxiliarySymbolName(TargetPointer ip, [NotNullWhen(true)] out string? symbolName) + { + symbolName = null; + + TargetCodePointer codePointer = CodePointerUtils.CodePointerFromAddress(ip, _target); + + TargetPointer helperArrayPtr = _target.ReadGlobalPointer(Constants.Globals.AuxiliarySymbols); + uint helperCount = _target.Read(_target.ReadGlobalPointer(Constants.Globals.AuxiliarySymbolCount)); + + Target.TypeInfo typeInfo = _target.GetTypeInfo(DataType.AuxiliarySymbolInfo); + uint entrySize = typeInfo.Size!.Value; + + for (uint i = 0; i < helperCount; i++) + { + TargetPointer entryAddr = helperArrayPtr + (ulong)(i * entrySize); + Data.AuxiliarySymbolInfo entry = _target.ProcessedData.GetOrAdd(entryAddr); + + if (entry.Address == codePointer) + { + if (entry.Name != TargetPointer.Null) + { + symbolName = _target.ReadUtf8String(entry.Name); + return true; + } + + return false; + } + } + + return false; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AuxiliarySymbolInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AuxiliarySymbolInfo.cs new file mode 100644 index 00000000000000..77f0fb9054d499 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AuxiliarySymbolInfo.cs @@ -0,0 +1,21 @@ +// 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 AuxiliarySymbolInfo : IData +{ + static AuxiliarySymbolInfo IData.Create(Target target, TargetPointer address) + => new AuxiliarySymbolInfo(target, address); + + public AuxiliarySymbolInfo(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.AuxiliarySymbolInfo); + + Address = target.ReadCodePointer(address + (ulong)type.Fields[nameof(Address)].Offset); + Name = target.ReadPointer(address + (ulong)type.Fields[nameof(Name)].Offset); + } + + public TargetCodePointer Address { get; init; } + public TargetPointer Name { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs index 51ae00d6d1b962..6c044447446cdd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Text; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -22,4 +23,19 @@ public static unsafe void CopyStringToBuffer(char* stringBuf, uint bufferSize, u target[nullTerminatorLocation] = '\0'; } } + + public static unsafe void CopyUtf8StringToBuffer(byte* stringBuf, uint bufferSize, uint* neededBufferSize, string str) + { + int byteCount = Encoding.UTF8.GetByteCount(str); + if (neededBufferSize is not null) + *neededBufferSize = checked((uint)(byteCount + 1)); + + if (stringBuf is not null && bufferSize > 0) + { + int maxBytes = Math.Min(byteCount, (int)bufferSize - 1); + Span target = new Span(stringBuf, checked(maxBytes)); + Encoding.UTF8.GetEncoder().Convert(str.AsSpan(), target, true, out _, out int bytesWritten, out _); + stringBuf[bytesWritten] = (byte)'\0'; + } + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 632f46f7ffb35a..adf920f3160154 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2031,7 +2031,45 @@ int ISOSDacInterface.GetILForModule(ClrDataAddress moduleAddr, int rva, ClrDataA return hr; } int ISOSDacInterface.GetJitHelperFunctionName(ClrDataAddress ip, uint count, byte* name, uint* pNeeded) - => _legacyImpl is not null ? _legacyImpl.GetJitHelperFunctionName(ip, count, name, pNeeded) : HResults.E_NOTIMPL; + { + 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(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; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index bc4766f0e019a5..3a92a27eea6d89 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -50,6 +50,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(ISyncBlock)] = new SyncBlockFactory(), [typeof(IBuiltInCOM)] = new BuiltInCOMFactory(), [typeof(IConditionalWeakTable)] = new ConditionalWeakTableFactory(), + [typeof(IAuxiliarySymbols)] = new AuxiliarySymbolsFactory(), }; foreach (IContractFactory factory in additionalFactories) diff --git a/src/native/managed/cdac/tests/AuxiliarySymbolsTests.cs b/src/native/managed/cdac/tests/AuxiliarySymbolsTests.cs new file mode 100644 index 00000000000000..84c744a50b3843 --- /dev/null +++ b/src/native/managed/cdac/tests/AuxiliarySymbolsTests.cs @@ -0,0 +1,142 @@ +// 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.Text; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class AuxiliarySymbolsTests +{ + private static readonly MockDescriptors.TypeFields AuxiliarySymbolInfoFields = new() + { + DataType = DataType.AuxiliarySymbolInfo, + Fields = + [ + new(nameof(Data.AuxiliarySymbolInfo.Address), DataType.pointer), + new(nameof(Data.AuxiliarySymbolInfo.Name), DataType.pointer), + ] + }; + + private static Target CreateTarget( + MockTarget.Architecture arch, + (ulong Address, string Name)[] helpers) + { + TargetTestHelpers targetTestHelpers = new(arch); + MockMemorySpace.Builder builder = new(targetTestHelpers); + + Dictionary types = + MockDescriptors.GetTypesForTypeFields(targetTestHelpers, [AuxiliarySymbolInfoFields]); + uint entrySize = types[DataType.AuxiliarySymbolInfo].Size!.Value; + + MockMemorySpace.BumpAllocator allocator = builder.CreateAllocator(0x1000_0000, 0x2000_0000); + + // Allocate the array (only if non-empty) + ulong arrayAddress = 0; + if (helpers.Length > 0) + { + MockMemorySpace.HeapFragment arrayFragment = allocator.Allocate(entrySize * (ulong)helpers.Length, "AuxiliarySymbolInfoArray"); + + // Write each entry + Target.TypeInfo typeInfo = types[DataType.AuxiliarySymbolInfo]; + for (int i = 0; i < helpers.Length; i++) + { + int addressOffset = typeInfo.Fields[nameof(Data.AuxiliarySymbolInfo.Address)].Offset; + int nameOffset = typeInfo.Fields[nameof(Data.AuxiliarySymbolInfo.Name)].Offset; + Span entryData = arrayFragment.Data.AsSpan((int)(i * entrySize), (int)entrySize); + + // Write the code pointer address + targetTestHelpers.WritePointer(entryData.Slice(addressOffset), helpers[i].Address); + + // Allocate and write the UTF-8 name string + byte[] nameBytes = Encoding.UTF8.GetBytes(helpers[i].Name + '\0'); + MockMemorySpace.HeapFragment nameFragment = allocator.Allocate((ulong)nameBytes.Length, $"Name_{helpers[i].Name}"); + nameBytes.CopyTo(nameFragment.Data.AsSpan()); + builder.AddHeapFragment(nameFragment); + + targetTestHelpers.WritePointer(entryData.Slice(nameOffset), nameFragment.Address); + } + builder.AddHeapFragment(arrayFragment); + arrayAddress = arrayFragment.Address; + } + + // Allocate global for the count + MockMemorySpace.HeapFragment countFragment = allocator.Allocate(sizeof(uint), "HelperCount"); + targetTestHelpers.Write(countFragment.Data, (uint)helpers.Length); + builder.AddHeapFragment(countFragment); + + (string Name, ulong Value)[] globals = + [ + (Constants.Globals.AuxiliarySymbols, arrayAddress), + (Constants.Globals.AuxiliarySymbolCount, countFragment.Address), + ]; + + var target = new TestPlaceholderTarget(arch, builder.GetMemoryContext().ReadFromTarget, types, globals); + + Mock platformMetadata = new(); + platformMetadata.Setup(p => p.GetCodePointerFlags()).Returns(default(CodePointerFlags)); + + IContractFactory factory = new AuxiliarySymbolsFactory(); + Mock reg = new(); + reg.SetupGet(c => c.PlatformMetadata).Returns(platformMetadata.Object); + reg.SetupGet(c => c.AuxiliarySymbols).Returns(() => factory.CreateContract(target, 1)); + target.SetContracts(reg.Object); + + return target; + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetAuxiliarySymbolName_MatchFound_ReturnsTrue(MockTarget.Architecture arch) + { + ulong writeBarrierAddr = 0x7FFF_0100; + ulong checkedBarrierAddr = 0x7FFF_0200; + + var target = CreateTarget(arch, + [ + (writeBarrierAddr, "@WriteBarrier"), + (checkedBarrierAddr, "@CheckedWriteBarrier"), + ]); + + bool found = target.Contracts.AuxiliarySymbols.TryGetAuxiliarySymbolName( + new TargetPointer(writeBarrierAddr), out string? name); + + Assert.True(found); + Assert.Equal("@WriteBarrier", name); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetAuxiliarySymbolName_NoMatch_ReturnsFalse(MockTarget.Architecture arch) + { + ulong writeBarrierAddr = 0x7FFF_0100; + + var target = CreateTarget(arch, + [ + (writeBarrierAddr, "@WriteBarrier"), + ]); + + bool found = target.Contracts.AuxiliarySymbols.TryGetAuxiliarySymbolName( + new TargetPointer(0xDEAD_BEEF), out string? name); + + Assert.False(found); + Assert.Null(name); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetAuxiliarySymbolName_EmptyArray_ReturnsFalse(MockTarget.Architecture arch) + { + var target = CreateTarget(arch, []); + + bool found = target.Contracts.AuxiliarySymbols.TryGetAuxiliarySymbolName( + new TargetPointer(0x7FFF_0100), out string? name); + + Assert.False(found); + Assert.Null(name); + } +}