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..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 @@ -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,74 @@ 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; + } + + methodDebugInfo->localsSize = 0; + + 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; + } + } + 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..715bf9f68d57aa 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,18 +522,32 @@ 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]; - 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]; 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]; 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..7bc9d63fc3f050 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,323 @@ #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; + + size_t mappingCount = cBytes / sizeof(ICorDebugInfo::OffsetMapping); + _ASSERTE(mappingCount * sizeof(ICorDebugInfo::OffsetMapping) == cBytes); + + return reinterpret_cast(new ICorDebugInfo::OffsetMapping[mappingCount]); + } + + 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; + NewArrayHolder 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; + + int sequencePointIndex = 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; + } + + while (sequencePointIndex + 1 < methodDebugInfo.size && + (ULONG32)methodDebugInfo.points[sequencePointIndex + 1].ilOffset <= ilOffset) + { + sequencePointIndex++; + } + + while (sequencePointIndex > 0 && + methodDebugInfo.points[sequencePointIndex].lineNumber == (int)HiddenLineNumber) + { + 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 +389,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 +611,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 +629,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 +660,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 +709,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 +722,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 +786,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..817378ad728241 --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs @@ -0,0 +1,185 @@ +// 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; + } + + 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. + /// + /// 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; + 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) + { + 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; + try + { + 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++; + } + } + 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++) + { + nativePoints[i].ilOffset = int.MaxValue; + nativePoints[i].lineNumber = HiddenLineNumber; + nativePoints[i].fileName = null; + } + + 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; + } + } + } +}