Wie kann ich SIGSEGV (Segmentierungsfehler) abfangen und unter JNI unter Android einen Stack-Trace abrufen?

92

Ich verschiebe ein Projekt auf das neue Android Native Development Kit (dh JNI) und möchte SIGSEGV abfangen, falls es auftreten sollte (möglicherweise auch SIGILL, SIGABRT, SIGFPE), um stattdessen einen schönen Dialog zur Absturzberichterstattung zu präsentieren (oder vorher) was aktuell passiert: der sofortige, zügellose Tod des Prozesses und möglicherweise ein Versuch des Betriebssystems, ihn neu zu starten. ( Bearbeiten: Die JVM / Dalvik-VM fängt das Signal ab und protokolliert einen Stack-Trace und andere nützliche Informationen. Ich möchte dem Benutzer nur die Möglichkeit bieten, diese Informationen wirklich per E-Mail an mich zu senden.)

Die Situation ist: Ein großer Teil des C-Codes, den ich nicht geschrieben habe, erledigt den größten Teil der Arbeit in dieser Anwendung (die gesamte Spielelogik), und obwohl er auf zahlreichen anderen Plattformen gut getestet wurde, ist es durchaus möglich, dass ich in meinem Android Port, wird es Müll füttern und einen Absturz im nativen Code verursachen, daher möchte ich die Absturz-Dumps (sowohl native als auch Java), die derzeit im Android-Protokoll angezeigt werden (ich denke, es wäre stderr in einer Nicht-Android-Situation). Es steht mir frei, sowohl C- als auch Java-Code willkürlich zu ändern, obwohl die Rückrufe (sowohl beim Ein- als auch beim Verlassen von JNI) etwa 40 betragen und natürlich Bonuspunkte für kleine Unterschiede.

Ich habe von der Signalverkettungsbibliothek in J2SE, libjsig.so, gehört, und wenn ich einen solchen Signalhandler sicher auf Android installieren könnte, würde dies den auffälligen Teil meiner Frage lösen, aber ich sehe keine solche Bibliothek für Android / Dalvik .

Chris Boyle
quelle
Wenn Sie die Java-VM über ein Wrapper-Skript starten können, können Sie überprüfen, ob die App abnormal beendet wurde, und die Fehlerberichterstattung durchführen. Das würde es Ihnen ermöglichen, alle Arten von abnormalen Ausgängen sauber zu fangen, sei es SIGSEGV, SIGKILL oder was auch immer. Ich glaube jedoch nicht, dass dies mit Standard-Android-Apps möglich ist. Veröffentlichen Sie dies daher als Kommentar (konvertiert von der Antwort).
Sleske
Siehe auch: Mit Valgrind kann kein Java-Android-Programm ausgeführt werden, um zu erfahren, wie eine Android-App mit einem Wrapper-Skript (in der ADB-Shell) gestartet wird.
Sleske
1
Die Antwort muss aktualisiert werden. Der in der akzeptierten Antwort angegebene Quellcode führt zu einem undefinierten Verhalten aufgrund des Aufrufs nicht asynchroner signal-sicherer Funktionen. Bitte sehen Sie hier: stackoverflow.com/questions/34547199/…
user1506104

Antworten:

82

Edit: Von Jelly Bean ab können Sie den Stack - Trace erhalten, weil READ_LOGSweg ging . :-(

Ich habe tatsächlich einen Signal-Handler zum Laufen gebracht , ohne etwas zu Exotisches zu tun, und habe damit Code veröffentlicht, den Sie auf github sehen können (bearbeiten: Verknüpfung mit historischer Version; ich habe den Crash-Handler seitdem entfernt). Hier ist wie:

  1. Verwenden Sie sigaction()diese Option , um die Signale abzufangen und die alten Handler zu speichern. ( android.c: 570 )
  2. Die Zeit vergeht, ein Segfault tritt auf.
  3. Rufen Sie im Signalhandler ein letztes Mal JNI auf und rufen Sie dann den alten Handler auf. ( android.c: 528 )
  4. Protokollieren Sie in diesem JNI-Aufruf alle nützlichen Debugging-Informationen und rufen Sie startActivity()eine Aktivität auf, die als in ihrem eigenen Prozess vorhanden markiert ist. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Wenn Sie von Java zurückkehren und diesen alten Handler aufrufen, stellt das Android-Framework eine Verbindung her, debuggerdum eine nette native Ablaufverfolgung für Sie zu protokollieren, und der Vorgang wird dann beendet. ( debugger.c , debuggerd.c )
  6. In der Zwischenzeit wird Ihre Crash-Handling-Aktivität gestartet. Sie sollten die PID wirklich übergeben, damit sie warten kann, bis Schritt 5 abgeschlossen ist. Ich mache das 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 threadtimeund starten Sie eine ACTION_SENDmit Empfänger, Betreff und Text ausgefüllt. Der Benutzer muss Senden drücken. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml : 41
  7. Achten Sie darauf, dass Sie logcatversagen oder länger als ein paar Sekunden dauern. Ich bin auf ein Gerät gestoßen, das T-Mobile Pulse / Huawei U8220, bei dem logcat sofort in den T(verfolgten) Zustand wechselt und hängt. ( CrashHandler.java:70 , strings.xml : 51 )

In einer Situation ohne Android wäre dies teilweise anders. Sie müssten Ihre eigene native Spur sammeln und diese andere Frage sehen , je nachdem, welche Art von Bibliothek Sie haben. Sie müssten das Speichern dieser Ablaufverfolgung, das Starten Ihres separaten Crash-Handler-Prozesses und das Senden der E-Mail auf eine für Ihre Plattform geeignete Weise durchführen, aber ich denke, der allgemeine Ansatz sollte weiterhin funktionieren.

Chris Boyle
quelle
2
Im Idealfall überprüfen Sie, ob der Absturz in Ihrer Bibliothek aufgetreten ist. Wenn es irgendwo anders auftritt (z. B. innerhalb der VM), können Ihre JNI-Aufrufe vom Signalhandler die Dinge ziemlich stark verwirren. Es ist nicht das Ende der Welt, da Sie sich sowieso mitten im Absturz befinden, aber es könnte die Diagnose eines VM-Absturzes erschweren (oder einen bizarren VM-Absturz verursachen, der in einem Android-Fehlerbericht endet und alle verblüfft).
Fadden
Sie sind wundervoll @Chris, wenn Sie Ihr Forschungsprojekt dazu teilen!
Olafure
Vielen Dank, dies war hilfreich, um herauszufinden, wo mein JNI verrückt wurde. Hallo von einem DCS-Alumnus!
Nick
3
Das Starten einer Aktivität in einem neuen Prozess von einem Dienst erfordert auch den folgenden Code:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme
1
Ist diese Lösung unter Jelly Bean noch gültig? Wird Schritt 6 nicht fehlschlagen, um irgendwelche debuggerdAusgaben zu protokollieren ?
Josh
14

Ich bin ein wenig spät, aber ich hatte genau das gleiche Bedürfnis, und ich habe eine kleine Bibliothek entwickelt , diese auszuräumen, durch gemeinsame Abstürze zu kontrollieren ( SEGV, SIBGUSusw.) innerhalb JNI Code , und ersetzen Sie sie durch regelmäßige java.lang.Error Ausnahmen . Bonus, wenn der Client unter Android> = ausgeführt wird 4.1.1, bettet der Stack-Trace den aufgelösten Backtrace des Absturzes ein (ein Pseudo-Trace, der den vollständigen nativen Stack-Trace enthält). Sie werden sich nicht von bösartigen Abstürzen erholen (z. B. wenn Sie beispielsweise den Allokator beschädigen), aber zumindest sollten Sie sich von den meisten davon erholen können. (Bitte melden Sie Erfolge und Misserfolge, der Code ist brandneu)

Weitere Informationen unter https://github.com/xroche/coffeecatch (Code ist BSD 2-Klauseln-Lizenz )

Xroche
quelle
6

FWIW, Google Breakpad funktioniert gut auf Android. Ich habe die Portierungsarbeit erledigt und wir versenden sie als Teil von Firefox Mobile. Es erfordert ein wenig Setup, da es Ihnen keine Stack-Traces auf der Clientseite gibt, sondern Ihnen den Raw-Stack-Speicher sendet und den Stack-Walking-Server ausführt (sodass Sie keine Debug-Symbole mit Ihrer App versenden müssen ).

Ted Mielczarek
quelle
1
Es ist fast unmöglich, Breakpad zu konfigurieren, wenn man die absolut fehlende Dokumentation berücksichtigt
Shader
Es ist wirklich nicht so schwer und es gibt jede Menge Dokumentation im Projekt-Wiki. Tatsächlich gibt es für Android jetzt ein NDK-Build-Makefile, das sehr einfach zu bedienen sein sollte: code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek
Sie müssen auch ein Modul kompilieren, das Debug-Symboldateien für Android vorverarbeitet, und das können Sie nur unter Linux kompilieren. Wenn Sie auf einem Mac kompilieren, wird nur ein Mac / iOS-dSym-Präprozessor erstellt.
Shader
5

Nach meiner begrenzten Erfahrung (nicht mit Android) stürzt SIGSEGV im JNI-Code im Allgemeinen die JVM ab, bevor die Steuerung an Ihren Java-Code zurückgegeben wird. Ich erinnere mich vage, dass ich von einer Nicht-Sun-JVM gehört habe, mit der Sie SIGSEGV fangen können, aber AFAICR können Sie nicht erwarten, dass Sie dazu in der Lage sind.

Sie können versuchen, sie in C abzufangen (siehe Sigaction (2)), obwohl Sie nach einem SIGSEGV- (oder SIGFPE- oder SIGILL-) Handler nur sehr wenig tun können, da das laufende Verhalten eines Prozesses offiziell undefiniert ist.

mas90
quelle
Nun, das Verhalten ist undefiniert, nachdem "ein SIGFPE-, SIGILL- oder SIGSEGV-Signal ignoriert wurde, das nicht durch Kill (2) oder Raise (3) erzeugt wurde", aber nicht unbedingt während des Abfangens eines solchen Signals. Derzeit ist geplant, einen C-Signal-Handler zu verwenden, der Java zurückruft und den Thread irgendwie beendet, ohne den Prozess zu beenden. Dies kann möglich sein oder nicht. :-)
Chris Boyle
1
C Backtrace-Anweisungen: stackoverflow.com/questions/76822/…
Chris Boyle
1
... außer ich kann backtrace () nicht verwenden, da Android kein glibc verwendet, sondern Bionic. :-( Etwas Beteiligung _Unwind_Backtracevon unwind.hstattdessen benötigt.
Chris Boyle