دیباگ کرش نیتیو (Native Crash) در اندروید — راهنمای پیشرفته

۲۰۶ بازدید
آخرین به‌روزرسانی: ۲۹ شهریور ۱۴۰۲
زمان مطالعه: ۹ دقیقه
دیباگ کرش نیتیو (Native Crash) در اندروید — راهنمای پیشرفته

100 اپلیکیشن برتر در لیست محبوب‌ترین اپلیکیشن‌های اندرویدی تا زمان نگارش این مقاله بیش از 54 میلیارد بار نصب شده‌اند. 85 درصد از این اپلیکیشن‌ها دارای کد «نیتیو» (native) با استفاده از بیش از 1000 کتابخانه نیتیو هستند. اگر تجربه کار روی چنین اپلیکیشن‌ها یا هر اپلیکیشن بزرگ دیگری را داشته باشید، می‌دانید که احتمال بروز کرش نیتیو بسیار بالا است.

997696

توسعه‌دهندگان اندروید می‌بایست در زمینه دیباگ کردن «رد پشته» (Stack Trace) کرش نیتیو که در زبان اندرویدی «سنگ قبر» (Tombstone) نامیده می‌شود، تجربه مناسبی داشته باشند. اما کرش اپلیکیشن در بخش نیتیو (یعنی در کدهای سطح پایین C یا ++C) در اغلب موارد پیچیده و درک آن دشوار است. علاوه بر آن امکان از کار افتادن JVM (ماشین مجازی جاوا) پیش از بازگشت کنترل به کد جاوا/کاتلین نیز وجود دارد. این بدان معنی است که شما امکان به دست آوردن «استثنا» (Exception) را در سطح اپلیکیشن نخواهید داشت و تجربه کاربری ناخوشایندی رقم می‌خورد.

پیش از آغاز

مستندات توسعه‌دهندگان اندروید اطلاعات مفید زیادی در مورد عیب‌یابی کرش نیتیو (+) ارائه کرده است، اما جای مثال‌های جامع و مفیدی که به تفهیم بهتر موضوع کمک کند، خالی است.

نکته: اگر با کد نیتیو روی پلتفرم اندروید آشنایی ندارید، بهتر است ابتدا راهنمای NDK اندروید (+) را مطالعه کنید.

کتابخانه‌های نیتیو در بسیاری از اپلیکیشن‌ها مفید هستند؛ اما برخی از کاربردهای آن‌ها به شرح زیر است:

  • بهره‌گیری از سطوح بالاتری از عملکرد دستگاه برای رسیدن به تأخیر پایین یا اجرای اپلیکیشن‌های سنگین از نظر محاسبات مانند بازی یا شبیه‌سازی‌های فیزیکی.
  • استفاده مجدد از کتابخانه‌های C یا ++C که از سوی شما یا توسعه‌دهندگان دیگر توسعه یافته‌اند.
  • به علاوه کتابخانه‌های نیتیو قادرند امنیت اپلیکیشن را افزایش دهند و می‌توانند در اپلیکیشن‌هایی که برای پلتفرم‌های مختلف نوشته می‌شوند، مورد استفاده قرار گیرند.

مثال‌هایی از دنیای واقعی

تصور کنید در یک تیم Android SDK مشغول به کار هستید که در پروژه خود با کتابخانه شخص ثالثی سر و کار دارید که شامل کدهای نیتیو است. اشیای مشترک (فایل‌های so.) نیز به صورت pre-obfuscated هستند که موجب می‌شود دیباگ کردن هر گونه کرش دشوار باشد.

شاید کتابخانه مشترکی که در اپلیکیشن شما گنجانده شده، از قبل obfuscated باشد و می‌بایست از obfuscation مجدد جلوگیری کنید. اگر از obfuscation مجدد جلوگیری نکنید، احتمال بالایی وجود دارد که با مشکل مواجه شوید.

در زمان یکپارچه‌سازی این کتابخانه با اپلیکیشن، اگر با یک کرش در runtime در build-های release مواجه شوید که obfuscation شده است، عملاً با موقعیت بسیار دشواری روبرو شده‌اید. Obfuscation کد برای حفظ امنیت اپلیکیشن ضروری است و از این رو باید کرش را به سرعت پیش از انتشار بعدی رفع کنید.

در این موارد باید یک راه‌حل برای دیباگ کردن اپلیکیشن بیابید. به اپلیکیشن نمونه ساده زیر توجه کنید. مراحل تحلیل و دیباگ کردن برای رفع کرش نیتیو در این اپلیکیشن استفاده شده‌اند.

اپلیکیشن نمونه: NativeCrashApp

نمودار توالی برای اپلیکیشن نمونه

نکته: این اپلیکیشن نمونه به عنوان یک اپلیکیشن نهایی هیچ مناسبتی ندارد و صرفاً با مقاصد آموزشی ارائه شده است.

گردش کار اپلیکیشن ساده (و غیر ضروری) است، اما رفتار جالبی را شامل می‌شود. تابع ابتدایی و منفرد برای نمایش نام دستگاه در قالبی کاربرپسند به کاربر استفاده می‌شود و صرفاً یک نام بی‌معنی مدل از سوی Build.MODEL بازگشت نمی‌یابد. به این منظور از کتابخانه AndroidDeviceNames (+) استفاده شده است.

1. کاربر اپلیکیشن را اجرا می‌کند

اپلیکیشن در زمان اجرا شدن با استفاده از کتابخانه سفارشی اندروید به دنبال نام دستگاه می‌گردد.

کرش نیتیو در اندروید

2. فراخوانی‌های کتابخانه به سطح نیتیو

سطح نیتیو (کتابخانه ++C) از طریق JNI یا «رابط نیتیو جاوا» (Java Native Interface) فراخوانی می‌شود.

کرش نیتیو در اندروید

3. فراخوانی بازگشتی به کتابخانه اندروید از طریق بازتاب

در این مرحله یک فراخوانی بازگشتی به کتابخانه اندروید از طریق reflection برای بررسی نام دستگاه (قابل خواندن از سوی انسان) صورت می‌گیرد.

کرش نیتیو در اندروید

4. بازگشت دادن نام دستگاه به سطوح اولیه

در نهایت نام دستگاه به اپلیکیشن بازگشت و روی صفحه نمایش می‌یابد.

کرش نیتیو در اندروید
تصویری از اپلیکیشن نمونه

نکته: بدیهی است که همه این اتفاقات می‌توانست در Activity رخ دهد. کتابخانه Android و کتابخانه ++C کاملاً غیر ضروری هستند؛ اما این روش جالب‌تر است.

باگ کجاست؟

ما به منظور مقاصد آموزشی مقداری باگ در کد فوق اضافه کرده‌ایم. برای مشاهده این باگ‌ها به flavor مربوط به نسخه broken این پروژه در این آدرس (+) مراجعه کنید تا باگ‌هایی را که نیازمند دیباگ شدن هستند را ببینید.

Shrinking و Obfuscation کد

تصور کنید ما به عنوان یک توسعه‌دهنده مسئولیت‌پذیر اندروید، می‌خواهیم امنیت اپلیکیشن خود را از طریق ابزارهای Shrinking و Obfuscation کد افزایش دهیم. بدین ترتیب باید ابزار منتخب Obfuscation کد مانند ProGuard (+) را مورد استفاده قرار دهیم. در این فرایند کلاس‌ها، فیلدها، متدها و خصوصیت‌های بی‌استفاده تشخیص داده شده و از اپلیکیشن بسته‌بندی‌شده حذف می‌شوند.

1android {
2    buildTypes {
3        release {
4            minifyEnabled true
5            proguardFiles 'custom-proguard-rules.pro'
6        }
7    }
8}

باگ شماره 1

متأسفانه زمانی که build مربوط به release اپلیکیشن خود را تست می‌کنیم با یک کرش مواجه می‌شویم.

16495-6495/com.jacksoncheek.nativecrashapp.broken E/AndroidRuntime: FATAL EXCEPTION: main
2    Process: com.jacksoncheek.nativecrashapp.broken, PID: 6495
3    java.lang.UnsatisfiedLinkError: No implementation found for void com.jacksoncheek.a.a.a(boolean) (tried Java_com_jacksoncheek_a_a_a and Java_com_jacksoncheek_a_a_a__Z)
4        at com.jacksoncheek.a.a.a(Native Method)
5        at com.jacksoncheek.a.a.a(Unknown Source:17)
6        at com.jacksoncheek.nativecrashapp.MainActivity.onCreate(Unknown Source:39)
7        at android.app.Activity.performCreate(Activity.java:7136)
8        at android.app.Activity.performCreate(Activity.java:7127)
9        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
10        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
11        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
12        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
13        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
14        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
15        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
16        at android.os.Handler.dispatchMessage(Handler.java:106)
17        at android.os.Looper.loop(Looper.java:193)
18        at android.app.ActivityThread.main(ActivityThread.java:6669)
19        at java.lang.reflect.Method.invoke(Native Method)
20        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
21        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

هیچ پیاده‌سازی برای کلاس (com.jacksoncheek.a.a.a(boolean وجود ندارد؛ اما شاید کلاً معنی این را نمی‌دانید. اگر فایل نگاشت mapping.txt را که ProGuard در خروجی ارائه کرده بررسی کنیم، می‌بینیم که شامل ترجمه‌ای بین یک کلاس، متد و نام فیلدهای اصلی و obfuscated است.

1com.jacksoncheek.devicepropertieslib.DevicePropertiesNative -> com.jacksoncheek.a.a:
2    java.lang.String TAG -> a
3    boolean libraryLoaded -> b
4    boolean nativeLibraryLoaded() -> b
5    java.lang.String getDeviceNameNative() -> a
6    java.lang.String checkDeviceProperties() -> c
7    void setLogDebugMessages(boolean) -> a
8    void <init>() -> <init>

اینک می‌دانیم که ProGuard برخی از متدهای ما را به طور نادرستی obfuscate کرده است. این نوع از خطا در زمان obfuscation امری معمول است.

نکته پیشرفته: ProGuard کد نیتیو را بررسی نمی‌کند و از این رو به طور خودکار کلاس‌ها یا اعضای کلاس‌هایی را که از طریق reflection در کد نیتیو فراخوانی می‌شوند، نگهداری نمی‌کند. اینک زمان آن رسیده است که این متدها را نیز از طریق فلگ keep- در پروژه حفظ کنیم.

1-keepclasseswithmembernames,includedescriptorclasses class * { 
2    native <methods>; 
3}
  • keepclasseswithmembernames  - نام کلاس و متدهای نیتیو را حفظ می‌کند.
  • includedescriptorclasses  - انواع بازگشتی و پارامترها را حفظ می‌کند.

باگ شماره 2

بدین ترتیب یک بار دیگر اپلیکیشن را تست می‌کنیم و با کرش دیگری مواجه می‌شویم.

16615-6615/? A/crashapp.broke: JNI DETECTED ERROR IN APPLICATION: JNI CallObjectMethodV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/jacksoncheek/devicepropertieslib/DevicePropertiesNative;.getDeviceName()Ljava/lang/String;"
26615-6615/? A/crashapp.broke:   at java.lang.String com.jacksoncheek.devicepropertieslib.DevicePropertiesNative.checkDeviceProperties() ((null):-2)
36615-6615/? A/crashapp.broke:   at java.lang.String com.jacksoncheek.devicepropertieslib.DevicePropertiesNative.a() ((null):-1)
46615-6615/? A/crashapp.broke:   at void com.jacksoncheek.nativecrashapp.MainActivity.onCreate(android.os.Bundle) ((null):-1)
56615-6615/? A/crashapp.broke:   at void android.app.Activity.performCreate(android.os.Bundle, android.os.PersistableBundle) (Activity.java:7136)
66615-6615/? A/crashapp.broke:   at void android.app.Activity.performCreate(android.os.Bundle) (Activity.java:7127)
76615-6615/? A/crashapp.broke:   at void android.app.Instrumentation.callActivityOnCreate(android.app.Activity, android.os.Bundle) (Instrumentation.java:1271)
86615-6615/? A/crashapp.broke:   at android.app.Activity android.app.ActivityThread.performLaunchActivity(android.app.ActivityThread$ActivityClientRecord, android.content.Intent) (ActivityThread.java:2893)
96615-6615/? A/crashapp.broke:   at android.app.Activity android.app.ActivityThread.handleLaunchActivity(android.app.ActivityThread$ActivityClientRecord, android.app.servertransaction.PendingTransactionActions, android.content.Intent) (ActivityThread.java:3048)
106615-6615/? A/crashapp.broke:   at void android.app.servertransaction.LaunchActivityItem.execute(android.app.ClientTransactionHandler, android.os.IBinder, android.app.servertransaction.PendingTransactionActions) (LaunchActivityItem.java:78)
116615-6615/? A/crashapp.broke:   at void android.app.servertransaction.TransactionExecutor.executeCallbacks(android.app.servertransaction.ClientTransaction) (TransactionExecutor.java:108)
126615-6615/? A/crashapp.broke:   at void android.app.servertransaction.TransactionExecutor.execute(android.app.servertransaction.ClientTransaction) (TransactionExecutor.java:68)
136615-6615/? A/crashapp.broke:   at void android.app.ActivityThread$H.handleMessage(android.os.Message) (ActivityThread.java:1808)
146615-6615/? A/crashapp.broke:   at void android.os.Handler.dispatchMessage(android.os.Message) (Handler.java:106)
156615-6615/? A/crashapp.broke:   at void android.os.Looper.loop() (Looper.java:193)
166615-6615/? A/crashapp.broke:   at void android.app.ActivityThread.main(java.lang.String[]) (ActivityThread.java:6669)
176615-6615/? A/crashapp.broke:   at java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) (Method.java:-2)
186615-6615/? A/crashapp.broke:   at void com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run() (RuntimeInit.java:493)
196615-6615/? A/crashapp.broke:   at void com.android.internal.os.ZygoteInit.main(java.lang.String[]) (ZygoteInit.java:858)
206615-6615/? A/crashapp.broke: 
216615-6615/? A/crashapp.broke:     in call to CallObjectMethodV
226615-6615/? A/crashapp.broke:     from java.lang.String com.jacksoncheek.devicepropertieslib.DevicePropertiesNative.checkDeviceProperties()
236615-6615/? A/crashapp.broke: "main" prio=5 tid=1 Runnable
246615-6615/? A/crashapp.broke:   | group="main" sCount=0 dsCount=0 flags=0 obj=0x75233ee0 self=0xe525d000
256615-6615/? A/crashapp.broke:   | sysTid=6615 nice=-10 cgrp=default sched=0/0 handle=0xe9e84494
266615-6615/? A/crashapp.broke:   | state=R schedstat=( 179191705 116256360 118 ) utm=9 stm=8 core=1 HZ=100
276615-6615/? A/crashapp.broke:   | stack=0xff79a000-0xff79c000 stackSize=8MB
286615-6615/? A/crashapp.broke:   | held mutexes= "mutator lock"(shared held)

به نظر می‌رسد که یک خطای دیگر obfuscation وجود دارد.

1java.lang.NoSuchMethodError: no non-static method "Lcom/jacksoncheek/devicepropertieslib/DevicePropertiesNative;.getDeviceName()Ljava/lang/String;"

این خطا کمی پیچیده‌تر است. چنان که شاهد هستید، نام کلاس DevicePropertiesNative، نام متد getDeviceName؛ نوع پارامتر () یعنی void و نوع بازگشتی Ljava/lang/String پیدا نشده است.

بنابراین باید متدهای کلاس و نیتیو را از obfuscate شدن بازداریم؛ اما انواع بازگشتی و پارامترها چنین حالتی ندارند. این وضعیت تضمین می‌کند که کد «امضای متد» (method signature) با کتابخانه نیتیو سازگار خواهد بود.

ما باید یک قاعده keep- در پیکربندی ProGuard اضافه کنیم تا از obfuscate شدن متد ()getDeviceName جلوگیری کنیم. راهنمای ProGuard (+) اطلاعات زیادی در مورد گزینه‌های پیکربندی مختلف ارائه می‌کند.

1-keepclassmembers class com.jacksoncheek.devicepropertieslib.DevicePropertiesNative {
2     java.lang.String getDeviceName();
3}

باگ شماره 3

در ادامه پروژه را مجدداً تست می‌کنیم و می‌بینیم که بار دیگر یک کرش نیتیو داریم!

16759-6759/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
26759-6759/? A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:9/PSR1.180720.061/5075414:userdebug/dev-keys'
36759-6759/? A/DEBUG: Revision: '0'
46759-6759/? A/DEBUG: ABI: 'x86'
56759-6759/? A/DEBUG: pid: 6740, tid: 6740, name: crashapp.broken  >>> com.jacksoncheek.nativecrashapp.broken <<<
66759-6759/? A/DEBUG: signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0xff799ffc
76759-6759/? A/DEBUG:     eax e51dc375  ebx e60f4754  ecx 00001000  edx e60f6254
86759-6759/? A/DEBUG:     edi 0000000a  esi ff79a568
96759-6759/? A/DEBUG:     ebp ff79a048  esp ff79a000  eip e607a7c4
106759-6759/? A/DEBUG: backtrace:
116759-6759/? A/DEBUG:     #00 pc 000727c4  /system/lib/libc.so (__sfvwrite+452)
126759-6759/? A/DEBUG:     #01 pc 00068b65  /system/lib/libc.so (__vfprintf+11221)
136759-6759/? A/DEBUG:     #02 pc 00087cac  /system/lib/libc.so (printf+92)
146759-6759/? A/DEBUG:     #03 pc 000008e8  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+56)
156759-6759/? A/DEBUG:     #04 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)
166759-6759/? A/DEBUG:     #05 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)
176759-6759/? A/DEBUG:     #06 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)
186759-6759/? A/DEBUG:     #07 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)
196759-6759/? A/DEBUG:     #08 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)
206759-6759/? A/DEBUG:     #09 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)
216759-6759/? A/DEBUG:     #10 pc 000008fe  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+78)

این یک خطای segmentation به صورت SIGSEGV در آدرس حافظه مجازی 0xff799ffc است؛ اما در عمل اطلاعات مفید چندانی ارائه نمی‌کند. SEGV_ACCERR زمانی رخ می‌دهد که یک اشاره‌گر بخواهد شیئی را که مجوزهای دسترسی نامعتبری دارد بنویسد.

اینک نوبت آن رسیده است که به بررسی log-ها بپردازیم و tombstone را که همان dump کرش برای کرش‌های نیتیو است، پیدا کنیم. اگر در log-ها برای یافتن ابتدای tombstone، عبارت *** *** را جستجو کنید، با اطلاعات زیر مواجه می‌شوید:

  • اثر انگشت بیلد: با مشخصه سیستم ro.build.fingerprint مطابقت دارد.
  • بازبینی سخت‌افزاری: با مشخصه سیستم ro.revision مطابقت دارد.
  • ABI (اینترفیس باینری اپلیکیشن): دستورالعمل پردازنده برای تعیین معماری است که armeabi-v7a برای دستگاه‌های اندرویدی متداول‌ترین گزینه است.
  • نام پردازش از کارافتاده >>> ... <<< (و شناسه پردازش) و نام نخ به صورت ...:name و شناسه نخ.
  • نوع سیگنال خاتمه به صورت SIGSEGV، روش دریافت آن سیگنال در SEGV_ACCER و آدرس خطا در حافظه.
  • ثبات‌های سی‌پی‌یو
  • محتوای پشته مورد فراخوانی (backtrace).

دیباگ کردن کرش‌های نیتیو

در این بخش با روش‌های دیباگ کردن کرش‌های نیتیو آشنا می‌شویم.

بررسی Backtrace

مقادیر PC (شمارنده برنامه) آدرس‌های متناظر حافظه با موقعیت کتابخانه مشترک هستند. این همان جایی است که بیشترین اطلاعات در مورد کرش نیتیو و مکان آن در کتابخانه را به دست می‌آوریم.

16759-6759/? A/DEBUG: #03 pc 000008e8  /data/app/com.jacksoncheek.nativecrashapp.broken-MzKT-aIUbK4D4IxEzg3yTA==/lib/x86/libproperty-checker.so (accidentallyForceStackOverflow(int)+56)

کرش ما در آدرس حافظه 000008e8 در ابتدای پشته فراخوانی در libproperty-checker.so رخ داده است.

پشته Android NDK دو ابزار ارائه می‌کند که به دیباگ کردن tombstone-ها کمک می‌کند و ndk-stack و addr2line نام دارند. ابزارهای NDK را با ابزار مدیریت اندروید استودیو نصب کنید و دایرکتوری NDK را به مسیر bash_profile. اضافه کنید.

ndk-stack

ابزار ndk-stack (+) اقدام به نمادسازی از ردهای پشته برای یک tombstone می‌کند. در واقع این ابزار آدرس‌های حافظه را به فایل‌های منبع مرتبط تبدیل می‌کند و شماره خطوط را از کد منبع کتابخانه نیتیو نمایش می‌دهد.

1$NDK/ndk-stack -sym <path> [-dump <path>]

addr2line

امکان استفاده از این ابزار addr2line نیز برای دریافت آدرس حافظه‌ای که کد نیتیو موجب کرش شده وجود دارد. بدین ترتیب نام فایل منبع و خط مربوطه به دست می‌آید. این ابزار بخشی از مجموعه ابزار NDK است. باید مطمئن شوید که از addr2line برای نوع ABI صحیح دستگاه یعنی x86 (نامتداول)، armeabi یا armeabi-v7a (متداول) استفاده می‌کنید.

در این مورد مسیر addr2line برای انواع ABI به صورت x86 به صورت زیر است:

1~/Library/Android/sdk/ndk-bundle/toolchains/x86–4.9/prebuilt/darwin-x86_64/bin/i686-linux-android-addr2line

کاربرد

1addr2line -C -f -e <libPath> <memoryAddress>

مثال

1i686-linux-android-addr2line -C -f -e libproperty-checker.so 000008e8
2accidentallyForceStackOverflow(int)
3~/NativeCrashApp/brokendevicepropertieslib/src/main/jni/propertyChecker.cpp:64

اکنون می‌دانیم که متد نیتیو به نام (accidentallyForceStackOverflow(int در فایل منبع propertyChecker.cpp و شماره خط 64 موجب بروز کرش نیتیو شده است.

کرش نیتیو

بدین ترتیب باگ نیتیو خود را یافته‌ایم. این کتابخانه به صورت تصادفی با فراخوانی یک تابع بازگشتی غیر پایانی به صورت نامتناهی موجب یک خطای «سرریز پشته» (stack overflow) شده است. راه‌حل سریع در این بخش حذف همه کاربردهای این متد است.

در دنیای واقعی ممکن است با نسخه‌های release از یک ارائه‌دهنده کتابخانه کار کنید و از این رو دسترسی به کد منبع برای دیباگ کردن نداشته باشید. از طرف دیگر همه فایل‌های so. برای دیباگ کردن با ndk-stack مناسب نیستند، زیرا کتابخانه‌های منتشر شده عموماً از stripped binaries استفاده می‌کنند که باعث می‌شود دیباگ کردن آن‌ها دشوارتر شود.

این همان جایی است که ابزار addr2line واقعاً به ابزار مفیدی تبدیل می‌شود. اگر نام متد نیتیو که کرش در آن رخ داده است در tombstone نمایش نیابد، که برای همه دستگاه‌ها هم چنین تضمینی وجود ندارد، می‌توانید از addr2line برای دریافت نام متد نیتیو استفاده کنید.

ابتدا فایل apk. را دی‌کامپایل بکنید (کافی است آن را unzip بکنید) و فایل‌های so. بسته‌بندی‌شده در اپلیکیشن را از دایرکتوری lib/ استخراج کنید. سپس کتابخانه مشترک را برای نوع دستگاه ABI مثلاً armeabi-v7a استخراج کنید.

نکته: این فایل‌ها در دایرکتوری /app/src/main/jniLibs نیز قرار دارند.

1./i686-linux-android-addr2line -C -f -e libproperty-checker.so 000008e8
2accidentallyForceStackOverflow(int)
3??:?

در این روش شماره خط فایل منبعی که کرش رخ داده است به دست نمی‌آید، چون APK تنها شامل فایل‌های stripped binaries است؛ اما نام متد را به صورت  (accidentallyForceStackOverflow(int به دست می‌آوریم که در نوع خود مفید است.

جمع‌بندی

در این بخش مراحل مورد نیاز برای دیباگ کردن کرش‌های نیتیو را به صوت فهرست‌وار ارائه می‌کنیم.

  1. ابتدا باگ را روی انواع معماری‌های مختلف دستگاه‌ها بررسی کنید.
  2. فایل apk. را دی کامپایل کرده و مطمئن شوید که فایل‌های کتابخانه مشترک so. برای هر معماری موجود هستند.
  3. بررسی کنید که ابزار مدیریت بسته اندروید به درستی کد نیتیو را همراه با اپلیکیشن نصب می‌کند. به این منظور بررسی کنید که کتابخانه مشترک so. در runtime بارگذاری می‌شود یا نه. شما باید از ابزار Native Libs Monitor (+) برای بررسی آسان اپلیکیشن‌های دارای کتابخانه‌های نیتیو روی دستگاه خود استفاده کنید؛ اما هیچ تضمینی برای امنیت استفاده از این اپلیکیشن روی دستگاه‌هایی که build-های دیباگ مالکانه دارند وجود ندارد.
  4. قواعد keep- خاصی را به پیکربندی ProGuard اضافه کنید تا متدهای کلاس و نیتیو را از obfuscate شدن منع کنید. این مورد در خصوص انواع بازگشتی و پارامترها صدق نمی‌کند.
  5. Tombstone-های کرش نیتیو را با استفاده از ابزارهای ndk-stack و addr2line بررسی کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
proandroiddev
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *