Guten Morgen zusammen,
ich programmiere gerade eine GUI in Visual Studio mit Qt5. Ich stehe vor
dem Problem, dass der Release-Build sporadisch folgende Exception beim
starten der GUI wirft und abstürzt:
1
Ausnahme ausgelöst bei 0x00007FFF8C7AB533 (Qt5Core.dll) in GUI.exe: 0xC0000005: Zugriffsverletzung beim Schreiben an Position 0x00007FFF8CA560D4.
Im Debug-Build tritt die Exception nicht auf, da funktioniert die GUI
ohne merkliche Probleme. Mit Auskommentieren von Codeblöcken und
Ausgaben via qDebug() hatte ich bisher keinen Erfolg.
Mit welchen Taktiken findet ihr den Übeltäter?
Gruß,
Peter (Hacke)
Release mit Debuginfo bauen. Ist leider mit QT nicht so einfach, weil
qmake die Option automatisch entfernt. Eventuell halt nicht als Release,
sondern als Profile bauen, dann gehts.
Oliver
Hm, auch mit qmake kann man ReleaseWithDebugInfo-Builds erstellen. Das
ist genau das was du hier willst, einen Release-Build (mit
Optimierungen, ohne Asserts), der Debug-Symbole hat sodass du einen
Stacktrace bekommst. Ich denke das ist hier die erste Strategie.
Den Release-Build im Debugger starten. Falls aus irgendwelchen Gründen
keine Debug-Infos möglich sind, muss man eben auf Assembler-Basis
debuggen.
Wenn Fehler nur im Release auftauchen, ist das oft ein Zeichen für die
Ausnutzung von Undefined Behaviour. Daher:
Mit vielen Warnungen kompilieren (-Wall -Wextra -Wconversion -pedantic)
und alle beheben. Mit -Wold-style-cast kompilieren und alle C-Casts in
Named Casts umbauen. Dann alle reinterpret_cast scharf anschauen, ob
da nicht eine Strict-Aliasing-Rule verletzt wird. Alle union-Zugriffe
genau unter die Lupe nehmen, ob hier nicht die Zugriffsreihenfolge
ungültig ist. Bei allen Pointer-Casts extra aufs Alignment achten.
Sven B. schrieb:> Hm, auch mit qmake kann man ReleaseWithDebugInfo-Builds erstellen.
Im .pro-File
CONFIG += force_debug_info
hinzufügen, das wars.
Oliver
Ich hatte mal vor einer Dekade beobachtet, dass Visual Studio im
Debuggmode den Speicher mit 0 Initialisiert, beim Release jedoch nicht.
Keine Ahnung ob das immer nocht so ist, aber vielleicht mal dein Code
auf uninitialisierten Speicher prüfen, das könnte das Sporadische
Abstürzen erklären.
Hallo zusammen,
danke für die guten Methoden!
Bzgl. der erweiterten Warnung und dem Release-Build mit Debugsymbolen
werde ich mal schauen. Ich werde euch berichten.
Peter (Hacke)
Post-morten Debug? D.h. mit einem Debugger das Core-Dump analysieren.
Wie man Erzeugung von Core-Dump aktiviert kann ich dir für VS aber nicht
sagen.
Evtl. verbessern sich auch die Ausgaben, wenn Debug-Infos aktiviert
sind. Beim GCC etwa ist garantiert, dass bei Aktivierung /
Deaktivierung von Debug-Info der gleiche ausführbare Code generiert
wird. Wenn das bei deinem Compiler nicht der Fall ist ist das natütlich
blöd...
Evtl. ist auch möglich, das ganze in einem Debugger zu starten und nen
Breakpoint auf abort o.ä. oder an die entsprechende Stelle in
Qt5Core.dll bzw, den Anfang der dortigen Funktion zu setzen. Falls die
Adresse immer die gleiche ist, d.h. keine randomisiertes Laden aktiviert
ist, versuchen den Breakpoint auf die Adresse zu setzen oder davor, dazu
kann Disassembly der Qt5Core.dll hilfreich sein oder eine Debug-Version
dieser DLL.
Sven B. schrieb:> Hm, auch mit qmake kann man ReleaseWithDebugInfo-Builds erstellen. Das> ist genau das was du hier willst, einen Release-Build (mit> Optimierungen, ohne Asserts), der Debug-Symbole hat sodass du einen> Stacktrace bekommst. Ich denke das ist hier die erste Strategie.
Das hat mich auf jeden Fall weiter gebracht. Als Übeltäter konnte ich
folgende Zeile ausmachen:
1
QMutexLockerlocker(&mutex);
Wenn ich diese Zeile entferne funktioniet das Programm. Jetzt muss ich
nur noch rausfinden, weshalb diese Zeile Probleme macht. In anderen
Methoden der Klasse funktioniert der Zugriff auf das Mutex. Das Mutex
sollte eigentlich auch vor dem ersen Zugriff generiert worden sein ...
Danke an Alle!
Da D. schrieb:> Ein Deadlock löst normalerweise keine Zugriffsverletzung aus.
Richtig, aber ein Abbruch aufgrund eines erkannten Deadlocks könnte sich
ein einer ähnlichen Fehlermeldung manifestieren; abort() sieht manchmal
wie ein Absturz aus.
Qt erkennt aber m.E. keine deadlocks.
Peter Hacke schrieb:> Wenn ich diese Zeile entferne funktioniet das Programm. Jetzt muss ich> nur noch rausfinden, weshalb diese Zeile Probleme macht.
Kannst ja mal berichten, was es war. Denn so ganz plausibel klingt das
noch nicht.
Oliver
Oliver S. schrieb:> Denn so ganz plausibel klingt das> noch nicht.
Stimme ich zu. Das bei mir verwendete Producer/Consumer Konstrukt habe
ich schon öfters in Verbindung mit (Q)Threads eingesetzt und keinerlei
Probleme/Deadlocks gehabt.
Da ich bisher das Problem nicht ausfindig machen kann, werde ich mal ein
Minimalbeispiel mit dem Fehlerbild erstellen und hier posten.
Oliver S. schrieb:> Qt erkennt aber m.E. keine deadlocks.
Aber der Kernel, der die Mutexe ja letztlich implementiert. Naja, ist
nur Spekulation. Deadlocks sind nur eine Art von Fehler, die sich
zufällig oder bei Änderung der Umgebung (z.B. Debug/Release Umschaltung)
manifestieren können.
Peter Hacke schrieb:> werde ich mal ein Minimalbeispiel mit dem Fehlerbild erstellen und hier> posten.
Das ist sowieso immer das beste Vorgehen.
Dr. Sommer schrieb:> Das ist sowieso immer das beste Vorgehen.
Definitiv! Der Fehler war so offensichtlich und trivial, dass ich diesen
nicht gesehen habe. Ist mir beim Zusammenkopieren und Einkürzen für das
Minimalbeispiel aufgefallen.
Ich benutze ein Flag um die Endlosschleife in meinem Thread zu beenden.
Ich habe vergessen das Flag mit true zu initialisieren. Demnacht ist es
dem Zufall überlassen ob meine Endlosschleife ausgeführt wird oder
nicht, ... Heul
Aber wieso kommt es nicht zu diesem Fehler beim Debug-Build? Werden da
bool mit true initialisiert?
Meine DSP Klasse startet den Thread und wird folgendermaßen
initialisiert und "connected". Man kann gut erkennen, dass wenn das
Signal "finished" vom Thread kommt, dass das Objekt DSPThread gelöscht
wird. Deshalb kam es auch zu dem Fehler in der DSPThread::insert
Methode, ...
Peter Hacke schrieb:> Aber wieso kommt es nicht zu diesem Fehler beim Debug-Build? Werden da> bool mit true initialisiert?
Initialisierung ist a priori zufällig, außer bei globalen und statischen
Variablen, die immer mit 0 initialisiert werden, wenn nicht anders
angegeben.
Du beendest also den Thread, in dem der Destruktor aufgerufen wird, du
aber nicht darauf wartest, dass er tatsächlich weg ist? D.h. der Thread
kann nach Aufruf des Destruktors noch eine Weile weiter laufen obwohl
die Klasse schon gelöscht wurde?
Peter Hacke schrieb:> emit finished();
Wird das finished Signal dann auch auf dem Thread ausgeführt?
Warum genau brauchst du überhaupt Threads? Willst du mehrere
Prozessorkerne gleichzeitig nutzen? Bietet Qt eigentlich keine
Muktithreading-Sichere Task-Queue mit Warte Funktion, welche das
hässliche und ineffiziente Sleep sparen würde? Du sperrst alle 50us den
Mutex, prüfst die Queue, und entsperrst es wieder. Super ineffizient.
Warum nicht std::condition_variable, oder gleich std::future?
Dr. Sommer schrieb:> Du beendest also den Thread, in dem der Destruktor aufgerufen wird, du> aber nicht darauf wartest, dass er tatsächlich weg ist? D.h. der Thread> kann nach Aufruf des Destruktors noch eine Weile weiter laufen obwohl> die Klasse schon gelöscht wurde?
Ist nicht elegant, normalerweise nimmt man
QThread::currentThread()->isInterruptionRequested() um die
Endlosschleife von außen zu beenden ohne den Destructor zu missbrauchen.
Bisher habe ich mich immer darauf verlassen, dass Qt alles aufräumt :D
Dr. Sommer schrieb:> Warum genau brauchst du überhaupt Threads? Willst du mehrere> Prozessorkerne gleichzeitig nutzen?
Das Problem ist, dass in der Signalverarbeitung mit 1 MSamples gerechnet
wird und die gesamte Berechnung knapp 100 ms benötigt (einige
Faltungsoperationen, Filterung etc.). Deshalb habe ich die
Signalverarbeitung in einen separaten Thread ausgelagert damit die GUI
und Plots nicht leiden.
Dr. Sommer schrieb:> Du sperrst alle 50us den> Mutex, prüfst die Queue, und entsperrst es wieder. Super ineffizient.
Der Teil ist nur integriert falls die Kommunikation mit dem Sensor nicht
aktiv ist und der preBuffer leer bleibt, sonst frisst der Thread unnötig
Ressourcen und sperrt ständig das Mutex. Bei laufender Kommunikation
sind immer Daten zum lesen vorhanden und der Mutex wird im knapp 100 ms
Zyklus vom Thread gesperrt.
Dr. Sommer schrieb:> Warum nicht std::condition_variable, oder gleich std::future?
Bisher hatte ich nicht die Not sowas zu verwenden, aber ich lerne gerne
dazu:
Die QWaitCondition habe ich gerade mal eingebaut, ist definitiv
eleganter als mein Konstrukt mit dem sleep.
Mit std::future (In Qt: QFuture, QConcurrent) habe ich noch nicht
gearbeitet, habe davon aber gelesen.
In Qt scheint ein netter Mechanismus mit QFutureWatcher eingebaut zu
sein, der meine GUI signalisiert wenn eine Berechnung in einem Thread
fertig ist. Ich spiele tatsächlich mit dem Gedanken das mal zu testen,
damit könnte ich die Architektur der Klassen ein wenig vereinfachen.
Danke für die konstruktive Kritik. Ich lerne gerne dazu!
Peter Hacke schrieb:> Deshalb habe ich die> Signalverarbeitung in einen separaten Thread ausgelagert damit die GUI> und Plots nicht leiden.
Schau dir mal QtConcurrent an. Gerade so etwas kann man oft sehr gut
damit machen, ohne dass man von den Threads viel mitbekommt. Du bekommst
dann einfach ein Signal, wenn's fertig ist.
Edit: Ah, hast du schon geschrieben dass du das gesehen hast. Ist eine
gute Idee, man kann viel weniger Fehler machen als mit selbstgebauten
Threads.
Peter Hacke schrieb:> DSPThread::DSPThread(void)> {> flagIsAlive = true; // <---- Habe ich vergessen!> }>> //> ------------------------------------------------------------------------ -> //> DSPThread::~DSPThread(void)> {> flagIsAlive = false;> }
Wo kommt flagIsAlive her? Ist es eine Member-Variable von DSPThread?
versehe ich das richtig mit der änderung geht es?
Peter Hacke schrieb:> DSPThread::DSPThread(void)> {> flagIsAlive = true; // <---- Habe ich vergessen!> }
Dann hatte ich ja mit meiner Aussage recht und Visual Studio macht das
immer noch.
imonbln schrieb:> Ich hatte mal vor einer Dekade beobachtet, dass Visual Studio im> Debuggmode den Speicher mit 0 Initialisiert, beim Release jedoch nicht.
irgendwie ist das Fies wenn die IDE zwischen Debugger und Release die
Bedingungen ändert...