Come posso rilevare SIGSEGV (errore di segmentazione) e ottenere una traccia dello stack sotto JNI su Android?


92

Sto spostando un progetto nel nuovo Android Native Development Kit (cioè JNI) e vorrei catturare SIGSEGV, se dovesse verificarsi (possibilmente anche SIGILL, SIGABRT, SIGFPE) per presentare una bella finestra di dialogo di segnalazione di crash, invece di (o prima) ciò che accade attualmente: la morte immediata e senza cerimonie del processo e forse qualche tentativo da parte del sistema operativo di riavviarlo. ( Modifica: la VM JVM / Dalvik cattura il segnale e registra una traccia dello stack e altre informazioni utili; Voglio solo offrire all'utente la possibilità di inviarmi queste informazioni tramite e-mail.)

La situazione è: una grande quantità di codice C che non ho scritto fa la maggior parte del lavoro in questa applicazione (tutta la logica del gioco) e sebbene sia ben testato su numerose altre piattaforme, è del tutto possibile che io, nel mio Android port, alimenterà la spazzatura e causerà un arresto anomalo nel codice nativo, quindi voglio i dump di arresto anomalo (sia nativi che Java) che attualmente vengono visualizzati nel registro di Android (immagino che sarebbe stderr in una situazione non Android). Sono libero di modificare arbitrariamente sia il codice C che Java, sebbene i callback (sia in entrata che in uscita da JNI) siano circa 40 e, ovviamente, punti bonus per piccole differenze.

Ho sentito parlare della libreria di concatenamento del segnale in J2SE, libjsig.so, e se potessi installare in sicurezza un gestore di segnali come quello su Android, risolverebbe la parte interessante della mia domanda, ma non vedo tale libreria per Android / Dalvik .


Se è possibile avviare Java VM tramite uno script wrapper, è possibile verificare se l'app è stata chiusa in modo anomalo ed eseguire la segnalazione degli errori. Ciò ti consentirebbe di catturare in modo pulito tutti i tipi di uscite anomale, siano esse SIGSEGV, SIGKILL o qualsiasi altra cosa. Tuttavia, non penso che ciò sia possibile con le app Android di serie, quindi pubblicalo come commento (convertito dalla risposta).
sleske

Vedi anche: Impossibile eseguire un programma Android Java con Valgrind per informazioni su come avviare un'app Android con uno script wrapper (nella shell adb).
sleske

1
La risposta deve essere aggiornata. Il codice sorgente fornito nella risposta accettata risulterà in un comportamento indefinito dovuto alla chiamata a funzioni non asincrone di sicurezza. Si prega di consultare qui: stackoverflow.com/questions/34547199/...
user1506104

Risposte:


82

Modifica: da Jelly Bean in poi non è possibile ottenere la traccia dello stack, perché è READ_LOGSandato via . :-(

In realtà ho un gestore di segnali che funziona senza fare nulla di troppo esotico e ho rilasciato del codice che lo utilizza, che puoi vedere su GitHub (modifica: collegamento alla versione storica; da allora ho rimosso il gestore dei crash). Ecco come:

  1. Utilizzare sigaction()per catturare i segnali e memorizzare i vecchi gestori. ( android.c: 570 )
  2. Il tempo passa, accade un segfault.
  3. Nel gestore del segnale, chiama JNI un'ultima volta e poi chiama il vecchio gestore. ( android.c: 528 )
  4. In quella chiamata JNI, registra tutte le informazioni di debug utili e chiama startActivity()un'attività contrassegnata come necessaria nel proprio processo. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Quando torni da Java e chiami quel vecchio gestore, il framework Android si connetterà a debuggerdper registrare una bella traccia nativa per te, e quindi il processo morirà. ( debugger.c , debuggerd.c )
  6. Nel frattempo, inizia la tua attività di gestione degli incidenti. In realtà dovresti passargli il PID in modo che possa attendere il completamento del passaggio 5; Io non lo faccio. Qui ti scusi con l'utente e chiedi se puoi inviare un log. In tal caso, raccogliere l'output di logcat -d -v threadtimee avviare un messaggio ACTION_SENDcon destinatario, oggetto e corpo compilati. L'utente dovrà premere Invia. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Fai attenzione se logcatfallisci o impieghi più di pochi secondi. Ho incontrato un dispositivo, il T-Mobile Pulse / Huawei U8220, in cui logcat entra immediatamente nello stato T(tracciato) e si blocca. ( CrashHandler.java:70 , strings.xml: 51 )

In una situazione non Android, alcuni di questi sarebbero diversi. Dovresti raccogliere la tua traccia nativa, vedere quest'altra domanda , a seconda del tipo di libc che hai. Dovresti gestire il dumping di quella traccia, avviare il tuo processo di gestione degli arresti anomali separato e inviare l'e-mail in alcuni modi appropriati per la tua piattaforma, ma immagino che l'approccio generale dovrebbe ancora funzionare.


2
Idealmente dovresti controllare se il crash si è verificato nella tua libreria. Se si è verificato da qualche altra parte (ad esempio, all'interno della VM), le chiamate JNI dal gestore del segnale potrebbero confondere le cose piuttosto male. Non è la fine del mondo, dal momento che sei comunque a metà incidente, ma potrebbe rendere più difficile la diagnosi di un arresto anomalo della VM (o causare un bizzarro arresto anomalo della VM che finisce in un bug report di Android e sconcerta tutti).
fadden

Sei meraviglioso @Chris per aver condiviso il tuo progetto di ricerca su questo!
olafure

Grazie, è stato utile per scoprire dove il mio JNI stava impazzendo. Inoltre, ciao da un alunno DCS!
Nick

3
L'avvio di un'attività in un nuovo processo da un servizio richiede anche il seguente codice:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme

1
Questa soluzione è ancora valida con Jelly Bean? Il passaggio 6 non riuscirà a registrare alcun debuggerdoutput?
Josh

14

Sono un po 'in ritardo, ma ho avuto la stessa necessità, e ho sviluppato una piccola biblioteca per affrontarlo, con la cattura di crash comuni ( SEGV, SIBGUS, ecc) all'interno del codice JNI , e li sostituisce con regolari java.lang.Error eccezioni . Bonus, se il client è in esecuzione su Android> = 4.1.1, la traccia dello stack incorpora il backtrace risolto del crash (una pseudo-traccia contenente la traccia dello stack nativa completa). Non ti riprenderai da arresti anomali (ad esempio se corrompi l'allocatore, per esempio), ma almeno dovrebbe permetterti di ripristinarli dalla maggior parte di essi. (si prega di segnalare successi e fallimenti, il codice è nuovo di zecca)

Maggiori informazioni su https://github.com/xroche/coffeecatch (il codice è la licenza BSD a 2 clausole )


6

FWIW, Google Breakpad funziona bene su Android. Ho eseguito il porting e lo spediremo come parte di Firefox Mobile. Richiede una piccola configurazione, dal momento che non ti dà tracce dello stack sul lato client, ma ti invia la memoria dello stack non elaborato e fa lo stack walking lato server (quindi non devi spedire simboli di debug con la tua app ).


1
È quasi impossibile configurare Breakpad considerando la documentazione assolutamente mancante
shader

Non è davvero così difficile e c'è molta documentazione sul wiki del progetto. In effetti, per Android ora c'è un Makefile di build NDK e dovrebbe essere semplicissimo da usare: code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek

Devi anche compilare un modulo che preprocessa i file di simboli di debug per Android e puoi compilarlo solo su Linux. Quando compili su un Mac, compila solo il preprocessore dSym per Mac / iOS.
shader

5

Nella mia esperienza limitata (non Android), SIGSEGV nel codice JNI generalmente bloccherà la JVM prima che il controllo venga restituito al codice Java. Ricordo vagamente di aver sentito parlare di una JVM non Sun che ti consente di catturare SIGSEGV, ma AFAICR non puoi aspettarti di poterlo fare.

Puoi provare a catturarli in C (vedi sigaction (2)), sebbene tu possa fare molto poco dopo un gestore SIGSEGV (o SIGFPE o SIGILL) poiché il comportamento in corso di un processo è ufficialmente indefinito.


Bene, il comportamento non è definito dopo "aver ignorato un segnale SIGFPE, SIGILL o SIGSEGV che non è stato generato da kill (2) o raise (3)", ma non necessariamente durante la cattura di tale segnale. Il piano attuale è provare un gestore di segnali C che richiami a Java e, in qualche modo, termini il thread senza terminare il processo. Questo può o non può essere possibile. :-)
Chris Boyle

1
Istruzioni di backtrace C: stackoverflow.com/questions/76822/…
Chris Boyle

1
... tranne che non posso usare backtrace (), perché Android non usa glibc, usa Bionic. :-( Sarà invece necessario qualcosa di coinvolgente _Unwind_Backtraceda unwind.h.
Chris Boyle
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.