¿Cómo puedo detectar SIGSEGV (error de segmentación) y obtener un seguimiento de pila en JNI en Android?

92

Estoy moviendo un proyecto al nuevo kit de desarrollo nativo de Android (es decir, JNI) y me gustaría detectar SIGSEGV, en caso de que ocurra (posiblemente también SIGILL, SIGABRT, SIGFPE) para presentar un cuadro de diálogo de informe de fallas agradable, en lugar de (o antes) de lo que sucede actualmente: la muerte inmediata y sin ceremonias del proceso y posiblemente algún intento del sistema operativo para reiniciarlo. ( Editar: JVM / Dalvik VM capta la señal y registra un seguimiento de pila y otra información útil; solo quiero ofrecer al usuario la opción de enviarme esa información por correo electrónico realmente).

La situación es: un gran cuerpo de código C que no escribí hace la mayor parte del trabajo en esta aplicación (toda la lógica del juego) y, aunque está bien probado en muchas otras plataformas, es muy posible que yo, en mi Android port, lo alimentará con basura y provocará un bloqueo en el código nativo, por lo que quiero los volcados de emergencia (tanto nativos como Java) que aparecen actualmente en el registro de Android (supongo que sería stderr en una situación que no sea de Android). Soy libre de modificar tanto el código C como el código Java arbitrariamente, aunque las devoluciones de llamada (tanto que entran como que salen de JNI) suman alrededor de 40 y obviamente, puntos de bonificación para diferencias pequeñas.

He oído hablar de la biblioteca de encadenamiento de señales en J2SE, libjsig.so, y si pudiera instalar de manera segura un controlador de señales como ese en Android, eso resolvería la parte de captura de mi pregunta, pero no veo tal biblioteca para Android / Dalvik .

Chris Boyle
fuente
Si puede iniciar la máquina virtual Java a través de un script de envoltura, puede verificar si la aplicación salió de manera anormal y hacer el informe de errores. Eso le permitiría atrapar limpiamente todo tipo de salidas anormales, ya sean SIGSEGV, SIGKILL o lo que sea. Sin embargo, no creo que esto sea posible con las aplicaciones estándar de Android, por lo que publicar esto como un comentario (convertido de respuesta).
sleske
Consulte también: No se puede ejecutar un programa de Android Java con Valgrind para saber cómo iniciar una aplicación de Android con un script de envoltura (en adb shell).
Sábado
1
La respuesta debe actualizarse. El código fuente proporcionado en la respuesta aceptada dará como resultado un comportamiento indefinido debido a la llamada a funciones no seguras de señal asíncrona. Consulte aquí: stackoverflow.com/questions/34547199/…
user1506104

Respuestas:

82

Editar: Desde Jelly Bean en adelante, no puede obtener el seguimiento de la pila, porque READ_LOGSdesapareció . :-(

De hecho, conseguí que un controlador de señales funcionara sin hacer nada demasiado exótico, y publiqué un código que lo usa, que puede ver en github (editar: vincular a la versión histórica; eliminé el controlador de fallos desde entonces). Así es cómo:

  1. Úselo sigaction()para captar las señales y almacenar los antiguos controladores. ( android.c: 570 )
  2. Pasa el tiempo, ocurre una falla secundaria.
  3. En el manejador de señales, llame a JNI una última vez y luego llame al manejador anterior. ( android.c: 528 )
  4. En esa llamada JNI, registre cualquier información de depuración útil y llame startActivity()a una actividad que esté marcada como necesaria para estar en su propio proceso. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Cuando regrese de Java y llame a ese antiguo controlador, el marco de Android se conectará debuggerdpara registrar un buen seguimiento nativo para usted, y luego el proceso morirá. ( debugger.c , debuggerd.c )
  6. Mientras tanto, comienza su actividad de manejo de accidentes. Realmente debería pasarle el PID para que pueda esperar a que se complete el paso 5; Yo no hago esto. Aquí te disculpas con el usuario y le preguntas si puedes enviar un registro. Si es así, recopile el resultado de logcat -d -v threadtimee inicie un ACTION_SENDdestinatario, el asunto y el cuerpo completados. El usuario deberá presionar Enviar. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Tenga cuidado con logcatfallar o tomar más de unos segundos. Encontré un dispositivo, el T-Mobile Pulse / Huawei U8220, donde logcat entra inmediatamente en el estado T(rastreado) y se cuelga. ( CrashHandler.java:70 , strings.xml: 51 )

En una situación sin Android, algo de esto sería diferente. Debería recopilar su propio rastro nativo, ver esta otra pregunta , dependiendo del tipo de libc que tenga. Debería manejar el volcado de ese rastro, iniciar su proceso de manejo de fallas por separado y enviar el correo electrónico de alguna manera apropiada para su plataforma, pero imagino que el enfoque general aún debería funcionar.

Chris Boyle
fuente
2
Lo ideal sería comprobar si el bloqueo ocurrió en su biblioteca. Si ocurrió en otro lugar (digamos, dentro de la VM), sus llamadas JNI desde el manejador de señales podrían confundir bastante las cosas. No es el fin del mundo, ya que de todos modos está en medio de una falla, pero podría dificultar el diagnóstico de una falla de VM (o causar una falla extraña de VM que termina en un informe de error de Android y desconcierta a todos).
fadden
¡Eres maravilloso @Chris por compartir tu proyecto de investigación sobre esto!
Olafure
Gracias, esto fue útil para encontrar dónde se estaba volviendo loco mi JNI. Además, ¡hola de parte de un ex alumno de DCS!
Nick
3
Iniciar una actividad en un proceso nuevo desde un servicio también requiere el siguiente código:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme
1
¿Esta solución sigue siendo válida bajo Jelly Bean? ¿No fallará el paso 6 al registrar debuggerdsalidas?
Josh
14

Estoy un poco tarde, pero tenía exactamente la misma necesidad, y he desarrollado una pequeña biblioteca para hacer frente a ella, por la captura de los accidentes comunes ( SEGV, SIBGUS, etc.) en el interior de código JNI , y reemplazarlos por regulares java.lang.Error excepciones . Bonificación, si el cliente se ejecuta en Android> = 4.1.1, el seguimiento de pila incrusta el resuelta traza del accidente (un pseudo-trace que contiene la traza completa pila nativa). No se recuperará de fallas viciosas (es decir, si corrompe el asignador, por ejemplo), pero al menos debería permitirle recuperarse de la mayoría de ellas. (informe los éxitos y los fracasos, el código es nuevo)

Más información en https://github.com/xroche/coffeecatch (el código es licencia BSD de 2 cláusulas )

xroche
fuente
6

FWIW, Google Breakpad funciona bien en Android. Hice el trabajo de portabilidad y lo enviaremos como parte de Firefox Mobile. Requiere una pequeña configuración, ya que no le da rastros de pila en el lado del cliente, pero le envía la memoria de pila sin procesar y hace que la pila recorra el lado del servidor (por lo que no tiene que enviar símbolos de depuración con su aplicación ).

Ted Mielczarek
fuente
1
Es casi imposible configurar Breakpad considerando la documentación absolutamente faltante
shader
Realmente no es tan difícil y hay mucha documentación en la wiki del proyecto. De hecho, para Android ahora hay un Makefile de compilación NDK y debería ser muy fácil de usar: code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek
También necesita compilar un módulo que preprocesa los archivos de símbolos de depuración para Android y solo puede compilarlo en Linux. Cuando compila en una Mac, solo compila el preprocesador dSym de Mac / iOS.
shader
5

En mi experiencia limitada (no Android), SIGSEGV en el código JNI generalmente bloqueará la JVM antes de que se devuelva el control a su código Java. Recuerdo vagamente haber escuchado acerca de una JVM que no es de Sun y que le permite capturar SIGSEGV, pero AFAICR no puede esperar poder hacerlo.

Puede intentar capturarlos en C (consulte sigaction (2)), aunque puede hacer muy poco después de un controlador SIGSEGV (o SIGFPE o SIGILL), ya que el comportamiento continuo de un proceso no está definido oficialmente.

mas90
fuente
Bien, el comportamiento no está definido después de "ignorar [ing] una señal SIGFPE, SIGILL o SIGSEGV que no fue generada por kill (2) o raise (3)", pero no necesariamente durante la captura de dicha señal. El plan actual es probar un controlador de señal C que vuelva a llamar a Java y, de alguna manera, termine el hilo sin terminar el proceso. Esto puede ser posible o no. :-)
Chris Boyle
1
Instrucciones de seguimiento de
Chris Boyle
1
... excepto que no puedo usar backtrace (), porque Android no usa glibc, usa Bionic. :-( En su lugar, se necesitará algo que involucre _Unwind_Backtracede unwind.h.
Chris Boyle