2015-02-01 47 views
6

Ich habe ein C++ Windows-Programm, das den Beendigungscode nicht festlegen kann. Das Programm ist sehr komplex und kann derzeit nicht mit einem einfachen Testfall reproduziert werden. Ich weiß, dass das Programm exit(1) aufruft, weil ich einen Haltepunkt in dieser Zeile habe. Unmittelbar nachdem ich über sie Schritt, die Debugger (VS2010) druckt The program program.exe has exited with code 0 (0x0). Wenn ich es von der Shell ausgeführt wird %ERRORLEVEL% auch auf 0 gesetztExit schlägt fehl Fehlercode

Ich benutze subsystem:console und plain old main (kein WinMain).

Dies geschieht nur unter Windows Server 2008 R2, nicht auf meinem Windows 8.1 Laptop. Ich laufe die gleiche ausführbare Datei auf beiden.

Ich habe versucht, exit zu verwenden, _exit, ExitProcess und return (der betreffende Anruf in main), aber keiner von denen scheinen keine Wirkung zu haben. Ich habe auch versucht, andere Codes zurückzugeben, auch ohne Ergebnis.

Es gibt eine similar question, aber ich kann die darin beschriebenen Ergebnisse nicht reproduzieren. Mein Programm verwendet Threads.

Wie kann ich dieses Problem beheben? Ich bin eher verwirrt.

+0

UB irgendwo? Stack überschrieben? Nichts anderes scheint seltsam oder fehl am Platz? –

+1

@JoachimPileborg ist es schwer zu sagen. Es kann irgendwo UB geben, aber wie könnte ich es finden? Der Stapel scheint in Ordnung zu sein. Ich verstehe UB ist UB, aber in diesem Fall müssen wir nicht einmal durch Regeln von C++ gebunden sein. WINAPI-Funktionen wie 'ExitProcess' sollten einfach funktionieren (wir können sie aus einem Assemblerprogramm aufrufen). Ich bin absolut sicher, dass "1" an "exit" übergeben wird, weil ich den Standard-Bibliothekscode im Debugger sehen und tatsächlich mit dem Debugger beobachten kann, wie 'ExitProcess (1)' aufgerufen wird. sogar auf der Maschinenbefehlsebene. Ich kann 'ExitProcess' jedoch nicht weiter debuggen. –

Antwort

11

Ich habe versucht, Ausgang zu verwenden, _exit, Exitprocess, und das Rück

Sie alle angemessenen Erklärungen beseitigt haben, vor allem mit Exitprocess(). Es gibt nur noch eine Möglichkeit, Sie müssen TerminateProcess() ausprobieren. Wenn immer noch nicht den Exit-Code festlegen, dann müssen Sie diese Maschine aus einem vierten Story-Fenster schieben.

Aber mit der Erwartung, dass es jetzt funktioniert. Der Unterschied zwischen ExitProcess() und TerminateProcess() besteht darin, dass das erstere sicherstellt, dass alle DLLs durch die Beendigung benachrichtigt werden. Ihre DllMain() - Funktion wird mit fdwReason = DLL_PROCESS_DETACH aufgerufen. Das gibt einer DLL die Möglichkeit, etwas icky zu machen, wie Exit/TerminateProcess() selbst aufzurufen, wodurch der Exit-Code verschraubt wird.

Das Finden einer solchen DLL kann schwierig sein, wenn Sie nicht den gesamten Quellcode haben. Könnte auch ein injizierter sein, es gibt heutzutage zu viele zu viele. Es empfiehlt sich, beim Systemaufruf einen Haltepunkt festzulegen, damit Sie ihn während des Vorgangs erfassen können. Wahrscheinlich möchten Sie dies auch unabhängig davon tun.

Sobald Sie in main() eingetreten sind, verwenden Sie Debug> New Breakpoint> Break at Function und geben Sie {,,ntdll.dll}[email protected] ein. Drücken Sie F5, und der Debugger stoppt jetzt, bevor das Programm beendet wird. Sieh dir den Call Stack an, um den Übeltäter zu finden.

+0

Ich habe versucht, TerminateProcess aufzurufen und es scheint zu funktionieren. Aber ich kann keinen Haltepunkt dafür setzen. Sind Sie sicher, dass die '{,, ntdll.dll} _NtTerminateProcess @ 8'-Syntax korrekt ist? Die IDE warnt vor ungültigem Kontext, es wurde kein Haltepunkt gesetzt. –

+0

Schwer zu erraten, warum es bei Ihnen nicht funktioniert, ich weiß nichts über Ihre VS- oder Windows-Version. Eine Alternative besteht darin, auf eine ntdll.dll-Funktion im Aufruf-Stack zu doppelklicken, die Warnung zu ignorieren, dass keine Quelle vorhanden ist, und dann '_NtTerminateProcess @ 8' als Funktionsnamen zu verwenden. Letztes Keuchen ist, TerminateProcess() in einem kleinen Testprogramm aufzurufen und den Maschinencode einzustufen, damit Sie den Funktionsnamen ntdll.dll in Ihrer Windows-Version sehen können. Anyhoo, du hast viel gelernt, jetzt weißt du, dass es eine DLL ist, die das vermasselt. –

+0

@ n.m. Warum rufen Sie FreeLibrary nicht einfach nur für einige verdächtige DLLs an und sehen Sie, welche davon Ihren Prozess beendet? Setzen Sie die Trace-Zeilen vor und nach jedem Aufruf, damit Sie wissen, ob FreeLibrary zurückkehrt. Hat auch der VS-Debugger beim Beenden nicht die Reihenfolge der Entlade-DLLs geschrieben? Vielleicht könntest du das untersuchen. – sashoalm

2

Seltsame Symptome, die exit(), _exit(), ExitProcess() und andere in einem Multithread-Programm enthalten - insbesondere wenn die Symptome zwischen Hosts variieren - haben den Geruch einer Variablen, die von verschiedenen Threads geändert oder aufgerufen wird, ohne Synchronisation . Wenn Sie sich den anderen Thread ansehen, mit dem Sie verbunden sind, scheint es, dass Sie eine flüchtige Variable verwenden, um zwischen Threads zu kommunizieren, aber keine Form der Synchronisation (z. B. Code, der auf den Wert dieser Variablen zugreift und Code, der dies ändert) Wert muss über einen kritischen Abschnitt, Mutex oder ein vergleichbares Konstrukt kooperieren).

Das bisschen indirekte Beweise macht den Geruch noch stärker.Das grundlegende Problem, das ich vermute, ist, dass die Deklaration einer Variablen als flüchtig weder notwendig noch ausreichend ist, um sicherzustellen, dass Variablen immer Werte haben, die für Ihr Programm sinnvoll sind. Insbesondere reicht es nicht aus, zu verhindern, dass ein Thread, der eine Variable verändert, vorzeitig beendet wird, wenn die Modifikation nur teilweise abgeschlossen ist, und dass ein anderer Thread versucht, auf die betroffene Variable zuzugreifen oder diese zu modifizieren.

Wenn Sie einige Artikel von Herb Sutter (besonders diejenigen, die sich mit Thread-Synchronisation in seiner "Guru der Woche" -Serie beschäftigen) nachschlagen, finden Sie detaillierte Erklärungen, warum das so ist. Andere Autoren beschreiben auch solche Dinge, aber Sutters Artikel sind solche, an die ich mich spontan erinnere.

Die Lösung besteht darin, einige Mittel der Synchronisation einzuführen, und für jeden Thread in Ihrem Programm, es religiös zu verwenden, bevor Sie auf Variablen zugreifen oder diese ändern. Dies vermeidet die verschiedenen Probleme (Rassenzustände, Operationen, die teilweise durchschritten werden), die Symptome verursachen würden, die Sie beschreiben.

Solche Probleme werden selten von einem Debugger durchbrochen. Der Grund dafür ist, dass die Symptome eine emergente Eigenschaft sind. Mehrere unwahrscheinliche und oft unabhängige Ereignisse in verschiedenen Ausführungsthreads müssen zusammen auftreten. Debugger ändern in der Regel das Timing von Ereignissen in Programmen, und Timing ist eine kritische Überlegung bei den auftretenden Symptomen.

Optionen beinhalten die Schlüsselvariablen atomar (so dass bestimmte Operationen nicht verhindert werden können), kritische Abschnitte (wo die Threads explizit innerhalb eines Programms kooperieren) oder Mutexe (die je nach Definition Threads in verschiedenen Programmen erlauben, zuvor explizit zusammenzuarbeiten) Zugriff auf Shared Memory).

Ja, dies führt zu einem Flaschenhals in Ihrem Programm - ein Punkt, wo jeder Thread Rendezvous und potenziell aufeinander warten muss. Dies kann den Durchsatz Ihres Programms beeinflussen. Einige Leute befürworten die Verwendung von volatilen Variablen, um solche Bedenken zu vermeiden. Meistens sind intermittierende Symptome in lang laufenden Programmen, wie Sie sie in dieser Frage beschrieben haben, und der "ähnlichen Frage", zu der Sie verlinkt sind, die Folge.

Es spielt keine Rolle, ob Sie Standardmittel der Synchronisation (z. B. in C++ 11 eingeführt) oder Windows-spezifische Mittel (WIN API-Funktionen) verwenden. Wichtig ist, dass Sie eine absichtliche Synchronisationsmethode verwenden, anstatt Variablen nur flüchtig zu machen. Verschiedene Optionen für die Synchronisierung haben unterschiedliche Kompromisse, so dass Sie eine Entscheidung treffen müssen, die für die Bedürfnisse Ihres Programms relevant ist.

Eine weitere Überlegung ist, alle Threads zu signalisieren, damit sie sauber schließen, warten, bis sie alle geschlossen sind, ihre Exit-Codes erfassen und DANN das Programm beenden. Es ist oft weniger fehleranfällig, dies in dem Thread zu tun, der main() ausführt - was letztendlich den Prozess startet, so dass er wahrscheinlich Zugriff auf Informationen hat, die er richtig bereinigen muss. Wenn ein anderer Thread entscheidet, dass das Programm beendet werden muss, dann ist es besser, wenn er diesen Bedarf zurück zu main() kommuniziert, um dies zu tun.

+0

Ich verstehe nicht ganz, warum es möglicherweise der Fall sein kann. Ein unsynchronisierter Zugriff ist zwar möglich, aber beim Aufrufen von ExitProcess spielen C++ - Regeln keine Rolle mehr, es handelt sich um eine WINAPI-Funktion, und der einzige Punkt, der zählt, ist der Parameter, der an ihn übergeben wird. Und es ist eine Konstante. –

+0

Ich beschrieb Bedenken mit der Synchronisation zwischen Threads, nicht "C++ Regeln". Alles, was es benötigt, ist, dass ein Thread aufgrund einer unangemessenen Interaktion mit Daten, die mit einem anderen Thread geteilt oder von ihm verwendet werden, etwas Unerwartetes tut. – Rob

+0

Das stimmt, aber 'ExitProcess (1)' soll keine Daten mit anderen Threads teilen. Wenn die Synchronisierung nicht richtig durchgeführt wird, befinden sich die freigegebenen Daten möglicherweise im ungültigen Status, aber wie kann 'ExitProcess (1)' möglicherweise vom Status aller gemeinsam genutzten Daten abhängen? –