Skip to content

Windows PDB: woven types with long fully qualified names get garbage, run-dependent PDB module names #36

@gfraiteur

Description

@gfraiteur

Summary

Split off from #35. While diagnosing that non-determinism, it turned out the underlying defect also corrupts (cosmetically) the Windows PDB content of all .NET Framework builds that emit a classic (full) PDB - not only Deterministic=true builds: Platform/WindowsPdb/PdbWriter.cs hands the same MetaDataEmitImpl shim to both ISymUnmanagedWriter.Initialize(...) (normal path) and InitializeDeterministic(...).

Root cause

MetaDataEmitImpl.GetTypeDefProps / GetMethodProps (the IMetaDataImport shim that the unmanaged symbol writer, diasymreader, uses to query type and method names while writing the PDB) reported name lengths in bytes instead of wide characters, and skipped the copy entirely when the caller's buffer looked too small under that doubled count.

For any woven type whose fully qualified name exceeds 127 characters, the reported length exceeds diasymreader's fixed name buffer. diasymreader does not truncate in that case: it falls back to naming the PDB module (DBI compiland) after a heap address (e.g. 9AD278F0), different on every run. The same fallback also triggers for names genuinely longer than its internal buffer (empirically between 251 and 271 characters), regardless of the reported length.

Typical trigger: binding types generated for aspects on explicit interface implementations (<Ns.IInterface.Method>z__Binding) - these embed the dotted method name and easily exceed 127 characters.

Impact

  • The affected per-class PDB modules get garbage, run-dependent names, and the methods of one class can end up split across several such modules. DIA-based tooling that enumerates compilands shows nonsense entries.
  • In Deterministic=true builds, this is the source of the non-reproducibility described in Patterns weavers produce non-deterministic output even with Deterministic=true #35.
  • No functional impact on debugging: sequence points, local variables, scopes and async info are stored and looked up by method token; Pdb2Xml dumps (using ISymUnmanagedReader, the same API debuggers use) of affected builds are complete, correct, and identical across rebuilds. There is also no memory-safety window: the skipped copy always coincided with the over-large length report, which diverts diasymreader to its fallback before it reads the buffer.

Fix

Fixed together with #35 (commit 1dadc2f95c on topic/2024.0/test-reorganization): lengths are now reported in characters with a proper truncating, null-terminated copy, and names exposed to the symbol writer are deterministically clamped to 250 characters (this only affects the PDB module grouping name, not debug info). Covered by the long-name explicit-interface-implementation scenario added to Tests/Core/TestDeterministicBuild.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions