Conversation
a74nh
commented
Apr 13, 2026
- 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
* 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
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR adds source/line debug-info support to perfmap/jitdump by introducing a managed PDB sequence-point reader and wiring CoreCLR’s perfmap/jitdump pipeline to emit JIT_CODE_DEBUG_INFO records (gated by a new config knob).
Changes:
- Add
PerfMapSymbolReader(System.Diagnostics.StackTrace) to read sequence points from associated/embedded Portable PDBs for a given method token. - Retarget native delegate creation to
System.Diagnostics.StackTrace.GetInfoForMethodin System.Private.CoreLib and add re-entrancy guards. - Extend jitdump logging to optionally emit
JIT_CODE_DEBUG_INFOrecords and plumb debug-info payloads fromperfmap.cppintoPAL_PerfJitDump_LogMethod.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs | New managed helper to read sequence points from associated/embedded PDBs into a native buffer. |
| src/libraries/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj | Include new source file in the StackTrace library build. |
| src/libraries/System.Diagnostics.StackTrace/src/ILLink/ILLink.Descriptors.LibraryBuild.xml | Preserve PerfMapSymbolReader for trimming scenarios. |
| src/coreclr/vm/perfmap.h | Add s_EmitDebugInfo toggle for debug-info emission. |
| src/coreclr/vm/perfmap.cpp | Build/emit serialized jitdump debug-info payload from managed sequence points when enabled. |
| src/coreclr/vm/gdbjithelpers.h | Update debug-info delegate signature to return int and take MethodDebugInfo*. |
| src/coreclr/vm/gdbjit.cpp | Add re-entrancy guard and update delegate invocation for new signature. |
| src/coreclr/pal/src/misc/perfjitdump.cpp | Add JIT_CODE_DEBUG_INFO record support and extend logging API to accept debug/unwind payloads + sizes. |
| src/coreclr/pal/inc/pal.h | Update PAL_PerfJitDump_LogMethod signature to include debug/unwind payload sizes and const pointers. |
| src/coreclr/inc/clrconfigvalues.h | Add PerfMapEmitDebugInfo config knob. |
| src/coreclr/dlls/mscoree/exports.cpp | Create the native delegate against System.Private.CoreLib / System.Diagnostics.StackTrace.GetInfoForMethod. |
src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs
Show resolved
Hide resolved
| /// <summary> | ||
| /// Populate a native buffer with sequence points for the specified method token using the assembly's associated or embedded PDB. | ||
| /// </summary> | ||
| /// <returns>true if at least one sequence point was written; otherwise false.</returns> | ||
| 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; | ||
| } |
|
This PR is useful for any tool or developer that uses linux perf to analyse .NET applications. Specifically, support is being added for use by Arm Performix, with the aim of using this to help migrate users. Example output of perf annotate showing the line numbers from an example C# that was run through perf: This PR was entirely written by codex. I've spent some time testing and reviewing the code. It looks sensible and works for me, however I don't claim to be familiar with some of these areas of CoreCLR. |
|
Still need to add tests. But, taking this out of draft to get some comments to see if this is in the right direction |
There was a problem hiding this comment.
Pull request overview
This PR adds managed symbol-reading support to enrich perfmap/jitdump output with source/line information by reading Portable PDB sequence points, and wires the runtime to use this new managed helper when emitting perfmap/jitdump data.
Changes:
- Add
PerfMapSymbolReader(System.Diagnostics.StackTrace) and a CoreLibStackTrace.GetInfoForMethodunmanaged callback to retrieve sequence points from associated/embedded Portable PDBs. - Extend perfmap/jitdump plumbing to optionally emit
JIT_CODE_DEBUG_INFOrecords (gated byPerfMapEmitDebugInfo) and update PAL jitdump logging to accept a debug-info payload. - Retarget the runtime delegate creation from SOS to
System.Private.CoreLib/System.Diagnostics.StackTracefor debug-info retrieval.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/PerfMapSymbolReader.cs | New managed helper to read Portable PDB sequence points and fill a native buffer. |
| src/libraries/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj | Includes the new PerfMapSymbolReader.cs in the build. |
| src/libraries/System.Diagnostics.StackTrace/src/ILLink/ILLink.Descriptors.LibraryBuild.xml | Preserves PerfMapSymbolReader for trimming scenarios. |
| src/coreclr/vm/perfmap.h | Adds s_EmitDebugInfo configuration flag. |
| src/coreclr/vm/perfmap.cpp | Emits optional jitdump debug-info payload and passes it to PAL jitdump logging. |
| src/coreclr/vm/gdbjithelpers.h | Updates the managed debug-info delegate signature to use a pointer and int return. |
| src/coreclr/vm/gdbjit.cpp | Adds re-entrancy guard and updates delegate invocation to new signature. |
| src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs | Adds unmanaged callback (GetInfoForMethod) and UnsafeAccessor bridge into System.Diagnostics.StackTrace. |
| src/coreclr/pal/src/misc/perfjitdump.cpp | Adds JIT_CODE_DEBUG_INFO record support and extends LogMethod parameters. |
| src/coreclr/pal/inc/pal.h | Updates PAL_PerfJitDump_LogMethod signature to include debug/unwind buffers + sizes. |
| src/coreclr/inc/clrconfigvalues.h | Adds PerfMapEmitDebugInfo config value. |
| src/coreclr/dlls/mscoree/exports.cpp | Retargets delegate creation to System.Diagnostics.StackTrace.GetInfoForMethod and enables it for FEATURE_PERFMAP too. |
| 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; |
| 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++; | ||
| } | ||
|
|
||
| for (int i = writeIndex; i < size; i++) | ||
| { | ||
| nativePoints[i].ilOffset = int.MaxValue; | ||
| nativePoints[i].lineNumber = HiddenLineNumber; | ||
| nativePoints[i].fileName = null; | ||
| } | ||
|
|
||
| return writeIndex > 0; |
| 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; |
| 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; | ||
| } | ||
| } |
|
@noahfalk, @brianrob, @steveisok, PTAL if it aligns with our diagnostics story. This change is related to the Performix tool supporting source annotation. |
