אכיפת ממשקים של מחיצות מוצרים

ב-Android 11, המחיצה product מנותקת מהמחיצות system ו-vendor. במסגרת השינויים האלה, עכשיו אפשר לשלוט בגישה של המחיצה product לממשקים מקומיים ולממשקי Java (בדומה לאופן שבו אכיפת הממשק פועלת במחיצות vendor).

אכיפת ממשקים מקומיים

כדי להפעיל את אכיפת הממשק המקומי, מגדירים את PRODUCT_PRODUCT_VNDK_VERSION לערך current. (הגרסת מוגדרת באופן אוטומטי ל-current כשרמת ה-API של המשלוח ליעד גבוהה מ-29). אכיפה מאפשרת:

  • מודולים מקוריים במחיצה product לקישור:
    • באופן סטטי או דינמי למודול אחרים במחיצה product שכוללים ספריות סטטיות, ספריות משותפות או ספריות כותרות.
    • באופן דינמי לספריות VNDK במחיצה system.
  • ספריות JNI ב-APKs לא מקוובצים במחיצה product כדי לקשר לספריות ב-/product/lib או ב-/product/lib64 (בנוסף לספריות NDK).

האכיפה לא מאפשרת קישורים אחרים למחיצות מלבד המחיצה product.

אכיפת זמן build (Android.bp)

ב-Android 11, מודולים של מערכת יכולים ליצור וריאנט של קובץ אימג' של מוצר, בנוסף לווריאנטים של קובץ אימג' ליבה וקובץ אימג' של ספק. כשמופעלת אכיפה של ממשק נייטיב (PRODUCT_PRODUCT_VNDK_VERSION מוגדר ל-current):

  • מודולים מקומיים במחיצה product נמצאים בגרסת המוצר במקום בגרסת הליבה.

  • מודולים עם product_available: true בקובצי Android.bp זמינים לווריאציית המוצר.

  • ספריות או קבצים בינאריים שצוין בהם product_specific: true יכולים לקשר לספריות אחרות שצוין בהן product_specific: true או product_available: true בקבצים שלהן מסוג Android.bp.

  • בספריות VNDK צריך להופיע product_available: true בקובצי Android.bp שלהן, כדי שקבצים בינאריים של product יוכלו לקשר לספריות VNDK.

בטבלה הבאה מפורט סיכום של המאפיינים Android.bp שמשמשים ליצירת וריאנטים של תמונות.

מאפיינים ב-Android.bp הווריאציות נוצרו
לפני האכיפה אחרי האכיפה
ברירת מחדל (ללא) הליבה
(כולל /system, ‏ /system_ext ו-/product)
הליבה
(כולל /system ו-/system_ext, אבל לא את /product)
system_ext_specific: true ליבה ליבה
product_specific: true התוכן הקבוע מוצר
vendor: true ספק ספק
vendor_available: true ליבה, ספק core, vendor
product_available: true לא רלוונטי ליבה, מוצר
vendor_available: true וגם product_available: true לא רלוונטי core, ‏ product, ‏ vendor
system_ext_specific: true וגם vendor_available: true core, vendor core, vendor
product_specific: true וגם vendor_available: true core, vendor מוצר, ספק

אכיפה בזמן ה-build (Android.mk)

כשמופעלת אכיפה של הממשק המקורי, המודולים המקוריים שהותקנו במחיצה product כוללים קישור מסוג native:product שאפשר לקשר רק למודולים אחרים של native:product או native:vndk. ניסיון לקשר למודולים אחרים מלבד אלה יגרום למערכת ה-build ליצור שגיאה בבדיקת סוג הקישור.

אכיפה של זמן ריצה

כשהאכיפה של ממשק מקורי מופעלת, הגדרת הקישור של ה-linker של Bionic לא מאפשרת לתהליכי המערכת להשתמש בספריות product, ויוצרת קטע product לתהליכי product שלא יכולים לקשר לספריות מחוץ למחיצה product (עם זאת, תהליכים כאלה יכולים לקשר לספריות VNDK). ניסיונות להפר את הגדרת הקישור בסביבת זמן הריצה גורמים לכך שהתהליך נכשל ומונפקת הודעת השגיאה CANNOT LINK EXECUTABLE.

אכיפת ממשקי Java

כדי להפעיל את האכיפה של ממשק Java, מגדירים את PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE ל-true. (הערך מוגדר באופן אוטומטי ל-true כשרמת ה-API של שירותי המשלוח ליעד גבוהה מ-29). כשהאכיפה מופעלת, היא מאפשרת או אוסרת את הגישה הבאה:

API /system /system_ext /product /vendor /data
ממשק API ציבורי
‎@SystemApi
@hide API

כמו במחיצה vendor, אפליקציה או ספריית Java במחיצה product יכולות להשתמש רק בממשקי API ציבוריים וממשקי API של מערכת. אסור לקשר לספרייה שמשתמשת בממשקי API מוסתרים. ההגבלה הזו כוללת קישור בזמן ה-build והשתקפות בזמן הריצה.

אכיפת זמן Build

בזמן ה-build, ה-Make וה-Soong בודקים את השדות platform_apis ו-sdk_version כדי לוודא שממשקי ה-API המוסתרים לא נמצאים בשימוש במודולים של Java במחיצה product. השדה sdk_version של האפליקציות במחיצה product צריך להיות מלא ב-current, ב-system_current או בגרסה מספרית של ה-API, והשדה platform_apis צריך להיות ריק.

אכיפה של זמן ריצה

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

הפעלת אכיפה של ממשק המוצר

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

שלב משימה חובה
1 מגדירים קובץ makefile משלכם למערכת שמציין את החבילות למחיצה system, ואז מגדירים את בדיקת דרישות הנתיב של הארטיפקטים ב-device.mk (כדי למנוע התקנה של מודולים שאינם מערכת במחיצה system). לא
2 ניקוי רשימת ההיתרים. לא
3 לאכוף ממשקים נייטיב ולזהות כשלים בקישור בזמן ריצה (יכול לרוץ במקביל לאכיפה של Java). Y
4 אכיפת ממשקי Java ואימות התנהגות בסביבת זמן ריצה (אפשר להפעיל במקביל אכיפה מותאמת). Y
5 בדיקת התנהגויות של סביבת זמן ריצה. Y
6 מעדכנים את device.mk באמצעות אכיפה של ממשק המוצר. Y

שלב 1: יוצרים קובץ makefile ומפעילים בדיקה של נתיב הארטיפקט

בשלב הזה מגדירים את קובץ ה-makefile system.

  1. יוצרים קובץ makefile שמגדיר את החבילות למחיצה system. לדוגמה, יוצרים קובץ oem_system.mk עם הפרטים הבאים:

    $(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system.mk)
    $(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system.mk)
    
    # Applications
    PRODUCT_PACKAGES += \
        CommonSystemApp1 \
        CommonSystemApp2 \
        CommonSystemApp3 \
    
    # Binaries
    PRODUCT_PACKAGES += \
        CommonSystemBin1 \
        CommonSystemBin2 \
        CommonSystemBin3 \
    
    # Libraries
    PRODUCT_PACKAGES += \
        CommonSystemLib1 \
        CommonSystemLib2 \
        CommonSystemLib3 \
    
    PRODUCT_SYSTEM_NAME := oem_system
    PRODUCT_SYSTEM_BRAND := Android
    PRODUCT_SYSTEM_MANUFACTURER := Android
    PRODUCT_SYSTEM_MODEL := oem_system
    PRODUCT_SYSTEM_DEVICE := generic
    
    # For system-as-root devices, system.img should be mounted at /, so we
    # include ROOT here.
    _my_paths := \
     $(TARGET_COPY_OUT_ROOT)/ \
     $(TARGET_COPY_OUT_SYSTEM)/ \
    
    $(call require-artifacts-in-path, $(_my_paths),)
    
  2. בקובץ device.mk, יורשים את קובץ ה-getfile המשותף של המחיצה system ומפעילים את בדיקת הדרישות של הנתיב של פריט המידע שנוצר בתהליך הפיתוח (Artifact). לדוגמה:

    $(call inherit-product, $(SRC_TARGET_DIR)/product/oem_system.mk)
    
    # Enable artifact path requirements checking
    PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := strict
    

מידע על הדרישות לגבי נתיב הארטיפקט

כשההגדרה של PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS מוגדרת לערך true או לערך strict, מערכת ה-build מונעת התקנה של חבילות שהוגדרו בקובצי makefile אחרים בנתיבים שהוגדרו ב-require-artifacts-in-path וגם מונעת מחבילות שמוגדרות בקובץ ה-set הנוכחי להתקין ארטיפקטים מחוץ לנתיבים שהוגדרו ב-require-artifacts-in-path.

בדוגמה שלמעלה, כשהערך של PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS מוגדר כ-strict, קובצי makefile מחוץ ל-oem_system.mk לא יכולים לכלול מודולים שהותקנו במחיצה root או system. כדי לכלול את המודולים האלה, צריך להגדיר אותם בקובץ oem_system.mk עצמו או בקובץ makefile שכלול. ניסיונות להתקין מודולים בנתיבים אסורים גורמים להפסקות ב-build. כדי לתקן את ההפסקות, מבצעים אחת מהפעולות הבאות:

  • אפשרות 1: כוללים את מודול המערכת בקובצי ה-makefile שכלולים ב-oem_system.mk. כתוצאה מכך, המערכת עומדת בדרישה של הנתיב של פריט המידע שנוצר בתהליך הפיתוח (Artifact) (כי המודולים קיימים עכשיו בקובץ getfile כלול) ולכן אפשר להתקין את קבוצת הנתיבים ב-'Require-artifacts-in-path'.

  • אפשרות 2: מתקינים מודולים במחיצה system_ext או product (ולא מתקינים מודולים במחיצה system).

  • אפשרות 3: מוסיפים מודולים ל-PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST. ברשימה הזו מפורטים המודולים שמותר להתקין.

שלב 2: מרוקנים את רשימת ההיתרים

בשלב הזה, משאירים את PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST ריק כדי שכל המכשירים שמשתפים את oem_system.mk יוכלו לשתף גם תמונה אחת של system. כדי לרוקן את רשימת ההיתרים, מעבירים את המודולים ברשימה למחיצה system_ext או product, או מוסיפים אותם לקובצי make של system. השלב הזה הוא אופציונלי כי לא צריך להגדיר קובץ system משותף כדי להפעיל אכיפה של ממשק המוצר. עם זאת, ריקון רשימת ההיתרים עוזר להגדיר את הגבול של system באמצעות system_ext.

שלב 3: אכיפת ממשקים מותאמים

בשלב הזה מגדירים את PRODUCT_PRODUCT_VNDK_VERSION := current, מחפשים שגיאות ב-build ובזמן הריצה ומתקנים אותן. כדי לבדוק את ההפעלה ואת היומנים של המכשיר, ולמצוא ולתקן כשלים בקישורים בסביבת זמן הריצה:

  1. מגדירים את PRODUCT_PRODUCT_VNDK_VERSION := current.

  2. יוצרים את המכשיר ומחפשים שגיאות ב-build. סביר להניח שתראו כמה מעברי build חסרים בו וריאציות מוצר או וריאציות ליבה. הפסקות נפוצות כוללות:

    • כל מודול של hidl_interface שכולל את product_specific: true לא יהיה זמין למודולים של המערכת. כדי לתקן את הבעיה, מחליפים את product_specific: true ב-system_ext_specific: true.
    • יכול להיות שבמודולים חסרה הווריאציה של המוצר שנדרשת למודול המוצר. כדי לפתור את הבעיה, צריך להגדיר את המודול הזה כזמין למחיצה product באמצעות ההגדרה product_available: true, או להעביר את המודול למחיצה product באמצעות ההגדרה product_specific: true.
  3. פותרים שגיאות ב-build ומוודאים שה-build של המכשיר הצליח.

  4. צריך להעלות את התמונה להבהוב ולחפש שגיאות בזמן הריצה באתחול המכשיר וביומנים.

    • אם בתג linker ביומן של מקרי הבדיקה מוצגת הודעה CANNOT LINK EXECUTABLE, חסרה תלות בקובץ ה-create (והוא לא תועד בזמן ה-build).
    • כדי לבדוק את זה ממערכת ה-build, מוסיפים את הספרייה הנדרשת לשדה shared_libs: או required:.
  5. פותרים את יחסי התלות החסרים לפי ההנחיות שלמעלה.

שלב 4: אכיפה של ממשקי Java

בשלב הזה מגדירים את PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true, ואז מאתרים את שגיאות ה-build שנוצרו ותוקנים אותן. מחפשים שני סוגים ספציפיים של שגיאות:

  • שגיאות מסוג קישור השגיאה הזו מציינת שאפליקציה מקשרת למודולים של Java בעלי sdk_version רחב יותר. כדי לפתור את הבעיה, אפשר להרחיב את sdk_version של האפליקציה או להגביל את sdk_version של הספרייה. שגיאה לדוגמה:

    error: frameworks/base/packages/SystemUI/Android.bp:138:1: module "SystemUI" variant "android_common": compiles against system API, but dependency "telephony-common" is compiling against private API.Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.
    
  • שגיאות בסמל השגיאה הזו מציינת שאי אפשר למצוא סמל כי הוא נמצא ב-API מוסתר. כדי לפתור את הבעיה, צריך להשתמש בממשק API גלוי (לא מוסתר) או למצוא חלופה. שגיאה לדוגמה:

    frameworks/opt/net/voip/src/java/com/android/server/sip/SipSessionGroup.java:1051: error: cannot find symbol
                ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
                                               ^
      symbol:   class ProxyAuthenticate
      location: class SipSessionGroup.SipSessionImpl
    

שלב 5: בדיקת התנהגויות של סביבת זמן ריצה

בשלב הזה מוודאים שההתנהגויות בסביבת זמן הריצה תקינות. באפליקציות שאפשר לנפות בהן באגים, אפשר לעקוב אחרי השימוש ב-API מוסתר באמצעות יומן באמצעות StrictMode.detectNonSdkApiUsage (שמייצר יומן כשהאפליקציה משתמשת ב-API מוסתר). לחלופין, אפשר להשתמש בכלי הניתוח הסטטי veridex כדי לקבל את סוג השימוש (קישור או שיקוף), רמת ההגבלה וסטאק הקריאות.

  • תחביר Veridex:

    ./art/tools/veridex/appcompat.sh --dex-file={apk file}
  • דוגמה לתוצאה של Veridex:

    #1: Linking greylist-max-o Landroid/animation/AnimationHandler;-><init>()V use(s):
           Lcom/android/systemui/pip/phone/PipMotionHelper;-><init>(Landroid/content/Context;Landroid/app/IActivityManager;Landroid/app/IActivityTaskManager;Lcom/android/systemui/pip/phone/PipMenuActivityController;Lcom/android/internal/policy/PipSnapAlgorithm;Lcom/android/systemui/statusbar/FlingAnimationUtils;)V
    
    #1332: Reflection greylist Landroid/app/Activity;->mMainThread use(s):
           Landroidx/core/app/ActivityRecreator;->getMainThreadField()Ljava/lang/reflect/Field;
    

לפרטים על השימוש ב-Veridex, אפשר לעיין במאמר בדיקה באמצעות הכלי של Veridex.

שלב 6: מעדכנים את device.mk

אחרי שמתקנים את כל הכישלונות בזמן ה-build ובזמן הריצה, ומוודאים שההתנהגויות בזמן הריצה תואמות לציפיות, מגדירים את הפרמטרים הבאים ב-device.mk:

  • PRODUCT_PRODUCT_VNDK_VERSION := current
  • PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE := true