אתם יכולים להשתמש בכלים לניטור ממשק בינארי של אפליקציה (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 הנפוצה ולכמה ליבות ספציפיות למכשירים. הם מאמתים שנעשה שימוש בשרשרת הכלים הנכונה כשמבצעים build של הפצה של ליבה לצורך ניתוח.
רשימות סמלים
ממשק 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
שבוצעו בו שינויים עם הקובץ שחולץ מארטיפקטים של build, ומגדיר תווית 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, בדרך כלל אפשר לבחור את השינוי כדי לאשר אותו כמו שהוא. לדוגמה:
- נדרש aosp/1289677 כדי שהאודיו יפעל.
- aosp/1295945 נדרש כדי ש-USB יפעל.
אם שינוי ב-ABI הוא עבור תכונה שאפשר לשתף עם ACK, אפשר לבחור את השינוי הזה ולשתף אותו עם ACK כמו שהוא. לדוגמה, השינויים הבאים לא נדרשים לבדיקת CTS או VTS, אבל אפשר לשתף אותם עם ACK:
- aosp/1250412 הוא שינוי בתכונה של ניהול הטמפרטורה.
- aosp/1288857
הוא שינוי
EXPORT_SYMBOL_GPL
.
אם שינוי ב-ABI מוסיף תכונה חדשה שלא צריך לכלול ב-ACK, אפשר להוסיף את הסמלים ל-ACK באמצעות stub, כמו שמתואר בקטע הבא.
שימוש ב-stub ל-ACK
צריך להשתמש ב-Stubs רק לשינויים בליבת הקרנל שלא מועילים ל-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()
) לאלה שמופיעים ברשימת סמלים. כל שאר הסמלים לא מיוצאים, והטעינה של מודול שנדרש בו סמל לא מיוצא נדחית. ההגבלה הזו נאכפת בזמן הבנייה, ורשומות חסרות מסומנות.
למטרות פיתוח, אפשר להשתמש בגרסת ליבת 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
כחלק מתהליך הבנייה הרגיל. בקובץ הזה יש שורה אחת לכל סמל שמיוצא על ידי ליבת המערכת (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 כשמעלים מודול:
מבצעים build לליבת 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 ממש לפני השינוי הבעייתי ואז עם השינוי הבעייתי. שמירת כל הקבצים מאפשרת להשוות אותם בכמות גדולה.
לדוגמה,
for f in $(find good bad -name '*.symtypes' | sed -r 's;^(good|bad)/;;' | LANG=C sort -u); do
diff -N -U0 --label good/"$f" --label bad/"$f" <(LANG=C sort good/"$f") <(LANG=C sort bad/"$f")
done
אחרת, פשוט משווים בין הקבצים הספציפיים שמעניינים אתכם.
מקרה 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 כוללים ריווח כדי לאפשר את ההרחבה שלהם בלי לשבור מודולים קיימים של ספקים. אם קומיט במעלה הזרם (לדוגמה) מוסיף חבר למבנה כזה, יכול להיות שאפשר יהיה לשנות אותו כך שישתמש בחלק מהריפוד במקום זאת. השינוי הזה לא נכלל בחישוב של CRC.
מאקרו מתועד עצמית ותקני 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;
};