מעקב אחר ABI של ליבה של Android

אתם יכולים להשתמש בכלים למעקב אחרי ממשק בינארי של אפליקציה (ABI), שזמינים ב-Android מגרסה 11 ואילך, כדי לייצב את ה-ABI בתוך הליבה של ליבות Android. הכלי אוסף ומבצע השוואה בין ייצוגים של ABI מקבצים בינאריים קיימים של ליבת מערכת ההפעלה (vmlinux+ מודולים של GKI). הייצוגים האלה של ABI הם קובצי .stg ורשימות הסמלים. הממשק שבו הייצוג מספק תצוגה נקרא ממשק מודול הליבה (KMI). אפשר להשתמש בכלים כדי לעקוב אחרי שינויים ב-KMI ולצמצם אותם.

הכלים למעקב אחרי ABI פותחו ב-AOSP והם משתמשים ב-STG (או ב-libabigail ב-Android מגרסה 13 ומטה) כדי ליצור השוואות בין ייצוגים.

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

עיבוד

ניתוח ממשק ה-ABI של ליבת המערכת כולל כמה שלבים, שרובם יכולים להתבצע באופן אוטומטי:

  1. בונים את הליבה ואת ייצוג ה-ABI שלה.
  2. ניתוח ההבדלים בממשק ABI בין הגרסה לבין קובץ עזר.
  3. מעדכנים את ייצוג ה-ABI (אם נדרש).
  4. עבודה עם רשימות של סמלים.

ההוראות הבאות מתאימות לכל ליבת מערכת שאפשר לבנות באמצעות ערכת כלים נתמכת (כמו ערכת הכלים Clang שנבנתה מראש). ‫repo manifests זמינים לכל הענפים הנפוצים של ליבת Android ולכמה ליבות ספציפיות למכשיר. הם מוודאים שנעשה שימוש בשרשרת הכלים הנכונה כשיוצרים הפצה של ליבה לצורך ניתוח.

רשימות סמלים

ה-KMI לא כולל את כל הסמלים בקרנל, ואפילו לא את כל 30,000+ הסמלים המיוצאים. במקום זאת, הסמלים שאפשר להשתמש בהם במודולים של ספקים מפורטים באופן מפורש בקבוצה של קבצים עם רשימת סמלים שמתעדכנים באופן פומבי בעץ הליבה (gki/{ARCH}/symbols/* או android/abi_gki_{ARCH}_* ב-Android 15 ובגרסאות קודמות). האיחוד של כל הסמלים בכל קובצי רשימת הסמלים מגדיר את קבוצת סמלי ה-KMI שנשמרים כיציבים. קובץ רשימת סמלים לדוגמה הוא gki/aarch64/symbols/db845c, שמכריז על הסמלים הנדרשים עבור DragonBoard 845c.

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

לכל ענף ליבה של Android Common Kernel‏ (ACK) KMI יש קבוצה משלו של רשימות סמלים. לא נעשה ניסיון לספק יציבות של ABI בין ענפים שונים של ליבת KMI. לדוגמה, ה-KMI של android12-5.10 לא תלוי בכלל ב-KMI של android13-5.10.

כלי ABI משתמשים ברשימות סמלים של KMI כדי להגביל את הממשקים שצריך לעקוב אחריהם כדי לוודא שהם יציבים. הספקים צריכים לשלוח ולעדכן את רשימות הסמלים שלהם כדי לוודא שהממשקים שהם מסתמכים עליהם שומרים על תאימות ABI. לדוגמה, כדי לראות רשימה של רשימות סמלים עבור ליבת android16-6.12, אפשר לעיין בhttps://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols

רשימת הסמלים מכילה את הסמלים שדווחו כנדרשים עבור הספק או המכשיר הספציפיים. הרשימה המלאה שבה נעשה שימוש בכלים היא איחוד של כל קובצי רשימת הסמלים של KMI. הכלים של ABI קובעים את הפרטים של כל סמל, כולל חתימת פונקציה ומבני נתונים מקוננים.

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

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

הארכת ה-KMI

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

מידע על תקלות ב-KMI

לקרנל יש מקורות, והקובצים הבינאריים נוצרים מהמקורות האלה. ענפי ליבה בפיקוח ABI כוללים ייצוג ABI של GKI ABI הנוכחי (בפורמט של קובץ .stg). אחרי שגרסאות הבינאריות (vmlinux, Image וכל מודול GKI) נוצרות, אפשר לחלץ מהן ייצוג של ABI. כל שינוי שמתבצע בקובץ מקור של ליבת המערכת יכול להשפיע על הקבצים הבינאריים, ובתורו גם על .stg שחולץ. בניתוח התאימות של ABI מושווה קובץ .stg שהועלה עם קובץ שחולץ מארטיפקטים של בנייה, ואם נמצא הבדל סמנטי, מוגדרת תווית Lint-1 לשינוי ב-Gerrit.

טיפול בשינויים שגורמים לשבירת תאימות של ממשקי ABI

לדוגמה, התיקון הבא גורם לשבירה ברורה מאוד של ABI:

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
                ANDROID_KABI_RESERVE(1);
        } __randomize_layout;

+       int tickle_count;
        /*
         * The mm_cpumask needs to be at the end of mm_struct, because it
         * is dynamically sized based on nr_cpu_ids.

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

function symbol 'struct block_device* I_BDEV(struct inode*)' changed
  CRC changed from 0x8d400dbd to 0xabfc92ad

function symbol 'void* PDE_DATA(const struct inode*)' changed
  CRC changed from 0xc3c38b5c to 0x7ad96c0d

function symbol 'void __ClearPageMovable(struct page*)' changed
  CRC changed from 0xf489e5e8 to 0x92bd005e

... 4492 omitted; 4495 symbols have only CRC changes

type 'struct mm_struct' changed
  byte size changed from 992 to 1000
  member 'int tickle_count' was added
  member 'unsigned long cpu_bitmap[0]' changed
    offset changed by 64

זוהו הבדלים ב-ABI בזמן הבנייה

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

אם הסמל לא מופיע ברשימת הסמלים, קודם צריך לוודא שהוא מיוצא עם EXPORT_SYMBOL_GPL(symbol_name), ואז לעדכן את רשימת הסמלים ואת ייצוג ה-ABI. לדוגמה, השינויים הבאים מוסיפים את התכונה החדשה Incremental FS לענף android-12-5.10, שכוללת עדכון של רשימת הסמלים וייצוג ה-ABI.

  • דוגמה לשינוי בתכונה מופיעה ב-aosp/1345659.
  • דוגמה לרשימת סמלים מופיעה בכתובת aosp/1346742.
  • דוגמה לשינוי בייצוג של ABI מופיעה ב-aosp/1349377.

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

Comparing the KMI and the symbol lists:
+ build/abi/compare_to_symbol_list out/$BRANCH/common/Module.symvers out/$BRANCH/common/abi_symbollist.raw
ERROR: Differences between ksymtab and symbol list detected!
Symbols missing from ksymtab:
Symbols missing from symbol list:
 - simple_strtoull

כדי לפתור את הבעיה, צריך לעדכן את רשימת הסמלים של KMI גם בקרנל וגם ב-ACK (ראו עדכון של ייצוג ה-ABI). דוגמה לעדכון רשימת סמלים וייצוג ממשק ה-ABI ב-ACK מופיעה ב-aosp/1367601.

פתרון בעיות שקשורות לשינויים ב-ABI של ליבת המערכת

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

תרשים זרימה של שבירת ABI

איור 1. פתרון בעיות שקשורות ל-ABI

שינוי מבנה הקוד כדי למנוע שינויים ב-ABI

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

  • שינויים בשדות של מבנים. אם שינוי משנה את ממשק ה-ABI של תכונת ניפוי באגים, מוסיפים #ifdef מסביב לשדות (במבנים ובהפניות למקור) ומוודאים שה-CONFIG שמשמש ל-#ifdef מושבת עבור קובץ ה-defconfig של הייצור ו-gki_defconfig. דוגמה לאופן שבו אפשר להוסיף debug config למבנה בלי לשבור את ה-ABI מופיעה בסט התיקונים הזה.

  • שינוי מבנה התכונות כדי שלא ישנו את ליבת הקרנל. אם צריך להוסיף תכונות חדשות ל-ACK כדי לתמוך במודולים של השותף, כדאי לנסות לשנות את מבנה ה-ABI של השינוי כדי להימנע משינוי ה-ABI של ליבת המערכת. דוגמה לשימוש בממשק ה-ABI הקיים של ליבת המערכת כדי להוסיף יכולות נוספות בלי לשנות את ממשק ה-ABI של ליבת המערכת מופיעה בכתובת aosp/1312213.

תיקון ממשק ABI פגום ב-Android Gerrit

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

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

מידע על תוויות Lint-1

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

כל אחד מהניתוחים האלה של ABI עשוי להגדיר את התווית Lint-1 ולחסום את שליחת השינוי עד שכל הבעיות ייפתרו או שהתווית תבוטל.

עדכון של ה-ABI של הליבה

אם אין ברירה אלא לשנות את ממשק ה-ABI, צריך להחיל את שינויי הקוד, את ייצוג ה-ABI ואת רשימת הסמלים על ה-ACK. כדי ש-Lint יסיר את הערך ‎-1 ולא יפגע בתאימות ל-GKI, צריך לבצע את השלבים הבאים:

  1. מעלים את שינויי הקוד אל ה-ACK.

  2. מחכים לקבל Code-Review +2 עבור ערכת התיקונים.

  3. עדכון הייצוג של ABI

  4. ממזגים את השינויים בקוד ואת השינוי בעדכון ה-ABI.

העלאת שינויים בקוד ABI ל-ACK

העדכון של ממשק ה-ABI של ה-ACK תלוי בסוג השינוי שמבצעים.

  • אם שינוי בממשק ABI קשור לתכונה שמשפיעה על בדיקות CTS או VTS, בדרך כלל אפשר להשתמש בקוד מהסתעפות אחרת (Cherry-picking) את השינוי ולשלוח אותו ל-ACK כמו שהוא. לדוגמה:

  • אם שינוי בממשק ABI הוא עבור תכונה שאפשר לשתף עם ACK, אפשר להשתמש בקוד מהסתעפות אחרת (Cherry-picking) את השינוי הזה ולהוסיף אותו ל-ACK כמו שהוא. לדוגמה, השינויים הבאים לא נדרשים לבדיקת CTS או VTS, אבל אפשר לשתף אותם עם ACK:

    • aosp/1250412 הוא שינוי בתכונה של ניהול חום.
    • aosp/1288857 הוא שינוי EXPORT_SYMBOL_GPL.
  • אם שינוי ב-ABI מוסיף תכונה חדשה שלא צריך לכלול ב-ACK, אפשר להוסיף את הסמלים ל-ACK באמצעות stub, כמו שמתואר בקטע הבא.

שימוש ב-stub ל-ACK

קובצי stub צריכים להיות נחוצים רק לשינויים בליבת הקרנל שלא מועילים ל-ACK, כמו שינויים בביצועים ובצריכת החשמל. ברשימה הבאה מפורטות דוגמאות ל-stubs ול-partial cherry-picks ב-ACK ל-GKI.

  • ‫Core-isolate feature stub (aosp/1284493). היכולות ב-ACK לא נחוצות, אבל הסמלים צריכים להיות ב-ACK כדי שהמודולים יוכלו להשתמש בהם.

  • סמל placeholder למודול ספק (aosp/1288860).

  • ‫Cherry-pick של תכונת מעקב אחר אירועים לכל תהליך, רק ב-ABI‏ mm (aosp/1288454). התיקון המקורי נבחר באופן סלקטיבי ל-ACK ואז נחתך כך שיכלול רק את השינויים הנדרשים לפתרון ההבדל ב-ABI עבור task_struct ו-mm_event_count. בנוסף, תיקון האבטחה הזה מעדכן את ה-enum‏ mm_event_type כך שיכיל את החברים הסופיים.

  • הוספנו חלק מהשינויים ב-ABI של מבנה התרמי, שדרשו יותר מהוספה של שדות ה-ABI החדשים.

    • Patch aosp/1255544 resolved ABI differences between the partner kernel and ACK.

    • התיקון aosp/1291018 פתר את הבעיות הפונקציונליות שנמצאו במהלך בדיקת GKI של התיקון הקודם. התיקון כלל אתחול של מבנה הפרמטרים של החיישן כדי לרשום כמה אזורים תרמיים לחיישן יחיד.

  • CONFIG_NL80211_TESTMODE שינויים ב-ABI (aosp/1344321). בתיקון הזה נוספו השינויים הנדרשים במבנה בשביל ABI, ומוודאים שהשדות הנוספים לא גורמים להבדלים פונקציונליים. כך השותפים יכולים לכלול את CONFIG_NL80211_TESTMODE בקרנלים של הייצור שלהם ועדיין לשמור על תאימות ל-GKI.

אכיפת KMI בזמן הריצה

ליבות GKI משתמשות באפשרויות ההגדרה TRIM_UNUSED_KSYMS=y ו-UNUSED_KSYMS_WHITELIST=<union of all symbol lists>, שמגבילות את הסמלים המיוצאים (כמו סמלים שמיוצאים באמצעות EXPORT_SYMBOL_GPL()) לאלה שמופיעים ברשימת סמלים. כל שאר הסמלים לא מיוצאים, והטעינה של מודול שנדרש בו סמל לא מיוצא נדחית. ההגבלה הזו נאכפת משך זמן של תהליך build, ורשומות חסרות מסומנות.

למטרות פיתוח, אפשר להשתמש בגרסת ליבת GKI שלא כוללת חיתוך סמלים (כלומר, אפשר להשתמש בכל הסמלים שמיוצאים בדרך כלל). כדי לאתר את הגרסאות האלה, מחפשים את הגרסאות kernel_debug_aarch64 בכתובת ci.android.com.

אכיפת ה-KMI באמצעות ניהול גרסאות של מודולים

ליבות Generic Kernel Image‏ (GKI) משתמשות בניהול גרסאות של מודולים (CONFIG_MODVERSIONS) כאמצעי נוסף לאכיפת התאימות של KMI בזמן הריצה. אם KMI צפוי של מודול לא תואם ל-vmlinux KMI, יכול להיות שניהול הגרסאות של המודול יגרום לכשלים בבדיקת יתירות מחזורית (CRC) בזמן טעינת המודול. לדוגמה, זוהי שגיאה אופיינית שמתרחשת בזמן טעינת המודול בגלל חוסר התאמה של CRC לסמל module_layout():

init: Loading module /lib/modules/kernel/.../XXX.ko with args ""
XXX: disagrees about version of symbol module_layout
init: Failed to insmod '/lib/modules/kernel/.../XXX.ko' with args ''

שימושים בניהול גרסאות של מודולים

היתרונות של ניהול גרסאות של מודולים:

  • הגרסאות של המודולים מתעדות שינויים בחשיפה של מבנה הנתונים. אם מודולים משנים מבני נתונים אטומים, כלומר מבני נתונים שלא מהווים חלק מממשק KMI, הם יישברו אחרי שינויים עתידיים במבנה.

    לדוגמה, נניח שיש לכם את השדה fwnode ב-struct device. השדה הזה חייב להיות אטום למודולים, כדי שהם לא יוכלו לבצע שינויים בשדות של device->fw_node או להניח הנחות לגבי הגודל שלו.

    עם זאת, אם מודול כולל <linux/fwnode.h> (ישירות או בעקיפין), השדה fwnode ב-struct device כבר לא אטום לו. לאחר מכן, המודול יכול לבצע שינויים ב-device->fwnode->dev או ב-device->fwnode->ops. התרחיש הזה בעייתי מכמה סיבות, שמופיעות בהמשך:

    • היא עלולה לשבור הנחות שקוד הליבה של הקרנל מבצע לגבי מבני הנתונים הפנימיים שלו.

    • אם עדכון עתידי של הליבה ישנה את struct fwnode_handle (סוג הנתונים של fwnode), המודול לא יפעל יותר עם הליבה החדשה. בנוסף, stgdiff לא יציג הבדלים כי המודול שובר את KMI על ידי מניפולציה ישירה של מבני נתונים פנימיים בדרכים שלא ניתן לתעד על ידי בדיקה בלבד של הייצוג הבינארי.

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

הפעלת ניהול גרסאות של מודולים מונעת את כל הבעיות האלה.

בדיקה של אי התאמות ב-CRC בלי להפעיל את המכשיר

הכלי stgdiff משווה בין ליבות ומדווח על אי-התאמות ב-CRC, וגם על הבדלים אחרים ב-ABI.

בנוסף, יצירת ליבה מלאה עם CONFIG_MODVERSIONS מופעלת יוצרת קובץ Module.symvers כחלק מתהליך build הרגיל. בקובץ הזה יש שורה אחת לכל סמל שמיוצא על ידי הליבה (vmlinux) והמודולים. כל שורה מורכבת מערך ה-CRC, שם הסמל, מרחב השמות של הסמל, vmlinux או שם המודול שמייצא את הסמל, וסוג הייצוא (לדוגמה, EXPORT_SYMBOL לעומת EXPORT_SYMBOL_GPL).

אפשר להשוות בין קובצי Module.symvers ב-GKI build לבין ה-build שלכם כדי לבדוק אם יש הבדלים ב-CRC בסמלים שמיוצאים על ידי vmlinux. אם יש הבדל בערך ה-CRC בכל סמל שמיוצא על ידי vmlinux ו והסמל הזה נמצא בשימוש באחד מהמודולים שאתם טוענים במכשיר, המודול לא ייטען.

אם אין לכם את כל ארטיפקטי הבנייה, אבל יש לכם את קובצי vmlinux של ליבת ה-GKI ושל הליבה שלכם, אתם יכולים להריץ את הפקודה הבאה בשתי הליבות ולהשוות את הפלט כדי להשוות את ערכי ה-CRC של סמל ספציפי:

nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>

לדוגמה, הפקודה הבאה בודקת את ערך ה-CRC של הסמל module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

פתרון בעיות של אי התאמה ב-CRC

כדי לפתור בעיה של אי התאמה ב-CRC כשמטעינים מודול:

  1. יוצרים את ליבת ה-GKI ואת ליבת המכשיר באמצעות האפשרות --kbuild_symtypes, כמו שמוצג בפקודה הבאה:

    tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist

    הפקודה הזו יוצרת קובץ .symtypes לכל קובץ .o. פרטים נוספים מופיעים במאמר בנושא KBUILD_SYMTYPES ב-Kleaf.

    ב-Android מגרסה 13 ומגרסאות קודמות, כדי ליצור את ליבת ה-GKI ואת ליבת המכשיר, מוסיפים את KBUILD_SYMTYPES=1 לפני הפקודה שבה משתמשים ליצירת הליבה, כמו בפקודה הבאה:

    KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh

    כשמשתמשים ב-build_abi.sh,, הדגל KBUILD_SYMTYPES=1 מוגדר כבר באופן מרומז.

  2. כדי למצוא את הקובץ .c שאליו מיוצא הסמל עם אי-התאמה ב-CRC, משתמשים בפקודה הבאה:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. לקובץ .c יש קובץ תואם .symtypes ב-GKI, וארטיפקטים של בניית ליבת המכשיר. מאתרים את הקובץ .symtypes באמצעות הפקודות הבאות:

    cd bazel-bin/common/kernel_aarch64/symtypes
    ls -1 kernel/module/version.symtypes

    ב-Android מגרסה 13 ומגרסאות קודמות, כשמשתמשים בסקריפטים מדור קודם, המיקום יהיה כנראה out/$BRANCH/common או out_abi/$BRANCH/common.

    כל קובץ .symtypes הוא קובץ טקסט פשוט שמורכב מסוג וסמל תיאורים:

    • כל שורה היא מהצורה key description, והתיאור יכול להתייחס למפתחות אחרים באותו קובץ.

    • מקשים כמו [s|u|e|t]#foo מתייחסים ל-[struct|union|enum|typedef] foo. לדוגמה:

      t#bool typedef _Bool bool
      
    • מפתחות ללא הקידומת x# הם רק שמות של סמלים. לדוגמה:

      find_module s#module * find_module ( const char * )
      
  4. משווים בין שני הקבצים ומתקנים את כל ההבדלים.

מומלץ ליצור את symtypes באמצעות build ממש לפני השינוי הבעייתי, ואז ליצור אותו שוב אחרי השינוי הבעייתי. שמירת כל הקבצים מאפשרת להשוות אותם בכמות גדולה.

לדוגמה,

diff -r -N -U0 good bad

אחרת, פשוט משווים בין הקבצים הספציפיים שרוצים.

מקרה 1: הבדלים בגלל הרשאות גישה לסוגי נתונים

#include יכול להוסיף הגדרה חדשה של סוג (למשל של struct foo) לקובץ מקור. במקרים כאלה, התיאור שלו בקובץ .symtypes המתאים ישתנה מ-structure_type foo { } ריק להגדרה מלאה.

הפעולה הזו תשפיע על כל ערכי ה-CRC של כל הסמלים בקובץ .symtypes שהתיאורים שלהם תלויים ישירות או בעקיפין בהגדרת הסוג.

לדוגמה, הוספת השורה הבאה לקובץ include/linux/device.h בקרנל גורמת לאי התאמות ב-CRC, שאחת מהן היא עבור module_layout():

 #include <linux/fwnode.h>

השוואה בין module/version.symtypes של הסמל הזה חושפת את ההבדלים הבאים:

 $ diff -u <GKI>/kernel/module/version.symtypes <your kernel>/kernel/module/version.symtypes
  --- <GKI>/kernel/module/version.symtypes
  +++ <your kernel>/kernel/module/version.symtypes
  @@ -334,12 +334,15 @@
  ...
  -s#fwnode_handle structure_type fwnode_handle { }
  +s#fwnode_reference_args structure_type fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
  ...

אם ליבת ה-GKI כוללת את הגדרת הסוג המלאה, אבל היא חסרה בליבה שלכם (מאוד לא סביר), צריך למזג את ליבת Android Common Kernel העדכנית ביותר עם הליבה שלכם כדי להשתמש בבסיס ליבת ה-GKI העדכני ביותר.

ברוב המקרים, ליבת ה-GKI חסרה את הגדרת הסוג המלא ב-.symtypes, אבל בליבה שלכם יש אותה בגלל הנחיות נוספות של #include.

רזולוציה ל-Android מגרסה 16 ואילך

מוודאים שקובץ המקור המושפע כולל את כותרת הייצוב של Android KABI:

#include <linux/android_kabi.h>

לכל סוג מושפע, מוסיפים ANDROID_KABI_DECLONLY(name); בהיקף גלובלי לקובץ המקור המושפע.

לדוגמה, אם symtypes ההבדל היה כזה:

--- good/drivers/android/vendor_hooks.symtypes
+++ bad/drivers/android/vendor_hooks.symtypes
@@ -1051 +1051,2 @@
-s#ubuf_info structure_type ubuf_info { }
+s#ubuf_info structure_type ubuf_info { member pointer_type { const_type { s#ubuf_info_ops } } ops data_member_location(0) , member t#refcount_t refcnt data_member_location(8) , member t#u8 flags data_member_location(12) } byte_size(16)
+s#ubuf_info_ops structure_type ubuf_info_ops { member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } , formal_parameter t#bool ) -> base_type void } complete data_member_location(0) , member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } ) -> base_type int byte_size(4) encoding(5) } link_skb data_member_location(8) } byte_size(16)

הבעיה היא ש-struct ubuf_info מוגדר עכשיו באופן מלא ב-symtypes. הפתרון הוא להוסיף שורה ל-drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

ההוראה הזו אומרת ל-gendwarfksyms להתייחס לסוג שצוין כלא מוגדר בקובץ.

אפשרות מורכבת יותר היא ש-#include עצמו נמצא בקובץ כותרת. במקרה כזה, יכול להיות שתצטרכו להפיץ קבוצות שונות של קריאות מאקרו על פני קובצי מקור שמושכים באופן עקיף הגדרות נוספות של סוגים, כי יכול להיות שחלק מהם כבר כללו חלק מההגדרות האלה.ANDROID_KABI_DECLONLY

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

רזולוציה ב-Android מגרסה 15 ומטה

לרוב, הפתרון הוא פשוט להסתיר את #include החדש מgenksyms.

#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif

אם לא, כדי לזהות את #include שגורם להבדל, פועלים לפי השלבים הבאים:

  1. פותחים את קובץ הכותרת שמגדיר את הסמל או את סוג הנתונים שיש ביניהם את ההבדל הזה. לדוגמה, עריכה של include/linux/fwnode.h בשורה struct fwnode_handle.

  2. מוסיפים את הקוד הבא בחלק העליון של קובץ הכותרת:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. בקובץ .c של המודול שבו יש אי התאמה ב-CRC, מוסיפים את השורה הבאה כשורה הראשונה לפני כל השורות של #include.

    #define CRC_CATCH 1
    
  4. מהדרים את המודול. שגיאת זמן ה-build שמתקבלת מציגה את השרשרת של קובץ הכותרת #include שהובילה לחוסר ההתאמה הזה ב-CRC. לדוגמה:

    In file included from .../drivers/clk/XXX.c:16:`
    In file included from .../include/linux/of_device.h:5:
    In file included from .../include/linux/cpu.h:17:
    In file included from .../include/linux/node.h:18:
    .../include/linux/device.h:16:2: error: "Included from here"
    #error "Included from here"
    

    אחד מהקישורים בשרשרת הזו של #include נובע משינוי שבוצע בליבת המערכת, שחסר בליבת GKI.

מקרה 2: הבדלים בגלל שינויים בסוג הנתונים

אם אי ההתאמה של ה-CRC לסמל או לסוג נתונים לא נובעת מהבדל בנראות, היא נובעת משינויים בפועל (הוספות, הסרות או שינויים) בסוג הנתונים עצמו.

לדוגמה, ביצוע השינוי הבא בקרנל גורם לכמה אי התאמות של CRC, כי הרבה סמלים מושפעים באופן עקיף מסוג השינוי הזה:

diff --git a/include/linux/iommu.h b/include/linux/iommu.h
  --- a/include/linux/iommu.h
  +++ b/include/linux/iommu.h
  @@ -259,7 +259,7 @@ struct iommu_ops {
     void (*iotlb_sync)(struct iommu_domain *domain);
     phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
     phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
  -        dma_addr_t iova);
  +        dma_addr_t iova, unsigned long trans_flag);
     int (*add_device)(struct device *dev);
     void (*remove_device)(struct device *dev);
     struct iommu_group *(*device_group)(struct device *dev);

אי-התאמה אחת של CRC היא עבור devm_of_platform_populate().

אם משווים את הקבצים .symtypes של הסמל הזה, הם יכולים להיראות כך:

 $ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
  --- <GKI>/drivers/of/platform.symtypes
  +++ <your kernel>/drivers/of/platform.symtypes
  @@ -399,7 +399,7 @@
  ...
  -s#iommu_ops structure_type iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
    ( * add_device ) ( s#device * ) ; ...
  +s#iommu_ops structure_type iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...

כדי לזהות את הסוג שהשתנה, פועלים לפי השלבים הבאים:

  1. מחפשים את ההגדרה של הסמל בקוד המקור (בדרך כלל בקובצי .h).

    • כדי לראות את ההבדלים בסמלים בין הליבה שלכם לבין ליבת GKI, מריצים את הפקודה הבאה כדי למצוא את הקומיט:
    git blame
    • במקרה של סמלים שנמחקו (אם סמל נמחק בעץ אחד ורוצים למחוק אותו גם בעץ השני), צריך למצוא את השינוי שגרם למחיקת השורה. משתמשים בפקודה הבאה בעץ שבו השורה נמחקה:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. בודקים את רשימת הקומיטים שהוחזרה כדי לאתר את השינוי או המחיקה. הקומט הראשון הוא כנראה זה שחיפשתם. אם לא, עוברים ברשימה עד שמוצאים את הקומיט.

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

עדיפות לשימוש בריווח הפנימי הקיים

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

פקודת המאקרו המתועדת בעצמה ANDROID_KABI_RESERVE שומרת מקום (מיושר) בשווי u64. הוא משמש במקום הצהרת חבר.

לדוגמה:

struct data {
        u64 handle;
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
};

אפשר להשתמש ב-Padding בלי להשפיע על ערכי ה-CRC של הסמלים, באמצעות ANDROID_KABI_USE (או ANDROID_KABI_USE2 או וריאציות אחרות שאפשר להגדיר).

החבר sekret זמין כאילו הוא הוגדר ישירות, אבל המאקרו מתרחב למעשה לחבר איחוד אנונימי שמכיל את sekret וגם דברים שמשמשים את gendwarfksyms לשמירה על יציבות symtype.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
רזולוציה ל-Android מגרסה 16 ואילך

ערכי ה-CRC מחושבים על ידי gendwarfksyms שמשתמש במידע על תוצאות ניפוי הבאגים של DWARF, ולכן תומך בסוגים של C ו-Rust. הרזולוציה משתנה בהתאם לסוג השינוי. הנה מספר דוגמאות.

מعدادים חדשים או מعدادים ששונו

לפעמים נוספים ערכים חדשים לרשימת הערכים, ולפעמים גם ערך של MAX או ערך דומה מושפע. השינויים האלה בטוחים אם הם לא 'חורגים' מ-GKI או אם אנחנו יכולים להיות בטוחים שמודולים של ספקים לא יכולים להתייחס לערכים שלהם.

לדוגמה:

 enum outcome {
       SUCCESS,
       FAILURE,
       RETRY,
+      TRY_HARDER,
       OUTCOME_LIMIT
 };

אפשר להסתיר את התוספת של TRY_HARDER ואת השינוי ב-OUTCOME_LIMIT מחישוב ה-CRC באמצעות הפעלות מאקרו בהיקף גלובלי:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

כדי לשפר את הקריאות, כדאי למקם אותם מיד אחרי ההגדרה enum.

חבר חדש במבנה שתופס חור קיים

בגלל היישור, יהיו בייטים לא בשימוש בין urgent ל-scratch.

        void *data;
        bool urgent;
+       bool retry;
        void *scratch;

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

הפעולה הזו תסתיר אותו מחישוב ה-CRC:

        void *data;
        bool urgent;
+       ANDROID_KABI_IGNORE(1, bool retry);
        void *scratch_space;

החבר retry זמין כאילו הוא הוגדר ישירות, אבל המאקרו מתרחב למעשה לחבר איחוד אנונימי שמכיל את retry וגם דברים שמשמשים את gendwarfksyms לשמירה על יציבות symtype.

הרחבת מבנה עם חברים חדשים

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

struct data {
        u64 handle;
        u64 counter;
        ANDROID_KABI_IGNORE(1, void *sekret);
};

ANDROID_KABI_BYTE_SIZE(data, 16);

כדי לשפר את הקריאות, כדאי להציב את הקוד הזה מיד אחרי ההגדרה של struct.

כל השינויים האחרים בסוג או בסוג של סמל

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

במקרים כאלה, אפשר לספק את התיאור המקורי symtypes של סוג או סמל עם הפעלה של ANDROID_KABI_TYPE_STRING בהיקף גלובלי.

struct data {
        /* extensive changes */
};

ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");

כדי שיהיה קל יותר לקרוא את הקוד, כדאי להוסיף את השורה הזו מיד אחרי ההגדרה של הסוג או הסמל.

רזולוציה ב-Android מגרסה 15 ומטה

צריך להסתיר מ-genksyms שינויים בסוג ובסמל. אפשר לעשות זאת באמצעות שליטה בעיבוד המקדים עם __GENKSYMS__.

אפשר לבטא כך טרנספורמציות שרירותיות של קוד.

לדוגמה, כדי להסתיר חבר חדש שתופס מקום במבנה קיים:

struct parcel {
        void *data;
        bool urgent;
#ifndef __GENKSYMS__
        bool retry;
#endif
        void *scratch_space;
};