Comment puis-je attraper SIGSEGV (erreur de segmentation) et obtenir une trace de pile sous JNI sur Android?

92

Je déplace un projet vers le nouveau kit de développement natif Android (c'est-à-dire JNI) et j'aimerais attraper SIGSEGV, si cela se produit (peut-être aussi SIGILL, SIGABRT, SIGFPE) afin de présenter une belle boîte de dialogue de rapport de plantage, au lieu de (ou avant) ce qui se passe actuellement: la mort immédiate et sans cérémonie du processus et éventuellement une tentative du système d'exploitation pour le redémarrer. ( Modifier: la machine virtuelle JVM / Dalvik capte le signal et enregistre une trace de pile et d'autres informations utiles; je veux juste offrir à l'utilisateur la possibilité de m'envoyer ces informations par courrier électronique.)

La situation est la suivante: un grand corps de code C que je n'ai pas écrit fait l'essentiel du travail dans cette application (toute la logique du jeu) et bien qu'il soit bien testé sur de nombreuses autres plates-formes, il est tout à fait possible que moi, dans mon Android port, va le nourrir des déchets et provoquer un crash dans le code natif, donc je veux les vidages sur incident (natifs et Java) qui apparaissent actuellement dans le journal Android (je suppose que ce serait stderr dans une situation non Android). Je suis libre de modifier arbitrairement le code C et Java, bien que les rappels (entrant et sortant de JNI) soient environ 40 et, évidemment, des points bonus pour les petits diffs.

J'ai entendu parler de la bibliothèque de chaînage de signaux dans J2SE, libjsig.so, et si je pouvais installer en toute sécurité un gestionnaire de signaux comme celui-ci sur Android, cela résoudrait la partie captivante de ma question, mais je ne vois aucune bibliothèque de ce type pour Android / Dalvik .

Chris Boyle
la source
Si vous pouvez démarrer la machine virtuelle Java via un script wrapper, vous pouvez vérifier si l'application s'est fermée de manière anormale et générer le rapport d'erreur. Cela vous permettrait de capturer proprement toutes sortes de sorties anormales, qu'elles soient SIGSEGV, SIGKILL ou autre. Cependant, je ne pense pas que cela soit possible avec les applications Android courantes, alors publiez-le sous forme de commentaire (converti à partir de la réponse).
sleske
Voir aussi: Impossible d'exécuter un programme Java Android avec Valgrind pour savoir comment démarrer une application Android avec un script wrapper (dans le shell adb).
sleske
1
La réponse doit être mise à jour. Le code source fourni dans la réponse acceptée entraînera un comportement indéfini en raison d'un appel à des fonctions non sécurisées pour les signaux asynchrones. Veuillez voir ici: stackoverflow.com/questions/34547199/…
user1506104

Réponses:

82

Edit: À partir de Jelly Bean, vous ne pouvez pas obtenir la trace de la pile, car il READ_LOGSest parti . :-(

En fait, j'ai un gestionnaire de signaux fonctionnant sans rien faire de trop exotique, et j'ai publié du code en l'utilisant, que vous pouvez voir sur github (modifier: lien vers la version historique; j'ai supprimé le gestionnaire de crash depuis lors). Voici comment:

  1. Utilisez sigaction()pour attraper les signaux et stocker les anciens gestionnaires. ( android.c: 570 )
  2. Le temps passe, un segfault se produit.
  3. Dans le gestionnaire de signaux, appelez une dernière fois JNI, puis appelez l'ancien gestionnaire. ( android.c: 528 )
  4. Dans cet appel JNI, enregistrez toutes les informations de débogage utiles et appelez startActivity()une activité signalée comme devant être dans son propre processus. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Lorsque vous revenez de Java et que vous appelez cet ancien gestionnaire, le framework Android se connectera debuggerdpour enregistrer une belle trace native pour vous, puis le processus mourra. ( debugger.c , debuggerd.c )
  6. Pendant ce temps, votre activité de gestion des accidents démarre. Vous devriez vraiment lui transmettre le PID pour qu'il puisse attendre la fin de l'étape 5; Je ne fais pas ça. Ici, vous vous excusez auprès de l'utilisateur et demandez si vous pouvez envoyer un journal. Si tel est le cas, rassemblez la sortie logcat -d -v threadtimeet lancez un ACTION_SENDavec le destinataire, le sujet et le corps renseignés. L'utilisateur devra appuyer sur Envoyer. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Attention aux logcatéchecs ou aux prises de plus de quelques secondes. J'ai rencontré un appareil, le T-Mobile Pulse / Huawei U8220, où logcat passe immédiatement à l'état T(tracé) et se bloque. ( CrashHandler.java:70 , strings.xml: 51 )

Dans une situation non Android, certains de ces éléments seraient différents. Vous auriez besoin de rassembler votre propre trace native, voir cette autre question , selon le type de libc dont vous disposez. Vous auriez besoin de gérer le vidage de cette trace, le lancement de votre processus de gestion des incidents séparé et l'envoi de l'e-mail de manière appropriée pour votre plate-forme, mais j'imagine que l'approche générale devrait toujours fonctionner.

Chris Boyle
la source
2
Idéalement, vous vérifieriez si le plantage s'est produit dans votre bibliothèque. Si cela se produisait ailleurs (par exemple, à l'intérieur de la VM), vos appels JNI du gestionnaire de signaux pourraient perturber assez mal les choses. Ce n'est pas la fin du monde, puisque vous êtes en plein crash de toute façon, mais cela pourrait rendre le diagnostic d'un crash de VM plus difficile (ou provoquer un crash de VM bizarre qui se termine dans un rapport de bogue Android et déroute tout le monde).
fadden
Vous êtes formidable @Chris pour partager votre projet de recherche à ce sujet!
olafure
Merci, cela a été utile pour trouver où mon JNI allait devenir fou. Aussi, bonjour d'un ancien DCS!
Nick
3
Le démarrage d'une activité dans un nouveau processus à partir d'un service nécessite également le code suivant:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme
1
Cette solution est-elle toujours valable sous Jelly Bean? L'étape 6 n'échouera-t-elle pas à enregistrer les debuggerdsorties?
Josh
14

Je suis un peu en retard, mais j'avais le même besoin exact, et je l' ai développé une petite bibliothèque pour y remédier, en attrapant des accidents communs ( SEGV, SIBGUS, etc.) à l' intérieur du code JNI , et les remplacer par régulières java.lang.Error exceptions . Bonus, si le client s'exécute sur Android> = 4.1.1, la trace de pile intègre la trace arrière résolue du crash (une pseudo-trace contenant la trace de pile native complète). Vous ne récupérerez pas de plantages vicieux (c'est-à-dire si vous corrompez l'allocateur, par exemple), mais au moins cela devrait vous permettre de récupérer de la plupart d'entre eux. (veuillez signaler les succès et les échecs, le code est tout neuf)

Plus d'informations sur https://github.com/xroche/coffeecatch (le code est une licence BSD 2-Clauses )

Xroche
la source
6

FWIW, Google Breakpad fonctionne très bien sur Android. J'ai fait le travail de portage et nous l'expédions dans le cadre de Firefox Mobile. Il nécessite un peu de configuration, car il ne vous donne pas de traces de pile côté client, mais vous envoie la mémoire brute de la pile et fait la marche de la pile côté serveur (vous n'avez donc pas à expédier de symboles de débogage avec votre application ).

Ted Mielczarek
la source
1
Il est presque impossible de configurer Breakpad compte tenu de la documentation absolument manquante
shader
Ce n'est vraiment pas si difficile, et il y a beaucoup de documentation sur le wiki du projet. En fait, pour Android, il y a maintenant un Makefile de build NDK et il devrait être super facile à utiliser: code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek
Vous devez également compiler un module qui prétraite les fichiers de symboles de débogage pour Android et vous ne pouvez le compiler que sous Linux. Lorsque vous compilez sur un Mac, il ne construit que le préprocesseur dSym Mac / iOS.
shader
5

Dans mon expérience limitée (non-Android), SIGSEGV dans le code JNI plantera généralement la JVM avant que le contrôle ne soit retourné à votre code Java. Je me souviens vaguement avoir entendu parler d'une machine virtuelle Java non-Sun qui vous permet d'attraper SIGSEGV, mais AFAICR vous ne pouvez pas vous attendre à pouvoir le faire.

Vous pouvez essayer de les attraper en C (voir sigaction (2)), bien que vous puissiez faire très peu après un gestionnaire SIGSEGV (ou SIGFPE ou SIGILL) car le comportement en cours d'un processus est officiellement indéfini.

mas90
la source
Eh bien, le comportement est indéfini après avoir "ignoré un signal SIGFPE, SIGILL ou SIGSEGV qui n'a pas été généré par kill (2) ou rise (3)", mais pas nécessairement lors de la capture d'un tel signal. Le plan actuel est d'essayer un gestionnaire de signal C qui rappelle Java et, d'une manière ou d'une autre, termine le thread sans terminer le processus. Cela peut être possible ou non. :-)
Chris Boyle
1
Instructions de traçage en
Chris Boyle
1
... sauf que je ne peux pas utiliser backtrace (), car Android n'utilise pas la glibc, il utilise Bionic. :-( Quelque chose impliquant _Unwind_Backtracede unwind.hsera nécessaire à la place.
Chris Boyle