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

92

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

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

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

Крис Бойл
источник
Если вы можете запустить виртуальную машину Java с помощью сценария-оболочки, вы можете проверить, не завершилось ли приложение ненормально, и составить отчет об ошибках. Это позволит вам четко отслеживать все виды аномальных выходов, будь то SIGSEGV, SIGKILL или что-то еще. Однако я не думаю, что это возможно со стандартными приложениями Android, поэтому разместите это как комментарий (преобразованный из ответа).
sleske 07
См. Также: Невозможно запустить программу Android на Java с помощью Valgrind, чтобы узнать, как запустить приложение Android с помощью сценария оболочки (в оболочке adb).
sleske
1
Ответ необходимо обновить. Исходный код, представленный в принятом ответе, приведет к неопределенному поведению из-за вызова функций, не защищенных от асинхронного сигнала. См. Здесь: stackoverflow.com/questions/34547199/…
user1506104

Ответы:

82

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

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

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

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

Крис Бойл
источник
2
В идеале вы должны проверить, произошел ли сбой в вашей библиотеке. Если это произошло где-то еще (скажем, внутри виртуальной машины), ваши вызовы JNI из обработчика сигналов могут сильно запутать ситуацию. Это не конец света, так как вы в любом случае находитесь в середине сбоя, но это может затруднить диагностику сбоя виртуальной машины (или вызвать причудливый сбой виртуальной машины, который заканчивается отчетом об ошибке Android и сбивает всех с толку).
fadden 05
Вы замечательный @Chris, что поделился своим исследовательским проектом по этому поводу!
olafure
Спасибо, это было полезно для определения того, где мой JNI сходил с ума. А также привет от выпускника DCS!
Nick
3
Для запуска Activity в новом процессе из Сервиса также требуется следующий код:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme,
1
Это решение все еще актуально для Jelly Bean? Будет ли шаг 6 не записывать какие-либо debuggerdвыходы?
Джош
14

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

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

xroche
источник
6

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

Тед Мельчарек
источник
1
Настроить Breakpad практически невозможно из-за отсутствия документации
шейдер
Это действительно не так сложно, и в вики проекта есть много документации. Фактически, для Android теперь есть Makefile сборки NDK, и он должен быть очень простым в использовании: code.google.com/p/google-breakpad/source/browse/trunk/…
Тед Мильцарек
Вам также необходимо скомпилировать модуль, который предварительно обрабатывает файлы символов отладки для Android, и вы можете скомпилировать его только в Linux. Когда вы компилируете на Mac - он строит только препроцессор dSym для Mac / iOS.
шейдер
5

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

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

mas90
источник
Что ж, поведение не определено после "игнорирования сигнала SIGFPE, SIGILL или SIGSEGV, который не был сгенерирован kill (2) или raise (3)", но не обязательно во время перехвата такого сигнала. Текущий план состоит в том, чтобы попробовать обработчик сигналов C, который обращается к Java и каким-то образом завершает поток, не завершая процесс. Это может быть, а может и нет. :-)
Крис Бойл
1
Инструкции по трассировке C: stackoverflow.com/questions/76822/…
Крис Бойл,
1
... за исключением того, что я не могу использовать backtrace (), потому что Android не использует glibc, он использует Bionic. :-( Вместо этого потребуется что-нибудь с участием _Unwind_Backtracefrom unwind.h.
Крис Бойл,