אפשר להשתמש בכלי למעקב אחרי ממשק בינארי של אפליקציה (ABI), שזמין ב-Android מגרסה 11 ואילך, כדי לייצב את ה-ABI בתוך הליבה של ליבות Android. הכלי אוסף ומשויך בין ייצוגים של ABI מקובצי הבינארי הקיימים של הליבה (vmlinux
+ מודולי GKI). הייצוגים האלה של ABI הם קובצי .stg
ורשימות הסמלים. הממשק שבו הייצוג מספק תצוגה נקרא Kernel Module Interface (KMI). אפשר להשתמש בכלים כדי לעקוב אחרי השינויים ב-KMI ולצמצם אותם.
כלי המעקב אחרי ABI פותחו ב-AOSP, והם משתמשים ב-STG (או ב-libabigail
ב-Android 13 ומטה) כדי ליצור ולשפר את הייצוגים.
בדף הזה מתוארים הכלים, תהליך האיסוף והניתוח של ייצוגי ABI והשימוש בייצוגים כאלה כדי לספק יציבות ל-ABI בליבה. בדף הזה מופיע גם מידע על הוספת שינויים לליבת Android.
תהליך
ניתוח ה-ABI של הליבה כולל כמה שלבים, שרובם אפשר לבצע באופן אוטומטי:
- יצירת הליבה והייצוג שלה ב-ABI.
- ניתוח ההבדלים ב-ABI בין ה-build לבין קובץ העזר
- עדכון הייצוג של 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, הם נשמרים כיציבים ואי אפשר להסיר אותם מרשימת הסמלים או לשנות אותם אחרי שההסתעפות קופאת.
לכל הסתעפות של ליבה של KMI ב-Android Common Kernel (ACK) יש קבוצה משלה של רשימות סמלים. לא מתבצעת שום ניסיון לספק יציבות 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 למכשיר לפי ההוראות במאמר איך עובדים עם רשימות סמלים. שותפים רבים שולחים רשימת סמלים אחת לכל ACK, אבל זו לא דרישת חובה. אם זה עוזר בתחזוקה, אפשר לשלוח כמה רשימות סמלים.
הרחבת ה-KMI
סמלי ה-KMI והמבנים הקשורים נשארים יציבים (כלומר, אי אפשר לקבל שינויים שמפריעים לממשקי API יציבים בליבה עם KMI קפוא), אבל הליבה של GKI נשארת פתוחה להרחבות, כך שמכשירים שיושקו בהמשך השנה לא יצטרכו להגדיר את כל יחסי התלות שלהם לפני שה-KMI יקפא. כדי להרחיב את ה-KMI, אפשר להוסיף ל-KMI סמלים חדשים לפונקציות ליבה חדשות או קיימות שיוצאו, גם אם ה-KMI קפוא. יכול להיות שנקבל גם תיקוני ליבה חדשים אם הם לא משבשים את KMI.
מידע על שגיאות ב-KMI
לליבת לינוקס יש מקורות, והקובצי הבינארי נוצרים מהמקורות האלה.
ההסתעפויות של הליבה שנמצאות במעקב אחרי ABI כוללות ייצוג ABI של ABI הנוכחי של GKI (בצורת קובץ .stg
). אחרי היצירה של הקבצים הבינאריים (vmlinux
, Image
וכל המודולים של GKI), אפשר לחלץ מהם ייצוג ABI. כל שינוי שמתבצע בקובץ המקור של הליבה יכול להשפיע על הקבצים הבינאריים, ובתגובה גם על .stg
שחולץ. הכלי לניתוח AbiAnalyzer
משווה בין הקובץ .stg
שהוענק ל-commit לבין הקובץ שחולץ מ-artifacts של ה-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 שזוהו בזמן ה-build
הסיבה השכיחה ביותר לשגיאות היא כאשר הנהג משתמש בסמל חדש מהליבה שלא נמצא באף אחת מרשימות הסמלים.
אם הסמל לא נכלל ברשימת הסמלים, קודם צריך לוודא שהוא מיוצא באמצעות EXPORT_SYMBOL_GPL(symbol_name)
ואז לעדכן את רשימת הסמלים ואת הייצוג של ABI. לדוגמה, השינויים הבאים מוסיפים את התכונה החדשה Incremental FS להסתעפות android-12-5.10
, כולל עדכון של רשימת הסמלים והייצוג של ABI.
- דוגמה לשינוי בתכונה מופיעה ב-aosp/1345659.
- דוגמה לרשימת סמלים מופיעה ב-aosp/1346742.
- דוגמה לשינוי בייצוג של ABI מופיעה ב-aosp/1349377.
אם הסמל מיוצא (על ידכם או שהוא יוצא בעבר) אבל אף נהג אחר לא משתמש בו, ייתכן שתופיע שגיאת build דומה לזו הבאה.
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.
שינוי שדות של מבנה (struct) אם שינוי משנה את ה-ABI של תכונת ניפוי באגים, מוסיפים
#ifdef
סביב השדות (במבנים ובהפניות למקור) ומוודאים שה-CONFIG
שמשמש את ה-#ifdef
מושבת ב-defconfig של הייצור וב-gki_defconfig
. דוגמה לאופן שבו אפשר להוסיף הגדרת debug ל-struct בלי לשבור את ה-ABI מופיעה בחבילת התיקונים הזו.פיתוח מחדש של תכונות כדי לא לשנות את הליבה של הליבה. אם צריך להוסיף תכונות חדשות ל-ACK כדי לתמוך במודולים של השותפים, נסו לבצע ניתוח מחדש של החלק של ABI בשינוי כדי להימנע משינוי של ABI הליבה. דוגמה לשימוש ב-ABI הקיים של הליבה כדי להוסיף יכולות נוספות בלי לשנות את ה-ABI של הליבה מופיעה ב-aosp/1312213.
תיקון ABI שגוי ב-Android Gerrit
אם לא שברתם את ה-ABI של הליבה בכוונה, עליכם לחקור את הנושא לפי ההנחיות שמספקות הכלים למעקב אחר ABI. הסיבות הנפוצות ביותר לשיבושים הן שינויים במבנה הנתונים וב-CRC של הסמל המשויך, או שינויים באפשרויות התצורה שמובילים לאחד מהגורמים שצוינו למעלה. מתחילים לטפל בבעיות שהכלי מצא.
אפשר לשחזר את הממצאים של ABI באופן מקומי. תוכלו לקרוא על כך במאמר יצירת הליבה והייצוג שלה ב-ABI.
מידע על תוויות Lint-1
אם מעלים שינויים להסתעפות שמכילה KMI קפוא או סופי, השינויים חייבים לעבור את הבדיקה AbiAnalyzer
כדי לוודא שהם לא משפיעים על ה-ABI היציב באופן לא תואם. במהלך התהליך, הפקודה AbiAnalyzer
מחפשת את דוח ה-ABI שנוצר במהלך ה-build (build מורחב שמבצע את ה-build הרגיל ואז כמה שלבים של חילוץ והשוואה של ABI).
אם AbiAnalyzer
מוצא דוח לא ריק, הוא מגדיר את התווית Lint-1 והשינוי חסום לשליחה עד לפתרון הבעיה, עד שקבוצת התיקונים תקבל את התווית Lint+1.
עדכון ה-ABI של הליבה
אם אי אפשר להימנע משינוי ה-ABI, צריך להחיל את שינויי הקוד, את ייצוג ה-ABI ואת רשימת הסמלים על ה-ACK. כדי לגרום ל-Lint להסיר את הערך -1 ולא לשבור את התאימות ל-GKI, מבצעים את הפעולות הבאות:
ממתינים לקבלת אישור Code-Review +2 על ערכת התיקונים.
משלבים את השינויים בקוד עם השינוי בעדכון ה-ABI.
העלאת שינויים בקוד ABI לאישור
עדכון ה-ACK ABI תלוי בסוג השינוי שמתבצע.
אם שינוי ב-ABI קשור לתכונה שמשפיעה על בדיקות CTS או VTS, בדרך כלל אפשר לבחור את השינוי ולשלוח אותו לאישור כפי שהוא. לדוגמה:
- כדי שהאודיו יפעל, צריך את aosp/1289677.
- aosp/1295945 היא הגרסה הנדרשת כדי ש-USB יפעל.
אם שינוי ב-ABI הוא בתכונה שאפשר לשתף עם ACK, אפשר לבחור את השינוי הזה ולשלוח אותו ל-ACK כפי שהוא. לדוגמה, השינויים הבאים לא נדרשים לבדיקה של CTS או VTS, אבל אפשר לשתף אותם עם ACK:
- aosp/1250412 הוא שינוי בתכונה התרמית.
- aosp/1288857 הוא שינוי ב-
EXPORT_SYMBOL_GPL
.
אם שינוי ב-ABI כולל תכונה חדשה שלא צריך לכלול באישור, אפשר להוסיף את הסמלים לאישור באמצעות stub, כפי שמתואר בקטע הבא.
שימוש ב-stubs ל-ACK
צריך להשתמש ב-stubs רק בשינויים בליבה של הליבה שלא תורמים ל-ACK, כמו שינויים בביצועים ובצריכת החשמל. ברשימה הבאה מפורטות דוגמאות ל-stubs ולבחירות חלקיות ב-ACK ל-GKI.
גרסה ראשונית של תכונת Core-isolate (aosp/1284493). אין צורך ביכולות ב-ACK, אבל הסמלים צריכים להופיע ב-ACK כדי שהמודולים יוכלו להשתמש בהם.
סמל placeholder למודול של הספק (aosp/1288860).
בחירה ספציפית ל-ABI בלבד של תכונת מעקב האירועים
mm
לכל תהליך (aosp/1288454). התיקון המקורי נבחר ל-ACK ולאחר מכן צומצם כך שיכלול רק את השינויים הנדרשים כדי לפתור את ההבדל ב-ABI ביןtask_struct
ל-mm_event_count
. התיקון הזה גם מעדכן את המאפייןmm_event_type
enum כך שיכיל את המאפיינים הסופיים.רשימה חלקית של שינויים ב-ABI של מבנה תרמי שנדרשו יותר מאשר הוספת שדות ה-ABI החדשים.
התיקון aosp/1255544 פותר את ההבדלים ב-ABI בין הליבה של השותף לבין 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, ורשומות חסרות מסומנות.
למטרות פיתוח, אפשר להשתמש ב-build של ליבה של GKI שלא כולל חיתוך סמלים (כלומר, אפשר להשתמש בכל הסמלים שמיוצאים בדרך כלל). כדי לאתר את הגרסאות האלה, מחפשים את הגרסאות של kernel_debug_aarch64
בכתובת ci.android.com.
אכיפת ה-KMI באמצעות ניהול גרסאות של מודולים
ליבות של תמונות הליבה הגנריות (GKI) משתמשות בניהול גרסאות של מודולים (CONFIG_MODVERSIONS
) כמדד נוסף לאכיפת תאימות ל-KMI בסביבת זמן הריצה. ניהול גרסאות של מודולים עלול לגרום לכשלים בבדיקת יתירות מחזורית (CRC) בזמן טעינת המודול, אם ה-KMI הצפוי של המודול לא תואם ל-KMI של vmlinux
. לדוגמה, זוהי שגיאה אופיינית שמתרחשת בזמן טעינת המודול בגלל חוסר התאמה ב-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.
בנוסף, build מלא של הליבה עם הפעלת CONFIG_MODVERSIONS
יוצר קובץ Module.symvers
כחלק מתהליך ה-build הרגיל. בקובץ הזה יש שורה אחת לכל סמל שיוצא על ידי הליבה (vmlinux
) והמודולים. כל שורה מורכבת מערך ה-CRC, שם הסמל, מרחב השמות של הסמל, vmlinux
או שם המודול שמייצא את הסמל וסוג הייצוא (לדוגמה, EXPORT_SYMBOL
לעומת EXPORT_SYMBOL_GPL
).
אפשר להשוות בין קובצי Module.symvers
של ה-build של GKI לבין ה-build שלכם כדי לבדוק אם יש הבדלים ב-CRC בסימנים שיוצאו על ידי vmlinux
. אם יש הבדל בערך ה-CRC של סמל כלשהו שיוצא על ידי vmlinux
וגם הסמל הזה משמש את אחד מהמודולים שאתם מעלים למכשיר, המודול לא נטען.
אם אין לכם את כל ארטיפקטי ה-build, אבל יש לכם את הקבצים 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, וגם את ארטיפקטי ה-build של ליבה המכשיר. מאתרים את הקובץ.symtypes
באמצעות הפקודות הבאות:cd bazel-bin/common/kernel_aarch64/symtypes ls -1 kernel/module/version.symtypes
ב-Android מגרסה 13 וגרסאות קודמות, כשמשתמשים בסקריפטים הקודמים ל-build, סביר להניח שהמיקום יהיה
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 עם הליבה שלכם כדי להשתמש בבסיס הליבה העדכני ביותר של GKI.
ברוב המקרים, הגדרת הטיפוס המלאה ב-.symtypes
חסרה בליבה של GKI, אבל היא קיימת בליבה שלכם בגלל הנחיות #include
נוספות.
רזולוציה ל-Android מגרסה 16 ואילך
מוודאים שקובץ המקור המושפע כולל את הכותרת של Android KABI stabilization:
#include <linux/android_kabi.h>
לכל סוג מושפע, מוסיפים את ANDROID_KABI_DECLONLY(name);
ברמת ה-global לקובץ המקור המושפע.
לדוגמה, אם ההבדל ב-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
.מוסיפים את הקוד הבא בחלק העליון של קובץ ה-header:
#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 תצטרך לעבור בדיקה של בטיחות, ואם צריך, תוכלו לתעד הפסקה מותרת.
עדיפות לשימוש ב-padding קיים
חלק מהמבנים ב-GKI מרופדים כדי לאפשר את הרחבת שלהם בלי לשבור מודולים קיימים של ספקים. אם השמירה ב-upstream (לדוגמה) מוסיפה חבר למבנה כזה, יכול להיות שאפשר לשנות אותה כך שתשתמש בחלק מהמילוי במקום זאת. לאחר מכן, השינוי הזה מוסתר מהחישוב של 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
משתמש בהם כדי לשמור על יציבות של סוג הסימן.
struct data {
u64 handle;
ANDROID_KABI_USE(1, void *sekret);
ANDROID_KABI_RESERVE(2);
};
רזולוציה ל-Android מגרסה 16 ואילך
gendwarfksyms
מחשב את ה-CRCs באמצעות מידע של ניפוי באגים ב-DWARF, כך שיש תמיכה גם בסוגי C וגם בסוגי Rust. הפתרון משתנה בהתאם לסוג השינוי. הנה מספר דוגמאות.
מונים חדשים או משתנים
לפעמים מתווספים מונים חדשים, ולפעמים מושפע גם ערך של MAX
או ערך דומה של מונה. השינויים האלה בטוחים אם הם לא 'בורחים' מ-GKI, או אם אנחנו יכולים להיות בטוחים שמודוללי הספקים לא יכולים להתייחס לערכי השינויים האלה.
לדוגמה:
enum outcome {
SUCCESS,
FAILURE,
RETRY,
+ TRY_HARDER,
OUTCOME_LIMIT
};
אפשר להסתיר את ההוספה של TRY_HARDER
ואת השינוי ב-OUTCOME_LIMIT
מחישוב ה-CRC באמצעות קריאות למאקרו ברמת ה-global:
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
משתמש בהם כדי לשמור על יציבות של סוג הסימן.
הרחבת מבנה עם חברים חדשים
לפעמים חברים נוספים בסוף המבנה. הפעולה הזו לא משפיעה על הזזות של חברים קיימים או על משתמשים קיימים במבנה שרק ניגשים אליו באמצעות מצביע. גודל המבנה משפיע על ה-CRC שלו, וניתן למנוע שינויים ב-CRC באמצעות קריאה נוספת למאקרו ברמת ה-global, באופן הבא:
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
ברמת ה-global.
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;
};