From e361bfd02b322406a8c4424e5e468fbdac9ada6c Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 12:30:52 +0100 Subject: [PATCH 01/10] Add source info to perfmap/jitdump * Add PerfMapSymbolReader and GetInfoForMethod in System.Private.CoreLib to read PDB/embedded PDB sequence points for perfmap/jitdump. * Retarget the native delegate creation to System.Diagnostics.StackTrace from SOS.NETCore * Add JIT_CODE_DEBUG_INFO records using managed debug info, gated behind config variable --- .../System/Diagnostics/StackTrace.CoreCLR.cs | 54 +++ src/coreclr/dlls/mscoree/exports.cpp | 16 +- src/coreclr/inc/clrconfigvalues.h | 1 + src/coreclr/pal/inc/pal.h | 2 +- src/coreclr/pal/src/misc/perfjitdump.cpp | 61 ++- src/coreclr/vm/gdbjit.cpp | 13 +- src/coreclr/vm/gdbjithelpers.h | 2 +- src/coreclr/vm/perfmap.cpp | 346 +++++++++++++++++- src/coreclr/vm/perfmap.h | 1 + .../ILLink.Descriptors.LibraryBuild.xml | 2 +- .../src/System.Diagnostics.StackTrace.csproj | 1 + .../System/Diagnostics/PerfMapSymbolReader.cs | 152 ++++++++ 12 files changed, 619 insertions(+), 32 deletions(-) create mode 100644 src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs index 5d4bc6008e6400..b87748f2ef7acb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs @@ -1,6 +1,7 @@ // 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; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,12 +10,65 @@ namespace System.Diagnostics { public partial class StackTrace { + private const string PerfMapSymbolReaderTypeName = "System.Diagnostics.PerfMapSymbolReader, System.Diagnostics.StackTrace, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct PerfMapSequencePointInfoNative + { + public int lineNumber; + public int ilOffset; + public char* fileName; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct PerfMapLocalVarInfoNative + { + public int startOffset; + public int endOffset; + public char* name; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct PerfMapMethodDebugInfoNative + { + public PerfMapSequencePointInfoNative* points; + public int size; + public PerfMapLocalVarInfoNative* locals; + public int localsSize; + } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "StackTrace_GetStackFramesInternal")] private static partial void GetStackFramesInternal(ObjectHandleOnStack sfh, [MarshalAs(UnmanagedType.Bool)] bool fNeedFileInfo, ObjectHandleOnStack e); + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetInfoForMethod")] + private static extern unsafe bool GetPerfMapInfoForMethodFromReader( + [UnsafeAccessorType(PerfMapSymbolReaderTypeName)] object? perfMapSymbolReader, + [MarshalAs(UnmanagedType.LPUTF8Str)] string assemblyPath, + int methodToken, + IntPtr points, + int size); + + [DynamicDependency(nameof(GetInfoForMethod))] internal static void GetStackFramesInternal(StackFrameHelper sfh, bool fNeedFileInfo, Exception? e) => GetStackFramesInternal(ObjectHandleOnStack.Create(ref sfh), fNeedFileInfo, ObjectHandleOnStack.Create(ref e)); + [UnmanagedCallersOnly] + internal static unsafe int GetInfoForMethod(byte* assemblyPath, uint methodToken, PerfMapMethodDebugInfoNative* methodDebugInfo) + { + if (assemblyPath == null || methodDebugInfo == null || methodDebugInfo->points == null || methodDebugInfo->size <= 0) + { + return 0; + } + + string? assemblyPathString = Marshal.PtrToStringUTF8((IntPtr)assemblyPath); + if (string.IsNullOrEmpty(assemblyPathString)) + { + return 0; + } + + return GetPerfMapInfoForMethodFromReader(null, assemblyPathString, unchecked((int)methodToken), (IntPtr)methodDebugInfo->points, methodDebugInfo->size) ? 1 : 0; + } + internal static int CalculateFramesToSkip(StackFrameHelper StackF, int iNumFrames) { int iRetVal = 0; diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index 053993ad1918a3..3975b3fab1bda0 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -15,9 +15,9 @@ #include #include #include "../../vm/ceemain.h" -#ifdef FEATURE_GDBJIT +#if defined(TARGET_UNIX) && (defined(FEATURE_GDBJIT) || defined(FEATURE_PERFMAP)) #include "../../vm/gdbjithelpers.h" -#endif // FEATURE_GDBJIT +#endif // TARGET_UNIX && (FEATURE_GDBJIT || FEATURE_PERFMAP) #include "bundle.h" #include "pinvokeoverride.h" #include @@ -207,10 +207,10 @@ int coreclr_set_error_writer(coreclr_error_writer_callback_fn error_writer) return S_OK; } -#ifdef FEATURE_GDBJIT +#if defined(TARGET_UNIX) && (defined(FEATURE_GDBJIT) || defined(FEATURE_PERFMAP)) GetInfoForMethodDelegate getInfoForMethodDelegate = NULL; extern "C" int coreclr_create_delegate(void*, unsigned int, const char*, const char*, const char*, void**); -#endif //FEATURE_GDBJIT +#endif // TARGET_UNIX && (FEATURE_GDBJIT || FEATURE_PERFMAP) // // Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain @@ -328,12 +328,12 @@ int coreclr_initialize( { host.SuppressRelease(); *hostHandle = host; -#ifdef FEATURE_GDBJIT +#if defined(TARGET_UNIX) && (defined(FEATURE_GDBJIT) || defined(FEATURE_PERFMAP)) HRESULT createDelegateResult; createDelegateResult = coreclr_create_delegate(*hostHandle, *domainId, - "SOS.NETCore", - "SOS.SymbolReader", + "System.Private.CoreLib", + "System.Diagnostics.StackTrace", "GetInfoForMethod", (void**)&getInfoForMethodDelegate); @@ -341,7 +341,7 @@ int coreclr_initialize( if (!SUCCEEDED(createDelegateResult)) { fprintf(stderr, - "Can't create delegate for 'SOS.SymbolReader.GetInfoForMethod' " + "Can't create delegate for 'System.Diagnostics.StackTrace.GetInfoForMethod' " "method - status: 0x%08x\n", createDelegateResult); } #endif // _DEBUG diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 0d606d21dac3ed..0e37d100711d9f 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -435,6 +435,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapEnabled, W("PerfMapEnabled"), 0, "This RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapIgnoreSignal, W("PerfMapIgnoreSignal"), 0, "When perf map is enabled, this option will configure the specified signal to be accepted and ignored as a marker in the perf logs. It is disabled by default") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapShowOptimizationTiers, W("PerfMapShowOptimizationTiers"), 1, "Shows optimization tiers in the perf map for methods, as part of the symbol name. Useful for seeing separate stack frames for different optimization tiers of each method.") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapStubGranularity, W("PerfMapStubGranularity"), 0, "Report stubs with varying amounts of granularity (low bit being zero indicates attempt to group all stubs of a type together) (second lowest bit being non-zero records stubs at individual allocation sites, which is more expensive, but also more accurate).") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapEmitDebugInfo, W("PerfMapEmitDebugInfo"), 0, "When jitdump output is enabled, emit JIT_CODE_DEBUG_INFO records for JIT-compiled managed methods when source information is available.") #endif RETAIL_CONFIG_STRING_INFO(EXTERNAL_StartupDelayMS, W("StartupDelayMS"), "") diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 9110ec5e4391b2..f7f271c7b2f141 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -378,7 +378,7 @@ PALIMPORT int PALAPI // Log a method to the jitdump file. -PAL_PerfJitDump_LogMethod(void* pCode, size_t codeSize, const char* symbol, void* debugInfo, void* unwindInfo, bool reportCodeBlock); +PAL_PerfJitDump_LogMethod(void* pCode, size_t codeSize, const char* symbol, const void* debugInfo, size_t debugInfoSize, const void* unwindInfo, size_t unwindInfoSize, bool reportCodeBlock); PALIMPORT int diff --git a/src/coreclr/pal/src/misc/perfjitdump.cpp b/src/coreclr/pal/src/misc/perfjitdump.cpp index 3b45b8c8856d62..ee72148ded15cb 100644 --- a/src/coreclr/pal/src/misc/perfjitdump.cpp +++ b/src/coreclr/pal/src/misc/perfjitdump.cpp @@ -60,12 +60,13 @@ namespace #elif defined(HOST_S390X) ELF_MACHINE = EM_S390, #elif defined(HOST_POWERPC64) - ELF_MACHINE = EM_PPC64, + ELF_MACHINE = EM_PPC64, #else #error ELF_MACHINE unsupported for target #endif JIT_CODE_LOAD = 0, + JIT_CODE_DEBUG_INFO = 2, }; static bool UseArchTimeStamp() @@ -146,6 +147,17 @@ namespace // Null terminated name // Optional native code }; + + struct JitCodeDebugInfoRecord + { + JitCodeDebugInfoRecord() + { + header.id = JIT_CODE_DEBUG_INFO; + header.timestamp = GetTimeStampNS(); + } + + RecordHeader header; + }; }; struct PerfJitDumpState @@ -247,33 +259,52 @@ struct PerfJitDumpState return 0; } - int LogMethod(void* pCode, size_t codeSize, const char* symbol, void* debugInfo, void* unwindInfo, bool reportCodeBlock) + int LogMethod(void* pCode, size_t codeSize, const char* symbol, const void* debugInfo, size_t debugInfoSize, const void* unwindInfo, size_t unwindInfoSize, bool reportCodeBlock) { int result = 0; + (void)unwindInfo; + (void)unwindInfoSize; + if (enabled) { size_t symbolLen = strlen(symbol); JitCodeLoadRecord record; + JitCodeDebugInfoRecord debugRecord; size_t reportedCodeSize = reportCodeBlock ? codeSize : 0; - size_t bytesRemaining = sizeof(JitCodeLoadRecord) + symbolLen + 1 + reportedCodeSize; + size_t bytesRemaining = + (debugInfo != nullptr ? sizeof(JitCodeDebugInfoRecord) + debugInfoSize : 0) + + sizeof(JitCodeLoadRecord) + + symbolLen + + 1 + + reportedCodeSize; record.header.timestamp = GetTimeStampNS(); record.vma = (uint64_t) pCode; record.code_addr = (uint64_t) pCode; - record.code_size = codeSize; - record.header.total_size = bytesRemaining; + record.code_size = reportedCodeSize; + record.header.total_size = sizeof(JitCodeLoadRecord) + symbolLen + 1 + reportedCodeSize; + + debugRecord.header.timestamp = record.header.timestamp; + debugRecord.header.total_size = sizeof(JitCodeDebugInfoRecord) + debugInfoSize; + + iovec items[5]; + size_t itemsCount = 0; + + if (debugInfo != nullptr) + { + items[itemsCount++] = { &debugRecord, sizeof(JitCodeDebugInfoRecord) }; + items[itemsCount++] = { const_cast(debugInfo), debugInfoSize }; + } + + // TODO: insert unwindInfo record items here. - iovec items[] = { - // ToDo insert debugInfo and unwindInfo record items immediately before the JitCodeLoadRecord. - { &record, sizeof(JitCodeLoadRecord) }, - { (void *)symbol, symbolLen + 1 }, - { pCode, reportedCodeSize }, - }; - size_t itemsCount = sizeof(items) / sizeof(items[0]); + items[itemsCount++] = { &record, sizeof(JitCodeLoadRecord) }; + items[itemsCount++] = { (void *)symbol, symbolLen + 1 }; + items[itemsCount++] = { pCode, reportedCodeSize }; size_t itemsWritten = 0; @@ -389,9 +420,9 @@ PAL_PerfJitDump_IsStarted() int PALAPI -PAL_PerfJitDump_LogMethod(void* pCode, size_t codeSize, const char* symbol, void* debugInfo, void* unwindInfo, bool reportCodeBlock) +PAL_PerfJitDump_LogMethod(void* pCode, size_t codeSize, const char* symbol, const void* debugInfo, size_t debugInfoSize, const void* unwindInfo, size_t unwindInfoSize, bool reportCodeBlock) { - return GetState().LogMethod(pCode, codeSize, symbol, debugInfo, unwindInfo, reportCodeBlock); + return GetState().LogMethod(pCode, codeSize, symbol, debugInfo, debugInfoSize, unwindInfo, unwindInfoSize, reportCodeBlock); } int @@ -419,7 +450,7 @@ PAL_PerfJitDump_IsStarted() int PALAPI -PAL_PerfJitDump_LogMethod(void* pCode, size_t codeSize, const char* symbol, void* debugInfo, void* unwindInfo, bool reportCodeBlock) +PAL_PerfJitDump_LogMethod(void* pCode, size_t codeSize, const char* symbol, const void* debugInfo, size_t debugInfoSize, const void* unwindInfo, size_t unwindInfoSize, bool reportCodeBlock) { return 0; } diff --git a/src/coreclr/vm/gdbjit.cpp b/src/coreclr/vm/gdbjit.cpp index 847c19548e6dea..ae66afcac25350 100644 --- a/src/coreclr/vm/gdbjit.cpp +++ b/src/coreclr/vm/gdbjit.cpp @@ -498,12 +498,18 @@ GetDebugInfoFromPDB(MethodDesc* methodDescPtr, unsigned int &symInfoLen, LocalsInfo &locals) { + // Guard against re-entrancy + static thread_local int t_gdbJitDebugInfoCallbackDepth = 0; + NewArrayHolder map; ULONG32 numMap; if (!getInfoForMethodDelegate) return E_FAIL; + if (t_gdbJitDebugInfoCallbackDepth != 0) + return E_FAIL; + if (GetMethodNativeMap(methodDescPtr, &numMap, map, &locals.countVars, &locals.vars) != S_OK) return E_FAIL; @@ -516,8 +522,13 @@ GetDebugInfoFromPDB(MethodDesc* methodDescPtr, MethodDebugInfo methodDebugInfo(numMap, locals.countVars); - if (getInfoForMethodDelegate(szModName, methodDescPtr->GetMemberDef(), methodDebugInfo) == FALSE) + t_gdbJitDebugInfoCallbackDepth = 1; + if (getInfoForMethodDelegate(szModName, methodDescPtr->GetMemberDef(), &methodDebugInfo) == 0) + { + t_gdbJitDebugInfoCallbackDepth = 0; return E_FAIL; + } + t_gdbJitDebugInfoCallbackDepth = 0; symInfoLen = numMap; symInfo = new SymbolsInfo[numMap]; diff --git a/src/coreclr/vm/gdbjithelpers.h b/src/coreclr/vm/gdbjithelpers.h index b2e965f11fe199..c01e85eddd9568 100644 --- a/src/coreclr/vm/gdbjithelpers.h +++ b/src/coreclr/vm/gdbjithelpers.h @@ -36,7 +36,7 @@ struct MethodDebugInfo ~MethodDebugInfo(); }; -typedef BOOL (CALLBACK *GetInfoForMethodDelegate)(const char*, unsigned int, MethodDebugInfo& methodDebugInfo); +typedef int (CALLBACK *GetInfoForMethodDelegate)(const char*, unsigned int, MethodDebugInfo* methodDebugInfo); extern GetInfoForMethodDelegate getInfoForMethodDelegate; #endif // !__GDBJITHELPERS_H__ diff --git a/src/coreclr/vm/perfmap.cpp b/src/coreclr/vm/perfmap.cpp index d51c75dbe35f01..c7eefbd79c5fe5 100644 --- a/src/coreclr/vm/perfmap.cpp +++ b/src/coreclr/vm/perfmap.cpp @@ -9,6 +9,7 @@ #if defined(FEATURE_PERFMAP) && !defined(DACCESS_COMPILE) #include #include "perfmap.h" +#include "gdbjithelpers.h" #include "pal.h" #include @@ -27,12 +28,330 @@ #define TEMP_DIRECTORY_PATH "/data/local/tmp" #endif +namespace +{ + thread_local int t_jitDumpDebugInfoCallbackDepth = 0; + + // Scope guard to prevent recursive callbacks into the debug-info delegate. + class JitDumpDebugInfoCallbackScope + { + public: + JitDumpDebugInfoCallbackScope() + : _entered(t_jitDumpDebugInfoCallbackDepth == 0) + { + if (_entered) + { + t_jitDumpDebugInfoCallbackDepth = 1; + } + } + + ~JitDumpDebugInfoCallbackScope() + { + if (_entered) + { + t_jitDumpDebugInfoCallbackDepth = 0; + } + } + + bool Entered() const + { + return _entered; + } + + private: + bool _entered; + }; + + // RAII container for sequence points and locals returned from the managed helper. + struct PerfMapMethodDebugInfo + { + SequencePointInfo* points; + int size; + LocalVarInfo* locals; + int localsSize; + + PerfMapMethodDebugInfo(int numPoints, int numLocals) + { + points = (SequencePointInfo*)CoTaskMemAlloc(sizeof(SequencePointInfo) * numPoints); + if (points == nullptr) + { + COMPlusThrowOM(); + } + + memset(points, 0, sizeof(SequencePointInfo) * numPoints); + size = numPoints; + + if (numLocals == 0) + { + locals = nullptr; + localsSize = 0; + return; + } + + locals = (LocalVarInfo*)CoTaskMemAlloc(sizeof(LocalVarInfo) * numLocals); + if (locals == nullptr) + { + CoTaskMemFree(points); + COMPlusThrowOM(); + } + + memset(locals, 0, sizeof(LocalVarInfo) * numLocals); + localsSize = numLocals; + } + + ~PerfMapMethodDebugInfo() + { + if (locals != nullptr) + { + for (int i = 0; i < localsSize; i++) + { + CoTaskMemFree(locals[i].name); + } + + CoTaskMemFree(locals); + } + + if (points != nullptr) + { + for (int i = 0; i < size; i++) + { + CoTaskMemFree(points[i].fileName); + } + + CoTaskMemFree(points); + } + } + + MethodDebugInfo* AsMethodDebugInfo() + { + return reinterpret_cast(this); + } + }; + + // Header for serialized JIT dump debug info payload. + struct JitDumpDebugInfoPayloadHeader + { + uint64_t codeAddress; + uint64_t entryCount; + }; + + // Entry representing one native offset to source line mapping. + struct JitDumpDebugInfoEntry + { + uint64_t codeAddress; + uint32_t lineNumber; + uint32_t discriminator; + }; + + constexpr ULONG32 HiddenLineNumber = 0x00feefee; + + // Allocator used by DebugInfoManager when materializing offset maps. + BYTE* PerfMapDebugInfoNew(void*, size_t cBytes) + { + WRAPPER_NO_CONTRACT; + + return new BYTE[cBytes]; + } + + template + void AppendBytes(StackSArray& buffer, const T& value) + { + WRAPPER_NO_CONTRACT; + + COUNT_T previousCount = buffer.GetCount(); + buffer.SetCount(previousCount + static_cast(sizeof(T))); + memcpy(buffer.GetElements() + previousCount, &value, sizeof(T)); + } + + void AppendBytes(StackSArray& buffer, const void* data, COUNT_T size) + { + WRAPPER_NO_CONTRACT; + + COUNT_T previousCount = buffer.GetCount(); + buffer.SetCount(previousCount + size); + memcpy(buffer.GetElements() + previousCount, data, size); + } + + // Build a serialized jitdump debug-info payload from sequence points for a method. + bool TryGetMethodJitDumpDebugInfo(MethodDesc* pMethod, PCODE pCode, BYTE** payload, size_t* payloadSize) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(pMethod != nullptr); + PRECONDITION(pCode != nullptr); + PRECONDITION(payload != nullptr); + PRECONDITION(payloadSize != nullptr); + } + CONTRACTL_END; + + *payload = nullptr; + *payloadSize = 0; + + if (pMethod->IsLCGMethod() || pMethod->IsDynamicMethod()) + { + return false; + } + + Module* pModule = pMethod->GetModule(); + if (pModule == nullptr) + { + return false; + } + + DebugInfoRequest request; + request.InitFromStartingAddr(pMethod, PCODEToPINSTR(pCode)); + + ULONG32 nativeMapCount = 0; + NewHolder nativeMap(nullptr); + if (!DebugInfoManager::GetBoundariesAndVars( + request, + PerfMapDebugInfoNew, + nullptr, + BoundsType::Uninstrumented, + &nativeMapCount, + &nativeMap, + nullptr, + nullptr)) + { + return false; + } + + if (nativeMapCount == 0) + { + return false; + } + + if (getInfoForMethodDelegate == nullptr) + { + return false; + } + + JitDumpDebugInfoCallbackScope callbackScope; + if (!callbackScope.Entered()) + { + return false; + } + + SString modulePath{pModule->GetPEAssembly()->GetPath()}; + if (modulePath.IsEmpty()) + { + return false; + } + + PerfMapMethodDebugInfo methodDebugInfo(nativeMapCount, 0); + if (getInfoForMethodDelegate(modulePath.GetUTF8(), pMethod->GetMemberDef(), methodDebugInfo.AsMethodDebugInfo()) == 0) + { + return false; + } + + if (methodDebugInfo.size == 0) + { + return false; + } + + StackSArray serializedPayload; + JitDumpDebugInfoPayloadHeader header = {}; + header.codeAddress = static_cast(reinterpret_cast(pCode)); + header.entryCount = 0; + AppendBytes(serializedPayload, header); + + char currentFileName[4 * MAX_LONGPATH] = {}; + ULONG32 currentLineNumber = 0; + + for (ULONG32 nativeMapIndex = 0; nativeMapIndex < nativeMapCount; nativeMapIndex++) + { + const ULONG32 ilOffset = nativeMap[nativeMapIndex].ilOffset; + + if ((ilOffset == (ULONG32)ICorDebugInfo::NO_MAPPING) || + (ilOffset == (ULONG32)ICorDebugInfo::PROLOG) || + (ilOffset == (ULONG32)ICorDebugInfo::EPILOG)) + { + continue; + } + + int sequencePointIndex = 0; + for (; sequencePointIndex < methodDebugInfo.size; sequencePointIndex++) + { + if ((ULONG32)methodDebugInfo.points[sequencePointIndex].ilOffset >= ilOffset) + { + if (((ULONG32)methodDebugInfo.points[sequencePointIndex].ilOffset > ilOffset) && (sequencePointIndex > 0)) + { + sequencePointIndex--; + } + + break; + } + } + + if (sequencePointIndex == methodDebugInfo.size) + { + sequencePointIndex--; + } + + while ((methodDebugInfo.points[sequencePointIndex].lineNumber == (int)HiddenLineNumber) && (sequencePointIndex > 0)) + { + sequencePointIndex--; + } + + if ((methodDebugInfo.points[sequencePointIndex].lineNumber == 0) || + (methodDebugInfo.points[sequencePointIndex].lineNumber == (int)HiddenLineNumber) || + (methodDebugInfo.points[sequencePointIndex].fileName == nullptr)) + { + continue; + } + + int convertedLength = WideCharToMultiByte( + CP_UTF8, + 0, + methodDebugInfo.points[sequencePointIndex].fileName, + -1, + currentFileName, + ARRAY_SIZE(currentFileName), + nullptr, + nullptr); + + if (convertedLength == 0) + { + currentFileName[0] = '\0'; + continue; + } + + currentLineNumber = (ULONG32)methodDebugInfo.points[sequencePointIndex].lineNumber; + + JitDumpDebugInfoEntry entry = {}; + entry.codeAddress = static_cast(reinterpret_cast(pCode) + nativeMap[nativeMapIndex].nativeOffset); + entry.lineNumber = currentLineNumber; + entry.discriminator = 0; + + AppendBytes(serializedPayload, entry); + AppendBytes(serializedPayload, currentFileName, static_cast(strlen(currentFileName) + 1)); + header.entryCount++; + } + + if (header.entryCount == 0) + { + return false; + } + + memcpy(serializedPayload.GetElements(), &header, sizeof(header)); + + *payloadSize = serializedPayload.GetCount(); + *payload = new BYTE[*payloadSize]; + memcpy(*payload, serializedPayload.GetElements(), *payloadSize); + return true; + } +} + Volatile PerfMap::s_enabled = false; Volatile PerfMap::s_dependenciesReady = false; PerfMap * PerfMap::s_Current = nullptr; bool PerfMap::s_ShowOptimizationTiers = false; bool PerfMap::s_GroupStubsOfSameType = false; bool PerfMap::s_IndividualAllocationStubReporting = false; +bool PerfMap::s_EmitDebugInfo = false; unsigned PerfMap::s_StubsMapped = 0; CrstStatic PerfMap::s_csPerfMap; @@ -77,6 +396,7 @@ void PerfMap::InitializeConfiguration() DWORD granularity = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapStubGranularity); s_GroupStubsOfSameType = (granularity & 1) != 1; s_IndividualAllocationStubReporting = (granularity & 2) != 0; + s_EmitDebugInfo = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapEmitDebugInfo) != 0; } void PerfMap::Enable(PerfMapType type, bool sendExisting) @@ -298,7 +618,7 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod CONTRACTL{ THROWS; - GC_NOTRIGGER; + GC_TRIGGERS; MODE_PREEMPTIVE; PRECONDITION(pMethod != nullptr); PRECONDITION(pCode != nullptr); @@ -316,9 +636,17 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod optimizationTier = PrepareCodeConfig::GetJitOptimizationTierStr(pConfig, pMethod); } + NewArrayHolder jitDumpDebugInfo(nullptr); + size_t jitDumpDebugInfoSize = 0; + // Logging failures should not cause any exceptions to flow upstream. EX_TRY { + if (PAL_PerfJitDump_IsStarted() && s_EmitDebugInfo) + { + TryGetMethodJitDumpDebugInfo(pMethod, pCode, &jitDumpDebugInfo, &jitDumpDebugInfoSize); + } + // Get the full method signature. SString name; pMethod->GetFullMethodInfo(name); @@ -339,7 +667,15 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod s_Current->WriteLine(line); } - PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr, /*reportCodeBlock*/true); + PAL_PerfJitDump_LogMethod( + (void*)pCode, + codeSize, + name.GetUTF8(), + jitDumpDebugInfo.GetValue(), + jitDumpDebugInfoSize, + nullptr, + 0, + /*reportCodeBlock*/true); } } EX_CATCH{} EX_END_CATCH @@ -380,7 +716,7 @@ void PerfMap::LogPreCompiledMethod(MethodDesc * pMethod, PCODE pCode) if (methodRegionInfo.hotSize > 0) { CrstHolder ch(&(s_csPerfMap)); - PAL_PerfJitDump_LogMethod((void*)methodRegionInfo.hotStartAddress, methodRegionInfo.hotSize, name.GetUTF8(), nullptr, nullptr, /*reportCodeBlock*/true); + PAL_PerfJitDump_LogMethod((void*)methodRegionInfo.hotStartAddress, methodRegionInfo.hotSize, name.GetUTF8(), nullptr, 0, nullptr, 0, /*reportCodeBlock*/true); } if (methodRegionInfo.coldSize > 0) @@ -393,7 +729,7 @@ void PerfMap::LogPreCompiledMethod(MethodDesc * pMethod, PCODE pCode) name.Append(W("[PreJit-cold]")); } - PAL_PerfJitDump_LogMethod((void*)methodRegionInfo.coldStartAddress, methodRegionInfo.coldSize, name.GetUTF8(), nullptr, nullptr, /*reportCodeBlock*/true); + PAL_PerfJitDump_LogMethod((void*)methodRegionInfo.coldStartAddress, methodRegionInfo.coldSize, name.GetUTF8(), nullptr, 0, nullptr, 0, /*reportCodeBlock*/true); } } EX_CATCH{} EX_END_CATCH @@ -457,7 +793,7 @@ void PerfMap::LogStubs(const char* stubType, const char* stubOwner, PCODE pCode, // Even when the memory is committed, block-level stubs are reported at commit time // before the actual stub code has been written, so the code bytes would be zeros or // uninitialized. We therefore skip code bytes for block allocations entirely. - PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr, /*reportCodeBlock*/ stubAllocationType != PerfMapStubType::Block); + PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, 0, nullptr, 0, /*reportCodeBlock*/ stubAllocationType != PerfMapStubType::Block); } } EX_CATCH{} EX_END_CATCH diff --git a/src/coreclr/vm/perfmap.h b/src/coreclr/vm/perfmap.h index 4dc23f65e107db..e7e261f4eff688 100644 --- a/src/coreclr/vm/perfmap.h +++ b/src/coreclr/vm/perfmap.h @@ -36,6 +36,7 @@ class PerfMap // Indicate current stub granularity rules static bool s_GroupStubsOfSameType; static bool s_IndividualAllocationStubReporting; + static bool s_EmitDebugInfo; // Set to true if an error is encountered when writing to the file. static unsigned s_StubsMapped; diff --git a/src/libraries/System.Diagnostics.StackTrace/src/ILLink/ILLink.Descriptors.LibraryBuild.xml b/src/libraries/System.Diagnostics.StackTrace/src/ILLink/ILLink.Descriptors.LibraryBuild.xml index e5563bfb0d545d..c6c475f561d6af 100644 --- a/src/libraries/System.Diagnostics.StackTrace/src/ILLink/ILLink.Descriptors.LibraryBuild.xml +++ b/src/libraries/System.Diagnostics.StackTrace/src/ILLink/ILLink.Descriptors.LibraryBuild.xml @@ -2,6 +2,6 @@ + - diff --git a/src/libraries/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj b/src/libraries/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj index ba75d0631e037b..9be931c9d626f2 100644 --- a/src/libraries/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj @@ -9,6 +9,7 @@ + diff --git a/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs new file mode 100644 index 00000000000000..9a394acbcb16b5 --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Runtime.InteropServices; + +namespace System.Diagnostics +{ + internal static unsafe class PerfMapSymbolReader + { + private const int HiddenLineNumber = unchecked((int)0x00feefee); + + [StructLayout(LayoutKind.Sequential)] + private struct SequencePointInfoNative + { + public int lineNumber; + public int ilOffset; + public char* fileName; + } + + /// + /// Populate a native buffer with sequence points for the specified method token using the assembly's associated or embedded PDB. + /// + /// true if at least one sequence point was written; otherwise false. + internal static bool GetInfoForMethod([MarshalAs(UnmanagedType.LPUTF8Str)] string assemblyPath, int methodToken, IntPtr points, int size) + { + if (string.IsNullOrEmpty(assemblyPath) || points == IntPtr.Zero || size <= 0) + { + return false; + } + + MetadataReaderProvider? provider = TryOpenReaderFromAssemblyFile(assemblyPath); + if (provider is null) + { + return false; + } + + using (provider) + { + MetadataReader reader = provider.GetMetadataReader(); + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.IsNil || handle.Kind != HandleKind.MethodDefinition) + { + return false; + } + + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + MethodDebugInformation methodInfo = reader.GetMethodDebugInformation(methodDebugHandle); + if (methodInfo.SequencePointsBlob.IsNil) + { + return false; + } + + int count = 0; + foreach (SequencePoint _ in methodInfo.GetSequencePoints()) + { + count++; + } + + if (count == 0) + { + return false; + } + + SequencePointInfoNative* nativePoints = (SequencePointInfoNative*)points; + int writeIndex = 0; + foreach (SequencePoint point in methodInfo.GetSequencePoints()) + { + if (writeIndex >= size) + { + break; + } + + string? documentName = null; + if (!point.Document.IsNil) + { + documentName = reader.GetString(reader.GetDocument(point.Document).Name); + } + + nativePoints[writeIndex].ilOffset = point.Offset; + nativePoints[writeIndex].lineNumber = point.StartLine == SequencePoint.HiddenLine ? HiddenLineNumber : point.StartLine; + nativePoints[writeIndex].fileName = documentName is null ? null : (char*)Marshal.StringToCoTaskMemUni(documentName); + writeIndex++; + } + + return writeIndex > 0; + } + } + + private static MetadataReaderProvider? TryOpenReaderFromAssemblyFile(string assemblyPath) + { + using PEReader? peReader = TryGetPEReader(assemblyPath); + if (peReader is null) + { + return null; + } + + if (peReader.TryOpenAssociatedPortablePdb(assemblyPath, TryOpenFile, out MetadataReaderProvider? provider, out _)) + { + return provider; + } + + foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) + { + if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) + { + try + { + return peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + break; + } + } + } + + return null; + } + + private static PEReader? TryGetPEReader(string assemblyPath) + { + Stream? peStream = TryOpenFile(assemblyPath); + if (peStream is null) + { + return null; + } + + return new PEReader(peStream); + } + + private static Stream? TryOpenFile(string path) + { + if (!File.Exists(path)) + { + return null; + } + + try + { + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); + } + catch + { + return null; + } + } + } +} From 1c8baa59e521d82556d08c4dbdf9568a1ea4805f Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 15:23:00 +0100 Subject: [PATCH 02/10] array safe allocation for IL native map data --- src/coreclr/vm/perfmap.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/perfmap.cpp b/src/coreclr/vm/perfmap.cpp index c7eefbd79c5fe5..3a80bbd161a94c 100644 --- a/src/coreclr/vm/perfmap.cpp +++ b/src/coreclr/vm/perfmap.cpp @@ -150,7 +150,10 @@ namespace { WRAPPER_NO_CONTRACT; - return new BYTE[cBytes]; + size_t mappingCount = cBytes / sizeof(ICorDebugInfo::OffsetMapping); + _ASSERTE(mappingCount * sizeof(ICorDebugInfo::OffsetMapping) == cBytes); + + return reinterpret_cast(new ICorDebugInfo::OffsetMapping[mappingCount]); } template @@ -205,7 +208,7 @@ namespace request.InitFromStartingAddr(pMethod, PCODEToPINSTR(pCode)); ULONG32 nativeMapCount = 0; - NewHolder nativeMap(nullptr); + NewArrayHolder nativeMap(nullptr); if (!DebugInfoManager::GetBoundariesAndVars( request, PerfMapDebugInfoNew, From 260baa04729797f7c5edaab2528c786191c3e70f Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 15:24:26 +0100 Subject: [PATCH 03/10] fill the unused slots with a sentinel ilOffset --- .../src/System/Diagnostics/PerfMapSymbolReader.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs index 9a394acbcb16b5..2b05d80ad7d32a 100644 --- a/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs +++ b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs @@ -86,6 +86,13 @@ internal static bool GetInfoForMethod([MarshalAs(UnmanagedType.LPUTF8Str)] strin writeIndex++; } + for (int i = writeIndex; i < size; i++) + { + nativePoints[i].ilOffset = int.MaxValue; + nativePoints[i].lineNumber = HiddenLineNumber; + nativePoints[i].fileName = null; + } + return writeIndex > 0; } } From a246dbecca4a51840b9e8b063c11b12411bf784f Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 17:09:19 +0100 Subject: [PATCH 04/10] Explicity set locals and localsSize --- .../src/System/Diagnostics/StackTrace.CoreCLR.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs index b87748f2ef7acb..0269dfd7684383 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs @@ -60,6 +60,9 @@ internal static unsafe int GetInfoForMethod(byte* assemblyPath, uint methodToken return 0; } + methodDebugInfo->locals = null; + methodDebugInfo->localsSize = 0; + string? assemblyPathString = Marshal.PtrToStringUTF8((IntPtr)assemblyPath); if (string.IsNullOrEmpty(assemblyPathString)) { From f6b729b055c5c01ef2cd09d9ad54d1fa84d299a5 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 17:10:09 +0100 Subject: [PATCH 05/10] Add try/catch --- .../src/System/Diagnostics/StackTrace.CoreCLR.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs index 0269dfd7684383..f2b04529d970cf 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs @@ -63,13 +63,20 @@ internal static unsafe int GetInfoForMethod(byte* assemblyPath, uint methodToken methodDebugInfo->locals = null; methodDebugInfo->localsSize = 0; - string? assemblyPathString = Marshal.PtrToStringUTF8((IntPtr)assemblyPath); - if (string.IsNullOrEmpty(assemblyPathString)) + try + { + string? assemblyPathString = Marshal.PtrToStringUTF8((IntPtr)assemblyPath); + if (string.IsNullOrEmpty(assemblyPathString)) + { + return 0; + } + + return GetPerfMapInfoForMethodFromReader(null, assemblyPathString, unchecked((int)methodToken), (IntPtr)methodDebugInfo->points, methodDebugInfo->size) ? 1 : 0; + } + catch { return 0; } - - return GetPerfMapInfoForMethodFromReader(null, assemblyPathString, unchecked((int)methodToken), (IntPtr)methodDebugInfo->points, methodDebugInfo->size) ? 1 : 0; } internal static int CalculateFramesToSkip(StackFrameHelper StackF, int iNumFrames) From 8bda1baa9ff0b4aba7b4e95988ba9667c61c9fff Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 17:10:50 +0100 Subject: [PATCH 06/10] safer GetInfoForMethod --- .../System/Diagnostics/PerfMapSymbolReader.cs | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs index 2b05d80ad7d32a..817378ad728241 100644 --- a/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs +++ b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs @@ -21,6 +21,18 @@ private struct SequencePointInfoNative public char* fileName; } + private static void FreeAllocatedNames(SequencePointInfoNative* points, int count) + { + for (int i = 0; i < count; i++) + { + if (points[i].fileName != null) + { + Marshal.FreeCoTaskMem((IntPtr)points[i].fileName); + points[i].fileName = null; + } + } + } + /// /// Populate a native buffer with sequence points for the specified method token using the assembly's associated or embedded PDB. /// @@ -40,7 +52,15 @@ internal static bool GetInfoForMethod([MarshalAs(UnmanagedType.LPUTF8Str)] strin using (provider) { - MetadataReader reader = provider.GetMetadataReader(); + MetadataReader reader; + try + { + reader = provider.GetMetadataReader(); + } + catch (BadImageFormatException) { return false; } + catch (IOException) { return false; } + catch (InvalidOperationException) { return false; } + Handle handle = MetadataTokens.Handle(methodToken); if (handle.IsNil || handle.Kind != HandleKind.MethodDefinition) { @@ -67,24 +87,30 @@ internal static bool GetInfoForMethod([MarshalAs(UnmanagedType.LPUTF8Str)] strin SequencePointInfoNative* nativePoints = (SequencePointInfoNative*)points; int writeIndex = 0; - foreach (SequencePoint point in methodInfo.GetSequencePoints()) + try { - if (writeIndex >= size) + foreach (SequencePoint point in methodInfo.GetSequencePoints()) { - break; + if (writeIndex >= size) + { + break; + } + + string? documentName = null; + if (!point.Document.IsNil) + { + documentName = reader.GetString(reader.GetDocument(point.Document).Name); + } + + nativePoints[writeIndex].ilOffset = point.Offset; + nativePoints[writeIndex].lineNumber = point.StartLine == SequencePoint.HiddenLine ? HiddenLineNumber : point.StartLine; + nativePoints[writeIndex].fileName = documentName is null ? null : (char*)Marshal.StringToCoTaskMemUni(documentName); + writeIndex++; } - - string? documentName = null; - if (!point.Document.IsNil) - { - documentName = reader.GetString(reader.GetDocument(point.Document).Name); - } - - nativePoints[writeIndex].ilOffset = point.Offset; - nativePoints[writeIndex].lineNumber = point.StartLine == SequencePoint.HiddenLine ? HiddenLineNumber : point.StartLine; - nativePoints[writeIndex].fileName = documentName is null ? null : (char*)Marshal.StringToCoTaskMemUni(documentName); - writeIndex++; } + catch (BadImageFormatException) { FreeAllocatedNames(nativePoints, writeIndex); return false; } + catch (IOException) { FreeAllocatedNames(nativePoints, writeIndex); return false; } + catch (InvalidOperationException) { FreeAllocatedNames(nativePoints, writeIndex); return false; } for (int i = writeIndex; i < size; i++) { From cb9e1538ede25f4339cf1bb746d71056f415b134 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Mon, 13 Apr 2026 17:11:47 +0100 Subject: [PATCH 07/10] Optimized the sequence point lookup --- src/coreclr/vm/perfmap.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/coreclr/vm/perfmap.cpp b/src/coreclr/vm/perfmap.cpp index 3a80bbd161a94c..7bc9d63fc3f050 100644 --- a/src/coreclr/vm/perfmap.cpp +++ b/src/coreclr/vm/perfmap.cpp @@ -264,6 +264,8 @@ namespace char currentFileName[4 * MAX_LONGPATH] = {}; ULONG32 currentLineNumber = 0; + int sequencePointIndex = 0; + for (ULONG32 nativeMapIndex = 0; nativeMapIndex < nativeMapCount; nativeMapIndex++) { const ULONG32 ilOffset = nativeMap[nativeMapIndex].ilOffset; @@ -275,26 +277,14 @@ namespace continue; } - int sequencePointIndex = 0; - for (; sequencePointIndex < methodDebugInfo.size; sequencePointIndex++) - { - if ((ULONG32)methodDebugInfo.points[sequencePointIndex].ilOffset >= ilOffset) - { - if (((ULONG32)methodDebugInfo.points[sequencePointIndex].ilOffset > ilOffset) && (sequencePointIndex > 0)) - { - sequencePointIndex--; - } - - break; - } - } - - if (sequencePointIndex == methodDebugInfo.size) + while (sequencePointIndex + 1 < methodDebugInfo.size && + (ULONG32)methodDebugInfo.points[sequencePointIndex + 1].ilOffset <= ilOffset) { - sequencePointIndex--; + sequencePointIndex++; } - while ((methodDebugInfo.points[sequencePointIndex].lineNumber == (int)HiddenLineNumber) && (sequencePointIndex > 0)) + while (sequencePointIndex > 0 && + methodDebugInfo.points[sequencePointIndex].lineNumber == (int)HiddenLineNumber) { sequencePointIndex--; } From 7a0ca3861605cf35eaac0ac16065d0b63192a6dc Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Tue, 14 Apr 2026 11:39:44 +0100 Subject: [PATCH 08/10] init vars in gdbjit --- src/coreclr/vm/gdbjit.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/coreclr/vm/gdbjit.cpp b/src/coreclr/vm/gdbjit.cpp index ae66afcac25350..f1ce49a2684010 100644 --- a/src/coreclr/vm/gdbjit.cpp +++ b/src/coreclr/vm/gdbjit.cpp @@ -530,6 +530,12 @@ GetDebugInfoFromPDB(MethodDesc* methodDescPtr, } t_gdbJitDebugInfoCallbackDepth = 0; + // Defensive: managed helper may not provide locals; skip converting locals if names are absent. + if (methodDebugInfo.locals == nullptr || methodDebugInfo.localsSize == 0) + { + locals.size = 0; + } + symInfoLen = numMap; symInfo = new SymbolsInfo[numMap]; @@ -539,6 +545,14 @@ GetDebugInfoFromPDB(MethodDesc* methodDescPtr, for (int i = 0; i < locals.size; i++) { + if (methodDebugInfo.locals[i].name == nullptr) + { + locals.localsName[i] = nullptr; + locals.localsScope[i].ilStartOffset = 0; + locals.localsScope[i].ilEndOffset = 0; + continue; + } + size_t sizeRequired = WideCharToMultiByte(CP_UTF8, 0, methodDebugInfo.locals[i].name, -1, NULL, 0, NULL, NULL); locals.localsName[i] = new char[sizeRequired]; From 44c1fa9ff7770d4edf9d8406e95bfd143b9fb9a1 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Tue, 14 Apr 2026 12:05:11 +0100 Subject: [PATCH 09/10] don't set locals to null --- .../src/System/Diagnostics/StackTrace.CoreCLR.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs index f2b04529d970cf..490447a7d0f261 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs @@ -60,7 +60,6 @@ internal static unsafe int GetInfoForMethod(byte* assemblyPath, uint methodToken return 0; } - methodDebugInfo->locals = null; methodDebugInfo->localsSize = 0; try From 91866fa142b2237855598d98cad51e7163122cb8 Mon Sep 17 00:00:00 2001 From: Alan Hayward Date: Tue, 14 Apr 2026 12:07:23 +0100 Subject: [PATCH 10/10] Only consume locals if both pointer and size are valid --- src/coreclr/vm/gdbjit.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/coreclr/vm/gdbjit.cpp b/src/coreclr/vm/gdbjit.cpp index f1ce49a2684010..715bf9f68d57aa 100644 --- a/src/coreclr/vm/gdbjit.cpp +++ b/src/coreclr/vm/gdbjit.cpp @@ -530,16 +530,11 @@ GetDebugInfoFromPDB(MethodDesc* methodDescPtr, } t_gdbJitDebugInfoCallbackDepth = 0; - // Defensive: managed helper may not provide locals; skip converting locals if names are absent. - if (methodDebugInfo.locals == nullptr || methodDebugInfo.localsSize == 0) - { - locals.size = 0; - } - symInfoLen = numMap; symInfo = new SymbolsInfo[numMap]; - locals.size = methodDebugInfo.localsSize; + // Only consume locals if both pointer and size are valid. + locals.size = (methodDebugInfo.locals != nullptr && methodDebugInfo.localsSize > 0) ? methodDebugInfo.localsSize : 0; locals.localsName = new NewArrayHolder[locals.size]; locals.localsScope = new LocalsInfo::Scope [locals.size];