mikrocontroller.net

Forum: PC-Programmierung Taktiken zum Debuggen bei Release Builds


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Peter Hacke (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
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:
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)

Autor: Peter Hacke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
P.S. Ich habe das VS Projekt bewusst nicht angehängt, ich würde gerne 
eure Taktiken beim Debuggen hören.

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Memory sanitizer aufdrehn sofern das von der Performance her möglich is.

/edit
UB sanitizer wär wohl auch angebracht wenn das nur im Release passiert

: Bearbeitet durch User
Autor: Oliver S. (oliverso)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
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

Autor: Sven B. (scummos)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
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.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
4 lesenswert
nicht lesenswert
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.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sven B. schrieb:
> Hm, auch mit qmake kann man ReleaseWithDebugInfo-Builds erstellen.

Im .pro-File

CONFIG += force_debug_info

hinzufügen, das wars.

Oliver

Autor: imonbln (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
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.

Autor: Peter Hacke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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)

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter Hacke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
 QMutexLocker locker(&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!

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Hacke schrieb:
> Jetzt muss ich
> nur noch rausfinden, weshalb diese Zeile Probleme macht.

Bestimmt ein Deadlock. Willkommen in der Hölle!

Autor: Da D. (dieter)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ein Deadlock löst normalerweise keine Zugriffsverletzung aus.

Eher ist der Speicher auf den &mutex zeigt nicht mehr gültig.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
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

: Bearbeitet durch User
Autor: Peter Hacke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter Hacke (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
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?
DSPThread::DSPThread(void)
{
  flagIsAlive = true; // <---- Habe ich vergessen! 
}

// ------------------------------------------------------------------------- //
DSPThread::~DSPThread(void)
{
  flagIsAlive = false;
}

// ------------------------------------------------------------------------- //
void DSPThread::thread(void)
{
  QVector<double> buffer;
  while (flagIsAlive)
  {
    QMutexLocker locker(&mutex);
    if (!preBuffer.isEmpty())
    {
      buffer = preBuffer;
      preBuffer.clear();
      locker.unlock();
    }
    else
    {
      QThread::usleep(50);
      locker.unlock();
      continue;
    }

    qDebug() << Q_FUNC_INFO << "Daten werden im Thread verarbeitet.";
  }

  emit finished();
}

// ------------------------------------------------------------------------- //
void DSPThread::insert(QVector<double> vec)
{
  // !!! Hier stieg das Programm sporadisch aus !!! 
  QMutexLocker locker(&mutex);
  preBuffer.append(vec);
}

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, ...
// ------------------------------------------------------------------------- //
DSP::DSP(void)
{
  thThread = new QThread;
  thread = new DSPThread;
  thread->moveToThread(thThread);

  connect(thThread, &QThread::started, thread, &DSPThread::thread);
  connect(thread, &DSPThread::finished, thThread, &QThread::quit);
  connect(thread, &DSPThread::finished, thread, &DSPThread::deleteLater);
  connect(thThread, &QThread::finished, thThread, &QThread::deleteLater);
  thThread->start();
}

// ------------------------------------------------------------------------- //
void DSP::input(QVector<double> vec)
{
  thread->insert(vec);
}

Danke an alle Teilnehmer des Fadens!

Gruß,
Peter (Hacke)

Autor: Coredumper (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Hacke schrieb:

> Mit welchen Taktiken findet ihr den Übeltäter?

Den Coredump (bzw. das Windows Äquivalent dazu) dazu auswerten.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Peter Hacke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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!

Autor: Sven B. (scummos)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
Autor: tictactoe (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Peter Hacke (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
tictactoe schrieb:
> Ist es eine Member-Variable von DSPThread

Ja.

Autor: imonbln (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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...

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
imonbln schrieb:
> Dann hatte ich ja mit meiner Aussage recht und Visual Studio macht das
> immer noch.

Nö. Denn 0 entspräche false, nicht true.

Oliver

Autor: Bernd K. (prof7bit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
imonbln schrieb:
> dass Visual Studio im
> Debuggmode den Speicher mit 0 Initialisiert

Dein Startupcode initialisiert den Speicher, nicht die IDE.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.