האפליקציה עבדה ב-Android 9 ומתה באותו טלפון אחרי העדכון ל-Android 10. הפרופיל שלכם לא נגע, האישורים זהים, והקריסה מגיעה לפני כל תעבורת אימות. logcat מציג תחילה מצביע אפס, וכמה שורות מעליו מופיע Cannot run program ...: error=13, Permission denied. ההודעה הזו עוסקת בהרצת קובץ, לא בקריאת התצורה שלכם.

מה באמת נדחה על ידי שגיאה 13

Android 10, רמת API 29, הפסיק לאפליקציות להריץ קבצים שכתבו לתיקיית הנתונים שלהן. הכלל מכוון לאחסון שאפשר לכתוב בו ואחר כך להריץ ממנו, וזה בדיוק מה שיוצר משגר שמעתיק מתוך assets. גרסאות ישנות של ליבת ics-openvpn מחלצות קובץ הרצה openvpn מתוך ה-APK בהרצה הראשונה ומפעילות אותו באמצעות ProcessBuilder. החילוץ עדיין מצליח; ההרצה היא שנדחית. אפליקציה שמכוונת ל-API 29 יורשת את הכלל גם כשהיא פועלת במכשיר שהגיע עם גרסה ישנה יותר, ולכן עדכון יכול לשבור בנייה שאיש לא נגע בה.

אין בכלל דבר ייחודי לשם תיקייה אחד. קובץ שהאפליקציה יצרה תחת תיקיית הנתונים שלה נחשב לא ניתן להרצה ללא קשר לנתיב, ולכן getFilesDir(), getCacheDir() ותת-תיקייה מותאמת כולם נכשלים באותה דרך.

הפרידו בין קריסת העזר לבין בעיית פרופיל

קראו את הלוג סביב תחילת התהליך במקום לגלול מלמעלה. החתימה לחפש היא שורת error=13, ועקבות המחסנית שמעליה בדרך כלל מנותבות לשיטת המשגר שקראה ל-ProcessBuilder:

adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"

אם העזר מעולם לא הופעל, לא תראו שום פלט לוג של OpenVPN: לא משא ומתן על צופן, לא נתיבים שנדחפו, לא תצורת DNS. אם השורות האלה כן מופיעות והכשל מגיע מאוחר יותר, הבינארי רץ והבעיה היא בפרופיל, בשרת או בשכבת הניתוב. ההבחנה האחת הזו חוסכת זמן רב שהיה מתבזבז על עריכת קבצי .ovpn שלא היו אשמים מעולם. היא גם אומרת לכם איזה משני התיקונים שלמטה אתם באמת צריכים.

בדקו גם את רמת ה-API במכשיר שאתם בודקים, לא זו שבתצורת הבנייה שלכם. תמונת אמולטור ב-API 28 לא משחזרת דבר, כי המגבלה נושכת רק מ-29 ומעלה. בדקו בו-זמנית את SDK היעד בקובץ הבנייה שלכם: העלאתו היא מה שמכניס את הכלל.

adb shell getprop ro.build.version.sdk

למה העתקת הבינארי למקום אחר לא עוזרת

העקיפה הברורה, כתיבת העזר לתיקייה אחרת שאפשר לכתוב בה, נכשלת מאותה סיבה שנכשל המקור. Android עדיין מתיר להריץ ספריות משותפות שהותקנו בתיקיית הספריות המקוריות של האפליקציה, מפני שהמתקין אורז אותן ולא כותב אותן בזמן ריצה. התיקייה הזו היא המקום היחיד שהמשגר אמור להצביע עליו. כל מה שאתם מחלצים בעצמכם בזמן ריצה, איך שלא תקראו לו, נשאר בקטגוריה הנדחית.

אחסון חיצוני גרוע יותר: הוא מותקן עם noexec, וקובץ עזר שמונח שם לא יכול להיות מופעל על ידי שום אפליקציה. גם מחיקת נתוני האפליקציה לא מחזירה את ההתנהגות הישנה, כי ההרצה הבאה פשוט יוצרת מחדש את אותה עותק לא ניתן להרצה.

כוונו את המשגר לספרייה ארוזה

שנו את שם העזר לשם של ספרייה משותפת וספקו אותו תחת jniLibs לכל ABI שאתם תומכים בו, ואז תנו למשגר להחזיר את נתיב הספרייה המותקנת מ-API 28 והלאה:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    return new File(context.getApplicationInfo().nativeLibraryDir,
                    "libovpnexec.so").getPath();
}

הספרייה חייבת להיות מחולצת בזמן ההתקנה כדי שהנתיב הזה יכיל קובץ אמיתי, לכן הגדירו android:extractNativeLibs="true" על רכיב <application>. אם ממזג מניפסט מביא ערך אחר, הוסיפו tools:replace="android:extractNativeLibs" לצידו. הפניית רכיב application מתעדת מה התכונה הזו שולטת בו.

שימו לב למשתנה מקומי כפול כשאתם מתקנים את המשגר ביד. מקורות ישנים מכריזים על nativeAPI מאוחר יותר באותה שיטה, והכרזה שנייה היא שגיאת קומפילציה ולא שגיאת ריצה, כך שהבנייה נכשלת מהר והטעות קלה לזיהוי. שמרו על רשימת ה-ABI בבלוק Gradle מסונכרנת עם התיקיות שאתם מוסיפים.

התאימו את תוצר השחרור לבניית הבדיקה

APK של ניפוי באגים יכול להעלות את המנהרה בזמן ש-bundle של שחרור עדיין קורס, מפני ש-bundle אורז ספריות מקוריות בצורה אחרת ויכול להגישן דחוסות מהארכיון. אם רק בניות שמקורן ב-bundle נכשלות, הוסיפו את זה ל-gradle.properties ובנו מחדש:

android.bundle.enableUncompressedNativeLibs=false

לאחר מכן ודאו שהחבילה המותקנת מדווחת על ה-ABI שאתם מצפים לו, וחזרו על הבדיקה במכשיר 32 סיביות ובמכשיר 64 סיביות אם אתם מספקים את שניהם. מנהל החבילות מציין את ה-ABI שבחר, ולכן מכשיר שנפל חזרה ל-32 סיביות מופיע כאן ולא בדוח קריסה:

adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbi

ספרייה חסרה ל-ABI אחד משחזרת את שגיאת ההרשאה המקורית רק במכשיר ההוא, ולכן טלפון בדיקה אחד שהצליח אינו הוכחה. ספקו את דגל המניפסט, את שם הספרייה ואת הגדרת ה-bundle יחד; שינוי אחד ושכחת האחרים מחזיר אתכם לאותה קריסה. התיקון המתמשך הוא לעבור לגרסת ליבה מתוחזקת במקום לשאת את הטלאי לנצח.

מאמרים קשורים