Was ist eine PDB (Program Database)? — Debugging-Informationen, Symbole und Source Link verstehen

· · .NET, C#, Visual Studio, PDB, Debugging, Symbole, SourceLink, Diagnose, Betrieb, Nutzung vorhandener Ressourcen

1. Das Wichtigste zuerst

Wenn Sie eine .NET- oder C++-Anwendung bauen, wird manchmal zusätzlich zur .dll oder .exe eine .pdb-Datei erzeugt.

Zum Beispiel eine solche Ausgabe:

MyApp.exe
MyApp.dll
MyApp.pdb

Entwickelt man weiter, ohne zu wissen, was diese .pdb ist, tauchen Fragen wie diese auf:

  • Darf .pdb in der Produktionsumgebung liegen?
  • Läuft die Anwendung ohne .pdb nicht?
  • Ist es seltsam, dass bei einem Release-Build eine .pdb entsteht?
  • Enthält .pdb den kompletten Quellcode?
  • Lässt sich mit .pdb garantiert immer ein Haltepunkt setzen?
  • Warum wird .pdb für Dump-Analyse und Fehleruntersuchung benötigt?
  • Wie sollte .pdb in NuGet-Paketen behandelt werden?
  • Worin unterscheiden sich Source Link, Symbolserver und .snupkg?

Im alltäglichen Implementieren spielt die PDB keine Hauptrolle. Bei Fehleruntersuchung, Dump-Analyse, Bibliotheksverteilung, der Lesbarkeit von Betriebslogs und beim Debugging ist sie jedoch ziemlich wichtig.

Zunächst das Fazit.

Die PDB ist eine Debugging-Informationsdatei, die die ausführbare Datei bzw. Assembly mit dem Quellcode verbindet. Sie ist nicht der Hauptteil, der die Anwendung zum Laufen bringt, sondern dient dazu, Debugger und Diagnosewerkzeugen mitzuteilen, „welcher Befehl welcher Quellzeile entspricht“, „welche lokale Variable was ist“ und „welche Quelle man sich ansehen sollte“.

Dieser Artikel behandelt die PDB nicht als bloße „Debugging-Beigabe“, sondern ordnet ein, wie man sie in der Praxis als Arbeitsergebnis behandeln sollte.

Lesehinweise nach Zielsetzung

Insgesamt gibt es 43 Kapitel, aber eine vollständige Lektüre ist nicht zwingend. Wenn Sie bereits wissen, was Sie interessiert, genügen die folgenden Kapitel.

Was Sie wissen möchten Zu lesende Kapitel
Was eine PDB ist, was darin enthalten ist Kapitel 2 → 5 → 6
Nur entscheiden, ob sie in die Produktion darf Kapitel 24 (Entscheidungsachsen) → 25 (Vertraulichkeit) → 27 (Ablagemuster)
Nur die Richtlinie für die NuGet-Verteilung festlegen Kapitel 20 (Source Link) → 22 (embedded) → 23 (.snupkg) → 41 (Konfigurationsbeispiele)
Nur die Build-Einstellungen festlegen Kapitel 13 (PDB-Typen) → 14 (DebugType) → 41 (Empfohlene Einstellungen)
Beim Debuggen lassen sich Symbole nicht laden Kapitel 18 (Suchreihenfolge) → 29 (Eingrenzungsschritte) → 30 (Just My Code)
Dumps von Produktionsstörungen analysieren Kapitel 11 (Stacktrace) → 12 (Dump-Analyse) → 26 (Aufbewahrung in CI/CD)
Verbreitete Missverständnisse zuerst ausräumen Kapitel 7 bis 10
Nur die Konfiguration interessiert Der nächste Abschnitt und Kapitel 41

Vorab nur das Fazit — Empfohlene Einstellungen für die Praxis

Statt bis Kapitel 41 zu warten, hier vorab nur die Ergebnisse für die drei gebräuchlichsten Muster. Begründungen und Ausnahmen finden sich im jeweiligen Kapitel.

Zielgruppe Empfohlene Einstellung Kurz gesagt
Interne Anwendung <DebugType>portable</DebugType>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
Auch bei Release eine PDB erzeugen und als CI-Artefakt unbedingt aufbewahren
Öffentliche NuGet-Bibliothek Zusätzlich zum Obigen
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
Source Link aktivieren und die PDB als .snupkg verteilen
Kleines internes Tool <DebugType>embedded</DebugType> Vergessenes Mitliefern der PDB vermeiden. Die Größenzunahme wird in Kauf genommen

Bei extern verteilten Produkten liegen die Dinge unterschiedlich – lesen Sie dazu Kapitel 41. Und es gibt einen Grundsatz, der für jedes Muster gilt.

Unabhängig davon, ob sie in die Produktion ausgeliefert wird: Die bei diesem Build entstandene PDB muss unbedingt aufbewahrt werden.

Denn eine identische PDB im Nachhinein neu zu erzeugen, ist selbst bei einem Rebuild aus demselben Commit überraschend schwierig (Kapitel 40).

2. Was ist eine PDB

PDB steht für Program Database und wird im Deutschen als Programmdatenbank bezeichnet. Die .pdb-Datei wird oft auch Symboldatei genannt.

Ein Symbol ist grob gesagt eine Information über einen Namen oder eine Position im Programm. Zum Beispiel:

  • Funktionsnamen
  • Methodennamen
  • Lokale Variablennamen
  • Parameternamen
  • Typinformationen
  • Quelldateinamen
  • Quellzeilennummern
  • Die Zuordnung von Positionen im Quellcode zu Befehlen nach der Kompilierung
  • Informationen, die der Debugger zum Setzen von Haltepunkten braucht
  • Informationen zum Abrufen des Quellcodes für Source Link

Während Sie Quellcode schreiben, gibt es solche für Menschen verständlichen Namen.

public decimal CalculateTotalPrice(Order order)
{
    var subtotal = order.Lines.Sum(x => x.Price * x.Quantity);
    var tax = subtotal * 0.10m;
    return subtotal + tax;
}

Die fertig gebaute .dll oder .exe ist jedoch nicht der Quellcode selbst. Bei .NET sind es IL und Metadaten, bei nativem C++ eine Binärdatei, die dem Maschinencode nahekommt.

Dadurch werden solche Informationen aus der ausführbaren Datei allein nicht mehr ausreichend oder nur schwer ersichtlich.

Zu welcher Zeile welcher .cs-Datei gehört dieser Maschinencode / diese IL?
Zu welcher Position welcher Funktion gehört diese Adresse?
Wie hieß diese lokale Variable eigentlich?
An welcher Befehlsposition sollte dieser Haltepunkt tatsächlich gesetzt werden?
Welcher Quelle entspricht dieser Stapel-Frame?

Die PDB ist die Datei, die diese Lücke schließt.

3. Wird die PDB für die Ausführung benötigt

Normalerweise wird die PDB für die Ausführung der Anwendung nicht benötigt. Mit .dll oder .exe lässt sich die Anwendung starten. Nur weil .pdb fehlt, bedeutet das nicht, dass der normale Ablauf nicht ausgeführt werden kann.

Ohne PDB wird jedoch Folgendes schwierig:

Was Sie tun möchten Was ohne PDB zum Problem wird
In Visual Studio präzise Einzelschritte ausführen Die Zuordnung von Quellzeile und Ausführungsposition fehlt
Einen Haltepunkt setzen Die zugehörige Befehlsposition ist unbekannt, der Haltepunkt kann unaufgelöst bleiben
Dateiname und Zeilennummer im Exception-Stacktrace anzeigen Die Zeilennummerninformation fehlt oder ist unzureichend
Eine Dump-Datei analysieren Stapel, Variablen und Typen sind schwer auszulesen
In eine externe Bibliothek hineinspringen Keine Verbindung zum Quellcode der Bibliothek möglich
Einen nativen Absturz lesen Nur Adressen, Funktionsname und Position bleiben unbekannt

Die PDB ist also keine „Datei zum Ausführen“, sondern eine „Datei zum Untersuchen“.

Dieser Unterschied ist wichtig. Tritt eine Produktionsstörung auf, läuft die Anwendung zwar auch ohne PDB, aber der Untersuchende gerät ohne PDB in Schwierigkeiten. Deshalb sollte die PDB unabhängig davon, ob sie in der Ausführungsumgebung liegt, unbedingt als Build-Artefakt aufbewahrt werden.

4. Was bringt eine PDB

Mit PDB können Debugger und Diagnosewerkzeuge die Binärdatei leichter in für Menschen lesbare Informationen zurückverwandeln.

Eine Absturzinformation ohne PDB kann zum Beispiel so aussehen:

MyApp.dll!0x00007ff9a1234567
MyApp.dll!0x00007ff9a1234abc
MyApp.dll!0x00007ff9a1234def

Wird die PDB korrekt geladen, wird so viel sichtbar:

MyApp.Services.OrderService.CalculateTotalPrice(Order order) Line 42
MyApp.Controllers.OrderController.Post(CreateOrderRequest request) Line 87
MyApp.Program.Main(string[] args) Line 16

Dieser Unterschied ist erheblich.

Bei der ersten Variante muss die Untersuchung bei der Adresse beginnen. Bei der zweiten gelangt man von Anfang an dorthin, „welche Methode, welche Zeile“.

Bei der Fehleruntersuchung ist entscheidend, ob man sich in den ersten 30 Minuten der Ursache nähern kann. Allein dadurch, dass die PDB erhalten geblieben ist, ändert sich der Ausgangspunkt der Untersuchung vollständig.

5. Was in der PDB enthalten ist

Welche Informationen in einer PDB stecken, hängt von Sprache, Compiler, PDB-Format und Build-Einstellungen ab. Man kann daher nicht pauschal sagen: „Das hier ist auf jeden Fall in einer PDB enthalten.“

Aus Sicht von .NET-Entwicklern werden aber im Wesentlichen diese Informationen erwartet.

Zuordnung von Quelldatei und kompiliertem Code
Quellzeilennummern
Symbole von Methoden und Funktionen
Lokale Variablennamen
Scope-Informationen
Pfad und Prüfsumme der Quelldatei
Source-Link-Informationen
Gegebenenfalls eingebetteter Quellcode

Besonders wichtig ist die Zuordnung zwischen Position im Quellcode und Position zur Laufzeit.

Eine in C# geschriebene Zeile kann in IL oder im nach dem JIT erzeugten nativen Code zu mehreren Befehlen werden. Umgekehrt können durch Optimierung mehrere Quellzeilen zusammengefasst werden, verschwinden oder in scheinbar veränderter Reihenfolge erscheinen.

Der Debugger nutzt die Informationen der PDB, um zu bestimmen, „welche Quellzeile jetzt angezeigt werden soll“.

6. Was nicht in der PDB enthalten ist

Bei der PDB verringert sich ein Missverständnis eher, wenn man versteht, was nicht enthalten ist, statt was enthalten ist.

Die PDB ist normalerweise nicht Folgendes:

  • Nicht die Anwendung selbst
  • Keine für die Ausführung zwingend erforderliche Laufzeitdatei
  • Kein vollständiges Backup des gesamten Quellcodes
  • Kein Ersatz für ein Git-Repository
  • Kein Mittel, um sämtliche Build-Einstellungen oder Umgebungsinformationen vollständig wiederherzustellen
  • Kein Mittel, das die Ursache eines Fehlers allein automatisch erklärt

Zu beachten ist aber Folgendes.

Eine PDB kann Pfade von Quelldateien, Typnamen, Funktionsnamen, lokale Variablennamen und gegebenenfalls Source-Link-Informationen oder eingebetteten Quellcode enthalten.

Man kann daher nicht sagen: „Weil es nicht der Quellcode selbst ist, kann man sie bedenkenlos veröffentlichen.“

Es können interne Projektnamen, Pfade mit Benutzernamen, die interne Verzeichnisstruktur, unveröffentlichte Typnamen sowie Namen enthalten sein, aus denen sich Geschäftslogik erraten lässt.

7. Verbreitetes Missverständnis 1: Mit PDB wird die Produktion langsamer

Nur weil eine PDB danebenliegt, wird die normale Anwendungsverarbeitung nicht langsamer.

Die PDB wird verwendet, wenn Debugger oder Diagnosewerkzeuge Symbolinformationen benötigen. Die normale Verarbeitung der Anwendung läuft nicht so ab, dass jedes Mal die PDB gelesen wird.

Natürlich wird sie in Situationen genutzt, in denen Datei- und Zeilennummer im Exception-Stacktrace aufgelöst werden, ein Debugger angehängt wird, oder Profiler und Diagnosewerkzeuge Symbole lesen.

Aber die Auffassung „Eine PDB macht alles dauerhaft langsamer, deshalb darf sie unter keinen Umständen in der Produktion liegen“ ist zu pauschal.

In der Praxis ist es sicherer, so zu denken:

Es besteht kaum Grund, die PDB allein aus Performance-Gründen zu löschen
Die Ablagestrategie richtet sich nach Offenlegungsumfang, Informationslecks, Artefaktgröße und Betriebsrichtlinie
Auch wenn sie nicht ausgeliefert wird: Die PDB desselben Builds muss unbedingt aufbewahrt werden

8. Verbreitetes Missverständnis 2: Bei Release-Builds braucht man keine PDB

Auch bei Release-Builds ist die PDB nützlich. Gerade für die Untersuchung von Produktionsstörungen wird die PDB des Release-Builds benötigt.

Läuft in der Produktion ein Release-Build, nützt eine PDB des Debug-Builds nichts. Der Debugger benötigt genau die PDB, die beim Erstellen dieser Produktionsbinärdatei erzeugt wurde.

Wichtig ist hier diese Unterscheidung:

Punkt Bedeutung
Debug / Release Build-Konfiguration wie Optimierung, bedingte Kompilierung, Ausgabeeinstellungen
Vorhandensein einer PDB Ob Debugging-Informationen erzeugt und gespeichert werden
Debugbarkeit Bestimmt durch Optimierungsgrad, Inhalt der PDB, Übereinstimmung mit der Quelle, JIT-Verhalten und mehr

Release-Builds sind oft optimiert, daher ist die Einzelschrittausführung schwerer nachzuvollziehen als bei Debug-Builds. Lokale Variablen können durch Optimierung verschwinden, oder das Programm hält nicht mehr in der Reihenfolge der Quellzeilen an.

Trotzdem lassen sich mit PDB folgende Informationen leichter gewinnen:

  • Die Quellzeile, an der die Exception auftrat
  • Die Methodennamen im Stapel
  • Die zugeordnete Position bei der Dump-Analyse
  • Die Funktionsnamen im Profiling-Ergebnis
  • Der Abgleich von Log und Quellcode

Release bedeutet nicht, dass keine PDB nötig ist. Gerade weil es Release ist, muss die zu diesem Build gehörende PDB aufbewahrt werden.

9. Verbreitetes Missverständnis 3: Mit PDB lässt sich jede beliebige Binärdatei debuggen

Eine PDB lässt sich nicht beliebig für jede .dll oder .exe wiederverwenden.

Der Debugger prüft, ob die Zielbinärdatei und die PDB übereinstimmen. Erzwingt man die Verwendung einer nicht übereinstimmenden PDB, geraten Quellzeilen, Funktionen und Variablen aus dem Gleichgewicht.

In folgenden Situationen zum Beispiel ist die PDB, auch wenn sie vorhanden ist, unbrauchbar oder nutzlos:

Man versucht, eine lokal neu gebaute PDB auf die Produktions-DLL anzuwenden
Dieselbe Versionsnummer, aber tatsächlich aus einem anderen Commit gebaut
Man versucht, eine DLL nach einem Hotfix mit der PDB von vor dem Hotfix zu laden
Optimierungseinstellungen oder bedingte Kompilierung unterscheiden sich

Man sollte bei der PDB nicht davon ausgehen, dass „bei gleichem Quellcode meist alles passt“, sondern davon, dass sie „zu genau demselben Build-Ergebnis gehören muss“.

Deshalb ist es in CI/CD üblich, in dieser Einheit zu speichern:

Commit-ID
Build-Nummer
Version des Artefakts
.dll / .exe
.pdb
Informationen zur Quellreferenz

Wichtig ist, diese Kombination nicht auseinanderzureißen.

10. Verbreitetes Missverständnis 4: Mit PDB lässt sich der Code auch ohne Quellcode vollständig lesen

Auch wenn eine PDB vorhanden ist, bedeutet das nicht zwingend, dass der Quellcode darin enthalten ist.

Die PDB enthält hauptsächlich Informationen, die Quelle und Binärdatei einander zuordnen. Sie kann Pfad und Prüfsumme der Quelldatei sowie Source-Link-Informationen enthalten, aber eine gewöhnliche PDB enthält nicht immer den vollständigen Quelltext.

Um in einem Debugger in eine externe Bibliothek hineinzuspringen, benötigt man daher eine der folgenden Voraussetzungen:

Dieselbe Quelldatei liegt lokal vor
Source Link kann die Quelle des richtigen Commits abrufen
Der Quellcode ist in der PDB eingebettet
Man behilft sich mit dekompiliertem Quellcode

Visual Studio verfügt auch über eine Funktion, .NET-Assemblies zu dekompilieren und anzuzeigen. Das Ergebnis der Dekompilierung ist jedoch nicht der ursprüngliche Quellcode selbst. Kommentare, Leerzeichen, lokale Variablennamen, die ursprüngliche Schreibweise und Präprozessorbedingungen gehen verloren oder ändern sich.

Für die Untersuchung ist das praktisch, sollte aber nicht blind als Ersatz für die Originalquelle vertraut werden.

11. PDB und Stacktrace

Bei .NET-Exception-Stacktraces ändert sich die Darstellung je nachdem, ob eine PDB vorhanden ist.

Auch ohne PDB werden manchmal Methoden- und Typnamen angezeigt. Das liegt daran, dass .NET-Assemblies Metadaten enthalten.

Wenn Sie aber auch Dateiname und Zeilennummer sehen möchten, wird die PDB wichtig.

Ohne PDB sieht der Stacktrace zum Beispiel oft so aus:

System.InvalidOperationException: Order is invalid
   at MyApp.Services.OrderService.Validate(Order order)
   at MyApp.Controllers.OrderController.Post(CreateOrderRequest request)

Ist die PDB vorhanden und lassen sich Zeilennummern auflösen, ändert sich das so:

System.InvalidOperationException: Order is invalid
   at MyApp.Services.OrderService.Validate(Order order) in /src/MyApp/Services/OrderService.cs:line 42
   at MyApp.Controllers.OrderController.Post(CreateOrderRequest request) in /src/MyApp/Controllers/OrderController.cs:line 87

Zeigt sich dieser Unterschied im Betrieb, ändert sich die Untersuchungsgeschwindigkeit erheblich.

Allerdings kann das Erscheinen von Dateipfaden selbst eine Offenlegung von Informationen darstellen. Weitere Maßnahmen sind nötig, etwa keine Stacktraces in nach außen zurückgegebenen Fehlerantworten auszugeben oder den Aufbewahrungsort der Logs einzuschränken.

12. PDB und Dump-Analyse

Am dankbarsten wird die PDB bei der Dump-Analyse empfunden.

Angenommen, in der Produktionsumgebung tritt eines dieser Probleme auf:

  • Der Prozess ist abgestürzt
  • Die CPU-Auslastung bleibt dauerhaft hoch
  • Es sieht nach einem Deadlock aus
  • Der Speicherverbrauch wächst kontinuierlich
  • Es kommt keine Antwort zurück
  • Der Absturz liegt an der Grenze zu einer nativen Bibliothek

In diesem Fall wird eine Dump-Datei erfasst und analysiert.

Der Dump allein reicht jedoch nicht aus. Im Dump enthalten ist nur der Prozesszustand zu diesem Zeitpunkt. Um die darin sichtbaren Stapel und Module in für Menschen lesbare Namen und Quellzeilen zu übersetzen, wird die passende PDB benötigt.

Bei .NET erfolgt die Analyse mit dotnet-dump, Visual Studio, WinDbg, SOS und Ähnlichem. Sind native Anteile beteiligt, wird die Symbolkonfiguration von WinDbg wichtig.

Häufige Fehler bei der Dump-Analyse:

Die Produktions-DLL ist noch vorhanden, aber die PDB fehlt
Eine PDB ist vorhanden, aber es handelt sich um ein anderes, lokal neu gebautes Exemplar
Die Symbole von Windows / .NET-Runtime lassen sich nicht laden
Die PDB der eigenen Anwendung ist vorhanden, aber Symbole von Drittanbieter-Bibliotheken fehlen
Der Symbolpfad ist nicht konfiguriert, sodass der Debugger die PDB nicht findet

Für die Dump-Analyse reicht es oft nicht, sich erst nach dem Auftreten des Problems vorzubereiten. Wichtig ist, die PDB zum Zeitpunkt des Builds zu sichern, sodass sie bei der Untersuchung abrufbar ist.

13. Windows PDB und Portable PDB

Es gibt mehrere PDB-Formate.

In der Praxis sind vor allem diese zwei relevant:

Typ Hauptkontext Merkmale
Windows PDB Visual C++, klassisches Windows-Debugging Format, das bei nativer Windows-Entwicklung häufig verwendet wird
Portable PDB .NET / .NET Core und neuer Plattformübergreifend nutzbares Format für .NET

Ab .NET Core ist die Portable PDB wichtig. Die Portable PDB ist ein Format, das nicht nur unter Windows, sondern auch unter Linux und macOS verarbeitet werden kann.

Bei Projekten aus der .NET-Framework-Ära oder alten Visual-Studio-Konfigurationen begegnet man gelegentlich noch der Windows PDB. Bei aktuellen SDK-Style-Projekten von .NET geht man inzwischen zunehmend standardmäßig von der Portable PDB aus.

Zu beachten ist dabei, dass beide dieselbe Dateiendung .pdb haben.

Allein an der Dateiendung lässt sich der Formatunterschied nicht erkennen. Ist von „PDB“ die Rede, sollte man prüfen, in welchem Kontext davon gesprochen wird.

Geht es um die Portable PDB von .NET?
Geht es um die Windows PDB von Visual C++?
Geht es um ein altes Projekt unter .NET Framework?
Geht es um die PDB für die NuGet-Verteilung?
Geht es um Symbole, wie sie in WinDbg verwendet werden?

14. DebugType in .NET

In C#-Projekten lässt sich mit DebugType festlegen, wie Debugging-Informationen ausgegeben werden.

Die gängigsten Werte sind:

DebugType Bedeutung
portable Erzeugt eine Portable PDB als separate Datei
embedded Bettet Debugging-Informationen im Umfang einer Portable PDB in .dll / .exe ein
full Erzeugt die PDB im Standardformat der jeweiligen Plattform
pdbonly Ab C# 6.0 praktisch ohne Unterschied zu full
none Erzeugt keine PDB

In aktuellen .NET-SDK-Style-Projekten ist DebugType von C# sowohl für Debug als auch für Release standardmäßig portable. Es ist daher normalerweise nicht nötig, DebugType nur zu diesem Zweck explizit anzugeben, um eine Portable PDB zu erzeugen.

Eine explizite Angabe ist sinnvoll, wenn ein Projekt oder eine Organisation festschreiben möchte, „dieses Format zu fixieren“, wenn der Unterschied zu älteren Projekten sichtbar gemacht werden soll, oder wenn man sich abweichend vom Standard für embedded bzw. none entscheidet.

Bei NuGet-Bibliotheken oder externer Verteilung wird abgewogen, ob portable, embedded oder .snupkg gewählt wird.

Um zum Beispiel explizit eine Portable PDB auszugeben, schreiben Sie:

<PropertyGroup>
  <DebugType>portable</DebugType>
</PropertyGroup>

Soll die PDB nicht als separate Datei, sondern in die Assembly eingebettet werden:

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

Wenn Sie unbedingt keine PDB im Release-Build ausgeben möchten, können Sie schreiben:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugType>none</DebugType>
</PropertyGroup>

Das sollte jedoch mit Bedacht entschieden werden. Eine Einstellung ohne Release-PDB kann bei einer Untersuchung von Produktionsstörungen zum eigenen Nachteil werden.

15. Genügt allein DebugSymbols=false

Möchte man die Erzeugung der PDB stoppen, findet man manchmal Beispiele, bei denen DebugSymbols auf false gesetzt wird.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugSymbols>false</DebugSymbols>
</PropertyGroup>

Ist die Absicht jedoch, sicher keine PDB zu erzeugen, ist es eindeutiger, DebugType auf none zu setzen.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugType>none</DebugType>
</PropertyGroup>

Diese Einstellung kommt gelegentlich als Verteilungsrichtlinie für Bibliotheken oder Anwendungen zum Einsatz. Aber – es sei wiederholt – keine PDB zu erzeugen, senkt die Fähigkeit zur Fehleruntersuchung.

In der Praxis wird oft so unterschieden:

Die PDB wird unbedingt als Build-Artefakt erzeugt
Ob sie auf den Produktionsserver ausgeliefert wird, wird separat entschieden
Wird sie nicht ausgeliefert, wird sie dennoch im CI-Artefakt oder Symbolserver aufbewahrt

16. Die PDB bei C++ unterscheidet sich etwas von .NET

Die PDB bei C++ hat einen etwas anderen Kontext als die PDB bei .NET.

Bei Visual C++ wird die PDB durch Optionen wie /Zi oder /ZI erzeugt. Außerdem spielen sowohl die vom Compiler verwendete PDB als auch die vom Linker für die endgültige .exe / .dll erzeugte PDB eine Rolle.

Bei C++ ist die PDB sehr wichtig, um Adressen, Funktionen, Typen, lokale Variablen, Inlining und Positionen nach der Optimierung im nativen Code zu lesen.

Auch bei der Untersuchung nativer Störungen ergibt sich ohne PDB oft dieser Zustand:

Die Ausnahmeadresse ist bekannt
Der Modulname ist bekannt
Aber Funktionsname und Quellzeile bleiben unbekannt

Bei Anwendungen, die C++ und C# vermischen, bei P/Invoke, bei C++/CLI oder bei .NET-Anwendungen, die native DLLs aufrufen, wird nicht nur die PDB der .NET-Seite, sondern auch die der nativen Seite benötigt.

17. Public Symbols und Private Symbols

In der Welt der Windows-Symbole gibt es die Unterscheidung zwischen public symbols und private symbols.

Grob gesagt ist das der Unterschied:

Typ Vorstellung der enthaltenen Informationen
private symbols Nahezu vollständige Informationen, einschließlich lokaler Variablen, Typen, Parameter und detaillierter interner Angaben
public symbols Auf Funktionsnamen und Adressen beschränkte Informationen für die öffentliche Weitergabe

Bei extern verteilten Symbolen werden private symbols mitunter entfernt, sodass nur public symbols übrig bleiben.

Das dient dazu, Debugbarkeit und Umfang der Informationsoffenlegung ins Gleichgewicht zu bringen.

Man möchte zum Beispiel für die Absturzanalyse des eigenen Produkts wenigstens die Funktionsnamen ausgeben. Interne lokale Variablennamen oder Typinformationen möchte man aber nicht preisgeben. In solchen Fällen kommt eine stripped PDB zum Einsatz.

Bei nativer Entwicklung für Windows wird mit Werkzeugen wie PDBCopy mitunter eine PDB erzeugt, aus der private symbols entfernt wurden.

Für die interne Fehleruntersuchung muss dagegen die vollständige PDB aufbewahrt werden. Ist nur die für extern beschnittene PDB übrig, gerät man bei einer tiefergehenden Untersuchung in Schwierigkeiten.

18. Wo sucht der Debugger nach der PDB

Visual Studio und WinDbg suchen die PDB an mehreren Orten.

Typische Orte sind unter anderem:

Der Ausgabeordner des Projekts
Derselbe Ordner wie .dll / .exe
Der in .dll / .exe eingetragene ursprüngliche Pfad der PDB
Der in den Symboleinstellungen von Visual Studio angegebene Ordner
Der lokale Symbol-Cache
Ein interner Symbolserver
Der Microsoft Symbol Server
Der NuGet.org Symbol Server
Symbolserver wie Azure Artifacts

Wird die PDB nicht geladen, obwohl sie vorhanden ist, prüfen Sie der Reihe nach diese Punkte:

Stimmt die PDB mit der Zielbinärdatei überein?
Liegt die PDB im Suchpfad des Debuggers?
Ist der Symbolserver erreichbar?
Liegt im lokalen Cache noch eine veraltete Version?
Ist das Laden von Symbolen für das Zielmodul deaktiviert?
Wird das Modul durch die Just-My-Code-Einstellung als externer Code behandelt?

In Visual Studio lässt sich beim Debuggen im Fenster Modules der Symbolladestatus jedes Moduls prüfen.

Debug
  Windows
    Modules

Hier betrachten Sie den Symbol Status der Ziel-DLL.

Häufig sind diese vier Anzeigen:

Symbols loaded.
Cannot find or open the PDB file.
PDB does not match image.
Skipped loading symbols.

Hakt es an der PDB, ist ein Blick ins Modules-Fenster meist der schnellste Weg.

19. Was ist ein Symbolserver

Von hier bis Kapitel 23 geht es darum, wie man die PDB „ablegt und dem Debugger zustellt“. Da viele Akteure beteiligt sind, zunächst ein Diagramm des Gesamtzusammenhangs.

Im selben Ordner ablegenIm CI-Artefakt aufbewahrenAn Symbolserver veröffentlichenÜber NuGet verteilenIn die dll einbettenBuilddll / exe und pdb entstehen gleichzeitigdll / exewird verteilt / ausgeführtpdbDebugging-InformationenQuell-Repositoryder Commit dieses BuildsWie wird die PDB zugestellt?pdb neben der dllKapitel 27 Muster 1CI-ArtefaktKapitel 27 Muster 2SymbolserverKapitel 19snupkgKapitel 23embedded PDBKapitel 22Debugger / DiagnosewerkzeugSource LinkKapitel 20 und 21Stacktrace mit ZeilennummernEinzelschrittausführungDump-Analyse

Die linke Hälfte des Diagramms zeigt „wie die PDB aufbewahrt und verteilt wird“, die rechte Hälfte „wie der Debugger sie nutzt“. Darunter nimmt allein Source Link eine andere Rolle ein. Es ist kein Mittel, um die PDB zuzustellen, sondern eine Information, mit der man sich von der zugestellten PDB aus den Quellcode besorgt. Binärdatei, PDB und Quellcode werden getrennt verwaltet, sodass es das Thema der Kapitel 19 bis 23 ist, diese drei wieder miteinander zu verknüpfen.

Beginnen wir mit dem Symbolserver.

Ein Symbolserver ist ein Mechanismus, mit dem der Debugger PDB und andere Symboldateien bei Bedarf abrufen kann.

Es lässt sich bis zu einem gewissen Grad auch betreiben, indem man die PDB einfach in einen freigegebenen Ordner legt. Sobald jedoch die Anzahl der Versionen wächst, bricht das schnell zusammen.

Die PDB von MyApp v1.0.0
Die PDB von MyApp v1.0.1
Die PDB von MyApp v1.0.1 nach dem Hotfix
Die PDB von MyApp v1.1.0-beta
Eine PDB mit abweichender Konfiguration nur für Kunde A

Es entstehen Dutzende Dateien mit demselben Namen MyApp.pdb.

Ein Symbolserver ordnet die PDB nicht nach bloßem Dateinamen, sondern anhand von Informationen zur Übereinstimmung mit der Binärdatei. Dadurch findet der Debugger leichter „die zu dieser DLL passende PDB“.

Beispiele für in der Praxis denkbare Konstellationen:

Microsoft Symbol Server
  Wird verwendet, um Symbole für Windows- oder .NET-Runtime zu beziehen

NuGet.org Symbol Server
  Wird verwendet, um Symbole öffentlicher NuGet-Pakete zu beziehen

Interner Symbolserver
  Speichert die PDB eigener Anwendungen und Bibliotheken

Lokaler Symbol-Cache
  Nutzt einmal abgerufene PDBs erneut, um das Debugging zu beschleunigen

Wer Produktionsstörungen bei einem internen Dienst untersucht, profitiert von einer Einrichtung, bei der die CI die PDB automatisch an einen internen Symbolspeicher veröffentlicht.

Source Link ist ein Mechanismus, der die PDB mit dem Quellverwaltungssystem verbindet.

Selbst wenn die PDB weiß, „diese Position entspricht dieser Quelldatei“, kann der Debugger sie nicht anzeigen, wenn diese Quelldatei nicht lokal vorliegt.

Mit Source Link kann der Debugger anhand der in der PDB enthaltenen Informationen die Quelldatei des passenden Commits von GitHub, Azure Repos, GitLab, Bitbucket und ähnlichen Diensten abrufen.

Source Link löst also folgendes Problem:

Man möchte in eine über NuGet bezogene Bibliothek hineinspringen
Man hat den Quellcode dieser Bibliothek nicht lokal geklont
Aber PDB und Repository-Informationen liegen vor
Der Debugger holt sich die Quelle des richtigen Commits

Das verbessert die Erfahrung von Bibliotheksnutzern erheblich.

Der entscheidende Punkt bei Source Link ist, dass nicht „der aktuellste main-Branch“, sondern „der Commit, aus dem diese Binärdatei entstand“ referenziert wird.

Es ist bedeutsam, sich mit der Quelle zum Zeitpunkt des Builds zu verbinden, nicht mit der neuesten Quelle.

Ab .NET 8 hat sich der Umgang mit Source Link im SDK verbessert.

Bei häufig genutzten Anbietern wie GitHub, Azure Repos, GitLab und Bitbucket ist der Source-Link-Mechanismus mittlerweile bereits im .NET-SDK selbst enthalten.

Die frühere Vorstellung, man müsse zwingend explizit Pakete wie Microsoft.SourceLink.GitHub hinzufügen, ist daher zunehmend veraltet.

In manchen Fällen ist eine Überprüfung dennoch nötig:

Der Build erfolgt mit einem SDK älter als .NET 8
Es handelt sich um ein altes Projekt, das nicht im SDK-Style ist
Es wird ein eigenes On-Premises-Git-Hosting verwendet
Es wird ein nicht standardmäßig unterstützter Source-Link-Anbieter verwendet
Auch die Metadaten des NuGet-Pakets sollen vollständig eingerichtet werden

Um bei einem NuGet-Paket Repository-Informationen auszugeben, verwenden Sie diese Einstellung:

<PropertyGroup>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>

Müssen nicht versionierte Dateien in die PDB eingebettet werden, ziehen Sie diese Einstellung in Betracht:

<PropertyGroup>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

Das Einbetten wirkt sich jedoch auf den Umfang der Informationsoffenlegung aus. Was in der PDB enthalten sein soll, muss passend zum Verteilungsziel und zur Betriebsrichtlinie entschieden werden.

22. Was ist eine embedded PDB

Setzt man DebugType auf embedded, werden die Debugging-Informationen der Portable PDB in .dll oder .exe eingebettet. In diesem Fall wird keine separate .pdb-Datei erzeugt.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

Das ist zum Beispiel in diesen Situationen praktisch:

  • Man möchte möglichst nahe an einer Einzeldatei-Verteilung bleiben
  • Man möchte vermeiden, die PDB versehentlich nicht mitzuliefern
  • Bei einem kleinen internen Tool sollen die Debugging-Informationen mitgeführt werden
  • Bei einem NuGet-Paket soll der Aufwand für die PDB-Verteilung verringert werden

Es gibt aber auch Nachteile.

  • Die Assembly-Größe wächst
  • Das Verteilungsartefakt enthält immer Debugging-Informationen
  • Die Kontrolle über den Umfang der Informationsoffenlegung wird gröber
  • Bei großen Bibliotheken wirkt sich das auf Restore und Verteilungsgröße aus

Die embedded PDB ist praktisch, aber nicht die Lösung nach dem Motto „im Zweifel einfach alles auf embedded stellen“.

Besonders bei extern veröffentlichten Bibliotheken sollte überlegt werden, ob ein Symbolpaket via .snupkg, Source Link, das Mitliefern der gewöhnlichen PDB oder embedded am besten passt.

23. Was ist .snupkg

.snupkg ist das Symbolpaketformat von NuGet.

Ein gewöhnliches NuGet-Paket ist eine .nupkg. Ein Symbolpaket ist eine .snupkg.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

Die .nupkg enthält die Bibliothek selbst, auf die Nutzer verweisen. Die .snupkg dient dazu, die PDB für Debugging-Zwecke zu verteilen.

Wichtig ist hier, dass .snupkg grundsätzlich für die Portable PDB von verwaltetem Code gedacht ist. Zumindest der Symbolserver von NuGet.org unterstützt ausschließlich die Portable PDB – die Windows PDB, wie sie native Projekte etwa in C++ erzeugen, wird nicht akzeptiert. Möchten Sie eine Windows PDB verteilen oder aufbewahren, ziehen Sie andere Wege in Betracht, etwa das ältere .symbols.nupkg, einen internen Symbolserver oder ein CI-Artefakt.

Zum Erstellen schreiben Sie zum Beispiel:

<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

Es lässt sich auch über die Kommandozeile angeben:

dotnet pack -c Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg

Bei einer öffentlichen NuGet-Bibliothek lässt sich das Gleichgewicht zwischen Verteilungsgröße und Debugging-Erlebnis leichter herstellen, wenn Sie statt die PDB in die .nupkg selbst zu packen, .snupkg zusammen mit Source Link verwenden.

Achten Sie jedoch auf den Unterstützungsstand von Feed und Werkzeugen. Unterstützt der interne NuGet-Feed kein .snupkg, müssen Sie einen anderen Weg wählen.

24. Sollte die PDB in die Produktion

„Sollte die PDB in die Produktion“ lässt sich nicht mit einem einfachen Ja/Nein beantworten.

Es gibt vier Entscheidungsachsen:

Erleichterung der Fehleruntersuchung
Risiko der Offenlegung von Informationen
Verteilungsgröße
Betriebsregeln

Bei internen Systemen ist es durchaus praktikabel, .dll und .pdb im selben Ordner abzulegen. Exception-Logs zeigen dann leichter Zeilennummern, auch die Dump-Analyse wird einfacher.

Bei extern verteilten Anwendungen sollte man vorsichtig abwägen, ob die PDB einfach mitgeliefert wird. Interner Aufbau, lokale Variablennamen und Quellpfade können sichtbar werden. Bei Bedarf werden Optionen wie die Beschränkung auf public symbols, die Bereitstellung über einen Symbolserver oder nur die Aufbewahrung für den Support gewählt.

Bei Webdiensten sollte man neben der Frage, ob die PDB auf dem Server liegt, auch den Umgang mit im Log ausgegebenen Stacktraces bedenken. Stacktraces sollten nicht in nach außen gerichteten Antworten zurückgegeben werden. Auch wenn sie im internen Log verbleiben, sollten Zugriffsrechte und Aufbewahrungsdauer festgelegt werden.

Die praktische Empfehlung lautet:

Die PDB wird unbedingt erzeugt
Die PDB wird als Build-Artefakt aufbewahrt
Ob sie in die Produktion ausgeliefert wird, richtet sich nach dem Offenlegungsumfang des Systems
Bei externer Veröffentlichung wird der Offenlegungsumfang der Informationen geprüft
Sie wird an einem Ort abgelegt, von dem sie für die Dump-Analyse abrufbar ist

25. Ist die PDB eine vertrauliche Information

Eine PDB muss nicht zwingend so behandelt werden wie der eigentliche Quellcode oder ein privater Schlüssel. Sie als harmlose Datei zu behandeln, ist jedoch ebenfalls riskant.

Was sich aus einer PDB möglicherweise erkennen lässt:

  • Lokale Pfade der Entwickler
  • Die interne Ordnerstruktur
  • Projektnamen
  • Klassen- und Methodennamen
  • Lokale Variablennamen
  • Interne API-Namen
  • Fachbegriffe aus dem Geschäftsbereich
  • Die Repository-URL von Source Link
  • Eingebetteter Quellcode
  • Konfiguration des Source Server

Insbesondere bei alten Source-Server-Verfahren oder bei bestimmten Funktionen nativer PDBs kann ein Mechanismus beteiligt sein, bei dem der Debugger zum Abrufen der Quelle Befehle ausführt. Nicht vertrauenswürdige PDBs oder Symbolserver sollte man nicht bedingungslos verwenden.

Auch beim Füttern von Werkzeugen oder Bibliotheken, die PDBs analysieren, mit einer manipulierten PDB ist Vorsicht geboten. Bei Mechanismen, die von außen empfangene PDBs automatisch verarbeiten, sollte die Eingabe grundsätzlich als nicht vertrauenswürdig gelten.

Zusammengefasst ist folgender Umgang mit der PDB sicher:

Die vollständige interne PDB wird als internes Artefakt geschützt
Bei extern veröffentlichten PDBs werden Inhalt und Offenlegungsumfang geprüft
Ist das Source-Link-Ziel ein privates Repository, werden Authentifizierung und Berechtigungen verwaltet
Nicht vertrauenswürdige Symbolserver werden nicht im Debugger registriert

26. Aufbewahrungsrichtlinie für die PDB in CI/CD

Es nützt wenig, wenn die PDB nur auf dem lokalen PC des Entwicklers erhalten bleibt. Bei einer Produktionsstörung wird die PDB benötigt, die zu dem Build gehört, der tatsächlich veröffentlicht wurde.

Deshalb wird in CI/CD in dieser Form aufbewahrt:

Build-Nummer: 2026.06.10.1234
Commit-ID: abcdef123456...
Artefakte:
  MyApp.dll
  MyApp.pdb
  MyApp.deps.json
  MyApp.runtimeconfig.json
  package.zip
  container image digest

Nach Möglichkeit sollten außerdem diese Informationen verknüpft werden:

Git-Commit
Git-Tag
Release-Nummer
Umgebungsname
Build-Konfiguration
Ziel-Framework
RID
Digest des Container-Images
NuGet-Lock-Datei

Wichtig ist nicht, die PDB isoliert aufzubewahren, sondern nicht zu verlieren, zu welcher Binärdatei welche PDB gehört.

Ein Betrieb, bei dem auf einer Dateifreigabe dauerhaft nur MyApp.pdb abgelegt wird, bricht irgendwann zusammen. Sie sollte pro Build, pro Version und pro Commit abrufbar sein.

27. Ablagemuster für die PDB

Für den Umgang mit der PDB gibt es mehrere gängige Praxismuster.

Muster 1: Die PDB im selben Ordner wie die DLL ablegen

Am einfachsten.

publish/
  MyApp.dll
  MyApp.pdb

Der Vorteil ist die einfache Konfiguration. Debugger und Laufzeit finden sie leicht, auch Zeilennummern im Exception-Log lassen sich so leichter anzeigen.

Der Nachteil ist, dass das Verteilungsartefakt Debugging-Informationen enthält. Für eine externe Verteilung ist das mitunter ungeeignet.

Muster 2: Die PDB nicht ausliefern, sondern im CI-Artefakt aufbewahren

Auf dem Produktionsserver wird keine PDB abgelegt, sie verbleibt im CI-Artefakt.

release-artifacts/
  app.zip
  symbols.zip

Bei einer Produktionsstörung werden Dump und Log erfasst, und die PDB der passenden Build-Nummer wird zur Analyse herausgesucht.

Der Offenlegungsumfang lässt sich so leichter begrenzen, allerdings ist bei der Untersuchung ein zusätzlicher Abrufschritt nötig.

Muster 3: An einen internen Symbolserver veröffentlichen

In großen Teams ist das gut handhabbar.

Die CI veröffentlicht die PDB beim Build an einen Symbolspeicher. Visual Studio / WinDbg von Entwicklern und Untersuchenden verweisen auf diesen Symbolserver.

Der Vorteil ist, dass sich mehrere PDB-Versionen sicher handhaben lassen. Der Nachteil ist der nötige Ersteinrichtungs- und Zugriffskontrollaufwand.

Muster 4: Über NuGets .snupkg verteilen

Bei öffentlichen Bibliotheken eine starke Option.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

Nutzer stellen nur das gewöhnliche Paket wieder her, und nur die beim Debugging benötigten Symbole werden abgerufen.

In Kombination mit Source Link lässt sich auch bei externen Bibliotheken leichter in die Quelle hineinspringen.

Muster 5: Zu einer embedded PDB machen

Praktisch, um das versehentliche Nicht-Mitliefern der PDB zu vermeiden.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

Achten Sie jedoch auf Assembly-Größe und Offenlegungsumfang.

28. Was bei einem bestehenden Projekt zuerst zu prüfen ist

Wollen Sie den Umgang mit der PDB in einem bestehenden .NET-Projekt überarbeiten, prüfen Sie zunächst dies:

Wird bei Release-Builds eine PDB erzeugt?
Wo wird die erzeugte PDB gespeichert?
Werden die in die Produktion ausgelieferte DLL und die PDB einander zugeordnet aufbewahrt?
Lässt sich die PDB bei der Dump-Analyse abrufen?
Ist Source Link aktiviert?
Wird bei einer NuGet-Bibliothek eine .snupkg ausgegeben?
Enthält die PDB unnötig viele Informationen?

In der csproj prüfen Sie in etwa diese Einstellungen:

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

Bei einem NuGet-Paket kommt auch das infrage:

<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

Es ist jedoch nicht so, dass für alle Projekte dieselbe Einstellung passt. Bei internen Anwendungen, externen Bibliotheken, On-Premises-Produkten, SaaS und OSS unterscheidet sich die optimale Lösung.

29. Wie man vorgeht, wenn die PDB nicht geladen wird

Wird die PDB nicht geladen, sollten Sie nicht aus dem Bauch heraus neu bauen, sondern der Reihe nach eingrenzen.

1. Das Zielmodul prüfen

Suchen Sie im Modules-Fenster von Visual Studio die Ziel-DLL / -EXE.

Debug > Windows > Modules

Diese Spalten sollten Sie sich ansehen:

Module
Path
Symbol Status
Symbol File
Version
Timestamp

2. Den Symbolstatus betrachten

Die jeweiligen Anzeigen lassen sich in etwa so lesen:

Anzeige Bedeutung
Symbols loaded Bereits geladen
Cannot find or open the PDB file Die PDB wurde nicht gefunden
PDB does not match image Die PDB ist vorhanden, stimmt aber nicht mit der Zielbinärdatei überein
Skipped loading symbols Möglicherweise durch eine Einstellung nicht geladen

3. Prüfen, ob die vorliegende PDB zu demselben Build gehört

Ein häufiger Fehler ist die Verwendung einer lokal neu gebauten PDB.

Auch bei identischem Quellcode kann sie bei abweichenden Build-Bedingungen nicht übereinstimmen. Holen Sie sich aus dem CI-Artefakt genau die PDB, die zusammen mit der in die Produktion ausgelieferten Version entstanden ist.

4. Den Symbolpfad prüfen

In Visual Studio prüfen Sie hier:

Tools > Options > Debugging > Symbols

In WinDbg prüfen Sie es zum Beispiel so:

.sympath
.reload
!sym noisy

Nutzen Sie die öffentlichen Symbole von Microsoft, ist es praktisch, auch einen lokalen Cache anzugeben:

srv*C:\Symbols*https://msdl.microsoft.com/download/symbols

5. Den Cache in Verdacht nehmen

Manchmal greift man auf eine veraltete PDB oder einen beschädigten Cache zu. Prüfen Sie, ob sich das Löschen des Symbol-Caches, die Angabe eines anderen Caches oder ein detaillierterer Blick ins Lade-Log lohnt.

Wo finden sich die Einstellungen in Visual Studio

Hier eine Zusammenfassung der bisher genannten Einstellungsorte. Da sich die Hierarchie der Optionsfenster je nach Visual-Studio-Version unterscheidet, sind sowohl der alte als auch der neue Pfad angegeben.

Was Sie tun möchten Vollständiger Menüpfad
Status von Modul und Symbolen ansehen Debug > Windows > Modules (nur während des Debuggens aktiv. Strg + Alt + U)
Die Symbole eines Moduls manuell laden Im Modules-Fenster Rechtsklick auf das Modul > Load Symbols
Sehen, warum das Laden fehlschlug (wo gesucht wurde) Im Modules-Fenster Rechtsklick > Symbol Load Information
Die Suchorte für Symbole bearbeiten Tools (oder Debug) > Options > Debugging > Symbols
Im neuen Bildschirm Tools > Options > All Settings > Debugging > General > Symbols > Search Locations
Die Symbolserver von Microsoft / NuGet.org aktivieren Kontrollkästchen Microsoft Symbol Servers / NuGet.org Symbol Server auf demselben Bildschirm
Den lokalen Cache-Ort für Symbole angeben Cache symbols in this directory auf demselben Bildschirm
Vom Modules-Fenster zu den Symboleinstellungen springen Im Modules-Fenster Rechtsklick > Symbol Settings
Just My Code ein- / ausschalten Tools (oder Debug) > Options > Debugging > General > Enable Just My Code
Im neuen Bildschirm unter All Settings > Debugging > General
Source Link aktivieren Enable Source Link support im selben Bereich Debugging > General

Zum Cache-Ort gibt die offizielle Dokumentation zwei Hinweise:

  • Geben Sie keinen geschützten Ordner wie C:\Windows an. Verwenden Sie einen Ordner, in den Sie lesend und schreibend zugreifen können
  • Ist die Umgebungsvariable _NT_SYMBOL_PATH gesetzt, überschreibt sie die Einstellung Cache symbols in this directory

Ein weiterer Punkt: Enable Just My Code ist eine globale Einstellung für ganz Visual Studio. Sie gilt nicht pro Projekt – vergisst man, sie nach einem Anlassfall wieder zurückzustellen, schlägt das beim Debuggen eines anderen Projekts als „warum kann ich hier plötzlich nicht mehr hineinspringen“ zurück.

Bei DebugType empfiehlt es sich außerdem, es direkt in der <PropertyGroup> der csproj zu schreiben, statt es über die GUI anzufassen. Auch im Eigenschaftenfenster des Projekts gibt es einen entsprechenden Punkt, aber sowohl Name als auch Ort ändern sich je nachdem, ob es sich um ein SDK-Style-Projekt handelt und welche Visual-Studio-Version verwendet wird. Steht es in der csproj, bleibt es auch in Reviews und Diffs sichtbar (Kapitel 14).

30. Die PDB und „Just My Code“

Visual Studio verfügt über eine Einstellung namens Just My Code.

Sie beschränkt das Debugging-Ziel auf „eigenen Code“ und erschwert die Einzelschrittausführung durch externen Code. Im gewöhnlichen Entwicklungsalltag ist das praktisch, kann aber beim Überprüfen von PDB und Source Link für Verwirrung sorgen.

Können Sie zum Beispiel trotz vorhandener PDB und Source Link in einer externen NuGet-Bibliothek nicht hineinspringen, prüfen Sie Folgendes:

Ist Just My Code aktiviert und wird der Code dadurch als extern behandelt?
Ist Enable Source Link support aktiviert?
Ist der NuGet.org Symbol Server aktiviert?
Veröffentlicht das Zielpaket PDB / .snupkg?
Ist die Quelle erreichbar, von der der Code abgerufen wird?

Bevor Sie voreilig „die PDB ist schuld“ urteilen, prüfen Sie auch die Debugger-Einstellungen. Die Orte der Einstellungsbildschirme sind in Kapitel 29 unter „Wo finden sich die Einstellungen in Visual Studio“ zusammengefasst. Enable Just My Code und Enable Source Link support befinden sich beide unter Tools (oder Debug) > Options > Debugging > General.

31. PDB und Dekompilierung

Aktuelle Versionen von Visual Studio können .NET-Assemblies dekompilieren und beim Debuggen verwenden.

Dadurch lässt sich der Inhalt externer Bibliotheken bis zu einem gewissen Grad nachvollziehen, selbst wenn weder PDB noch Quellcode vorliegen.

Die Dekompilierung ist jedoch nicht allmächtig.

Die ursprünglichen Kommentare kommen nicht zurück
Die ursprünglichen Leerzeichen und die Struktur kommen nicht zurück
Lokale Variablennamen können sich ändern
async / Iteratoren / Pattern Matching und Ähnliches sehen anders aus als im Original
Bei optimiertem Code ist die Zuordnung schwer nachvollziehbar

Liegen PDB und Source Link vor, ist es grundsätzlich natürlicher, diese zu verwenden. Die Dekompilierung sollte man als „Hilfsmittel für den Fall, dass weder PDB noch Quellcode vorliegen“ betrachten.

32. Log-Design und PDB

Die PDB steht auch im Zusammenhang mit dem Log-Design.

Erscheinen im Exception-Log zum Beispiel Dateiname und Zeilennummer, erleichtert das die Untersuchung. Sich allein auf das Log zu verlassen, ist jedoch riskant.

Bei Produktionsstörungen kommt so etwas vor:

Die im Log stehende Zeilennummer passt nicht zum aktuellen main-Branch
Nach einem Hotfix wurde unter derselben Versionsnummer erneut deployt
Da die PDB nicht erhalten ist, lässt sich die Bedeutung der Zeilennummer nicht überprüfen
Das Container-Image ist noch vorhanden, aber der zugehörige Quell-Commit ist unbekannt

Deshalb sollte das Log nicht nur die Zeilennummer, sondern auch Build-Informationen ausgeben.

ApplicationVersion: 1.8.3
GitCommit: abcdef1234567890
BuildNumber: 20260610.12
Environment: Production

Sind PDB, Quelle, Log, Dump und Deploy-Historie miteinander verknüpft, wird die Fehleruntersuchung erheblich einfacher.

33. PDB im Container-Betrieb

Betreiben Sie eine .NET-Anwendung in einem Container, müssen Sie den Umgang mit der PDB klar festlegen.

Zum Beispiel, ob die PDB im Docker-Image enthalten sein soll.

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY publish/ .
ENTRYPOINT ["dotnet", "MyApp.dll"]

Liegt die PDB in publish/, landet sie unverändert auch im Image.

Das hat Vorteile.

  • Im Container erscheinen im Stacktrace leichter Zeilennummern
  • Beim Erfassen eines Dumps ist die PDB leicht im selben Dateisystem auffindbar
  • Der Abgleich bei der Untersuchung ist einfach

Andererseits gibt es auch Bedenken.

  • Die Image-Größe wächst
  • Debugging-Informationen befinden sich im Produktions-Image
  • Wird das Image extern verteilt, weitet sich der Offenlegungsumfang aus

Bei einem rein internen SaaS ist es durchaus vertretbar, die PDB im Image zu belassen. Bei einem On-Premises-Produkt, das an externe Kunden geht, ist es unter Umständen besser, die PDB separat aufzubewahren.

In jedem Fall ist es unabdingbar, die passende PDB als Artefakt aufzubewahren, auch wenn sie nicht im Image enthalten ist.

34. Einzeldatei-Veröffentlichung und PDB

.NET bietet die Einzeldatei-Veröffentlichung.

dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true

Auch hier muss der Umgang mit Debugging-Informationen geprüft werden.

Nur weil es eine Einzeldatei ist, entfällt die Notwendigkeit einer Fehleruntersuchung nicht. Im Gegenteil: Je spezieller die Verteilungsform, desto wichtiger ist es, wie die zugehörigen Symbole und Quellen aufbewahrt werden.

Als Richtlinie sind unter anderem folgende Punkte festzulegen:

Wird die PDB als separate Datei verteilt?
Wird DebugType=embedded gesetzt?
Werden die Symbole nur intern gespeichert?
Wie wird ein Absturz-Dump analysiert?

Bei Einzeldatei-Veröffentlichung, Trimming, AOT und Ähnlichem kann sich das Untersuchungserlebnis gegenüber einer gewöhnlichen IL-Assembly ändern. Es ist sicherer, vor der Veröffentlichung einmal auszuprobieren, „wie man im Absturzfall vorgeht“.

35. PDB bei Trimming / AOT

Im aktuellen .NET kommen mitunter Trimming oder Native AOT zum Einsatz.

In diesem Fall spielen nicht nur die PDB, sondern auch erzeugte native Symbole und plattformspezifische Debugging-Informationen eine Rolle.

Bei Linux zum Beispiel DWARF, bei macOS dSYM, bei Windows die PDB – auch die nativen Debugging-Informationen müssen mitgedacht werden.

Auch bei .NET-Anwendungen wird das Symboldesign in solchen Konstellationen komplexer.

Native AOT
Self-contained Publish
PublishSingleFile
ReadyToRun
Aufruf einer nativen DLL per P/Invoke
Einschluss von C++/CLI

Für gewöhnliche Webanwendungen oder Klassenbibliotheken genügt es zunächst, Portable PDB und Source Link zu verstehen. Je anspruchsvoller die Verteilungsform wird, desto mehr muss aber die Frage „wie wird dieser Absturz analysiert“ in das Build-Design einfließen.

36. Hinweise für .NET-Framework-Projekte

Bei alten .NET-Framework-Projekten unterscheiden sich Einstellungen und Standardwerte mitunter von SDK-Style-Projekten.

Zum Beispiel solche Unterschiede:

Das csproj-Format ist alt
Es wird packages.config verwendet
Der Standardwert von DebugType weicht vom aktuellen .NET ab
Es wird die Windows PDB verwendet
Für Source Link sind zusätzliche Pakete oder MSBuild-Einstellungen nötig
Die MSBuild-Version in der CI ist alt

Bei .NET Framework gilt für die PDB dasselbe Grundprinzip.

Für die Ausführung nicht zwingend erforderlich
Für Debugging und Fehleruntersuchung wichtig
Muss mit der Zielbinärdatei übereinstimmen
Die PDB des Release-Builds sollte aufbewahrt werden

Kopiert man jedoch die Erklärungen für aktuelles .NET unverändert, funktioniert das bei alten Projekten mitunter nicht wie erwartet.

Bei bestehenden Altsystemen prüfen Sie zunächst die tatsächliche Ausgabe.

msbuild MyApp.csproj /p:Configuration=Release
Get-ChildItem bin\Release -Filter *.pdb -Recurse

Anschließend richten Sie Build-Einstellungen und CI-Artefakte entsprechend ein.

37. Wie geht man bei OSS-Bibliotheken vor

Bei einer OSS-.NET-Bibliothek wird grundsätzlich diese Konfiguration empfohlen:

Auch bei Release eine PDB erzeugen
Portable PDB verwenden
Source Link aktivieren
.snupkg auf NuGet veröffentlichen
Repository-Metadaten konfigurieren
Auf einen deterministischen Build achten

Ein Beispiel:

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

Je nach SDK und Hosting-Anbieter ist ein zusätzliches Paket für Source Link mitunter nicht nötig. Bei alten SDKs oder speziellem Hosting fügen Sie das passende Microsoft.SourceLink.*-Paket hinzu.

Bei OSS ist es selbst schon ein Qualitätsmerkmal, dass Nutzer in die Bibliothek hineinspringen können. Eine „Bibliothek, die sich im Problemfall lesen lässt“, gewinnt allein dadurch leichter Vertrauen.

38. Wie geht man bei internen Bibliotheken vor

Auch bei internen Bibliotheken sind Source Link und PDB sinnvoll.

Gerade bei internen Bibliotheken ist es hilfreich, wenn man aus der Fachanwendung heraus hineinspringen kann.

Nutzen Sie einen internen NuGet-Feed, sollten Sie unter anderem Folgendes prüfen:

Unterstützt der interne Feed .snupkg?
Falls nicht: Wird die PDB stattdessen in die .nupkg aufgenommen?
Wird ein interner Symbolserver eingerichtet?
Wie wird die Authentifizierung gegenüber dem Git-Repository gehandhabt?
Bleibt der Zugriff auf die Quelle auch nach Austritt oder Wechsel von Mitarbeitenden erhalten?

Auch wenn es sich um eine rein interne Bibliothek handelt, sollte vermieden werden, dass die PDB nur auf dem lokalen PC vorliegt.

Erzeugen Sie sie in der CI und speichern Sie sie an einem Ort, von dem das Team sie abrufen kann.

39. Wie geht man bei On-Premises-Produkten vor

Bei On-Premises-Produkten, die an Kundenumgebungen ausgeliefert werden, wird der Umgang mit der PDB schwieriger.

Liefert man die PDB mit, lassen sich beim Kunden erfasste Dumps und Logs leichter lesen. Allerdings wird die interne Struktur dadurch sichtbarer.

Gängige Optionen sind unter anderem:

Die PDB wird nicht mitgeliefert, aber vollständig beim Hersteller aufbewahrt
Für den Kundensupport werden nur public symbols bereitgestellt
Im Störungsfall wird der Dump eingesammelt und beim Hersteller mithilfe der PDB analysiert
Für wichtige Kunden wird ein eingeschränktes Symbolpaket bereitgestellt

Entscheidend ist, die PDB nach der Veröffentlichung nicht zu verlieren.

Bei On-Premises-Produkten wird mitunter auch eine Störung untersucht, die eine mehrere Jahre alte Version betrifft. Fehlt dann die passende PDB, sinkt die Untersuchungsfähigkeit erheblich.

40. Was passiert, wenn man die PDB löscht

Löscht man die PDB, läuft die Anwendung trotzdem. Später gerät man dadurch aber in Schwierigkeiten.

Man könnte annehmen, ein Rebuild aus demselben Commit stelle den Zustand wieder her. Eine vollständige Reproduktion ist jedoch überraschend schwierig.

Die SDK-Version weicht ab
Das Ergebnis der NuGet-Auflösung weicht ab
Build-Zeitpunkt oder Umgebungsvariablen weichen ab
Der erzeugte Code weicht ab
Die bedingte Kompilierung weicht ab
Es gibt Einstellungen, die nur in der CI greifen
Abhängige native Werkzeuge weichen ab

Mit einem gut eingerichteten deterministischen Build steigt die Reproduzierbarkeit, aber selbst dann ist es sicherer, „sie von Anfang an als Artefakt aufzubewahren“.

Die PDB ist so etwas wie eine Versicherung. Ihren Wert entfaltet sie erst in dem Moment, in dem sie gebraucht wird. Fehlt sie in genau diesem Moment, ist das nicht wiedergutzumachen.

41. Empfohlene Einstellungen für die Praxis

Es gibt keine einzige Einstellung, die auf jedes Projekt passt. Für eine typische .NET-Anwendung lässt sich aber Folgendes als Ausgangspunkt nehmen.

Interne Anwendung

<PropertyGroup>
  <DebugType>portable</DebugType>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

Die Richtlinie lautet:

Auch bei Release eine PDB erzeugen
Ob sie in die Produktion kommt, entscheidet die Betriebsrichtlinie
Im CI-Artefakt unbedingt aufbewahren
Ein Verfahren für die Dump-Analyse bereithalten

Öffentliche NuGet-Bibliothek

<PropertyGroup>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

Die Richtlinie lautet:

Source Link aktivieren
.snupkg veröffentlichen
Unnötiges Einbetten von Quellcode vermeiden
Die veröffentlichten Metadaten prüfen

Kleines internes Tool

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

Die Richtlinie:

Vergessenes Mitliefern der PDB vermeiden
Die Größenzunahme des Verteilungsartefakts in Kauf nehmen
Auf die interne Nutzung beschränken

Extern verteiltes Produkt

Die vollständige PDB intern aufbewahren
Bei Bedarf separat public symbols erstellen
Prüfen, welcher PDB-Inhalt im Kundenartefakt enthalten ist
Verfahren für Dump-Erfassung und -Analyse im Supportfall festlegen

42. Checkliste für den Umgang mit der PDB

Zum Schluss noch eine Checkliste für Zweifelsfälle im Umgang mit der PDB.

Zu welcher DLL / EXE gehört diese PDB?
Aus welchem Commit wurde diese DLL / EXE gebaut?
Ist es die PDB für das Produktions-Release, nicht für den Debug-Build?
Wird die PDB als CI-Artefakt aufbewahrt?
Gibt es einen Symbolserver oder ein Abrufverfahren?
Ist Source Link aktiviert?
Sind die Berechtigungen für die Quelle angemessen?
Enthält die PDB Informationen, die nicht offengelegt werden sollen?
Gibt es bei externer Verteilung eine Richtlinie für public/private symbols?
Ist geprüft, dass sich die PDB bei der Dump-Analyse laden lässt?

Besonders wichtig sind diese drei Punkte:

Die PDB ist keine ausführbare Datei, sondern eine Information für Untersuchungszwecke
Die PDB muss mit der Zielbinärdatei übereinstimmen
Die PDB muss aufbewahrt werden, bevor eine Produktionsstörung auftritt

43. Zusammenfassung

Die PDB ist keine „unklare Datei“, die neben .dll oder .exe auftaucht, sondern eine Debugging-Informationsdatei, die die fertig gebaute Binärdatei mit dem für Entwickler lesbaren Quellcode verbindet.

Für die normale Ausführung der Anwendung ist sie nicht zwingend erforderlich. Bei Debugging, Exception-Untersuchung, Dump-Analyse, Profiling und dem Hineinspringen in externe Bibliotheken ist sie jedoch wichtig.

Auch bei Release-Builds ist die PDB nützlich. Gerade bei Produktionsstörungen wird die zum Release-Build gehörende PDB benötigt.

Ob die PDB in der Produktionsumgebung liegt, entscheidet sich nach Offenlegungsumfang und Betriebsrichtlinie. Die PDB zu erzeugen und als Build-Artefakt aufzubewahren, ist jedoch in den meisten Projekten notwendig.

In der Praxis eignet sich diese Richtlinie als Grundlage:

Auch bei Release eine PDB erzeugen
Die PDB als Artefakt von CI/CD aufbewahren
Binärdatei, PDB, Commit-ID und Build-Nummer miteinander verknüpfen
Mit Source Link den Weg zur Quelle nachvollziehbar machen
Bei NuGet-Bibliotheken .snupkg in Betracht ziehen
Bei externer Verteilung prüfen, welche Symbolinformationen offengelegt werden

Solange nichts schiefgeht, fällt die PDB nicht auf. Tritt jedoch ein Problem auf, ist sie der entscheidende Hinweis, der den Untersuchenden zum Quellcode zurückführt.

Statt „die PDB kann gelöscht werden, es läuft trotzdem“ sollte man so denken:

Die PDB ist die Landkarte, die eine künftige Fehleruntersuchung braucht.

Quellen

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Was ist eine PDB-Datei?
PDB steht für Program Database (Programmdatenbank) und ist eine Debugging-Informationsdatei, die auch als Symboldatei bezeichnet wird. Sie verbindet die fertig gebaute .dll oder .exe mit dem für Entwickler lesbaren Quellcode. Sie enthält Funktions- und Methodennamen, lokale Variablennamen, Quelldateinamen samt Zeilennummern, die Zuordnung von Positionen im Quellcode zu Befehlen nach der Kompilierung sowie Informationen zum Abrufen des Quellcodes für Source Link – und wird von Debuggern und Diagnosewerkzeugen genutzt, um zu bestimmen, „welcher Quellzeile dieser Befehl entspricht“.
Läuft die Anwendung auch ohne PDB-Datei? Kann ich sie bedenkenlos löschen?
Normalerweise wird die PDB für die Ausführung der Anwendung nicht benötigt – mit .dll oder .exe lässt sich die Anwendung starten. Ohne PDB werden jedoch Haltepunkte und Einzelschrittausführung, die Anzeige von Dateinamen und Zeilennummern in Exception-Stacktraces, die Dump-Analyse und das Hineinspringen in externe Bibliotheken schwierig. Die PDB ist keine „Datei zum Ausführen“, sondern eine „Datei zum Untersuchen“ – unabhängig davon, ob sie in der Produktionsumgebung liegt, sollte sie daher als Build-Artefakt unbedingt aufbewahrt werden. Löscht man sie, ist eine vollständige Reproduktion selbst durch einen Rebuild aus demselben Commit überraschend schwierig, was bei der Fehleruntersuchung zu einem nicht wiedergutzumachenden Verlust führen kann.
Wird die PDB auch für Release-Builds benötigt?
Ja. Gerade für die Untersuchung von Produktionsstörungen wird die PDB des Release-Builds benötigt. Läuft in der Produktion ein Release-Build, nützt die PDB eines Debug-Builds nichts – der Debugger braucht genau die PDB, die beim Erstellen dieser konkreten Produktionsbinärdatei generiert wurde. Da die PDB mit der Zielbinärdatei übereinstimmen muss, ist es in CI/CD üblich, Commit-ID, Build-Nummer, .dll/.exe und .pdb gemeinsam als Satz aufzubewahren. In aktuellen .NET-SDK-Style-Projekten wird übrigens sowohl für Debug als auch für Release standardmäßig eine Portable PDB erzeugt.
Darf ich PDB-Dateien in der Produktionsumgebung ablegen?
Das lässt sich nicht mit einem einfachen Ja/Nein beantworten – die Entscheidung erfolgt anhand von vier Achsen: Erleichterung der Fehleruntersuchung, Risiko der Offenlegung von Informationen, Verteilungsgröße und Betriebsregeln. Bei internen Systemen ist es praktikabel, .dll und .pdb im selben Ordner abzulegen, da Exception-Logs dann leichter Zeilennummern anzeigen. Bei externer Verteilung sollten Sie vorsichtig abwägen, da aus der PDB unter Umständen lokale Pfade, die interne Ordnerstruktur, Typ- und lokale Variablennamen oder die Repository-URL von Source Link ersichtlich werden – wählen Sie bei Bedarf Optionen wie die Beschränkung auf public symbols oder die Bereitstellung über einen Symbolserver. Auch wenn Sie die PDB nicht ausliefern, ist die Aufbewahrung der PDB desselben Builds Pflicht.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog