ฉันจะตรวจจับ SIGSEGV (ข้อผิดพลาดในการแบ่งส่วน) และรับสแต็กติดตามภายใต้ JNI บน Android ได้อย่างไร


92

ฉันกำลังย้ายโปรเจ็กต์ไปยัง 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 .


หากคุณสามารถเริ่ม Java VM ผ่านสคริปต์ Wrapper คุณสามารถตรวจสอบว่าแอปออกอย่างผิดปกติหรือไม่และทำการรายงานข้อผิดพลาด นั่นจะช่วยให้คุณสามารถจับทางออกที่ผิดปกติได้อย่างหมดจดไม่ว่าจะเป็น SIGSEGV, SIGKILL หรืออะไรก็ตาม อย่างไรก็ตามฉันไม่คิดว่าสิ่งนี้จะเป็นไปได้กับแอพ Android ในสต็อกดังนั้นการโพสต์สิ่งนี้เป็นความคิดเห็น (แปลงจากคำตอบ)
sleske

ดูเพิ่มเติมที่: ไม่สามารถรันโปรแกรม Java Android ด้วย Valgrindสำหรับวิธีเริ่มแอป Android ด้วยสคริปต์ wrapper (ใน adb shell)
sleske

1
คำตอบต้องได้รับการอัปเดต ซอร์สโค้ดที่ให้ไว้ในคำตอบที่ยอมรับจะส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนดเนื่องจากการเรียกใช้ฟังก์ชันที่ไม่ปลอดภัยของสัญญาณ async โปรดดูที่นี่: stackoverflow.com/questions/34547199/…
user1506104

คำตอบ:


82

แก้ไข:จาก Jelly Bean เป็นต้นไปคุณจะไม่สามารถได้รับการกองติดตามเพราะออกไปREAD_LOGS :-(

จริงๆแล้วฉันมีตัวจัดการสัญญาณที่ทำงานโดยไม่ต้องทำอะไรแปลกใหม่เกินไปและได้ปล่อยโค้ดโดยใช้มันซึ่งคุณสามารถดูได้ใน github (แก้ไข: เชื่อมโยงไปยังรุ่นที่ผ่านมาฉันได้ลบตัวจัดการข้อขัดข้องตั้งแต่นั้นมา) วิธีการมีดังนี้

  1. ใช้sigaction()เพื่อจับสัญญาณและจัดเก็บตัวจัดการเก่า ( android.c: 570 )
  2. เวลาผ่านไปความผิดพลาดเกิดขึ้น
  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
ตามหลักการแล้วคุณควรตรวจสอบเพื่อดูว่าเกิดข้อขัดข้องในห้องสมุดของคุณหรือไม่ หากเกิดขึ้นที่อื่น (เช่นภายใน VM) การโทร JNI ของคุณจากตัวจัดการสัญญาณอาจทำให้เกิดความสับสนได้ ไม่ใช่จุดจบของโลกเนื่องจากคุณอยู่ในช่วงกลางคันอยู่แล้ว แต่อาจทำให้การวินิจฉัยความผิดพลาดของ VM ยากขึ้น (หรือทำให้เกิดข้อผิดพลาด VM ที่แปลกประหลาดซึ่งจบลงในรายงานข้อบกพร่องของ Android และทำให้ทุกคนสับสน)
fadden

คุณวิเศษมากที่ @Chris แบ่งปันโครงการวิจัยของคุณเกี่ยวกับเรื่องนี้!
olafure

ขอบคุณสิ่งนี้มีประโยชน์ในการค้นหาว่า JNI ของฉันไปอยู่ที่ไหน นอกจากนี้สวัสดีจากศิษย์เก่า DCS!
นิค

3
การเริ่มต้นกิจกรรมในกระบวนการใหม่จากบริการจำเป็นต้องใช้รหัสต่อไปนี้newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme

1
วิธีนี้ยังใช้ได้ภายใต้ Jelly Bean หรือไม่? ขั้นตอนที่ 6 จะล้มเหลวในการบันทึกdebuggerdผลลัพธ์ใด ๆหรือไม่?
Josh

14

ฉันนิด ๆ ปลาย แต่ผมจำเป็นต้องเหมือนกันที่แน่นอนและผมได้พัฒนาห้องสมุดขนาดเล็กที่จะอยู่กับมันโดยการจับการเกิดปัญหาที่พบบ่อย ( SEGV, SIBGUSฯลฯ ) ภายในรหัส JNIและแทนที่พวกเขาโดยปกติข้อยกเว้นjava.lang.Error โบนัสหากไคลเอ็นต์กำลังทำงานบน Android> = 4.1.1การติดตามสแต็กจะฝังbacktrace ที่แก้ไขแล้วของข้อขัดข้อง (การติดตามหลอกที่มีการติดตามสแต็กแบบเต็ม) คุณจะไม่เกิดปัญหาการกู้คืนจากหิน (เช่น. ถ้าคุณเสียหายจัดสรรตัวอย่างเช่น) แต่อย่างน้อยก็ควรจะช่วยให้คุณสามารถกู้คืนจากส่วนใหญ่ของพวกเขา (โปรดรายงานความสำเร็จและความล้มเหลวรหัสเป็นของใหม่)

ข้อมูลเพิ่มเติมที่https://github.com/xroche/coffeecatch (รหัสคือใบอนุญาต BSD 2-Clauses )


6

FWIW, Google Breakpadทำงานได้ดีบน Android ฉันทำการย้ายข้อมูลและเรากำลังจัดส่งเป็นส่วนหนึ่งของ Firefox Mobile ต้องมีการตั้งค่าเล็กน้อยเนื่องจากไม่ได้ให้สแต็กเทรซในฝั่งไคลเอ็นต์ แต่จะส่งหน่วยความจำสแต็กดิบให้คุณและทำสแต็กที่เดินฝั่งเซิร์ฟเวอร์ (ดังนั้นคุณไม่จำเป็นต้องส่งสัญลักษณ์การดีบักกับแอปของคุณ ).


1
แทบจะเป็นไปไม่ได้เลยที่จะกำหนดค่า Breakpad โดยพิจารณาจากเอกสารที่ขาดหายไปอย่างสิ้นเชิง
shader

มันไม่ยากเลยและมีเอกสารมากมายเกี่ยวกับวิกิโครงการ ในความเป็นจริงสำหรับ Android ตอนนี้มี NDK build Makefile แล้วและควรใช้งานง่ายสุด ๆ : code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek

คุณต้องคอมไพล์โมดูลที่ประมวลผลไฟล์สัญลักษณ์การดีบักล่วงหน้าสำหรับ Android และคุณสามารถคอมไพล์ได้ใน Linux เท่านั้น เมื่อคุณคอมไพล์บน Mac - มันจะสร้างตัวประมวลผลล่วงหน้า dSym ของ Mac / iOS เท่านั้น
shader

5

จากประสบการณ์ที่ จำกัด ของฉัน (ไม่ใช่ Android) SIGSEGV ในโค้ด JNI โดยทั่วไปจะทำให้ JVM ขัดข้องก่อนที่การควบคุมจะกลับไปที่โค้ด Java ของคุณ ฉันจำได้ไม่ชัดว่าเคยได้ยินเกี่ยวกับ JVM ที่ไม่ใช่ Sun ซึ่งให้คุณจับ SIGSEGV ได้ แต่ AFAICR คุณคาดไม่ถึงว่าจะทำได้

คุณสามารถลองจับมันใน C (ดู sigaction (2)) แม้ว่าคุณจะทำอะไรได้น้อยมากหลังจากตัวจัดการ SIGSEGV (หรือ SIGFPE หรือ SIGILL) เนื่องจากพฤติกรรมต่อเนื่องของกระบวนการนั้นไม่ได้กำหนดไว้อย่างเป็นทางการ


พฤติกรรมไม่ได้ถูกกำหนดไว้หลังจาก "เพิกเฉย [ing] สัญญาณ SIGFPE, SIGILL หรือ SIGSEGV ที่ไม่ได้สร้างขึ้นโดย kill (2) หรือเพิ่ม (3)" แต่ไม่จำเป็นต้องอยู่ในระหว่างการจับสัญญาณดังกล่าว แผนปัจจุบันคือลองใช้ตัวจัดการสัญญาณ C ที่เรียกกลับไปที่ Java และจะยุติเธรดโดยไม่ต้องยุติกระบวนการ อาจเป็นไปได้หรือไม่ก็ได้ :-)
Chris Boyle

1
C backtrace คำแนะนำ: stackoverflow.com/questions/76822/…
Chris Boyle

1
... ยกเว้นฉันไม่สามารถใช้ backtrace () ได้เนื่องจาก Android ไม่ใช้ glibc จึงใช้ Bionic :-( สิ่งที่เกี่ยวข้อง_Unwind_Backtraceจากunwind.hจะเป็นสิ่งที่จำเป็นแทน
Chris Boyle
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.