91 votes

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

Je déménage un projet vers le nouveau kit de développement natif d'Android (i.e. JNI) et j'aimerais attraper SIGSEGV, s'il se produit (éventuellement aussi SIGILL, SIGABRT, SIGFPE) afin de présenter une belle boîte de dialogue de rapport de crash, au lieu de (ou avant) ce qui se produit actuellement : la mort immédiate et sans cérémonie du processus et éventuellement une tentative par le système d'exploitation de le redémarrer. ( Edita: La JVM/Dalvik VM 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 la plupart du travail dans cette application (toute la logique du jeu) et bien qu'il soit bien testé sur de nombreuses autres plateformes, il est tout à fait possible que, dans mon portage Android, je le nourrisse de déchets et provoque un crash dans le code natif, donc je veux les crash dumps (à la fois natif et Java) qui apparaissent actuellement dans le log Android (je suppose que ce serait stderr dans une situation non-androïde). Je suis libre de modifier arbitrairement le code C et Java, bien que les callbacks (entrant et sortant de JNI) soient au nombre d'environ 40 et, évidemment, des points bonus pour les petites différences.

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-là sur Android, cela résoudrait la partie capture de ma question, mais je ne vois pas de bibliothèque de ce type pour Android/Dalvik.

81voto

Chris Boyle Points 6194

Edita: À partir de Jelly Bean, vous ne pouvez pas obtenir la trace de la pile, car READ_LOGS s'en est allé . :-(

J'ai réussi à faire fonctionner un gestionnaire de signal sans rien faire de trop exotique, et j'ai publié un code qui l'utilise, que vous pouvez voir sur github (edit : lien vers la version historique ; j'ai supprimé le gestionnaire de crash depuis). Voici comment procéder :

  1. Utilisation sigaction() pour capter les signaux et stocker les anciens gestionnaires. ( Android.c:570 )
  2. Le temps passe, une erreur de segmentation se produit.
  3. Dans le gestionnaire de signal, appelez JNI une dernière fois, puis appelez l'ancien gestionnaire. ( Android.c:528 )
  4. Dans cet appel JNI, enregistrez toutes les informations de débogage utiles, et appelez startActivity() sur une activité signalée comme devant faire l'objet d'un processus propre. ( SGTPuzzles.java:962 , AndroidManifest.xml:28 )
  5. Lorsque vous revenez de Java et que vous appelez cet ancien gestionnaire, le cadre Android se connecte à debuggerd pour enregistrer une belle trace native pour vous, puis le processus s'arrêtera. ( debugger.c , debuggerd.c )
  6. Pendant ce temps, votre activité de traitement des accidents démarre. Vous devriez vraiment lui transmettre le PID pour qu'elle puisse attendre la fin de l'étape 5 ; je ne le fais pas. Ici, vous vous excusez auprès de l'utilisateur et vous lui demandez si vous pouvez lui envoyer un journal. Si c'est le cas, rassemblez la sortie de logcat -d -v threadtime et lancer un ACTION_SEND avec le destinataire, l'objet et le corps du message. L'utilisateur devra appuyer sur Envoyer. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml:41
  7. Attention aux logcat échouer ou prendre plus de quelques secondes. J'ai rencontré un appareil, le T-Mobile Pulse / Huawei U8220, pour lequel logcat entre immédiatement dans la fonction T (tracé) et se suspend. ( CrashHandler.java:70 , strings.xml:51 )

Dans une situation autre qu'Android, certaines choses seraient différentes. Vous devriez recueillir vos propres traces natives, voir cette autre question selon le type de libc que vous avez. Vous devrez gérer le dumping de cette trace, lancer votre propre processus de gestion de crash, et envoyer l'email de manière appropriée à votre plateforme, mais j'imagine que l'approche générale devrait toujours fonctionner.

14voto

xroche Points 111

Je suis un peu en retard, mais j'avais exactement le même besoin, et j'ai développé une petite bibliothèque pour y répondre, en capturant les plantages courants ( SEGV , SIBGUS etc.) à l'intérieur Code JNI et les remplacer par des java.lang.Error exceptions . Bonus, si le client fonctionne sur Android >= 4.1.1 , la trace de la pile contient le message résolu backtrace de la panne (une pseudo-trace contenant la trace de pile native complète). Vous ne récupérerez pas les plantages vicieux (si vous corrompez l'allocateur, par exemple), mais cela devrait au moins vous permettre de récupérer les plantages suivants le plus d'entre eux. (merci de signaler les succès et les échecs, le code est tout neuf)

Plus d'informations à l'adresse suivante https://github.com/xroche/coffeecatch (le code est Licence BSD 2 clauses )

6voto

Ted Mielczarek Points 1996

A TITRE D'INFORMATION, Google Breakpad fonctionne bien sur Android. J'ai fait le travail de portage, et nous le livrons dans le cadre de Firefox Mobile. Il nécessite un peu d'installation, car il ne vous donne pas de traces de pile côté client, mais vous envoie la mémoire brute de la pile et effectue la marche de la pile côté serveur (vous n'avez donc pas à envoyer des symboles de débogage avec votre application).

5voto

mas90 Points 21

D'après mon expérience limitée (hors Android), SIGSEGV dans le code JNI plante généralement la JVM avant que le contrôle ne soit rendu à votre code Java. Je me souviens vaguement avoir entendu parler d'une JVM 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 ne puissiez pas faire grand-chose après un gestionnaire SIGSEGV (ou SIGFPE ou SIGILL), car le comportement continu d'un processus est officiellement indéfini.

0voto

sleske Points 29978

Vous pourriez essayer de démarrer votre application Java (c'est-à-dire la VM Java) par une sorte de wrapper script. Celui-ci pourrait alors vérifier si l'application s'est arrêtée de manière anormale, et faire le rapport d'erreur. Cela vous permettrait d'attraper proprement toutes sortes de sorties anormales, qu'il s'agisse de SIGSEGV, SIGKILL ou autre.

Il pourrait être difficile de déterminer le signal qui a tué l'application...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X