Ein Stack Trace zeigt dir, welche Programmfunktionen zum Zeitpunkt eines Fehlers aufgerufen wurden. Dadurch erhältst du einen Ausgangspunkt für die Fehlersuche. Besonders bei Abstürzen kann die Aufrufkette dabei helfen, den betroffenen Programmablauf einzugrenzen.
Was ist ein Stack Trace?
Ein Stack Trace ist eine Darstellung der Funktionsaufrufe, die zu einem bestimmten Zeitpunkt aktiv sind. Programme bestehen aus Funktionen, die bestimmte Aufgaben ausführen. Eine Funktion kann eine weitere Funktion aufrufen, die ihrerseits andere Funktionen startet.
Diese Aufrufe bilden eine Kette. Tritt während der Ausführung ein Fehler auf, kann ein Stack Trace zeigen, über welche Funktionen das Programm bis zu dieser Stelle gelangt ist.
Der Begriff setzt sich aus Stack und Trace zusammen. Stack bezeichnet hier den Aufrufstapel des Programms. Trace bedeutet Spur oder Nachverfolgung. Gemeint ist also die Nachverfolgung der aktiven Funktionsaufrufe.
Wie entsteht eine Aufrufkette?
Angenommen, dein Spieler interagiert mit einer Tür. Zunächst verarbeitet das Spiel die Eingabe. Anschließend startet es eine Funktion für die Interaktion, prüft das Inventar und versucht schließlich, die Tür zu öffnen.
Der Ablauf könnte vereinfacht so aussehen:
SpielerUpdate()
→ InteragiereMitTür()
→ PrüfeSchlüssel()
→ ÖffneTür()
Jede Funktion übernimmt dabei einen Teil der Aufgabe. Während ÖffneTür() ausgeführt wird, sind die vorherigen Aufrufe noch Teil der aktiven Aufrufkette.
Kommt es an dieser Stelle zu einem Fehler, kann der Stack Trace diese Kette sichtbar machen.
Ein einfaches Beispiel
Dein Spieler besitzt einen Schlüssel und soll damit eine verschlossene Tür öffnen. Beim Betätigen der Tür stürzt das Spiel ab.
Ein vereinfachter Stack Trace könnte folgendermaßen aussehen:
NullReferenceException
bei Tür.Öffnen() in Tür.cs:42
bei Tür.Interagieren() in Tür.cs:28
bei Spieler.Interagieren() in Spieler.cs:110
bei Spieler.Update() in Spieler.cs:58
Die Meldung nennt zunächst die Art des Fehlers. Anschließend folgen die beteiligten Funktionen. Zusätzlich können Dateinamen und Zeilennummern erscheinen.
In diesem Beispiel trat der Fehler während Tür.Öffnen() auf. Die Funktion wurde von Tür.Interagieren() aufgerufen, die wiederum über die Spielerinteraktion gestartet wurde.
Damit weißt du, welchen Programmablauf du zunächst untersuchen solltest.
Was bedeutet NullReferenceException?
Eine NullReferenceException ist eine Fehlermeldung, die beispielsweise in C# auftreten kann. Sie bedeutet, dass dein Programm versucht, auf ein Objekt zuzugreifen, obwohl an dieser Stelle keine gültige Objektreferenz vorhanden ist.
Angenommen, deine Tür soll einen Schlüssel im Inventar prüfen. Die dafür benötigte Inventarreferenz wurde jedoch nicht gesetzt. Versucht dein Programm trotzdem, darüber eine Funktion aufzurufen, kann eine NullReferenceException entstehen.
Der Stack Trace zeigt dir dann möglicherweise die Programmstelle, an der dieser ungültige Zugriff stattgefunden hat. Warum die Referenz fehlt, musst du anschließend untersuchen.
Was bedeuten Dateiname und Zeilennummer?
Ein Stack Trace kann neben Funktionsnamen auch Angaben wie Tür.cs:42 enthalten. Damit ist die Datei Tür.cs und die Zeile 42 gemeint.
Solche Angaben helfen dir, die betroffene Stelle im Programmcode zu finden. Allerdings stehen sie nicht in jedem Stack Trace zur Verfügung. Welche Informationen erscheinen, hängt unter anderem von der Programmiersprache, der Entwicklungsumgebung und den verfügbaren Debug-Informationen ab.
Bei kompilierten Spielen können zusätzliche Debug-Symbole erforderlich sein, damit sich technische Adressen wieder verständlichen Funktions- und Dateinamen zuordnen lassen.
Was sind Debug-Symbole?
Debug-Symbole enthalten Informationen, die eine Verbindung zwischen dem ausgeführten Programm und seinem ursprünglichen Programmcode herstellen.
Bei der Erstellung eines Spiels wird der Programmcode häufig in eine Form übersetzt, die der Computer ausführen kann. Dabei sind ursprüngliche Funktionsnamen, Dateinamen und Zeilennummern nicht immer unmittelbar in der ausführbaren Version verfügbar.
Debug-Symbole können helfen, diese Informationen bei der Fehleranalyse wieder zuzuordnen. Dadurch wird ein Stack Trace verständlicher und lässt sich leichter mit dem eigenen Programmcode vergleichen.
Die Reihenfolge richtig lesen
Ein Stack Trace zeigt die Aufrufkette häufig so an, dass die zuletzt ausgeführte Funktion oben steht. Darunter folgen die Funktionen, die sie aufgerufen haben.
Im Türbeispiel steht deshalb Tür.Öffnen() oberhalb von Tür.Interagieren() und Spieler.Interagieren().
Die genaue Darstellung kann je nach Entwicklungsumgebung variieren. Entscheidend ist, dass du erkennst, welche Funktion den nächsten Aufruf ausgelöst hat und an welcher Stelle der Fehler gemeldet wurde.
Was ein Stack Trace nicht beweist
Ein Stack Trace zeigt dir nicht automatisch die eigentliche Ursache eines Fehlers. Er beschreibt zunächst den Programmablauf zum Zeitpunkt der Fehlermeldung.
Angenommen, Tür.Öffnen() greift auf eine ungültige Inventarreferenz zu. Der Stack Trace verweist auf diese Funktion. Die Ursache könnte jedoch bereits früher entstanden sein, weil das Inventar beim Laden des Spielstands nicht korrekt initialisiert wurde.
Die gemeldete Programmstelle ist damit möglicherweise nur der Ort, an dem sich ein vorheriger Fehler bemerkbar macht.
Deshalb solltest du zwischen Fehlerstelle und Fehlerursache unterscheiden.
Stack Traces und Logs ergänzen sich
Ein Stack Trace zeigt die aktive Aufrufkette. Log-Dateien können dagegen Ereignisse dokumentieren, die dein Spiel zuvor ausgeführt hat.
Im Türbeispiel könnte das Log festhalten, dass der Spieler einen Schlüssel aufgenommen, einen Spielstand geladen und anschließend mit der Tür interagiert hat. Der Stack Trace zeigt zusätzlich, welche Funktionen beim Absturz aktiv waren.
Beide Informationen zusammen können den Ablauf genauer eingrenzen. Sie ersetzen jedoch nicht die eigentliche Untersuchung des Programmzustands.
Stack Traces in Unity
In Unity erscheinen Stack Traces beispielsweise in der Console, wenn bestimmte Fehler oder Exceptions auftreten. Die Console ist ein Fenster der Entwicklungsumgebung, in dem Meldungen, Warnungen und Fehler ausgegeben werden.
Bei einer NullReferenceException kannst du dort häufig erkennen, welche C#-Funktion betroffen ist. Über die angezeigten Dateinamen und Zeilennummern lässt sich die entsprechende Stelle im Skript öffnen.
Nicht jeder Fehler erzeugt allerdings automatisch einen Stack Trace. Ein Gegner, der sich falsch bewegt, oder ein Questziel, das nicht aktualisiert wird, kann ein logischer Fehler sein, ohne dass eine Exception auftritt.
Stack Traces in Unreal Engine
Auch in der Unreal Engine können bei Abstürzen und anderen technischen Fehlern Aufrufketten entstehen. Sie helfen dabei, den betroffenen Programmablauf nachzuvollziehen.
Bei C++-Projekten spielen Debug-Symbole eine wichtige Rolle, um technische Adressen verständlichen Funktionen und Quelldateien zuzuordnen.
Welche Informationen verfügbar sind, hängt vom jeweiligen Build und der Konfiguration ab. Ein interner Entwicklungs-Build kann deshalb andere Debug-Informationen bereitstellen als eine veröffentlichte Spielversion.
Was ist ein Build?
Ein Build ist eine aus deinem Entwicklungsprojekt erzeugte, ausführbare Version des Spiels.
Während der Entwicklung entstehen häufig mehrere Builds. Ein Fehler kann beispielsweise in Build 121 auftreten, während Build 120 noch funktioniert.
Die Zuordnung eines Stack Traces zur richtigen Spielversion ist deshalb wichtig. Nur so kannst du die angezeigten Funktionen und Zeilennummern sinnvoll mit dem passenden Entwicklungsstand vergleichen.
Stack Traces aus veröffentlichten Spielen
Nicht jeder Absturz tritt während deiner eigenen Tests auf. Ein Fehler kann beispielsweise nur auf einem bestimmten Rechner, einer Konsole oder unter bestimmten Spielbedingungen erscheinen.
Fehlererfassungssysteme können Stack Traces aus solchen Situationen sammeln, sofern die jeweilige Plattform und Integration dies unterstützen. Dadurch lassen sich auch Abstürze untersuchen, die du nicht selbst beobachtet hast.
Welche Informationen tatsächlich verfügbar sind, hängt von der technischen Einrichtung und den erfassten Daten ab.
Vom Stack Trace zur Fehlersuche
Ein Stack Trace ist besonders dann hilfreich, wenn du ihn mit weiteren Informationen verbindest. Zunächst untersuchst du die gemeldete Fehlerart und die beteiligten Funktionen. Anschließend prüfst du, welche Daten und Zustände an der betreffenden Stelle vorlagen.
Im Türbeispiel könntest du kontrollieren, ob die Inventarreferenz gültig ist, ob der Schlüssel tatsächlich vorhanden ist und ob der Spielstand die benötigten Informationen korrekt geladen hat.
Bestätigt sich eine Vermutung, kannst du die Ursache korrigieren und den Fehler anschließend erneut testen. Führt die Untersuchung zu keinem Ergebnis, überprüfst du weitere mögliche Ursachen.
Stack Traces als Teil des Debuggings
Der Stack Trace ist damit ein Werkzeug innerhalb des Debugging-Prozesses. Er zeigt dir eine technische Spur, die du mit dem beobachteten Verhalten deines Spiels vergleichen kannst.
Besonders bei Exceptions und Abstürzen kann diese Spur den Untersuchungsbereich eingrenzen. Bei logischen Fehlern ohne Fehlermeldung sind dagegen häufig zusätzliche Logs, gezielte Tests oder andere Debugging-Werkzeuge notwendig.
Weitere Artikel auf Games und Lyrik
Wenn du dich neben der technischen Spieleentwicklung auch für klassische Adventures interessierst, findest du auf Games und Lyrik weitere Beiträge. In Runaway 3: A Twist of Fate begleitest du Brian und Gina durch ein Point-and-Click-Adventure mit Rätseln, ungewöhnlichen Situationen und einer Geschichte voller Wendungen.
Weitere Informationen zur automatisierten Erfassung technischer Fehler findest du auf der offiziellen Website von Sentry.
