Как я могу поймать SIGSEGV (ошибка сегментации) и получить трассировку стека под JNI на Android?

Я перемещаю проект в новый Android Native Development Kit (т.е. JNI), и я бы хотел поймать SIGSEGV, если он (возможно, также SIGILL, SIGABRT, SIGFPE), чтобы представить хороший диалог отчетов о сбоях вместо (или до) того, что в настоящее время происходит: немедленная бесцеремонная смерть процесса и, возможно, некоторые попытки ОС перезапустить его. ( Изменить: JVM/Dalvik VM захватывает сигнал и записывает трассировку стека и другую полезную информацию, я просто хочу предложить пользователю возможность отправлять мне эту информацию по электронной почте.)

Ситуация такова: большой кусок кода C, который я не писал, делает большую часть работы в этом приложении (вся логика игры), и хотя он хорошо протестирован на многих других платформах, вполне возможно, что я, в моем Android-порт, будет кормить его мусором и вызвать сбой в собственном коде, поэтому я хочу, чтобы аварийные дампы (как родные, так и Java), которые в настоящее время отображаются в журнале Android (я думаю, это будет stderr в ситуации, отличной от Android). Я могу свободно изменять как код C, так и код Java, хотя обратные вызовы (как входящие, так и выходящие из JNI) насчитывают около 40 и, очевидно, бонусные баллы для небольших различий.

Я слышал о библиотеке цепочек сигналов в J2SE, libjsig.so, и если бы я мог безопасно установить обработчик сигнала, подобный этому на Android, это решило бы ловушку моего вопроса, но я не вижу такой библиотеки для Android/Dalvik.

Ответ 1

Изменить: От Jelly Bean и дальше вы не можете получить трассировку стека, потому что READ_LOGS ушел.: - (

Я действительно получил обработчик сигнала, не делая ничего экзотичного, и выпустил его код, который вы можете увидеть в github ( edit: ссылка на исторический релиз, с тех пор я удалил обработчик сбоя). Вот как:

  • Используйте sigaction(), чтобы поймать сигналы и сохранить старые обработчики. (android.c: 570)
  • Время проходит, происходит segfault.
  • В обработчике сигналов в последний раз вызовите JNI, а затем вызовите старый обработчик. (android.c: 528)
  • В этом вызове JNI зарегистрируйте любую полезную информацию об отладке и вызовите startActivity() в отношении действия, которое помечено как должно быть в свой собственный процесс. (SGTPuzzles.java:962, AndroidManifest.xml: 28)
  • Когда вы вернетесь с Java и вызовите этого старого обработчика, инфраструктура Android подключится к debuggerd, чтобы записать хороший родной трассу для вас, а затем процесс умрет. (debugger.c, debuggerd.c)
  • В то же время ваша деятельность по обработке аварийных сообщений запускается. Действительно, вы должны передать его PID, чтобы он мог дождаться завершения шага 5; Я этого не делаю. Здесь вы извиняетесь перед пользователем и спрашиваете, можете ли вы отправить журнал. Если да, соберите вывод logcat -d -v threadtime и запустите ACTION_SEND с заполненным получателем, субъектом и телом. Пользователь должен будет нажать "Отправить". (CrashHandler.java, SGTPuzzles.java:462, strings.xml: 41
  • Следите за тем, чтобы logcat не выполнялось или занимало больше нескольких секунд. Я столкнулся с одним устройством - T-Mobile Pulse/Huawei U8220, где logcat немедленно переходит в состояние T (отслеживается) и зависает. (CrashHandler.java:70, strings.xml: 51)

В ситуации, отличной от Android, некоторые из них будут разными. Вам нужно будет собрать собственный собственный след, см. этот другой вопрос, в зависимости от того, какой у вас есть libc. Вам нужно будет обработать сбрасывание этой трассировки, запуск отдельного процесса обработки аварийных сообщений и отправку электронной почты некоторыми подходящими способами для вашей платформы, но я полагаю, что общий подход все равно должен работать.

Ответ 2

Я немного опоздал, но у меня была такая же потребность, и я разработал небольшую библиотеку для ее решения, поймав общие сбои (SEGV, SIBGUS и т.д.) внутри Код JNI и замените их регулярными java.lang.Error исключениями. Бонус, если клиент работает на Android >= 4.1.1, трассировка стека включает в себя разрешенную обратную трассировку краха (псевдотрассы, содержащей полную собственную трассировку стека). Вы не будете восстанавливаться после порочных сбоев (например, если вы повредите распределитель, например), но по крайней мере это должно позволить вам восстановить большинство из них. (пожалуйста, сообщите об успехах и неудачах, код совершенно новый)

Дополнительная информация на https://github.com/xroche/coffeecatch (код - лицензия BSD 2-Clauses)

Ответ 3

FWIW, Google Breakpad отлично работает на Android. Я выполнил работу по переносу, и мы отправляем его как часть Firefox Mobile. Для этого требуется небольшая настройка, так как она не дает вам трассировки стека на стороне клиента, но отправляет вам исходную стек стек и делает стеки на стороне сервера (так что вам не нужно отправлять символы отладки с вашим приложением).

Ответ 4

В моем ограниченном опыте (не Android), SIGSEGV в коде JNI, как правило, приводит к сбою JVM до того, как элемент управления будет возвращен вашему Java-коду. Я смутно вспоминаю слух о некоторых не-Sun JVM, которые позволяют вам поймать SIGSEGV, но AFAICR вы не можете ожидать, что сможете это сделать.

Вы можете попытаться поймать их на C (см. sigaction (2)), хотя вы можете сделать очень мало после обработчика SIGSEGV (или SIGFPE или SIGILL), поскольку текущее поведение процесса официально undefined.