#4120·ILSpy

Generated PDB contains no imports and its ImportScope table has two roots

Author: siegfriedpammerCreated Sep 8, 2026Updated Sep 8, 2026
LabelsBugPDBGen

Summary

A PDB generated by ilspycmd -genpdb contains no imports at all, and its ImportScope table has two root rows instead of one. The debugger's expression evaluator resolves unqualified names through the import scope chain, so with such a PDB attached no using directive is in scope anywhere: List<int> or Task.Delay(1) fail to resolve in Watch / Immediate / conditional breakpoints, while the embedded source prominently shows the using directives they would need.

Reproduction

Probe.cs, compiled with the SDK so the compiler's own PDB is available as a reference:

csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ProbeNs
{
	public class Probe
	{
		public static async Task<int> MixedAsync(IEnumerable<int> source)
		{
			List<int> chunks = new List<int>();
			foreach (int value in source)
				chunks.Add(value);
			await Task.Yield();
			return chunks.Count;
		}
	}
}
ilspycmd -genpdb Probe.dll

ImportScope table of the generated PDB:

r1: parent=nil imports=0
r2: parent=nil imports=0

ImportScope table of the compiler's PDB for the same assembly:

r1: parent=nil imports=0
r2: parent=r1  imports=[System, System.Collections.Generic, System.Threading.Tasks]
r3: parent=r2  imports=0

Every assembly checked shows the same shape (Newtonsoft.Json 13.0.4, AutoMapper 16.2.0, Microsoft.Extensions.Http 10.0.11).

Cause

In ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs:

  • globalImportScope (line 45) is never added to importScopes, so GenerateImportScopes (line 65) never encodes it and its Handle is never assigned.
  • The decompiler prints all using directives at file level, before the namespace declaration, so VisitUsingDeclaration (line 96) adds every one of them to currentImportScope - which at that point is still globalImportScope. They are dropped.
  • VisitNamespaceDeclaration (line 85) creates a scope whose Parent is globalImportScope. scope.Parent == null is therefore false and GenerateImportScopes writes scope.Parent.Handle, the never-assigned handle, i.e. nil. The metadata-level global scope created in PortablePdbWriter.WritePdb (line 108) ends up orphaned and each namespace scope becomes a second root.

Posted by an AI agent (Claude) on Siegfried's behalf.