[C++] Errorunterhose - Fehlerbehandlung verbessern

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

[C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Hallo.

Ich überarbeite gerade meine Fehlerbehandlung/Fehlersystem in meinem Programm.

Meine Fehlerverarbeitung basierte anfangs hauptsächlich auf Exceptions (try, catch, throw).
Sämtliche Objekte/Ressourcen/Speicher werden durch spezielle Verwalter organisiert, wie z.B. im einfachsten Fall ein SmartPointer/std::shared_ptr, was ein "manuelles" Freigeben in der Regel überflüssig macht, da dies "automatisch" passiert (SmartPointer::Destructor->Release->Referenzzähler fällt auf 0->Delete).
Die Kombination von Exceptions und der "automatischen" Ressourcenverwaltung machte Anfangs die Fehlerbehandlung extrem einfach.
Ich konnte gigantische Funktionen einfach in einem try-Block packen und bei einem throw, egal wo und wie tief in der Funktion, waren bei der Ankunft im catch-Block alle in der Funktion angeforderten Ressourcen wieder korrekt freigegeben. Lief also super!

Mit der Zeit und dem Wachsen des Projektes gab es allerdings immer mehr Situationen, wo z.B. beim "Aufräumen" auch Exceptions geworfen wurden, was dann natürlich kompletter Käse war. Ich habe dann versucht das Exceptionhandling so anzupassen, dass während ein Fehler geworfen wird, keine weiteren Fehler geworfen werden können. Das hat auch nicht immer richtig funktioniert, vor allem bekam ich nachdem die Exception verarbeitet war zahlreiche Fehlermeldungen von diversen Kontrollsystemen (entdeckte Memoryleaks usw.).

Mir ist dann erst aufgefallen, dass ich eigentlich bei fast allen Fehlern das Programm so oder so beende. Nur Bei Exceptions, die ich erwarte und nicht vermeiden kann, wird das Programm fortgesetzt. Die Kapselung der Funktion IDirect3DDevice9::Present ist z.B. so ein Fall, dort wurde eine Exception erwartet und auch direkt abgefangen. Ich habe dann mein Programm genauer untersucht und fand tatsächlich keine Stelle, wo das Programm fortgesetzt wird, ohne das die Exception unmittelbar nach dem throw verarbeitet wird.

Zu Testzwecken habe ich mein Fehlersystem und die Behandlung von Fehlern komplett umgebaut. Exceptions gibt es nicht mehr, also kein try/catch/throw. Wird ein Fehler "gemeldet", was zuvor zu einer Exception geführt hätte, wird dieser angezeigt, gegebenenfalls protokolliert und dann das Programm direkt beendet (TerminateProcess). Damit ein Fehler nicht angezeigt wird und zur Beendigung des Programmes führt, muss man dies dem Fehlersystem zuvor melden, was über ein spezielles Objekt/Klasse geschieht.

Code: Alles auswählen

{
	ClassErrorCatcher ErrorCatcher;

	m_Direct3DDevice9->Present(Null, Null, Null, Null);

	if (0 != ErrorCatcher.Error.Number)
	{
		return ErrorCatcher.Error.Number;
	}
}
Durch das Entfernen der Excpetions (auch in den Optionen von Visual Studio) ist das Programm etwas schneller geworden (ca. 5%) und die Executable geschrumpft (ca. 30%).
Im Prinzip funktioniert jetzt alles wunderbar, das Einzige, was ich unschön finde, ist das sofortige Beenden des Programmes bei einem Fehler ohne "Aufräumen".
Auf der anderen Seite ist ein "korrektes" Aufräumen sehr schwer, wenn zuvor ein unerwarteter Fehler passiert ist.

Ich werde das ganze noch genauer weiter Testen und suche gerade nach Möglichkeiten, die Fehlernachricht besser zu gestalten. Die Fehlermeldung beinhaltet die Auflistung vom Stack, welche ich mit der Hilfe der dbghelp.dll (SymLoadModuleEx, SymFromAddr, UnDecorateSymbolName, usw.) erstelle. Allerdings möchte ich ungern die .pdb Datei immer mit ausliefern. Es gibt sicher eine Möglichkeit, die Stack-Informationen zu speichern/exportieren und nachträglich mit einer .pdb Datei zu vervollständigen (Namen auflösen, usw.).

Wie verarbeitet ihr eure Fehler und wie zeigt ihr diese an?
Ich bin für jeden Tip sehr dankbar!
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Schrompf »

Goderion hat geschrieben:Mir ist dann erst aufgefallen, dass ich eigentlich bei fast allen Fehlern das Programm so oder so beende.
Das ist der zentrale Satz, denke ich. Wenn mich Kollegen danach fragen, antworte ich üblicherweise:

a) Exceptions sind Ausnahmen. Wenn eine Operation auch scheitern kann und das trotzdem nicht das Ende der Welt bedeutet, dann ist das keine Ausnahme.

