Emit debug information for generated forwarder assemblies - #2540
Open
Sergio Pedri (Sergio0694) wants to merge 3 commits into
Open
Emit debug information for generated forwarder assemblies#2540Sergio Pedri (Sergio0694) wants to merge 3 commits into
Sergio Pedri (Sergio0694) wants to merge 3 commits into
Conversation
Sergio Pedri (Sergio0694)
requested a review
from Manodasan Wignarajah (manodasanW)
August 27, 2026 16:28
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Sergio Pedri (Sergio0694)
force-pushed
the
user/sergiopedri/forwarder-debug-symbols
branch
from
August 31, 2026 10:53
10433fc to
ccddf40
Compare
The forwarder assembly produced by 'cswinrtimplgen' is emitted directly as metadata rather than compiled, so it shipped with a completely empty PE debug directory. Since that assembly is what lands in 'lib/<tfm>' of a reference projection NuGet package, the whole package reported as having no symbols, and no Source Link, determinism or compiler flag information. There is nothing to carry over from the input either: reference projections are compiled with 'ProduceOnlyReferenceAssembly', and a reference-only compilation emits no debug information at all. The generator now synthesizes the debug information itself, emitting the same debug directory a deterministic build with embedded symbols produces, with an embedded portable PDB carrying a single generated document describing the type forwards, plus compilation options and metadata references. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Avoids encoding (and allocating) a constant string on every run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The smoke tests inspect the built assemblies through 'System.Reflection.PortableExecutable.PEReader', which comes from 'System.Reflection.Metadata'. That assembly does not ship with Windows PowerShell, so the reference projection smoke tests failed with a TypeNotFound error when run under it. Run them on PowerShell 7 instead, which has it in the box. 'build.cmd' now also checks for 'pwsh' up front, so a machine without it gets an actionable message rather than a failure from inside the tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sergio Pedri (Sergio0694)
force-pushed
the
user/sergiopedri/forwarder-debug-symbols
branch
from
August 31, 2026 13:45
ccddf40 to
f0e32c4
Compare
Sergio Pedri (Sergio0694)
marked this pull request as ready for review
August 31, 2026 13:45
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The forwarder assembly that
cswinrtimplgengenerates for a reference projection shipped with a completely empty PE debug directory. That assembly is what lands inlib/<tfm>of a reference projection NuGet package, so every such package reported as having no symbols at all.cswinrtimplgennow synthesizes the debug information for the forwarder, so it ships with an embedded portable PDB, Source Link, compiler flags, and aReproduciblemarker.Motivation
NuGet Package Explorer (and the other tooling that reads the same data) validates a package by inspecting only the assemblies under
lib/andruntimes/, and reports the worst case across them. For a reference projection package that is exactly one file: the forwarder. Because it carried no debug directory entries at all, the package health report came back as:All three rows are red for a single reason: when an assembly yields no symbols, the deterministic and compiler-flag results are force-set to match rather than being evaluated independently. The
ref/<tfm>reference assembly and themetadata/*.winmdfiles are never inspected, so the forwarder is the only actionable file.The forwarder had no symbols because it is emitted directly as metadata by
cswinrtimplgen(via AsmResolver) rather than compiled, and a freshly constructed module is written with an empty debug directory. There is also nothing to carry over from the input assembly: reference projections are compiled withProduceOnlyReferenceAssembly, and a reference-only compilation emits no debug information whatsoever.That last point rules out the obvious alternative of relaxing
ProduceOnlyReferenceAssemblyso the compiler produces a PDB to propagate. Reference projections are only ever meant to be compiled/refonly: their generated constructors implicitly call theWindowsRuntimeObject()constructor, which exists only in theWinRT.Runtimereference assembly and whose body isthrow null. Compiling those bodies for real fails to bind against the implementation assembly (over 6000CS1729errors when buildingsrc/Projections/Windows), and would otherwise emit IL calling a constructor that throwsMissingMethodExceptionat runtime. So the debug information has to be synthesized instead.Changes
src/WinRT.Impl.Generator/Writers/PortablePdbWriter.cs: new. Builds a portable PDB for the forwarder usingSystem.Reflection.Metadata, containing one embedded source document, the compilation options, and the compilation metadata references. The PDB id and checksum are both derived from a single content hash, so they stay consistent and the output is a pure function of its input.src/WinRT.Impl.Generator/Writers/TypeForwardsDocumentWriter.cs: new. Renders the document that the PDB embeds: an auto-generated C# file listing every type forward in the assembly as the[assembly: TypeForwardedTo(...)]that would produce it, along with the projection assembly each one resolves to. The forwarder has no source on disk, so this is written to be an accurate, human readable description of the entire contents of the assembly.src/WinRT.Impl.Generator/Writers/DebugDirectoryWriter.cs: new. Emits the debug directory entries, matching what a deterministic build with embedded symbols produces:CodeView,PdbChecksum,Reproducible, andEmbeddedPortablePdb.src/WinRT.Impl.Generator/Generation/ImplGenerator.cs: writes the assembly throughToPEImage()andManagedPEFileBuilderinstead of theModuleDefinition.Write(path)convenience overload (which is what that overload does anyway), so the debug directory can be populated before the file is written.src/Tests/ImplGeneratorTest/: new test project, following the same shape asWinMDGeneratorTest: it buildscswinrtimplgenand runs it end-to-end as a separate process.Test_DebugDirectorycovers the debug directory entries, their mutual consistency, the embedded document and its deterministic name, the compiler flags, byte-for-byte determinism across runs, and that symbols survive strong naming.src/Tests/SmokeTests/run-smoke-tests.ps1: the shared reference projection verification now also asserts the forwarder ships embedded symbols, so this is covered end-to-end against a real built package.build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml: runs the new test project, gated to x64 like the other generator tests.src/cswinrt.slnx,.github/copilot-instructions.md,.github/skills/testing/SKILL.md: register and document the new project and behavior.Notes
Everything written into the PDB is derived from the forwarder itself, so the result stays deterministic: two runs produce byte-identical output, which is what makes the
Reproducibleentry honest. TheMetadataReferencesentries deliberately record a zero timestamp, image size and MVID: those identify the exact image a reference resolved to, and the projection assemblies the forwarder points at do not exist until the consuming application is built, so there is nothing truthful to record.The size cost is small even in the worst case. For the full Windows SDK projection (5251 type forwards, an 872 KB generated document) the embedded PDB adds roughly 40 KB.
Verified end to end by building
src/Projections/Windowsand checking the resulting forwarder against the same rules the package health check applies:One thing found along the way but deliberately left alone, as it is pre-existing and unrelated: the generator signs the forwarder after writing it, but does not set the
StrongNameSignedCOR flag. The signature itself is valid (sn -vfpasses), and this behavior is identical before and after this change.