Bagaimana cara menangkap SIGSEGV (kesalahan segmentasi) dan mendapatkan pelacakan tumpukan di bawah JNI di Android?

92

Saya memindahkan proyek ke Android Native Development Kit baru (yaitu JNI) dan saya ingin menangkap SIGSEGV, seandainya itu terjadi (mungkin juga SIGILL, SIGABRT, SIGFPE) untuk menampilkan dialog pelaporan kerusakan yang bagus, alih-alih (atau sebelum) apa yang saat ini terjadi: proses mati tanpa basa-basi dan mungkin beberapa upaya oleh OS untuk memulai ulang. ( Sunting: VM JVM / Dalvik menangkap sinyal dan mencatat jejak tumpukan dan informasi berguna lainnya; Saya hanya ingin menawarkan kepada pengguna opsi untuk mengirimkan info itu kepada saya.)

Situasinya adalah: banyak kode C yang tidak saya tulis melakukan sebagian besar pekerjaan dalam aplikasi ini (semua logika permainan) dan meskipun telah diuji dengan baik di banyak platform lain, sangat mungkin bahwa saya, di Android saya port, akan memberinya sampah dan menyebabkan crash pada kode native, jadi saya ingin crash dump (baik native maupun Java) yang saat ini muncul di log Android (saya kira ini akan menjadi stderr dalam situasi non-Android). Saya bebas memodifikasi kode C dan Java secara sewenang-wenang, meskipun panggilan balik (keduanya masuk dan keluar dari JNI) berjumlah sekitar 40 dan jelas, poin bonus untuk perbedaan kecil.

Saya pernah mendengar tentang pustaka rantai sinyal di J2SE, libjsig.so, dan jika saya dapat dengan aman memasang penangan sinyal seperti itu di Android, itu akan menyelesaikan bagian yang menarik dari pertanyaan saya, tetapi saya tidak melihat pustaka semacam itu untuk Android / Dalvik .

Chris Boyle
sumber
Jika Anda dapat memulai Java VM melalui skrip pembungkus, Anda dapat memeriksa apakah aplikasi keluar secara tidak normal, dan melakukan pelaporan kesalahan. Itu akan memungkinkan Anda untuk menangkap semua jenis keluar abnormal dengan rapi, baik itu SIGSEGV, SIGKILL atau apa pun. Namun, saya tidak berpikir ini mungkin dengan aplikasi Android stok, jadi posting ini sebagai komentar (dikonversi dari jawaban).
sleske
Lihat juga: Tidak dapat menjalankan program Java Android dengan Valgrind untuk mengetahui cara memulai aplikasi Android dengan skrip pembungkus (di adb shell).
sleske
1
Jawabannya perlu diperbarui. Kode sumber yang diberikan dalam jawaban yang diterima akan menghasilkan perilaku yang tidak ditentukan karena panggilan ke fungsi non-async-signal-safe. Silakan lihat di sini: stackoverflow.com/questions/34547199/…
user1506104

Jawaban:

82

Sunting: Dari Jelly Bean dan seterusnya Anda tidak bisa mendapatkan jejak tumpukan, karena READ_LOGSpergi . :-(

Saya benar-benar mendapat penangan sinyal yang bekerja tanpa melakukan sesuatu yang terlalu eksotis, dan telah merilis kode yang menggunakannya, yang dapat Anda lihat di github (edit: menautkan ke rilis historis; Saya menghapus penangan kerusakan sejak itu). Begini caranya:

  1. Gunakan sigaction()untuk menangkap sinyal dan menyimpan penangan lama. ( android.c: 570 )
  2. Waktu berlalu, segfault terjadi.
  3. Di penangan sinyal, panggil JNI untuk terakhir kalinya dan panggil penangan lama. ( android.c: 528 )
  4. Dalam panggilan JNI tersebut, catat info debugging yang berguna, dan panggil startActivity()aktivitas yang ditandai sebagai perlu berada dalam prosesnya sendiri. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Ketika Anda kembali dari Java dan memanggil penangan lama itu, kerangka kerja Android akan terhubung ke debuggerduntuk mencatat jejak asli yang bagus untuk Anda, dan kemudian prosesnya akan mati. ( debugger.c , debuggerd.c )
  6. Sementara itu, aktivitas penanganan tabrakan Anda sedang dimulai. Sungguh, Anda harus meneruskan PID tersebut sehingga dapat menunggu hingga langkah 5 selesai; Saya tidak melakukan ini. Di sini Anda meminta maaf kepada pengguna dan bertanya apakah Anda dapat mengirim log. Jika demikian, kumpulkan output logcat -d -v threadtimedan luncurkan ACTION_SENDdengan penerima, subjek, dan isi yang terisi. Pengguna harus menekan Kirim. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Hati-hati jika logcatgagal atau membutuhkan waktu lebih dari beberapa detik. Saya telah menemukan satu perangkat, T-Mobile Pulse / Huawei U8220, di mana logcat segera masuk ke status T(dilacak) dan hang. ( CrashHandler.java:70 , strings.xml: 51 )

Dalam situasi non-Android, beberapa di antaranya akan berbeda. Anda perlu mengumpulkan jejak asli Anda sendiri, lihat pertanyaan lain ini , bergantung pada jenis libc yang Anda miliki. Anda perlu menangani pembuangan jejak itu, meluncurkan proses penanganan kerusakan yang terpisah, dan mengirim email dengan beberapa cara yang sesuai untuk platform Anda, tetapi saya membayangkan pendekatan umum harus tetap berfungsi.

Chris Boyle
sumber
2
Idealnya Anda akan memeriksa untuk melihat apakah crash terjadi di perpustakaan Anda. Jika itu terjadi di tempat lain (katakanlah, di dalam VM), panggilan JNI Anda dari penangan sinyal bisa sangat membingungkan. Ini bukan akhir dunia, karena Anda bagaimanapun juga sedang mengalami mid-crash, tetapi ini mungkin membuat diagnosis VM crash lebih sulit (atau menyebabkan crash VM aneh yang berakhir di laporan bug Android dan membingungkan semua orang).
fadden
Anda luar biasa @Chris karena membagikan proyek penelitian Anda tentang ini!
olafure
Terima kasih, ini berguna untuk menemukan di mana JNI saya menjadi gila. Juga, halo dari alumnus DCS!
Nick
3
Memulai Aktivitas dalam proses baru dari Layanan juga memerlukan kode berikut:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme
1
Apakah solusi ini masih berlaku untuk Jelly Bean? Tidak akankah langkah 6 gagal mencatat debuggerdkeluaran apa pun ?
Josh
14

Saya sedikit terlambat, tapi aku punya kebutuhan yang sama persis, dan saya telah mengembangkan sebuah perpustakaan kecil untuk mengatasi itu, dengan menangkap crash umum ( SEGV, SIBGUS, dll) di dalam kode JNI , dan menggantinya dengan biasa java.lang.Error pengecualian . Bonus, jika klien berjalan pada Android> = 4.1.1, jejak stack embeds diselesaikan backtrace kecelakaan (pseudo-jejak yang berisi penuh jejak stack asli). Anda tidak akan pulih dari kerusakan parah (mis. Jika Anda merusak pengalokasi, misalnya), tetapi setidaknya itu memungkinkan Anda untuk memulihkan dari sebagian besar crash . (harap laporkan keberhasilan dan kegagalan, kodenya masih baru)

Info lebih lanjut di https://github.com/xroche/coffeecatch (kode adalah lisensi BSD 2-Clauses )

xroche
sumber
6

FWIW, Google Breakpad berfungsi dengan baik di Android. Saya melakukan pekerjaan porting, dan kami mengirimkannya sebagai bagian dari Firefox Mobile. Ini memerlukan sedikit penyiapan, karena ini tidak memberi Anda pelacakan tumpukan di sisi klien, tetapi mengirimi Anda memori tumpukan mentah dan melakukan tumpukan berjalan di sisi server (jadi Anda tidak perlu mengirimkan simbol debug dengan aplikasi Anda ).

Ted Mielczarek
sumber
1
Hampir tidak mungkin untuk mengonfigurasi Breakpad mengingat dokumentasi yang benar-benar hilang
shader
Sebenarnya tidak terlalu sulit, dan ada banyak dokumentasi di wiki proyek. Faktanya, untuk Android sekarang ada NDK build Makefile dan seharusnya sangat mudah digunakan: code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek
Anda juga perlu mengkompilasi modul yang memproses file simbol debug untuk Android dan Anda hanya dapat mengkompilasinya di Linux. Ketika Anda mengkompilasi di Mac - itu hanya membangun Mac / iOS dSym preprocessor.
shader
5

Dalam pengalaman saya yang terbatas (non-Android), SIGSEGV dalam kode JNI biasanya akan merusak JVM sebelum kontrol dikembalikan ke kode Java Anda. Saya samar-samar ingat pernah mendengar tentang beberapa JVM non-Sun yang memungkinkan Anda menangkap SIGSEGV, tetapi AFAICR Anda tidak dapat berharap dapat melakukannya.

Anda dapat mencoba menangkapnya di C (lihat sigaction (2)), meskipun Anda dapat melakukan sangat sedikit setelah penangan SIGSEGV (atau SIGFPE atau SIGILL) karena perilaku berkelanjutan dari suatu proses secara resmi tidak ditentukan.

mas90
sumber
Nah, perilaku tidak ditentukan setelah "mengabaikan sinyal SIGFPE, SIGILL, atau SIGSEGV yang tidak dihasilkan oleh kill (2) atau raise (3)", tetapi tidak harus selama menangkap sinyal seperti itu. Rencana saat ini adalah mencoba penangan sinyal C yang memanggil kembali ke Java dan, entah bagaimana, mengakhiri utas tanpa menghentikan proses. Ini mungkin atau mungkin tidak mungkin. :-)
Chris Boyle
1
C instruksi pelacakan balik: stackoverflow.com/questions/76822/…
Chris Boyle
1
... kecuali saya tidak bisa menggunakan backtrace (), karena Android tidak menggunakan glibc, ia menggunakan Bionic. :-( Sesuatu yang melibatkan _Unwind_Backtracedari unwind.hakan dibutuhkan sebagai gantinya.
Chris Boyle