b) Funktionen, die scheitern dürfen, geben optionale Ergebnisse zurück, also im Fehlerfall Null-Zeiger oder boost::optional oder Artverwandte.

c) Assertions - zum Prüfen von Logikfehlern. Also Fehler der Art "wenn ich hier ankomme, müsste XY immer einen Wert im Bereich 0..1 enthalten. Das sind Bugs, die will man sofort sehen, wenn sie auftreten, und will im Debugger gleich vor Ort sein.

Exceptions sind zusammen mit dem C++-RAII-Idiom eine sehr wertvolle Sache - ich würde darauf nicht komplett verzichten wollen.

- Wenn eine kritische Ressource nicht geladen werden konnte, ist das eine Ausnahme, und ich würde die ehrlich gesagt einfach durchfallen lassen. Weitermachen geht nicht mehr, und das OS räumt dann auf.
- Wenn eine unkritische Ressource nicht geladen werden konnte, kommuniziert die Funktion das über einen Rückgabewert. Z.B. wenn eine Textur fehlt, zeigt man stattdessen halt ein rotweißes Schachbrett an.
- Wenn Usereingaben nicht den Erwartungen entsprechen oder per Netzwerk etwas komisches reinkommt, implementiere ich das als Rückgabewerte und behandle es unterbrechungsarm.
- Wenn Funktionen Vorbedingungen prüfen, z.B. Parameter auf Gültigkeit prüfen oder internen Zustand validieren, dann sind das Assertions - optional Assertions, die auch im Release-Build aktiv sind und an der Stelle halt den Anker werfen. Weiterzumachen hat so oder so keinen Sinn mehr, mit ungültigem State weiter zu laufen sorgt nur dafür, dass der unvermeidliche Crash später um so verwirrender zu lesen wird.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Krishty »

Krishty (https://zfx.info/viewtopic.php?f=9&t=868&start=1425#p52665) hat geschrieben:Ich habe meine komplette Fehlerbehandlung auf was eigenes Callback-basiertes umgestellt, und es war wohl die beste Entscheidung meiner Programmierlaufbahn.

Im Grund nimmt jetzt jede Funktion dieses struct entgegen:

  struct Supervisor {
    void * context;
    intptr_t (__stdcall * onEvent)(void * context, int event, void const * more, char * callStack);
  };


Passiert irgendwas, das nicht lokal behandelt werden kann, wird das onEvent-Callback aufgerufen. Erfordert das irgendeine Entscheidung des Users, wird der Rückgabewert ausgewertet. Braucht man zusätzliche Details (bei nicht vorhandener Datei z.B. den Pfad), wird das in dem Zusatzzeiger übergeben. Und alle Funktionen geben nun nur noch ein bool zurück, ob ein kritischer Abbruch vorliegt (Grund egal, wurde ja vorher an Ort und Stelle ausdiskutiert).

Die Event-Codes dürfen nicht doppelt vorkommen. Das ist eine globale Liste, die man warten muss, und davor hat es mir immer gegraust. Aber nach einem Jahr kann ich sagen: Sie ist es total wert, und 2³² Werte sind VIEL Platz.

Fortschrittsbalken mit Abbruchfunktion? Der Algorithmus muss einfach regelmäßig

  if(0 == report(supervisor, current_percentage, 0, 0)) {
    break;
  }


aufrufen. Und BUMM, sind alle UI-Probleme, die ich je hatte, gelöst:
  • Lokalisierung? Lächerlich einfach. In der Logik kommen nur noch Codes vor, die irgendwo im Callback zu Text umgewandelt werden. Von minimal-C-Konsolenanwendungen über „ich schreib alles in log.txt und lass es dann crashen“ bis zu High-End-C#-Programmen mit lokalisierter Grafik-UI, kein Problem mehr. Wenn ich meine Logik in einer Shell Extension verwenden will, darf sie nur noch den ersten Fehler als 4-B-COM-HRESULT zurückgeben? Ist in 30 Sekunden erledigt – ohne Logikänderungen.
  • Sprachbarrieren? struct + Callback kann jede Sprache. In 20 Zeilen hat man einen C#-Wrapper mit hübschen onError(), onResume, onD3D11Error(HResult)-Methoden.
  • Fortschrittsbalken & Abbruchfunktion hatten wir ja gerade. Und das optional beim Einbinden der Logik. Jeder Logik, sogar verschachtelt (Callbacks verketten).
  • Bei Fehlern erneut versuchen (Login abgelaufen, erneut einloggen! WLAN abgerissen; wiederholen, sobald die Verbindung wieder steht! Datenträger X einlegen!)? Geradezu lächerlich einfach, weil die UI einfach 1 oder 0 zurückgeben muss. Versucht das mal mit Codebases, die auf Exceptions aufbauen – wegschmeißen, neu machen; wegschmeißen, …
  • Welche Fehler kritisch sind, bestimmt jetzt die UI. Microsoft musste für IntelliSense einen neuen Compiler bauen, weil der Alte dafür entworfen war, jeden Fehler als kritisch zu behandeln? lol, Amateure! Wenn jetzt ein INI_PARSE_ERROR_EXPECTED_NEWLINE ankommt, kann ich mir aussuchen, ob ich den Text im Editor rot unterkringle oder das direkt das ganze System crashe!
  • Meine Fresse, erzeugt das geilen Code. Ein einfaches if(nullptr in die Logik, und der Compiler schmeißt jeden Code, der entfernt mit Fehlerbehandlung/Fortschrittsbehandlung/Userfragen zu tun hat aus der EXE raus, bis aufs letzte Byte.
Bleibt noch der callStack-Parameter:
  • Leute schreiben gern __FILE__, __LINE__, __FUNCTION__ in ihre Return Values/Exceptions/andere primitive Fehlerreflexe
  • das System freut sich über die Unmenge Strings
  • Angreifer freuen sich über die Funktionsnamen, den sie im Code wiederfinden
  • Programmierer freuen sich über die Erkenntnis, dass custom_memcpy() einen Nullzeiger hat, sind aber total ratlos, was denn kopiert werden sollte und was das Programm gerade macht
  • Call Stacks sind 1337 – kompakter und aussagekräftiger
  • dummerweise funktionieren die nicht über Sprachgrenzen hinweg – wenn sich die C#-UI oder die Python-Testfälle im der C#-/Python-Callback befinden, haben sie keinen Zugriff auf den Call Stack, der den Fehler eskaliert hat
  • also Call Stack einfach als ASCII-Text mitliefern
  • natürlich nicht bei trivialen Dingen wie Fortschrittsinformationen
Nächster Schritt? Die ganze Umgebung durch Callbacks virtualisieren.
  • Dateisystem, damit meine ganze Dateiverarbeitung sowohl mit der Platte als auch in Netzwerkstreams als auch in den PAK-Archiven von Spielen funzt (klingt wie Standard-C-Streams – nur, dass ich mit denen über Persönliches zerstritten bin)
  • Threads & Locks, damit ich meine geometrischen Algorithmen mit einer Zeile zwischen seriell/Thread Pool’d/Threaded portieren kann (OHNE TEMPLATES, weil bei mir noch immer alles in unter einer Sekunde kompiliert)
  • Speicheranforderungen, damit ich Systeme über verschiedene Anforderungen hinweg portieren kann (bei Servern immer ordentlich aufräumen vs. bei kleinen Apps einfach den Speicher zukleistern und alle Jubeljahre die Pools löschen, war bei Assimp ein Hauptproblem)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Vielen Dank für die Antworten.

Dem Post von Schrompf stimme ich fast vollständig zu und arbeite auch so, ich weiß nur nicht, wo ich sinnvoll Exceptions nutzen kann, wenn ich bei einem throw, bzw. im catch, das Programm sowieso beende.

Die Callback-basierte Fehlerbehandlung von Krishty muss ich erstmal genau verstehen... ;-)

Für mich klingt diese Variante etwas übertrieben. Bei mir geht es primär um Fehler, die entstehen, weil ich etwas nicht bedacht oder übersehen habe. Der Klassiker ist eine Assertion beim operator -> im SmartPointer. Ist der SmartPointer leer/Null, ist das ein kritischer Fehler und das Programm soll/muss sich beenden. Das darf einfach nicht passieren. In Situationen, wo ein leerer SmartPointer erlaubt ist, muss dieser zuvor gegen Null geprüft werden.
Man könnte es auch so sagen: Die meisten Fehlermeldungen, bzw. Prüfungen/Assertions, existieren fast ausschließlich, um meine eigenen Programmierfehler aufzudecken und sollen im Optimalfall nie bei einem Nutzer auftreten.
Fehler, die durch Benutzereingaben oder externe Daten entstehen, wie z.B. das Laden eines JPG, wo aber der Header kaputt/inkorrekt ist, behandel ich komplett anders.

Das mit dem callStack-Parameter verstehe ich auch nicht richtig. Was steht denn da drin?

Krishty, könntest Du mal bitte ein kleines Beispielprogramm schreiben, wie dein Callback-basiertes Fehlersystem zum Einsatz kommt? Irgendwas Einfaches, wie z.B. das Kopieren einer Datei von A nach B.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Krishty »

Goderion hat geschrieben:Bei mir geht es primär um Fehler, die entstehen, weil ich etwas nicht bedacht oder übersehen habe. Der Klassiker ist eine Assertion beim operator -> im SmartPointer. Ist der SmartPointer leer/Null, ist das ein kritischer Fehler und das Programm soll/muss sich beenden. Das darf einfach nicht passieren. In Situationen, wo ein leerer SmartPointer erlaubt ist, muss dieser zuvor gegen Null geprüft werden.
Man könnte es auch so sagen: Die meisten Fehlermeldungen, bzw. Prüfungen/Assertions, existieren fast ausschließlich, um meine eigenen Programmierfehler aufzudecken und sollen im Optimalfall nie bei einem Nutzer auftreten.
Warum dann nicht einfach Assertions nutzen? Auf so etwas würde ich niemals Laufzeitkosten verschwenden …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Krishty hat geschrieben:Warum dann nicht einfach Assertions nutzen? Auf so etwas würde ich niemals Laufzeitkosten verschwenden …
Wie meinst Du das? Ich benutze Assertions, aber nicht die aus <assert.h>, sondern selbst geschriebene Assertions, die aber das gleiche machen, nur die Fehlermeldung ist detaillierter.
Es gibt auch Fehler, die nicht durch eine Assertion ausgelöst werden, wie z.B. Lese/Schreibzugriff auf bestimmte Systeme/Programmmodule, ohne das man zuvor das nötige Recht dazu angefordert hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Krishty »

Ja; das klingt schon richtig. In Debug-Builds Assertions; in Release-Builds einfach krachen lassen.

Dein Problem ist nun, dass die Fehlermeldungen beim Absturz nicht detailliert genug sind? Du willst also Assertion Failures beim Endkunden fangen? Dann wäre die Antwort aber, vor der Auslieferung einfach besser zu testen …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Krishty hat geschrieben:Ja; das klingt schon richtig. In Debug-Builds Assertions; in Release-Builds einfach krachen lassen.
Im Release-Modus verliere ich ca. 3% Performance bei aktivierten Assertions/Kontrollfunktionen, deshalb habe ich beschlossen, die vorerst auch in der Release-Version aktiviert zu lassen.
Krishty hat geschrieben:Dein Problem ist nun, dass die Fehlermeldungen beim Absturz nicht detailliert genug sind? Du willst also Assertion Failures beim Endkunden fangen? Dann wäre die Antwort aber, vor der Auslieferung einfach besser zu testen …
Natürlich ist mein Ziel, dass beim Nutzer keine kritischen Fehler auftreten, die das Programm beenden, aber das zu erreichen, ist nicht immer einfach. Daher möchte ich eine detaillierte Fehlermeldung anzeigen, um das Problem so schnell wie möglich zu lösen, sollte es doch zu einem kritischen Fehler beim Nutzer kommen.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Krishty »

Dann würde ich alle Assertions im Release-Modus ein __debugbreak() auslösen lassen. Ein __try { } except() { } in der main() kann sie fangen, und dann entweder einen Call Stack schreiben oder einen Minidump (z.B., um den zu dir zu mailen).

Was den Call Stack ohne PDB angeht: Schreib die rohen Adressen relativ zum Anfang des Moduls. Mit deiner privaten PDB kannst du dann eine Session starten, die Adresse in den Debugger eingeben, und landest an der korrekten Stelle im Code.

Code: Alles auswählen

// Captures the current call stack (excluding this call) as a zero-terminated string.
//  • writes an empty string on failure
//  • layout:
//     our program.exe                  + 0x12345678
//     <unknown module>
//     Kernel32.dll                     + 0x12345678
//     MaximalLengthIs32CharactersPerF… + 0x12345678
//     …
//  • up to 16 entries
void captureCallStack(UTF16Unit result[16 * 48]) {
	auto toEndOfText = result;

	// Capture the raw call stack:
	void * stackPointers[16];
	auto       toCall = stackPointers;
	auto const toEnd  = toCall + RtlCaptureStackBackTrace(1, CAPACITY_OF(stackPointers), stackPointers, nullptr);
	while(toCall < toEnd) {

		// The addresses are useless with Address Space Layout Randomization – instead, get an offset into the module:
		//  • “GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS” allows to pass a code address instead of a string
		//  • “GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT” prevents taking a new reference to the module
		Module * toModule;
		if(WINAPI_SHOULD_SUCCEED(GetModuleHandleExW(6, (UTF16Unit const *)*toCall, &toModule))) {

			// Get the module’s name. Consider:
			//  • Windows XP does not zero-terminate long paths, so allocate an additional character and zero it manually
			UTF16Unit path[260];
			auto const toEndOfName = path + WINAPI_SHOULD_SUCCEED(GetModuleFileNameW(toModule, path, CAPACITY_OF(path) - 1));
			*toEndOfName = 0;

			// From the path’s end, seek backwards until encountering either
			//  • the path’s beginning
			//  • or a forward or backward slash
			auto toName = toEndOfName;
			while(toName > toBeginningOf(path) && *toName != UTF16('\\') && *toName != UTF16('/')) {
				--toName;
			}
			if(*toName == UTF16('\\') || *toName == UTF16('/')) {
				++toName;
			}
			auto const nameLength = toEndOfName - toName;

			// Copy the name:
			if(nameLength < 32) {
				toEndOfText = Text::copyUntilZero(toEndOfText, toName);
				toEndOfText = fill(toEndOfText, UTF16(' '), 32 - nameLength);
			} else {
				toEndOfText = movs(toEndOfText, toName, 31);
				*toEndOfText++ = UTF16Unit(0x85); // …
			}

			// Compute the call’s offset in the module:
			//  • always 4-B because even 64-bit Portable Executables are limited to 2³² bytes
			auto const offset = UInt4B(byteDistance((void *)toModule, *toCall));

			// Write the offset:
			*toEndOfText++ = '+';
			toEndOfText = Text::toHexadecimalZeroPaddedString(toEndOfText, offset);
			*toEndOfText++ = '\n';

		} else {
			toEndOfText = Text::copyZeroTerminated(toEndOfText, "<unknown module>\n");
		}

		++toCall;
	}

	*toEndOfText++ = 0; // zero-terminate the string
}
Die Lösung mit dem Minidump ist aber vorzuziehen. Falls du dein Programm digital signierst, kannst du dich auch im MSDN anmelden und erhälst Zugriff auf die Minidumps deiner User, die automatisch an Microsoft gesendet werden.

@Devs: Warum ersetzt ZFX alle einfachen Anführungszeichen in meinem Code durch '?!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Krishty hat geschrieben:Dann würde ich alle Assertions im Release-Modus ein __debugbreak() auslösen lassen. Ein __try { } except() { } in der main() kann sie fangen, und dann entweder einen Call Stack schreiben oder einen Minidump (z.B., um den zu dir zu mailen).
Was spricht dagegen, wenn ich den Call Stack und/oder den Minidump gleich in der Fehlermeldung der Assertion erzeuge und dort das Programm beende?
Wozu der "jump" in den except-Block? Dort würde ich doch dann das Programm auch beenden.
Mein Programm hat mindestens 5 Threads und in jedem können kritische Fehler vorkommen.
Krishty hat geschrieben:Was den Call Stack ohne PDB angeht: Schreib die rohen Adressen relativ zum Anfang des Moduls. Mit deiner privaten PDB kannst du dann eine Session starten, die Adresse in den Debugger eingeben, und landest an der korrekten Stelle im Code.
Soetwas habe ich mir vorgestellt, die Funktion RtlCaptureStackBackTrace kannte ich noch nicht und ist wohl genau das, was ich brauche.
Vielen Dank für den Tip!
Krishty hat geschrieben:Die Lösung mit dem Minidump ist aber vorzuziehen. Falls du dein Programm digital signierst, kannst du dich auch im MSDN anmelden und erhälst Zugriff auf die Minidumps deiner User, die automatisch an Microsoft gesendet werden.
Das klingt sehr interessant! Ist das kostenlos, das digital Signieren und die MSDN-Anmeldung?
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Krishty »

Goderion hat geschrieben:
Krishty hat geschrieben:Dann würde ich alle Assertions im Release-Modus ein __debugbreak() auslösen lassen. Ein __try { } except() { } in der main() kann sie fangen, und dann entweder einen Call Stack schreiben oder einen Minidump (z.B., um den zu dir zu mailen).
Was spricht dagegen, wenn ich den Call Stack und/oder den Minidump gleich in der Fehlermeldung der Assertion erzeuge und dort das Programm beende?
Naja, Lokalität halt. Ein Haltepunkt ist auf x86 bloß ein Byte groß und beeinflusst AFAIK – im Gegensatz zu einem Funktionsaufruf – nicht die Pipeline. Mit einer Funktion hättest du wohl das Problem, dass die CPU die dauernd spekulativ ausführen würde, obwohl gar kein Fehler auftritt. Kannst ja mal benchen.
Goderion hat geschrieben:
Krishty hat geschrieben:Die Lösung mit dem Minidump ist aber vorzuziehen. Falls du dein Programm digital signierst, kannst du dich auch im MSDN anmelden und erhälst Zugriff auf die Minidumps deiner User, die automatisch an Microsoft gesendet werden.
Das klingt sehr interessant! Ist das kostenlos, das digital Signieren und die MSDN-Anmeldung?
Das Signieren kostet dich 100–400 $, je nach Anbieter. Wie’s danach weitergeht, weiß ich nicht, aber gut dass du’s erwähnst – ich versuche, das diese Woche noch auszuprobieren. (Es war nicht die MSDN,
sondern WinQual.)
https://msdn.microsoft.com/en-us/library/windows/desktop/bb513641(v=vs.85).aspx hat geschrieben:Developers can register with Windows Quality Online Services to get information about the problems customers are experiencing with their applications and help customers fix these problems.
P.S.: Bezüglich Call Stacks solltest du immer x64 ausliefern, weil das bei x86 manchmal versagt (durch verschiedene Calling Conventions & Frame Pointer Optimization).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Schrompf »

MiniDumps habe ich auch gemacht, aber dann immer manuell die Leute im Steam-Forum bitten müssen, sie mir zuzuschicken. Und dann hatte ich das Problem, dass meine Build-Strategie nicht so ausgefeilt war, um verlässlich alte Build-Stände zu reproduzieren, weswegen das Debuggen des MiniDumps dann eine experimentelle Erfahrung war.

Mein CallStack dumpt bei Exceptions mit kompletter Benamung - das ist nützlich, aber weniger nützlich, als man am Anfang denkt. Dann die einfachen Fehler "Ach DA ist es verreckt!" findest Du schon beim Entwickeln. Und bei den komplizierten Fehlern "Was, die Funktion kann schief gehen?" nützt Dir der Callstack nur begrenzt. Dafür müsste ich aber immer die Debug-Symbole mit ausliefern, was nochmal ~50MB Daten waren. Die komprimieren zwar gut, aber es gibt wirklich verbissene Spieler da draußen, die sich dann über sowas beschweren. Krishtys Lösung ist da vielleicht die bessere.

Wenn Du's komplett haben willst, schau Dir mal Boost.Stacktrace an. Ich werde bei Gelegenheit meine Privat-Konstruktion rauswerfen und dadurch ersetzen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Vielen Dank für eure Hinweise!
Krishty hat geschrieben:Naja, Lokalität halt. Ein Haltepunkt ist auf x86 bloß ein Byte groß und beeinflusst AFAIK – im Gegensatz zu einem Funktionsaufruf – nicht die Pipeline. Mit einer Funktion hättest du wohl das Problem, dass die CPU die dauernd spekulativ ausführen würde, obwohl gar kein Fehler auftritt. Kannst ja mal benchen.
Ich habe das mal anhand der FPS verglichen und die Version mit __debugbreak() ist nicht schneller als die Version mit einem Funktionsaufruf.
Krishty hat geschrieben:Das Signieren kostet dich 100–400 $, je nach Anbieter.
Ich gehe mal davon aus, dass ich jedes mal, wenn sich die Binaries verändern, z.B. durch einen Patch, ich das Programm neu signieren lassen muss. Für später auf jeden Fall interessant, jetzt werde ich wohl aber erstmal eine Lösung finden müssen, wo ich selber dafür sorge, dass mir die Fehlerdaten geschickt werden. Spontan fällt mir da ein einfacher Dialog ein, mit der Bitte zum senden der Fehlerinformationen, dies kann man dann bestätigen oder ablehnen.
Krishty hat geschrieben:P.S.: Bezüglich Call Stacks solltest du immer x64 ausliefern, weil das bei x86 manchmal versagt (durch verschiedene Calling Conventions & Frame Pointer Optimization).
Ich bin leider aktuell aufgrund der Verwendung von Assembler an x86 gebunden. Ich denke, bzw. hoffe aber mal, dass das kein Problem darstellen wird, da ich nur die Windows-Standard-Bibliotheken und DirectX nutze. Ich musste aber schon das Unterdrücken der Framepointer ausschalten, da ich plötzlich nach einigen größeren Änderungen am Quellcode nur noch 4 Einträge im CallStack hatte, obwohl es 17 hätten sein müssen.
Schrompf hat geschrieben:MiniDumps habe ich auch gemacht, aber dann immer manuell die Leute im Steam-Forum bitten müssen, sie mir zuzuschicken. Und dann hatte ich das Problem, dass meine Build-Strategie nicht so ausgefeilt war, um verlässlich alte Build-Stände zu reproduzieren, weswegen das Debuggen des MiniDumps dann eine experimentelle Erfahrung war.
Ich denke das Schicken der Fehlerinformationen sollte automatisch geschehen, vorausgesetzt der Nutzer ist damit einverstanden (z.B. per Dialog fragen).
Der Quellcode wird in der Regel täglich mehrmals komplett gesichert, was die Wiederherstellung von bestimmten Build-Ständen sehr einfach macht.
Wir das Programm Personen zugänglich gemacht, egal warum (Test, Release, Patch), erstelle ich eine Kopie von allen relevanten Dateien (Quellcode, Binaries, Debugsymbole) und archiviere diese.
Schrompf hat geschrieben:Mein CallStack dumpt bei Exceptions mit kompletter Benamung - das ist nützlich, aber weniger nützlich, als man am Anfang denkt. Dann die einfachen Fehler "Ach DA ist es verreckt!" findest Du schon beim Entwickeln. Und bei den komplizierten Fehlern "Was, die Funktion kann schief gehen?" nützt Dir der Callstack nur begrenzt. Dafür müsste ich aber immer die Debug-Symbole mit ausliefern, was nochmal ~50MB Daten waren. Die komprimieren zwar gut, aber es gibt wirklich verbissene Spieler da draußen, die sich dann über sowas beschweren. Krishtys Lösung ist da vielleicht die bessere.
Die Debug-Symbole werde ich auf keinen Fall mit ausliefern, da es ein "Rumfummeln" in den Binaries, z.B. zum Cheaten, sehr einfach macht. Wenn ich das richtig verstanden habe, sollte das auch nicht nötig sein, wenn man die relativen Adressen im CallStack angibt. Ich muss selber aber auch erstmal eine Möglichkeit finden, wie ich rein mit der relativen Adresse und einer PDB-Datei den dazugehörigen Dateinamen, Zeile und Funktionsnamen ermitteln kann.
Schrompf hat geschrieben:Wenn Du's komplett haben willst, schau Dir mal Boost.Stacktrace an. Ich werde bei Gelegenheit meine Privat-Konstruktion rauswerfen und dadurch ersetzen.
Ich sehe da jetzt keinen Vorteil gegenüber einer selbst entwickelten Lösung, hab ich was übersehen? Ich bin auch kein Fan von solchen Bibliotheken wie Boost, verstehe aber wenn es Leute nutzen, vor allem wenn man es sich nicht leisten kann, alles selber zu machen, wie z.B. rein kommerzielle Projekte, da ist Zeit Geld und Boost boostet wohl die Entwicklungsgeschwindigkeit. ;-)

Ich suche noch nach sinnvollen Informationen, die man mit in den Fehlerreport (nicht die Fehlermeldung/MessageBox) packen könnte. Momentan schreibe ich alles rein, was ich durch GetVersionEx, GetSystemInfo und GlobalMemoryStatusEx in Erfahrung bringen kann. Fallen euch noch andere nützliche Infos für den Fehlerreport ein?
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Krishty »

Goderion hat geschrieben:
Krishty hat geschrieben:Naja, Lokalität halt. Ein Haltepunkt ist auf x86 bloß ein Byte groß und beeinflusst AFAIK – im Gegensatz zu einem Funktionsaufruf – nicht die Pipeline. Mit einer Funktion hättest du wohl das Problem, dass die CPU die dauernd spekulativ ausführen würde, obwohl gar kein Fehler auftritt. Kannst ja mal benchen.
Ich habe das mal anhand der FPS verglichen und die Version mit __debugbreak() ist nicht schneller als die Version mit einem Funktionsaufruf.
Und die Größe?
Goderion hat geschrieben:
Krishty hat geschrieben:Das Signieren kostet dich 100–400 $, je nach Anbieter.
Ich gehe mal davon aus, dass ich jedes mal, wenn sich die Binaries verändern, z.B. durch einen Patch, ich das Programm neu signieren lassen muss. Für später auf jeden Fall interessant
Nein; du bekommst das Zertifikat mit zeitlich begrenzter Gültigkeit (z.B. für zwei Jahre) und darfst deine Patches so viel und so oft signieren, wie du willst. Ich habe aber in das Microsoft-Portal reingeschaut, und es ist ein einziges Chaos – man muss zusätzlich zum Zertifikat wohl auch für einen Hunni einen Developer-Account anlegen, und es ist sehr App-orientiert.
Goderion hat geschrieben:Die Debug-Symbole werde ich auf keinen Fall mit ausliefern, da es ein "Rumfummeln" in den Binaries, z.B. zum Cheaten, sehr einfach macht.
Du kannst begrenzte Debug-Symbole ausliefern, die nur Funktionsnamen erhalten. Sowas tut Microsoft ja z.B. für alle Windows-DLLs.
Goderion hat geschrieben:Ich muss selber aber auch erstmal eine Möglichkeit finden, wie ich rein mit der relativen Adresse und einer PDB-Datei den dazugehörigen Dateinamen, Zeile und Funktionsnamen ermitteln kann.
SymFromAddr()
Goderion hat geschrieben:Ich suche noch nach sinnvollen Informationen, die man mit in den Fehlerreport (nicht die Fehlermeldung/MessageBox) packen könnte. Momentan schreibe ich alles rein, was ich durch GetVersionEx, GetSystemInfo und GlobalMemoryStatusEx in Erfahrung bringen kann. Fallen euch noch andere nützliche Infos für den Fehlerreport ein?
Liste aller geladenen Module im Prozess; Werte aller lokalen Varia…oh Moment, das ist ja ein Minidump ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Errorunterhose - Fehlerbehandlung verbessern

Beitrag von Goderion »

Krishty hat geschrieben:Und die Größe?
__debugbreak() = 1.839.616 Bytes
Functioncall = 1.938.432 Bytes
Krishty hat geschrieben:Nein; du bekommst das Zertifikat mit zeitlich begrenzter Gültigkeit (z.B. für zwei Jahre) und darfst deine Patches so viel und so oft signieren, wie du willst. Ich habe aber in das Microsoft-Portal reingeschaut, und es ist ein einziges Chaos – man muss zusätzlich zum Zertifikat wohl auch für einen Hunni einen Developer-Account anlegen, und es ist sehr App-orientiert.
App-orientiert... ich habe mich mit den Windows-Apps noch nicht beschäftigt... wenn sich Windows 10 S durchsetzen sollte, wird man da wohl nicht drumrum kommen.
Krishty hat geschrieben:Du kannst begrenzte Debug-Symbole ausliefern, die nur Funktionsnamen erhalten. Sowas tut Microsoft ja z.B. für alle Windows-DLLs.
Die Funktionsnamen reichen schon. Damit ist z.B. Reverse Engineering sehr sehr viel einfacher, sofern die Funktionsnamen entsprechend aussagekräftig sind.
Krishty hat geschrieben:SymFromAddr()
Die Funktion nutze ich bereits, um in der Debug-Version die Debug-Symbole zu laden. Ich weiß aber noch nicht so recht, wie ich das nachträglich mache. Wie öffne ich eine PDB direkt, mir ist bis jetzt nur die Variante per SymInitialize bekannt, da kann ich nur einen Prozess angeben, aber nicht direkt eine PDB Datei. Mir würde jetzt spontan nur einfallen, das durch 2 Prozesse zu lösen. Im ersten Prozess wird der zweite Prozess mit der betroffenen Exe gestartet und dann kann im ersten Prozess ein SymInitialize mit dem Handle zum zweiten Prozess gemacht werden. Ob das funktioniert?
Krishty hat geschrieben:Liste aller geladenen Module im Prozess; Werte aller lokalen Varia…oh Moment, das ist ja ein Minidump ;)
Ich habe von Minidumps überhaupt keine Ahnung. Ich vermute dafür nutzt man die Funktion MiniDumpWriteDump.
Unabhängig vom Minidump hätte ich gerne so viele Informationen wie möglich, die ich gleich ohne weitere Programme direkt in Textform lesen kann.
Antworten