פורמט הקובץ APEX

פורמט הקונטיינר Android Pony EXpress‏ (APEX) הוצג ב-Android 10, והוא משמש בתהליך ההתקנה של מודולים ברמה נמוכה יותר במערכת. הפורמט הזה מאפשר לעדכן רכיבי מערכת שלא מתאימים למודל האפליקציות הרגיל של Android. דוגמאות לרכיבים הם ספריות ושירותים מקומיים, שכבות הפשטה של חומרה (HAL), סביבת זמן ריצה (ART) וספריות של כיתות.

המונח 'APEX' יכול להתייחס גם לקובץ APEX.

רקע

מערכת Android תומכת בעדכונים של מודולים שתואמים למודל האפליקציה הרגיל (לדוגמה, שירותים, פעילויות) באמצעות אפליקציות להתקנת חבילות (כמו אפליקציית חנות Google Play). עם זאת, לשימוש במודל דומה לרכיבי מערכת הפעלה ברמה נמוכה יותר יש את החסרונות הבאים:

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

עיצוב

בקטע הזה מתוארים העיצוב הכללי של פורמט הקובץ APEX ושל מנהל APEX, שהוא שירות שמנהל קובצי APEX.

למידע נוסף על הסיבות לבחירה בתכנון הזה ל-APEX, ראו חלופות שנשקלו במהלך הפיתוח של APEX.

פורמט APEX

זהו הפורמט של קובץ APEX.

פורמט קובץ APEX

איור 1. פורמט קובץ APEX

ברמה העליונה, קובץ APEX הוא קובץ ZIP שבו הקבצים מאוחסנים ללא דחיסה וממוקמים בגבולות של 4KB.

ארבעת הקבצים בקובץ APEX הם:

  • apex_manifest.json
  • AndroidManifest.xml
  • apex_payload.img
  • apex_pubkey

קובץ apex_manifest.json מכיל את שם החבילה ואת הגרסה שלה, שמזהים קובץ APEX. זהו מאגר פרוטוקול של ApexManifest בפורמט JSON.

הקובץ AndroidManifest.xml מאפשר לקובץ ה-APEX להשתמש בתשתית ובכלים שקשורים ל-APK, כמו ADB, ‏ PackageManager ואפליקציות להתקנת חבילות (כמו Play Store). לדוגמה, קובץ APEX יכול להשתמש בכלי קיים כמו aapt כדי לבדוק מטא-נתונים בסיסיים מהקובץ. הקובץ מכיל את שם החבילה ואת פרטי הגרסה. בדרך כלל, המידע הזה זמין גם ב-apex_manifest.json.

מומלץ להשתמש ב-apex_manifest.json במקום ב-AndroidManifest.xml בקוד ובמערכות חדשים שקשורים ל-APEX. AndroidManifest.xml עשוי להכיל מידע נוסף על טירגוט שאפשר להשתמש בו בכלים הקיימים לפרסום אפליקציות.

apex_payload.img הוא קובץ אימג' של מערכת קבצים מסוג ext4 שמגודר על ידי dm-verity. האימג' מותקן בסביבת זמן הריצה באמצעות מכשיר לולאה חוזרת (loopback). באופן ספציפי, עץ הגיבוב וחסימת המטא-נתונים נוצרים באמצעות הספרייה libavb. מטען העבודה של מערכת הקבצים לא מנותח (כי התמונה צריכה להיות ניתנת לטעינה במקום). קבצים רגילים נכללים בקובץ apex_payload.img.

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

הנחיות למתן שמות ב-APEX

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

  • com.android.*
    • שמורה ל-APEXes של AOSP. הוא לא ייחודי לאף חברה או מכשיר.
  • com.<companyname>.*
    • שמור לחברה. יכול להיות שהחשבון הזה משמש כמה מכשירים של החברה הזו.
  • com.<companyname>.<devicename>.*
    • שמורות למזהי APEX ייחודיים למכשיר ספציפי (או לקבוצת משנה של מכשירים).

מנהל APEX

מנהל ה-APEX (או apexd) הוא תהליך מקורי עצמאי שאחראי על אימות, התקנה והסרה של קובצי APEX. התהליך הזה מופעל ומוכין בשלב מוקדם ברצף האתחול. קובצי APEX מותקנים בדרך כלל מראש במכשיר בקטע /system/apex. אם אין עדכונים זמינים, ברירת המחדל של מנהל APEX היא להשתמש בחבילות האלה.

רצף העדכון של APEX משתמש בClass PackageManager, והוא מפורט בהמשך.

  1. קובץ APEX מוריד באמצעות אפליקציית התקנת חבילות, ADB או מקור אחר.
  2. מנהל החבילות מתחיל את תהליך ההתקנה. כשמנהל החבילות מזהה שהקובץ הוא APEX, הוא מעביר את השליטה למנהל APEX.
  3. מנהל ה-APEX מאמת את קובץ ה-APEX.
  4. אם קובץ ה-APEX מאומת, מסד הנתונים הפנימי של מנהל ה-APEX מתעדכן כך שיציג שהקובץ הופעל בהפעלה הבאה.
  5. מבצע ההתקנה מקבל שידור לאחר אימות החבילה.
  6. כדי להמשיך את ההתקנה, צריך להפעיל מחדש את המערכת.
  7. בהפעלה הבאה, מנהל APEX מתחיל, קורא את מסד הנתונים הפנימי ומבצע את הפעולות הבאות לכל קובץ APEX שמופיע ברשימה:

    1. אימות קובץ ה-APEX.
    2. יצירת מכשיר לולאה חוזרת מקובץ APEX.
    3. יצירת מכשיר בלוק של ממפאי מכשירים מעל מכשיר הלולאה החוזרת.
    4. הכלי מחבר את מכשיר ה-block של ממפאי המכשירים לנתיב ייחודי (לדוגמה, /apex/name@ver).

כשכל קובצי ה-APEX שמפורטים במסד הנתונים הפנימי מותקנים, מנהל ה-APEX מספק שירות קישור (binder) לרכיבי מערכת אחרים, כדי שיוכלו לשלוח שאילתות לגבי קובצי ה-APEX המותקנים. לדוגמה, שאר רכיבי המערכת יכולים לשלוח שאילתה לרשימת קובצי ה-APEX שמותקנים במכשיר או לנתיב המדויק שבו מוצמד APEX ספציפי, כדי שתהיה גישה לקבצים.

קובצי APEX הם קובצי APK

קובצי APEX הם קובצי APK חוקיים כי הם ארכיונים של קובצי ZIP חתומים (באמצעות סכמת החתימה של APK) שמכילים קובץ AndroidManifest.xml. כך קבצי APEX יכולים להשתמש בתשתית של קובצי APK, כמו אפליקציית התקנת חבילות, כלי החתימה ומנהל החבילות.

קובץ AndroidManifest.xml בתוך קובץ APEX הוא מינימלי, והוא מורכב מחבילת name, מ-versionCode ומ-targetSdkVersion, מ-minSdkVersion ומ-maxSdkVersion (אופציונלי) לטירגוט מפורט. המידע הזה מאפשר להעביר קובצי APEX דרך ערוצים קיימים, כמו אפליקציות להתקנת חבילות ו-ADB.

סוגי הקבצים הנתמכים

פורמט APEX תומך בסוגי הקבצים הבאים:

  • ספריות משותפות מקוריות
  • קובצי הפעלה מקומיים
  • קובצי JAR
  • קובצי נתונים
  • קובצי תצורה

עם זאת, זה לא אומר ש-APEX יכול לעדכן את כל סוגי הקבצים האלה. היכולת לעדכן סוג קובץ תלויה בפלטפורמה וביציבות של ההגדרות של ממשקי ה-API לסוגי הקבצים.

אפשרויות חתימה

קבצי APEX חותמים בשתי דרכים. קודם כול, קובץ apex_payload.img (במיוחד, מתאר ה-vbmeta שמצורף ל-apex_payload.img) נחתם באמצעות מפתח. לאחר מכן, כל קובץ ה-APEX נחתם באמצעות APK signature scheme v3. בתהליך הזה נעשה שימוש בשני מפתחות שונים.

בצד המכשיר, מותקן מפתח ציבורי שתואם למפתח הפרטי שמשמש לחתימה על מתאר ה-vbmeta. מנהל ה-APEX משתמש במפתח הציבורי כדי לאמת את ה-APEXes שביקשו להתקין. כל קובץ APEX צריך להיות חתום במפתחות שונים, והאכיפה מתבצעת גם בזמן ה-build וגם בזמן הריצה.

APEX במחיצות מובנות

קובצי APEX יכולים להיות ממוקמים במחיצות מובנות כמו /system. המחיצה כבר מעל dm-verity, כך שקובצי APEX מותקנים ישירות מעל מכשיר הלולאה החוזרת.

אם יש APEX במחיצה מובנית, אפשר לעדכן את ה-APEX על ידי הצגת חבילת APEX עם אותו שם חבילה וקוד גרסה שגדול מקוד הגרסה הקיים או שווה לו. קובץ ה-APEX החדש מאוחסן ב-/data, וכמו בחבילות APK, הגרסה החדשה שמותקנת מצללת על הגרסה שכבר נמצאת במחיצה המובנית. אבל בניגוד לחבילות APK, הגרסה החדשה של APEX שמותקנת מופעלת רק אחרי הפעלה מחדש.

דרישות הליבה

כדי לתמוך במודולים של APEX במכשיר Android, נדרשות התכונות הבאות של ליבה של Linux: מנהל ההתקן של הלולאה החוזרת (loopback) ו-dm-verity. מנהל ה-loopback מחבר את קובץ האימג' של מערכת הקבצים למודול APEX, ו-dm-verity מאמת את מודול ה-APEX.

הביצועים של מנהל ה-loopback ושל dm-verity חשובים להשגת ביצועים טובים של המערכת כשמשתמשים במודולים של APEX.

גרסאות הליבה הנתמכות

מודולים של APEX mainline נתמכים במכשירים עם גרסאות ליבה 4.4 ואילך. במכשירים חדשים שמושקעים עם Android מגרסה 10 ואילך, צריך להשתמש בליבה בגרסה 4.9 ואילך כדי לתמוך במודולים של APEX.

תיקוני הליבה הנדרשים

התיקונים הנדרשים לליבה (kernel) לתמיכה במודולים של APEX כלולים בעץ המשותף של Android. כדי לקבל את התיקונים שתומכים ב-APEX, צריך להשתמש בגרסה העדכנית ביותר של עץ Android המשותף.

גרסת ליבה 4.4

הגרסה הזו נתמכת רק במכשירים ששודרגו מ-Android 9 ל-Android 10 ורוצים לתמוך במודולים של APEX. מומלץ מאוד לבצע מיזוג לאחור (down-merge) מההסתעפות android-4.4 כדי לקבל את התיקונים הנדרשים. בהמשך מופיעה רשימה של התיקונים הנפרדים הנדרשים לליבה בגרסה 4.4.

  • UPSTREAM: loop: add ioctl for changing logical block size (4.4)
  • BACKPORT: block/loop: set hw_sectors (4.4)
  • UPSTREAM: loop: Add LOOP_SET_BLOCK_SIZE in compat ioctl (4.4)
  • ANDROID: mnt: Fix next_descendent (4.4)
  • ANDROID: mnt: remount should propagate to slaves of slaves (4.4)
  • ANDROID: mnt: Propagate remount correctly (4.4)
  • החזרה לגרסה הקודמת של 'ANDROID: dm verity: add minimum prefetch size' (4.4)
  • UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)

גרסאות ליבה 4.9/4.14/4.19

כדי לקבל את התיקונים הנדרשים לגרסאות הליבה 4.9/4.14/4.19, מבצעים מיזוג לאחור מההסתעפות android-common.

אפשרויות הגדרה נדרשות של הליבה

ברשימה הבאה מפורטות דרישות ההגדרה הבסיסיות לתמיכה במודולים של APEX שהוצגו ב-Android 10. הפריטים שמסומנים בכוכבית (*) הם דרישות קיימות מ-Android 9 וגרסאות קודמות.

(*) CONFIG_AIO=Y # AIO support (for direct I/O on loop devices)
CONFIG_BLK_DEV_LOOP=Y # for loop device support
CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 # pre-create 16 loop devices
(*) CONFIG_CRYPTO_SHA1=Y # SHA1 hash for DM-verity
(*) CONFIG_CRYPTO_SHA256=Y # SHA256 hash for DM-verity
CONFIG_DM_VERITY=Y # DM-verity support

דרישות לפרמטרים של שורת הפקודה של הליבה

כדי לתמוך ב-APEX, צריך לוודא שהפרמטרים של שורת הפקודה של הליבה עומדים בדרישות הבאות:

  • אסור להגדיר את loop.max_loop
  • הערך של loop.max_part חייב להיות 8 או פחות

פיתוח אפליקציה ב-APEX

בקטע הזה נסביר איך ליצור קובץ APEX באמצעות מערכת ה-build של Android. בדוגמה הבאה מוצג Android.bp של דומיין APEX בשם apex.test.

apex {
    name: "apex.test",
    manifest: "apex_manifest.json",
    file_contexts: "file_contexts",
    // libc.so and libcutils.so are included in the apex
    native_shared_libs: ["libc", "libcutils"],
    binaries: ["vold"],
    java_libs: ["core-all"],
    prebuilts: ["my_prebuilt"],
    compile_multilib: "both",
    key: "apex.test.key",
    certificate: "platform",
}

apex_manifest.json דוגמה:

{
  "name": "com.android.example.apex",
  "version": 1
}

file_contexts דוגמה:

(/.*)?           u:object_r:system_file:s0
/sub(/.*)?       u:object_r:sub_file:s0
/sub/file3       u:object_r:file3_file:s0

סוגי קבצים ומיקומים ב-APEX

סוג הקובץ מיקום ב-APEX
ספריות משותפות /lib ו-/lib64 (/lib/arm עבור arm מתורגם ב-x86)
קובצי הפעלה /bin
ספריות Java /javalib
חבילות מוכנות מראש /etc

יחסי תלות טרנסיטיביים

קובצי APEX כוללים באופן אוטומטי יחסי תלות טרנזיטיביים של ספריות שיתופיות מקומיות או קובצי הפעלה. לדוגמה, אם libFoo תלויה ב-libBar, שתי הספריות נכללות כשרק libFoo מופיע בנכס native_shared_libs.

טיפול במספר ממשקי ABI

מתקינים את המאפיין native_shared_libs גם לממשקי ה-ABI הראשיים וגם לממשקי ה-ABI המשניים של האפליקציה במכשיר. אם קובץ APEX מטרגט מכשירים עם ABI יחיד (כלומר 32 ביט בלבד או 64 ביט בלבד), רק ספריות עם ה-ABI התואם מותקנות.

מתקינים את הנכס binaries רק לממשק ה-ABI הראשי של המכשיר, כפי שמתואר בהמשך:

  • אם המכשיר הוא 32 ביט בלבד, רק גרסת ה-32 ביט של הקובץ הבינארי תותקן.
  • אם המכשיר תומך ב-64 ביט בלבד, תותקן רק הגרסה של הקובץ הבינארי ל-64 ביט.

כדי להוסיף שליטה פרטנית על ה-ABI של הספריות והקובצי הבינאריים המקומיים, משתמשים במאפיינים multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries].

  • first: תואם ל-ABI הראשי של המכשיר. זוהי ברירת המחדל לקובצי בינארי.
  • lib32: תואם ל-ABI של 32 ביט של המכשיר, אם יש תמיכה.
  • lib64: תואם ל-ABI של 64 ביט של המכשיר, אם יש תמיכה.
  • prefer32: תואם ל-ABI של 32 ביט של המכשיר, אם יש תמיכה. אם אין תמיכה ב-ABI של 32 ביט, הוא תואם ל-ABI של 64 ביט.
  • both: תואם לשני ממשקי ה-ABI. זהו ערך ברירת המחדל של native_shared_libraries.

המאפיינים java,‏ libraries ו-prebuilts לא תלויים ב-ABI.

הדוגמה הזו מיועדת למכשיר שתומך ב-32/64 ולא מעדיף את 32:

apex {
    // other properties are omitted
    native_shared_libs: ["libFoo"], // installed for 32 and 64
    binaries: ["exec1"], // installed for 64, but not for 32
    multilib: {
        first: {
            native_shared_libs: ["libBar"], // installed for 64, but not for 32
            binaries: ["exec2"], // same as binaries without multilib.first
        },
        both: {
            native_shared_libs: ["libBaz"], // same as native_shared_libs without multilib
            binaries: ["exec3"], // installed for 32 and 64
        },
        prefer32: {
            native_shared_libs: ["libX"], // installed for 32, but not for 64
        },
        lib64: {
            native_shared_libs: ["libY"], // installed for 64, but not for 32
        },
    },
}

חתימה על vbmeta

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

# create an rsa key pair
openssl genrsa -out foo.pem 4096

# extract the public key from the key pair
avbtool extract_public_key --key foo.pem --output foo.avbpubkey

# in Android.bp
apex_key {
    name: "apex.test.key",
    public_key: "foo.avbpubkey",
    private_key: "foo.pem",
}

בדוגמה שלמעלה, השם של המפתח הציבורי (foo) הופך למזהה של המפתח. המזהה של המפתח שמשמש לחתימה על APEX כתוב ב-APEX. במהלך זמן הריצה, apexd מאמת את ה-APEX באמצעות מפתח ציבורי עם אותו מזהה במכשיר.

חתימה על APEX

חותמים על קובצי APEX באותו אופן שבו חותמים על חבילות APK. חותמים על קוד ה-APEX פעמיים: פעם אחת למערכת הקבצים המינימלית (קובץ apex_payload.img) ופעם אחת לכל הקובץ.

כדי לחתום על קובץ APEX ברמת הקובץ, מגדירים את הנכס certificate באחת משלוש הדרכים הבאות:

  • לא מוגדר: אם לא מוגדר ערך, ה-APEX נחתם באמצעות האישור שנמצא בכתובת PRODUCT_DEFAULT_DEV_CERTIFICATE. אם לא מגדירים דגל, ברירת המחדל של הנתיב היא build/target/product/security/testkey.
  • <name>: קובץ ה-APEX נחתם באמצעות האישור <name> שנמצא באותה ספרייה שבה נמצא PRODUCT_DEFAULT_DEV_CERTIFICATE.
  • :<name>: ה-APEX חתום על ידי האישור שמוגדר על ידי המודול של Soong שנקרא <name>. אפשר להגדיר את מודול האישור באופן הבא.
android_app_certificate {
    name: "my_key_name",
    certificate: "dir/cert",
    // this will use dir/cert.x509.pem (the cert) and dir/cert.pk8 (the private key)
}

התקנת APEX

כדי להתקין אפליקציית APEX, משתמשים ב-ADB.

adb install apex_file_name
adb reboot

אם הערך של supportsRebootlessUpdate מוגדר כ-true בקובץ apex_manifest.json וה-APEX שמותקן כרגע לא בשימוש (לדוגמה, השירותים שהוא מכיל הופסקו), אפשר להתקין APEX חדש בלי הפעלה מחדש באמצעות הדגל --force-non-staged.

adb install --force-non-staged apex_file_name

שימוש ב-APEX

אחרי ההפעלה מחדש, ה-APEX יאוחסן בספרייה /apex/<apex_name>@<version>. אפשר לטעון כמה גרסאות של אותו APEX בו-זמנית. מבין נתיבי הטעינה, הנתיב שתואם לגרסה האחרונה מצורף באמצעות קישור (bind) אל /apex/<apex_name>.

לקוחות יכולים להשתמש בנתיב הטעינה המצורף כדי לקרוא או להריץ קבצים מ-APEX.

בדרך כלל משתמשים ב-APEX באופן הבא:

  1. יצרן ציוד מקורי (OEM) או יצרן ציוד מקורי בהתאמה אישית (ODM) טוען מראש קובץ APEX בקטע /system/apex כשהמכשיר נשלח.
  2. הגישה לקבצים ב-APEX מתבצעת דרך הנתיב /apex/<apex_name>/.
  3. כשגרסה מעודכנת של APEX מותקנת ב-/data/apex, הנתיב מפנה ל-APEX החדש אחרי ההפעלה מחדש.

עדכון שירות באמצעות APEX

כדי לעדכן שירות באמצעות APEX:

  1. מסמנים את השירות במחיצה של המערכת כשירות שניתן לעדכון. מוסיפים את האפשרות updatable להגדרת השירות.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. יוצרים קובץ .rc חדש לשירות המעודכן. משתמשים באפשרות override כדי להגדיר מחדש את השירות הקיים.

    /apex/my.apex/etc/init.rc:
    
    service myservice /apex/my.apex/bin/myservice
        class core
        user system
        ...
        override
    

אפשר להגדיר הגדרות שירות רק בקובץ .rc של APEX. אין תמיכה בטריגרים של פעולות ב-Apex.

אם שירות מסומן כמתעדכן והוא מתחיל לפעול לפני שה-APEXes מופעלים, ההפעלה מתעכבת עד שההפעלה של ה-APEXes תושלם.

הגדרת המערכת לתמיכה בעדכוני APEX

כדי לתמוך בעדכוני קבצים של APEX, מגדירים את מאפיין המערכת הבא ל-true.

<device.mk>:

PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true

BoardConfig.mk:
TARGET_FLATTEN_APEX := false

או פשוט

<device.mk>:

$(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk)

APEX שטוח

במכשירים מדור קודם, לפעמים אי אפשר או לא מעשי לעדכן את הליבה הישנה כך שתתמוך באופן מלא ב-APEX. לדוגמה, יכול להיות שהליבת ה-kernel נוצרה ללא CONFIG_BLK_DEV_LOOP=Y, שחשובה מאוד לטעינה של קובץ האימג' של מערכת הקבצים בתוך APEX.

Flattened APEX הוא APEX שנוצר במיוחד ואפשר להפעיל אותו במכשירים עם ליבה מדור קודם. קבצים ב-APEX שטוח מותקנים ישירות בספרייה מתחת למחיצה המובנית. לדוגמה, lib/libFoo.so ב-APEX שטוח מותקן ב-/system/apex/my.apex/lib/libFoo.so.my.apex

הפעלת APEX שטוח לא כוללת את מכשיר הלולאה. כל הספרייה /system/apex/my.apex מוצמדת ישירות ל-/apex/name@ver.

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

Flattened APEX היא הגדרת ברירת המחדל. המשמעות היא שכל קובצי ה-APEX מוצגים כקובצי flat כברירת מחדל, אלא אם מגדירים במפורש את המכשיר ליצירת קובצי APEX לא שטוחים כדי לתמוך בעדכוני APEX (כפי שמוסבר למעלה).

אי אפשר לשלב במכשיר בין מודעות APEX שטוחות למודעות APEX לא שטוחות. כל ה-APEX במכשיר חייבים להיות לא שטוחים או שטוחים. זה חשוב במיוחד כששולחים חבילות APEX מוגדרות מראש בחתימה מראש לפרויקטים כמו Mainline. גם קוד APEX שלא נחתם מראש (כלומר, נוצר מהמקור) צריך להיות לא שטוח וחתום באמצעות מפתחות מתאימים. המכשיר צריך לרשת מ-updatable_apex.mk, כפי שמוסבר במאמר עדכון שירות באמצעות APEX.

מודעות APEX דחוסות

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

כדי לצמצם את ההשפעה על נפח האחסון, דחיסת APEX משתמשת בקבוצה דחוסה מאוד של קובצי APEX במחיצות לקריאה בלבד (כמו המחיצה /system). ב-Android מגרסה 12 ואילך נעשה שימוש באלגוריתם דחיסת zip של DEFLATE.

דחיסת הנתונים לא מבצעת אופטימיזציה לפריטים הבאים:

  • רכיבי APEX של Bootstrap שצריך לטעון מוקדם מאוד בסדרת האתחול.

  • מודולים של APEX שלא ניתן לעדכן. כדאי להשתמש בדחיסת נתונים רק אם מותקנת גרסה מעודכנת של APEX במחיצה /data. רשימה מלאה של רכיבי APEX שניתנים לעדכון זמינה בדף רכיבי מערכת מודולריים.

  • ספריות APEX משותפות דינמיות. מכיוון ש-apexd תמיד מפעיל את שתי הגרסאות של מודולי APEX כאלה (שהותקנו מראש ושודרגו), דחיסת הקבצים לא מוסיפה ערך.

פורמט קובץ APEX דחוס

זהו הפורמט של קובץ APEX דחוס.

תרשים שבו מוצג הפורמט של קובץ APEX דחוס

איור 2. פורמט קובץ APEX דחוס

ברמה העליונה, קובץ APEX דחוס הוא קובץ ZIP שמכיל את קובץ ה-apex המקורי בפורמט דחוס עם רמת דחיסה של 9, וקבצים אחרים ששמורים ללא דחיסה.

קובץ APEX מורכב מארבעה קבצים:

  • original_apex: דחיסה עם רמת דחיסה של 9 זהו קובץ APEX המקורי, ללא דחיסה.
  • apex_manifest.pb: מאוחסן בלבד
  • AndroidManifest.xml: מאוחסן בלבד
  • apex_pubkey: מאוחסן בלבד

הקבצים apex_manifest.pb,‏ AndroidManifest.xml ו-apex_pubkey הם עותקים של הקבצים התואמים שלהם ב-original_apex.

פיתוח APEX דחוס

אפשר ליצור קובץ APEX דחוס באמצעות הכלי apex_compression_tool.py שנמצא בכתובת system/apex/tools.

יש כמה פרמטרים שקשורים לדחיסת APEX שזמינים במערכת ה-build.

ב-Android.bp, היכולת לדחוס קובץ APEX נקבע על ידי המאפיין compressible:

apex {
    name: "apex.test",
    manifest: "apex_manifest.json",
    file_contexts: "file_contexts",
    compressible: true,
}

דגל המוצר PRODUCT_COMPRESSED_APEX קובע אם קובץ אימג' של מערכת שנוצר ממקור חייב לכלול קובצי APEX דחוסים.

כדי לבצע ניסויים מקומיים, אפשר לאלץ גרסה זמנית לדחוס קובצי APEX על ידי הגדרת OVERRIDE_PRODUCT_COMPRESSED_APEX= לערך true.

לקובצי APEX דחוסים שנוצרו על ידי מערכת ה-build יש את הסיומת .capex. התוסף מאפשר להבחין בקלות בין גרסאות דחוסות לבין גרסאות לא דחוסות של קובץ APEX.

אלגוריתמי דחיסה נתמכים

ב-Android 12 יש תמיכה רק בדחיסת deflate-zip.

הפעלת קובץ APEX דחוס במהלך האתחול

כדי להפעיל קובץ APEX דחוס, קובץ original_apex שבתוכו צריך לעבור תהליך של דחיסה לאחור (decompression) ולעבור לספרייה /data/apex/decompressed. קובץ ה-APEX שנוצר לאחר הפירוק יהיה מקושר באופן קבוע לספרייה /data/apex/active.

הדוגמה הבאה היא המחשה לתהליך שמתואר למעלה.

אפשר להתייחס ל-/system/apex/com.android.foo.capex כקובץ APEX דחוס שמופעל, עם versionCode‏ 37.

  1. הקובץ original_apex בתוך /system/apex/com.android.foo.capex decompresses ל-/data/apex/decompressed/com.android.foo@37.apex.
  2. הפקודה restorecon /data/apex/decompressed/com.android.foo@37.apex מבוצעת כדי לוודא שיש לה תווית SELinux נכונה.
  3. בדיקות האימות מתבצעות ב-/data/apex/decompressed/com.android.foo@37.apex כדי לוודא שהוא תקף: apexd בודק את המפתח הציבורי שצורף ל-/data/apex/decompressed/com.android.foo@37.apex כדי לוודא שהוא זהה למפתח שצורף ל-/system/apex/com.android.foo.capex.
  4. לקובץ /data/apex/decompressed/com.android.foo@37.apex יש קישור קשיח לספרייה /data/apex/active/com.android.foo@37.apex.
  5. הלוגיקה הרגילה להפעלה של קבצי APEX לא דחוסים מתבצעת ב-/data/apex/active/com.android.foo@37.apex.

אינטראקציה עם OTA

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

כדי לתמוך במערכת ה-OTA, apexd חושף את שני ממשקי ה-API הבאים של Binder:

  • calculateSizeForCompressedApex – חישוב הגודל הנדרש כדי לבצע דחיסה לאחור של קבצי APEX בחבילת OTA. אפשר להשתמש בכך כדי לוודא שיש במכשיר מספיק מקום לפני הורדת עדכון OTA.
  • reserveSpaceForCompressedApex – שמירת מקום בדיסק לשימוש עתידי על ידי apexd, לצורך דחיסה של קובצי APEX דחוסים בתוך חבילת ה-OTA.

במקרה של עדכון OTA מסוג A/B, apexd מנסה לבצע דחיסה ברקע כחלק מתהליך ה-OTA לאחר ההתקנה. אם הדחיסה לא מצליחה, apexd מבצעת את הדחיסה במהלך האתחול שבו מוחלים את העדכון ב-OTA.

חלופות שנשקלו במהלך הפיתוח של APEX

ריכזנו כאן כמה אפשרויות ש-AOSP שקלה כשעיצבה את הפורמט של קובץ APEX, ומסבירים למה הן נכללו או לא נכללו.

מערכות רגילות לניהול חבילות

להפצות Linux יש מערכות ניהול חבילות כמו dpkg ו-rpm, שהן חזקות, מפותחות ויעילות. עם זאת, הן לא אומצו ב-APEX כי הן לא יכולות להגן על החבילות אחרי ההתקנה. האימות מתבצע רק במהלך התקנת החבילות. תוקפים יכולים לשבור את תקינות החבילות המותקנות בלי שיבחינו בכך. זוהי נסיגה ב-Android, שבה כל רכיבי המערכת מאוחסנים במערכות קבצים לקריאה בלבד, שהשלמות שלהן מוגנת על ידי dm-verity לכל קלט/פלט. כל פגיעה ברכיבי המערכת חייבת להיות אסורה או לאפשר זיהוי, כדי שהמכשיר יוכל לסרב להפעלה אם הוא נפרץ.

dm-crypt לצורך תקינות

הקבצים בקונטיינר APEX מגיעים ממחיצות מובנות (לדוגמה, המחיצה /system) שמוגנות על ידי dm-verity, שבהן אסור לשנות את הקבצים גם אחרי שמחברות את המחיצות. כדי לספק אותה רמת אבטחה לקבצים, כל הקבצים ב-APEX מאוחסנים בתמונת מערכת קבצים שמשויכת לעץ גיבוב ולתיאור vbmeta. בלי dm-verity, APEX במחיצה /data חשוף לשינויים לא מכוונים שמתבצעים אחרי האימות וההתקנה שלו.

למעשה, המחיצה /data מוגנת גם על ידי שכבות הצפנה כמו dm-crypt. אמנם הדבר מספק רמה מסוימת של הגנה מפני פגיעה, אבל המטרה העיקרית שלו היא פרטיות, ולא תקינות. כשתוקף מקבל גישה למחיצה /data, אי אפשר להוסיף הגנה נוספת, וגם כאן מדובר בנסיגה בהשוואה למצב שבו כל רכיב המערכת נמצא במחיצה /system. עץ הגיבוב בקובץ APEX יחד עם dm-verity מספקים את אותה רמת הגנה על התוכן.

הפניה אוטומטית של נתיבים מ-‎ /system אל ‎ /apex

אפשר לגשת לקבצים של רכיבי המערכת שארוזים ב-APEX דרך נתיבים חדשים כמו /apex/<name>/lib/libfoo.so. כשהקבצים היו חלק מהמחיצה /system, אפשר היה לגשת אליהם דרך נתיבים כמו /system/lib/libfoo.so. לקוח של קובץ APEX (קבצי APEX אחרים או הפלטפורמה) חייב להשתמש בנתיבים החדשים. יכול להיות שתצטרכו לעדכן את הקוד הקיים כתוצאה משינוי הנתיב.

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

אפשרות אחרת הייתה לפרוץ לפונקציות של גישה לקבצים, כמו open, ‏ stat ו-readlink, כדי שהנתיבים שמתחילים ב-/system ינותבו לנתיבים התואמים שלהם ב-/apex. צוות Android דחה את האפשרות הזו כי לא ניתן לשנות את כל הפונקציות שמקבלות נתיבים. לדוגמה, חלק מהאפליקציות מקשרות באופן סטטי את Bionic, שמטמיע את הפונקציות. במקרים כאלה, האפליקציות האלה לא מועברות לכתובת אחרת.