Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Improvements to native AOT method debug information - #132735

Merged
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309
Aug 31, 2026
Merged

Improvements to native AOT method debug information#132735
MichalStrehovsky merged 5 commits into
dotnet:mainfrom
MichalStrehovsky:fix117309

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Fixes#117309

  • Make sure we have type information for the method and parameters show up in the stack
  • Format method names like the VS debugger does instead of using symbol name
  • Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too
  • this needs to be named this or the debugger will treat it as a regular parameter that is visible in the signature. AFAIK the only reason why we didn't use this originally was CppCodegen.

Before (notice no int n parameter):

image

After:

image

Fixesdotnet#117309
- Format method names like the VS debugger does instead of using symbol name
- Make sure we have type information for the method
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 improves NativeAOT-generated debug information (primarily CodeView/PDB) so debuggers and tooling can show richer method/parameter type details and more user-friendly method names.

Changes:

  • Emit a non-zero CodeView procedure type index (methodTypeIndex) and pass it through to the S_GPROC32_ID record.
  • Introduce CSharpTypeNameFormatter and use it to provide a C#-style method display name in CodeView symbols instead of the mangled symbol name.
  • Rename the implicit instance parameter from "___this" to "this" in ECMA PDB-backed parameter name enumeration.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/tools/Common/TypeSystem/IL/EcmaMethodIL.Symbols.csChanges implicit instance parameter name emitted by PDB-backed parameter enumeration to "this".
src/coreclr/tools/Common/TypeSystem/Common/Utilities/CSharpTypeNameFormatter.csAdds a new formatter for producing C#-style type (and method owning-type) names.
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csprojLinks the new formatter into the ILCompiler.TypeSystem build.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.csAlways computes and supplies method type ID and method display name during debug info emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.csUpdates debug function info signature and sequence-point gating logic for DWARF emission.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.csPasses the new display name through to CodeView symbol emission; preserves line info gating.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.csWrites methodTypeIndex and the new display name into the CodeView S_GPROC32_ID record.

Update the WebAssembly relocatable object writer for the debug function information signature introduced by this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 25, 2026 08:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:129

  • methodDisplayName is constructed for every IMethodNode during debug-info emission, but it’s not used by the DWARF path (e.g., UnixObjectWriter.Aot.cs ignores it) and the Wasm writer also ignores it. This adds per-method StringBuilder/string/UTF8 allocations in ObjectWritingOptions.GenerateDebugInfo builds on non-Windows targets. Consider computing the display name only in the CodeView/Coff path (or lazily in the override when needed) to avoid unnecessary work on other object writers.
 uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • debugNode.GetNativeSequencePoints() is enumerated for .Any() here, and then enumerated again later when passing sequence points into EmitLineInfo. If this enumerable is not a cheap materialized collection, this can add avoidable overhead. Consider capturing/materializing the sequence points once and reusing them for both the emptiness check and emission.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

`Subprogram` is expected to emit debug info for `this` for instance methods.
CopilotAI review requested due to automatic review settings August 26, 2026 05:48
@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Previously we'd skip emitting method debug information if there's no sequence points but I'm not clear on why so deleting that filter too

So this was used to paper over issues when no debug information for parameters is available. "No sequence points" was used to approximate "no parameter debug info", basically. DWARF debug info emission wasn't ready to deal with that. We now just emit no parameter debug information. This mostly affects compiler-generated IL.

Alternatively, we could name these parameters, but that sounds a bit problematic.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs:228

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
debugSymbolsBuilder.EmitLineInfo(
_debugFileTableBuilder,
methodName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs:276

  • GetNativeSequencePoints() is implemented as an iterator on MethodCodeNode and does non-trivial work per enumeration. The current pattern enumerates it twice (once for Any(), again when emitting line info). Materializing the sequence points once avoids doing all that work twice per method.
 if (debugNode.GetNativeSequencePoints().Any())
{
_dwarfBuilder.EmitLineInfo(
methodSymbol.SectionIndex,
section.SymbolName,

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs:128

  • methodDisplayName is computed for every method node unconditionally, but the derived emitters for DWARF and Wasm don’t use it. This adds avoidable per-method allocations/work on non-Windows NativeAOT builds. Consider only formatting the C# display name when targeting Windows (CodeView/PDB), and otherwise just pass methodName (or default).
 if (node is IMethodNode methodNode)
{
uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method);
Utf8String methodDisplayName = new Utf8String(CSharpTypeNameFormatter.Instance.FormatName(methodNode.Method));
EmitDebugFunctionInfo(methodTypeIndex, methodDisplayName, methodName, methodSymbol, debugNode);
}

@max-charlamb

max-charlamb commented Aug 26, 2026

Copy link
Copy Markdown
Member

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

It looks like this changes the scheme for method name symbols. Can we rev the SymbolName contract to n2 so tooling can handle both formats?

It does not change COFF symbol names, the symbol name is still the same. It changes the CodeView S_GPROC32_ID name of the method. This is same as C++ where:

classFoo
{
voidBar(int x);
};
voidFoo::Bar(int x) { }

The COFF symbol name for Bar is ?Bar@Foo@@AEAAXH@Z but S_GPROC32_ID symbol record assigns it name Foo::Bar.

Do we still need to rev SymboName contract?

@max-charlamb

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

/c @noahfalk

@noahfalk

Copy link
Copy Markdown
Member

I see, I misunderstood the change. However, I would still like to bump the version. This should give us flexibility in the future to support previous versions.

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I think it would be fine to do and right now the consequences are minimal. One issue that I do think we still need to address is documenting an explicit data contract for this so everyone agrees what behavior is or isn't covered by the contract.

Does cDAC interpret the symbol names in any way? The symbol names are supposed to be unique and deterministic for the exact same app, but e.g. two different apps might already assign different names to the same thing and there can be various butterfly effects when something in an assembly changes. They are not meant to be stable or reversible like the C++ mangled names. We can't use a reversible/stable C++-like name mangling because .NET naming is ridiculously long (std in C++ vs System.Collections.Generics in .NET).

CopilotAI review requested due to automatic review settings August 27, 2026 01:12

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

@jkotas

Copy link
Copy Markdown
Member

Does cDAC interpret the symbol names in any way?

Yes, for limited set of symbols in CoreLib where the ambiguity should not be a problem. As @noahfalk mentioned this needs to be documented for clarity.

CopilotAI review requested due to automatic review settings August 31, 2026 00:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc:262

  • Bumping the NativeAOT cDAC contract version for SymbolName from n1 to n2 has compatibility impact for dump readers, but this PR doesn't appear to include any corresponding contract shape/algorithm changes or managed reader updates. If this wasn't intentional, please revert the version bump; if it was intentional, the contract implementation + docs need to be updated in the same PR so readers can negotiate v2 successfully.
CDAC_GLOBAL_CONTRACT(StressLog, c2)
CDAC_GLOBAL_CONTRACT(Loader, n1)
CDAC_GLOBAL_CONTRACT(ExecutionManager, n1)
CDAC_GLOBAL_CONTRACT(SyncBlock, n1)
CDAC_GLOBAL_CONTRACT(SymbolName, n2)
CDAC_GLOBAL_CONTRACT(CodeVersions, n1)
CDAC_GLOBAL_CONTRACT(ReJIT, n1)
CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1)
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Displays the names of local variables, method arguments and method return values in IDA Pro. (NativeAOT)

5 participants

@MichalStrehovsky@max-charlamb@noahfalk@jkotas