web-dev-qa-db-fra.com

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

Je déplace n projet vers le nouveau Android Kit de développement natif (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 (ou avant) de 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 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 vraiment.)

La situation est: 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 plates-formes, il est tout à fait possible que, dans mon = Android port, l'alimentera en ordures et provoquera un crash dans le code natif, donc je veux les vidages sur incident (natifs et Java) qui apparaissent actuellement dans le Android = log (je suppose que ce serait stderr dans une situation non Android). Je suis libre de modifier à la fois C et Java code arbitrairement, bien que les rappels (entrant et sortant de JNI) compte environ 40 et évidemment, des points bonus pour les petits différentiels.

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 accrocheuse de ma question, mais je ne vois pas une telle bibliothèque pour Android/Dalvik .

89
Chris Boyle

Edit: À partir de Jelly bean, vous ne pouvez pas obtenir la trace de la pile, car READ_LOGS Est parti . :

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

  1. Utilisez sigaction() pour capturer les signaux et stocker les anciens gestionnaires. ( Android.c: 57 )
  2. Le temps passe, une erreur de segmentation se produit.
  3. Dans le gestionnaire de signaux, 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é qui est signalée comme devant être dans son propre processus. ( SGTPuzzles.Java:962 , AndroidManifest.xml: 28 )
  5. Lorsque vous revenez de Java et appelez cet ancien gestionnaire, le framework Android se connectera à debuggerd pour enregistrer une trace native de Nice pour vous , puis le processus se terminera. ( debugger.c , debuggerd.c )
  6. Pendant ce temps, votre activité de gestion des accidents démarre. Vraiment, vous devez lui passer le PID afin 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 c'est le cas, rassemblez la sortie de logcat -d -v threadtime Et lancez un ACTION_SEND Avec le destinataire, le sujet et le corps remplis. L'utilisateur devra appuyer sur Envoyer. ( CrashHandler.Java , SGTPuzzles.Java:462 , strings.xml: 41
  7. Attention à logcat échouant ou prenant 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:7 , strings.xml: 51 )

Dans une situation non Android, certains éléments seraient différents. Vous auriez besoin de rassembler votre propre trace native, voir cette autre question , selon le type de libc que vous avez. Vous auriez besoin de gérer le vidage de cette trace, de lancer votre processus distinct de gestion des plantages et d'envoyer le courrier électronique de certaines manières appropriées pour votre plateforme, mais j'imagine que l'approche générale devrait toujours fonctionner.

79
Chris Boyle

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 interceptant les plantages courants (SEGV, SIBGUS, etc.) à l'intérieur du code JNI , et remplacez-les par un Java.lang.Error exceptions . Bonus, si le client fonctionne sur Android> = 4.1.1, la trace de pile incorpore la trace résolue du plantage (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 nouveau)

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

14
xroche

FWIW, Google Breakpad fonctionne très bien sur Android. J'ai effectué le travail de portage et nous l'expédions dans le cadre de Firefox Mobile. Cela 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 pile côté serveur (vous n'avez donc pas à expédier de symboles de débogage avec votre application ).

6
Ted Mielczarek

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 revienne à votre Java code. Je me souviens vaguement d'avoir entendu parler de certaines JVM non Sun qui vous permettent 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 n'est officiellement pas défini.

5
mas90