Skip to content

Add source info to perfmap/jitdump#126836

Open
a74nh wants to merge 3 commits intodotnet:mainfrom
a74nh:linemapping_github
Open

Add source info to perfmap/jitdump#126836
a74nh wants to merge 3 commits intodotnet:mainfrom
a74nh:linemapping_github

Conversation

@a74nh
Copy link
Copy Markdown
Contributor

@a74nh 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
Copilot AI review requested due to automatic review settings April 13, 2026 14:09
@dotnet-policy-service dotnet-policy-service bot added linkable-framework Issues associated with delivering a linker friendly framework community-contribution Indicates that the PR has been added by a community member labels Apr 13, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.GetInfoForMethod in System.Private.CoreLib and add re-entrancy guards.
  • Extend jitdump logging to optionally emit JIT_CODE_DEBUG_INFO records and plumb debug-info payloads from perfmap.cpp into PAL_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.

Comment on lines +24 to +33
/// <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;
}
@a74nh
Copy link
Copy Markdown
Contributor Author

a74nh commented Apr 13, 2026

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:
image (8)


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.

@JulieLeeMSFT @dhartglassMSFT

@a74nh
Copy link
Copy Markdown
Contributor Author

a74nh commented Apr 13, 2026

Still need to add tests. But, taking this out of draft to get some comments to see if this is in the right direction

@a74nh a74nh marked this pull request as ready for review April 13, 2026 15:43
Copilot AI review requested due to automatic review settings April 13, 2026 15:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 CoreLib StackTrace.GetInfoForMethod unmanaged callback to retrieve sequence points from associated/embedded Portable PDBs.
  • Extend perfmap/jitdump plumbing to optionally emit JIT_CODE_DEBUG_INFO records (gated by PerfMapEmitDebugInfo) and update PAL jitdump logging to accept a debug-info payload.
  • Retarget the runtime delegate creation from SOS to System.Private.CoreLib / System.Diagnostics.StackTrace for 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.

{
return 0;
}

Comment on lines +58 to +69
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;
Comment on lines +43 to +96
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;
Comment on lines 523 to +531
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;
Comment on lines +267 to +290
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;
}
}
@JulieLeeMSFT
Copy link
Copy Markdown
Member

@noahfalk, @brianrob, @steveisok, PTAL if it aligns with our diagnostics story. This change is related to the Performix tool supporting source annotation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-VM-coreclr community-contribution Indicates that the PR has been added by a community member linkable-framework Issues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants