אתם יכולים להשתמש בכלים למעקב אחרי ממשק בינארי של אפליקציה (ABI), שזמינים ב-Android מגרסה 11 ואילך, כדי לייצב את ה-ABI בתוך הליבה של ליבות Android. הכלי אוסף ומבצע השוואה בין ייצוגים של ABI
מקבצים בינאריים קיימים של ליבת מערכת ההפעלה (vmlinux+ מודולים של GKI). הייצוגים האלה של ABI הם קובצי .stg ורשימות הסמלים. הממשק שבו הייצוג מספק תצוגה נקרא ממשק מודול הליבה (KMI). אפשר להשתמש בכלים כדי לעקוב אחרי שינויים ב-KMI ולצמצם אותם.
הכלים למעקב אחרי ABI פותחו ב-AOSP והם משתמשים ב-STG (או ב-libabigail ב-Android מגרסה 13 ומטה) כדי ליצור השוואות בין ייצוגים.
בדף הזה מתוארים כלי הפיתוח, תהליך האיסוף והניתוח של ייצוגי ABI, והשימוש בייצוגים כאלה כדי לספק יציבות ל-ABI בתוך הליבה. בדף הזה יש גם מידע על שליחת שינויים לליבות של Android.
עיבוד
ניתוח ממשק ה-ABI של ליבת המערכת כולל כמה שלבים, שרובם יכולים להתבצע באופן אוטומטי:
- בונים את הליבה ואת ייצוג ה-ABI שלה.
- ניתוח ההבדלים בממשק ABI בין הגרסה לבין קובץ עזר.
- מעדכנים את ייצוג ה-ABI (אם נדרש).
- עבודה עם רשימות של סמלים.
ההוראות הבאות מתאימות לכל ליבת מערכת שאפשר לבנות באמצעות ערכת כלים נתמכת (כמו ערכת הכלים 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. כדי להבין מה הגישה הכי מתאימה למצב שלכם, תוכלו להיעזר בתרשים הבא.
איור 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, צריך לבצע את השלבים הבאים:
מחכים לקבל Code-Review +2 עבור ערכת התיקונים.
ממזגים את השינויים בקוד ואת השינוי בעדכון ה-ABI.
העלאת שינויים בקוד ABI ל-ACK
העדכון של ממשק ה-ABI של ה-ACK תלוי בסוג השינוי שמבצעים.
אם שינוי בממשק ABI קשור לתכונה שמשפיעה על בדיקות CTS או VTS, בדרך כלל אפשר להשתמש בקוד מהסתעפות אחרת (Cherry-picking) את השינוי ולשלוח אותו ל-ACK כמו שהוא. לדוגמה:
- נדרש aosp/1289677 כדי שהאודיו יפעל.
- aosp/1295945 נדרש כדי ש-USB יפעל.
אם שינוי בממשק 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. בנוסף, תיקון האבטחה הזה מעדכן את ה-enummm_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 כשמטעינים מודול:
יוצרים את ליבת ה-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מוגדר כבר באופן מרומז.כדי למצוא את הקובץ
.cשאליו מיוצא הסמל עם אי-התאמה ב-CRC, משתמשים בפקודה הבאה:git -C common grep EXPORT_SYMBOL.*module_layout kernel/module/version.c:EXPORT_SYMBOL(module_layout);לקובץ
.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 * )
משווים בין שני הקבצים ומתקנים את כל ההבדלים.
מומלץ ליצור את 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 שגורם להבדל, פועלים לפי השלבים הבאים:
פותחים את קובץ הכותרת שמגדיר את הסמל או את סוג הנתונים שיש ביניהם את ההבדל הזה. לדוגמה, עריכה של
include/linux/fwnode.hבשורהstruct fwnode_handle.מוסיפים את הקוד הבא בחלק העליון של קובץ הכותרת:
#ifdef CRC_CATCH #error "Included from here" #endifבקובץ
.cשל המודול שבו יש אי התאמה ב-CRC, מוסיפים את השורה הבאה כשורה הראשונה לפני כל השורות של#include.#define CRC_CATCH 1מהדרים את המודול. שגיאת זמן ה-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 * ) ; ...
כדי לזהות את הסוג שהשתנה, פועלים לפי השלבים הבאים:
מחפשים את ההגדרה של הסמל בקוד המקור (בדרך כלל בקובצי
.h).- כדי לראות את ההבדלים בסמלים בין הליבה שלכם לבין ליבת GKI, מריצים את הפקודה הבאה כדי למצוא את הקומיט:
git blame- במקרה של סמלים שנמחקו (אם סמל נמחק בעץ אחד ורוצים למחוק אותו גם בעץ השני), צריך למצוא את השינוי שגרם למחיקת השורה. משתמשים בפקודה הבאה בעץ שבו השורה נמחקה:
git log -S "copy paste of deleted line/word" -- <file where it was deleted>בודקים את רשימת הקומיטים שהוחזרה כדי לאתר את השינוי או המחיקה. הקומט הראשון הוא כנראה זה שחיפשתם. אם לא, עוברים ברשימה עד שמוצאים את הקומיט.
אחרי שמזהים את הקומיט, אפשר לבטל אותו בקרנל או לעדכן אותו כדי למנוע את שינוי ה-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;
};