98 Stimmen

Wie kann ich SIGSEGV (Segmentation Fault) abfangen und einen Stapeltrace unter JNI auf Android erhalten?

Ich ziehe ein Projekt in das neue Android Native Development Kit (d.h. JNI) um und möchte SIGSEGV einfangen, sollte es auftreten (möglicherweise auch SIGILL, SIGABRT, SIGFPE), um einen schönen Absturzberichtsdialog anzuzeigen, anstelle von (oder vor) dem, was derzeit passiert: dem sofortigen unsanften Tod des Prozesses und möglicherweise dem Versuch des Betriebssystems, ihn neu zu starten. (Bearbeitung: Der JVM/Dalvik-VM fängt das Signal ab und protokolliert einen Stack-Trace und andere nützliche Informationen; ich möchte dem Benutzer einfach die Möglichkeit bieten, mir diese Informationen per E-Mail zu senden.)

Die Situation ist die: Ein großer C-Code, den ich nicht geschrieben habe, erledigt den Großteil der Arbeit in dieser Anwendung (alle Spiellogik) und obwohl er auf zahlreichen anderen Plattformen gut getestet ist, ist es durchaus möglich, dass ich bei meiner Android-Portierung Müll an ihn weiterleite und einen Absturz im nativen Code verursache, daher möchte ich die Absturzberichte (sowohl nativ als auch Java), die derzeit im Android-Protokoll auftauchen (ich vermute, es wäre stderr in einer nicht-Android-Situation). Ich bin frei, sowohl den C- als auch den Java-Code beliebig zu ändern, obwohl die Rückrufe (sowohl eingehend als auch ausgehend aus JNI) etwa 40 sind und offensichtlich, Bonuspunkte für kleine Unterschiede.

Ich habe von der Signalkettenbibliothek in J2SE, libjsig.so, gehört und wenn ich einen Signalhandler wie diesen sicher auf Android installieren könnte, würde das das Einfangen des Teils meiner Frage lösen, aber ich sehe keine solche Bibliothek für Android/Dalvik.

0 Stimmen

Wenn Sie die Java-VM durch ein Wrapper-Skript starten können, können Sie überprüfen, ob die App anormale beendet wurde, und die Fehlerberichterstattung durchführen. Dadurch können Sie alle Arten von abnormalen Beendigungen sauber einfangen, sei es SIGSEGV, SIGKILL oder was auch immer. Ich glaube jedoch nicht, dass dies mit Standard-Android-Apps möglich ist, daher wird dies als Kommentar gepostet (konvertiert von Antwort).

0 Stimmen

Auch sehen: Kann ein Java Android-Programm nicht mit Valgrind ausführen um zu erfahren, wie man eine Android-App mit einem Wrapper-Skript (in adb shell) startet.

2 Stimmen

Die Antwort muss aktualisiert werden. Der im akzeptierten Antwort angegebene Quellcode wird zu einem undefinierten Verhalten führen, da nicht asynchron-sichere Funktionen aufgerufen werden. Bitte siehe hier: stackoverflow.com/questions/34547199/…

84voto

Chris Boyle Punkte 11316

Bearbeiten: Ab Jelly Bean können Sie den Stack-Trace nicht mehr erhalten, weil READ_LOGS weggefallen ist. :-(

Ich habe tatsächlich einen Signalhandler zum Laufen gebracht, ohne etwas zu Exotisches zu tun, und habe Code veröffentlicht, in dem Sie auf github sehen können (Bearbeitung: Verlinkung zu historischer Version; Ich habe den Absturz-Handler seitdem entfernt). So geht's:

  1. Verwenden Sie sigaction(), um die Signale abzufangen und die alten Handler zu speichern. (android.c:570)
  2. Zeit vergeht, ein Segfault passiert.
  3. Rufen Sie im Signalhandler ein letztes Mal JNI auf und rufen Sie dann den alten Handler auf. (android.c:528)
  4. Rufen Sie in diesem JNI-Aufruf alle nützlichen Debugging-Informationen ab und rufen Sie startActivity() auf einer Aktivität auf, die als eigener Prozess gekennzeichnet ist. (SGTPuzzles.java:962, AndroidManifest.xml:28)
  5. Wenn Sie aus Java zurückkommen und den alten Handler aufrufen, wird das Android-Framework eine Verbindung zu debuggerd herstellen, um einen schönen nativen Trace für Sie zu protokollieren, und dann wird der Prozess beendet. (debugger.c, debuggerd.c)
  6. Unterdessen startet Ihre Absturzbehandlungsaktivität. Eigentlich sollten Sie ihr die PID übergeben, damit sie auf Abschnitt 5 warten kann, um abzuschließen; Das mache ich nicht. Hier entschuldigen Sie sich beim Benutzer und fragen, ob Sie ein Protokoll senden können. Wenn ja, sammeln Sie die Ausgabe von logcat -d -v threadtime und starten Sie ein ACTION_SEND mit Empfänger, Betreff und Text. Der Benutzer muss auf Senden drücken. (CrashHandler.java, SGTPuzzles.java:462, strings.xml:41
  7. Achten Sie darauf, dass logcat fehlschlägt oder länger als einige Sekunden dauert. Ich bin auf ein Gerät gestoßen, das T-Mobile Pulse / Huawei U8220, bei dem logcat sofort in den T (getracet) -Zustand wechselt und hängen bleibt. (CrashHandler.java:70, strings.xml:51)

In einer non-Android-Situation wäre einiges davon anders. Sie müssten Ihren eigenen nativen Trace sammeln, siehe diese andere Frage, je nachdem, welche Art von libc Sie haben. Sie müssten das Dumpen dieses Traces, das Starten Ihres separaten Absturz-Handler-Prozesses und das Senden der E-Mail auf geeignete Weise für Ihre Plattform handhaben, aber ich stelle mir vor, dass der allgemeine Ansatz immer noch funktionieren sollte.

2 Stimmen

Idealerweise überprüfen Sie, ob der Absturz in Ihrer Bibliothek aufgetreten ist. Wenn er woanders aufgetreten ist (sagen wir, innerhalb der VM), könnten Ihre JNI-Aufrufe aus dem Signalhandler die Dinge ziemlich durcheinander bringen. Es ist nicht das Ende der Welt, da Sie sowieso mitten im Absturz sind, aber es könnte die Diagnose eines VM-Absturzes erschweren (oder einen seltsamen VM-Absturz verursachen, der in einem Android-Fehlerbericht endet und alle verwirrt).

0 Stimmen

Du bist wunderbar @Chris, dass du dein Forschungsprojekt geteilt hast!

0 Stimmen

Vielen Dank, das war hilfreich, um herauszufinden, wo mein JNI verrückt spielte. Außerdem, hallo von einem DCS-Absolventen!

16voto

xroche Punkte 219

Ich bin ein bisschen spät dran, aber ich hatte genau die gleiche Notwendigkeit, und ich habe eine kleine Bibliothek entwickelt, um sie zu lösen, indem ich gemeinsame Abstürze (SEGV, SIBGUS, usw.) im JNI-Code abfange und sie durch reguläre java.lang.Error Ausnahmen ersetze. Als Bonus, wenn der Client auf Android >= 4.1.1 läuft, enthält der Stack-Trace den aufgelösten Backtrace des Absturzes (eine Pseudo-Spur, die den vollständigen nativen Stack-Trace enthält). Sie werden sich nicht von bösartigen Abstürzen erholen (zum Beispiel, wenn Sie den Speicherzuweiser beschädigen), aber zumindest sollte es Ihnen ermöglichen, sich von den meisten von ihnen zu erholen. (Bitte melden Sie Erfolge und Misserfolge, der Code ist brandneu)

Weitere Informationen unter https://github.com/xroche/coffeecatch (der Code steht unter der BSD 2-Klausel-Lizenz)

6voto

Ted Mielczarek Punkte 3744

Zum Mitnehmen, Google Breakpad funktioniert gut auf Android. Ich habe die Übertragungsarbeit gemacht und wir versenden sie als Teil von Firefox Mobile. Es erfordert ein wenig Einrichtung, da es Ihnen keine Stapelverfolgungen auf der Client-Seite bietet, sondern Ihnen den Rohstapelspeicher sendet und das Stapelgehen serverseitig durchführt (damit Sie keine Debug-Symbole mit Ihrer App versenden müssen).

1 Stimmen

Es ist nahezu unmöglich, Breakpad zu konfigurieren, wenn die Dokumentation vollständig fehlt.

0 Stimmen

Es ist wirklich nicht so schwer, und es gibt viele Dokumentationen im Projekt-Wiki. Tatsächlich gibt es nun für Android ein NDK-Build-Makefile, das sehr einfach zu verwenden sein sollte: code.google.com/p/google-breakpad/source/browse/trunk/…

0 Stimmen

Sie müssen auch ein Modul kompilieren, das Debug-Symboldateien für Android vorverarbeitet, und können das nur unter Linux kompilieren. Wenn Sie auf einem Mac kompilieren, wird nur der Mac/iOS dSym-Präprozessor erstellt.

5voto

mas90 Punkte 51

In meiner begrenzten Erfahrung (nicht-Android) führt ein SIGSEGV im JNI-Code in der Regel dazu, dass der JVM abstürzt, bevor die Kontrolle an Ihren Java-Code zurückgegeben wird. Ich erinnere mich vage daran, von einer nicht-Sun-JVM gehört zu haben, die es ermöglicht, SIGSEGV abzufangen, aber soweit ich mich erinnere, kann man nicht erwarten, dies tun zu können.

Sie können versuchen, sie in C zu fangen (siehe sigaction(2)), obwohl Sie nach einem SIGSEGV (oder SIGFPE oder SIGILL) Handler sehr wenig tun können, da das fortgesetzte Verhalten eines Prozesses offiziell undefiniert ist.

0 Stimmen

Nun, das Verhalten ist undefiniert nach dem "ignorieren eines SIGFPE-, SIGILL- oder SIGSEGV-Signals, das nicht von kill(2) oder raise(3) erzeugt wurde", aber nicht unbedingt während des Fangens eines solchen Signals. Der aktuelle Plan ist, einen C-Signalhandler zu versuchen, der auf Java zurückverweist und irgendwie den Thread beendet, ohne den Prozess zu beenden. Dies mag möglich sein oder auch nicht. :-)

1 Stimmen

C Backtrace-Anweisungen: stackoverflow.com/questions/76822/…

1 Stimmen

...außer ich kann nicht backtrace() verwenden, weil Android nicht glibc verwendet, sondern Bionic. :-( Stattdessen wird etwas mit _Unwind_Backtrace aus unwind.h benötigt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X