Làm cách nào để bắt SIGSEGV (lỗi phân đoạn) và lấy dấu vết ngăn xếp trong JNI trên Android?


92

Tôi đang chuyển một dự án sang Bộ phát triển Android Native mới (tức là JNI) và tôi muốn bắt SIGSEGV, nếu nó xảy ra (có thể cả SIGILL, SIGABRT, SIGFPE) để trình bày một hộp thoại báo cáo sự cố đẹp mắt, thay vì (hoặc trước đó) những gì hiện đang xảy ra: cái chết ngay lập tức của quy trình và có thể một số nỗ lực của hệ điều hành để khởi động lại nó. ( Chỉnh sửa: Máy ảo JVM / Dalvik bắt tín hiệu và ghi lại dấu vết ngăn xếp và thông tin hữu ích khác; tôi chỉ muốn cung cấp cho người dùng tùy chọn gửi email thông tin đó cho tôi thực sự.)

Tình hình là: một phần lớn mã C mà tôi không viết thực hiện hầu hết công việc trong ứng dụng này (tất cả logic trò chơi) và mặc dù nó đã được thử nghiệm tốt trên nhiều nền tảng khác, hoàn toàn có thể xảy ra trường hợp tôi, trong Android của mình cổng, sẽ cung cấp cho nó rác và gây ra sự cố trong mã gốc, vì vậy tôi muốn kết xuất lỗi (cả bản gốc và Java) hiện hiển thị trong nhật ký Android (tôi đoán nó sẽ là stderr trong tình huống không phải Android). Tôi có thể tự do sửa đổi cả mã C và Java một cách tùy ý, mặc dù các lệnh gọi lại (cả đi vào và đi ra khỏi JNI) là khoảng 40 và rõ ràng, điểm thưởng cho các khác biệt nhỏ.

Tôi đã nghe nói về thư viện chuỗi tín hiệu trong J2SE, libjsig.so, và nếu tôi có thể cài đặt một trình xử lý tín hiệu như vậy một cách an toàn trên Android, điều đó sẽ giải quyết được phần bắt câu hỏi của tôi, nhưng tôi không thấy thư viện nào như vậy cho Android / Dalvik .


Nếu bạn có thể khởi động máy ảo Java thông qua tập lệnh trình bao bọc, bạn có thể kiểm tra xem ứng dụng có thoát bất thường hay không và thực hiện báo cáo lỗi. Điều đó sẽ cho phép bạn bắt sạch tất cả các loại lối ra bất thường, có thể là SIGSEGV, SIGKILL hoặc bất cứ điều gì. Tuy nhiên, tôi không nghĩ điều này có thể xảy ra với các ứng dụng Android gốc, vì vậy hãy đăng bài này dưới dạng nhận xét (được chuyển đổi từ câu trả lời).
sleske

Cũng nên xem: Không thể chạy chương trình Java Android với Valgrind để biết cách khởi động ứng dụng Android với tập lệnh trình bao bọc (trong adb shell).
sleske

1
Câu trả lời cần được cập nhật. Mã nguồn được cung cấp trong câu trả lời được chấp nhận sẽ dẫn đến hành vi không xác định do lệnh gọi đến các hàm không an toàn tín hiệu không đồng bộ. Vui lòng xem tại đây: stackoverflow.com/questions/34547199/…
user1506104,

Câu trả lời:


82

Chỉnh sửa: Từ Jelly Bean trở đi, bạn không thể lấy dấu vết ngăn xếp, vì READ_LOGSđã biến mất . :-(

Tôi thực sự có một trình xử lý tín hiệu hoạt động mà không làm bất cứ điều gì quá kỳ lạ và đã phát hành mã bằng cách sử dụng nó, bạn có thể thấy trên github (chỉnh sửa: liên kết với bản phát hành lịch sử; tôi đã xóa trình xử lý sự cố kể từ đó). Đây là cách thực hiện:

  1. Sử dụng sigaction()để bắt tín hiệu và lưu trữ các trình xử lý cũ. ( android.c: 570 )
  2. Thời gian trôi qua, một segfault xảy ra.
  3. Trong trình xử lý tín hiệu, hãy gọi tới JNI lần cuối và sau đó gọi trình xử lý cũ. ( android.c: 528 )
  4. Trong cuộc gọi JNI đó, ghi lại bất kỳ thông tin gỡ lỗi hữu ích nào và gọi startActivity()một hoạt động được gắn cờ là cần phải có trong quy trình của riêng nó. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Khi bạn quay trở lại từ Java và gọi trình xử lý cũ đó, khung công tác Android sẽ kết nối với debuggerdđể ghi lại một dấu vết gốc tốt cho bạn và sau đó quá trình này sẽ chết. ( debugger.c , debuggerd.c )
  6. Trong khi đó, hoạt động xử lý sự cố của bạn đang bắt đầu. Thực sự bạn nên chuyển cho nó PID để nó có thể đợi bước 5 hoàn thành; Tôi không làm điều này. Tại đây bạn xin lỗi người dùng và hỏi bạn có thể gửi nhật ký không. Nếu vậy, hãy thu thập kết quả logcat -d -v threadtimevà khởi chạy một ACTION_SENDvới người nhận, chủ đề và nội dung đã được điền. Người dùng sẽ phải nhấn Gửi. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Coi chừng logcatthất bại hoặc mất hơn vài giây. Tôi đã gặp một thiết bị, T-Mobile Pulse / Huawei U8220, trong đó logcat ngay lập tức chuyển sang Ttrạng thái (theo dõi) và bị treo. ( CrashHandler.java:70 , string.xml : 51 )

Trong tình huống không phải Android, một số điều này sẽ khác. Bạn cần thu thập dấu vết gốc của riêng mình, hãy xem câu hỏi khác này , tùy thuộc vào loại libc bạn có. Bạn cần phải xử lý việc kết xuất dấu vết đó, khởi chạy quy trình xử lý sự cố riêng và gửi email theo một số cách thích hợp cho nền tảng của bạn, nhưng tôi cho rằng phương pháp chung vẫn sẽ hoạt động.


2
Tốt nhất là bạn nên kiểm tra xem liệu sự cố có xảy ra trong thư viện của bạn hay không. Nếu nó xảy ra ở một nơi khác (giả sử bên trong máy ảo), các lệnh gọi JNI của bạn từ trình xử lý tín hiệu có thể làm mọi thứ nhầm lẫn khá tệ. Đó không phải là ngày tận thế, vì dù sao thì bạn cũng đang gặp sự cố, nhưng nó có thể khiến việc chẩn đoán sự cố máy ảo trở nên khó khăn hơn (hoặc gây ra một sự cố máy ảo kỳ lạ dẫn đến một báo cáo lỗi Android và gây khó khăn cho mọi người).
fadden

Bạn thật tuyệt vời @Chris vì đã chia sẻ dự án nghiên cứu của bạn về điều này!
olafure

Cảm ơn, điều này rất hữu ích trong việc tìm ra nơi mà JNI của tôi đã bị hỏng. Ngoài ra, xin chào từ một cựu sinh viên DCS!
Nick,

3
Bắt đầu từ một hoạt động trong một quá trình mới từ một dịch vụ cũng đòi hỏi đoạn mã sau:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme

1
Giải pháp này có còn hiệu lực trong Jelly Bean không? Bước 6 sẽ không ghi được bất kỳ debuggerdkết quả đầu ra nào?
Josh

14

Tôi là một chút muộn, nhưng tôi đã có cùng một nhu cầu chính xác, và tôi đã phát triển một thư viện nhỏ để giải quyết nó, bằng cách bắt tai nạn thông thường ( SEGV, SIBGUS, vv) bên đang JNI , và thay thế chúng bằng cách thường xuyên java.lang.Error ngoại lệ . Tiền thưởng, nếu khách hàng đang chạy trên Android> = 4.1.1, stack trace nhúng giải quyết vết lùi của vụ tai nạn (một pseudo-trace chứa đầy đủ nguồn gốc stack trace). Bạn sẽ không thể phục hồi sau các sự cố nguy hiểm (ví dụ: nếu bạn làm hỏng trình phân bổ), nhưng ít nhất nó sẽ cho phép bạn khôi phục từ hầu hết chúng. (vui lòng báo cáo thành công và thất bại, mã là thương hiệu mới)

Thông tin thêm tại https://github.com/xroche/coffeecatch (mã là giấy phép BSD 2-khoản )


6

FWIW, Google Breakpad hoạt động tốt trên Android. Tôi đã thực hiện công việc chuyển cổng và chúng tôi đang chuyển nó như một phần của Firefox Mobile. Nó yêu cầu một chút thiết lập, vì nó không cung cấp cho bạn dấu vết ngăn xếp ở phía máy khách, nhưng gửi cho bạn bộ nhớ ngăn xếp thô và thực hiện ngăn xếp ở phía máy chủ (vì vậy bạn không phải gửi ký hiệu gỡ lỗi với ứng dụng của mình ).


1
Đó là hầu như không thể cấu hình Breakpad xem xét hoàn toàn thiếu tài liệu hướng dẫn
shader

Nó thực sự không khó và có rất nhiều tài liệu trên wiki của dự án. Trong thực tế, dành cho Android có bây giờ là một build NDK Makefile và nó phải siêu dễ sử dụng: code.google.com/p/google-breakpad/source/browse/trunk/...
Ted Mielczarek

Bạn cũng cần phải biên dịch mô-đun xử lý trước các tệp ký hiệu gỡ lỗi cho Android và bạn chỉ có thể biên dịch trên Linux. Khi bạn biên dịch trên máy Mac - nó chỉ xây dựng bộ xử lý trước dSym của Mac / iOS.
shader

5

Theo kinh nghiệm hạn chế của tôi (không phải Android), SIGSEGV trong mã JNI thường sẽ làm hỏng JVM trước khi quyền điều khiển được trả về mã Java của bạn. Tôi mơ hồ nhớ lại đã nghe về một số JVM không phải Sun cho phép bạn bắt SIGSEGV, nhưng AFAICR thì bạn không thể mong đợi có thể làm được như vậy.

Bạn có thể cố gắng bắt chúng trong C (xem sigaction (2)), mặc dù bạn có thể thực hiện rất ít sau trình xử lý SIGSEGV (hoặc SIGFPE hoặc SIGILL) vì hành vi đang diễn ra của một quy trình chính thức là không xác định.


Vâng, hành vi không được xác định sau "ignor [ing] một tín hiệu SIGFPE, SIGILL hoặc SIGSEGV không được tạo ra bởi kill (2) hoặc raise (3)", nhưng không nhất thiết phải bắt được tín hiệu như vậy. Kế hoạch hiện tại là thử một trình xử lý tín hiệu C gọi lại Java và bằng cách nào đó, kết thúc luồng mà không kết thúc quy trình. Điều này có thể có hoặc có thể không. :-)
Chris Boyle

1
Hướng dẫn backtrace C: stackoverflow.com/questions/76822/...
Chris Boyle

1
... ngoại trừ tôi không thể sử dụng backtrace (), vì Android không sử dụng glibc, nó sử dụng Bionic. :-( Một cái gì đó liên quan đến _Unwind_Backtracetừ unwind.hsẽ là cần thiết để thay thế.
Chris Boyle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.