מעקב באמצעות ממשק ABI בליבה (kernel) של Android

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

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

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

תהליך

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

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

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

רשימות סמלים

ה-KMI לא כולל את כל הסמלים בליבה, או אפילו את כל 30,000 הסמלים הייצואיים. במקום זאת, הסמלים שבהם אפשר להשתמש במודולים של ספקים רשומים בצורה מפורשת בקבוצה של קובצי רשימות סמלים שמנוהלים באופן ציבורי ברמה הבסיסית (root) של עץ הליבה. איחוד כל הסמלים בכל הקבצים של רשימת הסמלים מגדיר את קבוצת סמלי ה-KMI שנשמרת כיציבה. קובץ רשימת סמלים לדוגמה הוא abi_gki_aarch64_db845c, שמצהיר על הסמלים הנדרשים ל-DragonBoard 845c.

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

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

כלי ABI משתמשים ברשימות סמלים של KMI כדי להגביל את הממשקים שצריך לעקוב אחריהם כדי לוודא שהם יציבים. רשימת הסמלים הראשית מכילה את הסמלים הנדרשים למודול הליבה של GKI. הספקים נדרשים לשלוח ולעדכן רשימות סמלים נוספות כדי להבטיח שהממשקים שהם מסתמכים עליהם ימשיכו להיות תואמים ל-ABI. לדוגמה, כדי לראות רשימה של רשימות סמלים עבור android13-5.15, אפשר לעיין במאמר https://android.googlesource.com/kernel/common/+/refs/heads/android13-5.15/android.

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

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

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

הרחבת ה-KMI

סמלי ה-KMI והמבנים הקשורים נשארים יציבים (כלומר, אי אפשר לקבל שינויים שמפריעים לממשקי API יציבים בליבה עם KMI קפוא), אבל הליבה של GKI נשארת פתוחה להרחבות, כך שמכשירים שיושקו בהמשך השנה לא יצטרכו להגדיר את כל יחסי התלות שלהם לפני שה-KMI יקפא. כדי להרחיב את ה-KMI, תוכלו להוסיף ל-KMI סמלים חדשים לפונקציות ליבה (kernel) חדשות או קיימות, גם אם ה-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

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

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

  • דוגמה לשינוי בתכונה מופיעה ב-aosp/1345659.
  • דוגמה לרשימת סמלים מופיעה ב-aosp/1346742.
  • דוגמה לשינוי ב-XML של 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). דוגמה לעדכון של קובץ ה-XML של ה-ABI ורשימת הסמלים ב-ACK מופיעה ב-aosp/1367601.

פתרון בעיות ב-ABI של הליבה

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

תרשים זרימה של תקלות ABI

איור 1. פתרון של שגיאות ABI

ארגון קוד מחדש כדי למנוע שינויים ב-ABI

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

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

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

תיקון ממשק ABI לא תקין ב-Android Gerrit

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

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

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

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

אם AbiAnalyzer מוצא דוח לא ריק, הוא מגדיר את התווית Lint-1 והשינוי חסום לשליחה עד לפתרון הבעיה, עד שקבוצת התיקונים תקבל את התווית Lint+1.

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

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

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

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

  3. מעדכנים את ייצוג ה-ABI של ההפניה.

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

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

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

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

    • יש צורך ב-aosp/1289677 כדי שהאודיו יפעל.
    • aosp/1295945 היא הגרסה הנדרשת כדי ש-USB יפעל.
  • אם שינוי ב-ABI הוא בתכונה שאפשר לשתף עם ACK, אפשר לבחור את השינוי הזה ולשלוח אותו ל-ACK כפי שהוא. לדוגמה, השינויים הבאים לא נדרשים לבדיקה של CTS או VTS, אבל אפשר לשתף אותם עם ACK:

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

שימוש ב-stubs ל-ACK

צריך להשתמש ב-stubs רק בשינויים בליבה של הליבה שלא תורמים ל-ACK, כמו שינויים בביצועים ובצריכת החשמל. ברשימה הבאה מפורטות דוגמאות לקטעי קוד חלולים ולבחירות חלקיות ב-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 בזמן טעינת מודול:

  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 מיוצאים, באמצעות הפקודה הבאה:

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

    cd out/$BRANCH/common && ls -1 kernel/module.*
    kernel/module.o
    kernel/module.o.symversions
    kernel/module.symtypes

    אלה המאפיינים של קובץ .c:

    • הפורמט של קובץ .c הוא שורה אחת (שעשויה להיות ארוכה מאוד) לכל סמל.

    • [s|u|e|etc]# בתחילת השורה פירושו שהסמל הוא מסוג נתונים [struct|union|enum|etc]. לדוגמה:

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

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

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

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

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

 #include <linux/fwnode.h>

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

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

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

ברוב המקרים, הערך של הליבה של GKI הוא UNKNOWN, אבל הליבה שלכם מכילה את הפרטים הפנימיים של הסמל בגלל שינויים שבוצעו בליבה. הסיבה לכך היא שאחד מהקבצים בליבה הוסיף #include שלא קיים בליבה של GKI.

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

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

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

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

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

    #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.

  5. מזהים את השינוי, מבטלים אותו בליבה או מעלים אותו ל-ACK ומבצעים מיזוג.

מקרה 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 struct 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 struct 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. אחרי שמזהים את השינוי, אפשר לבטל אותו בליבה (kernel) או להעלות אותו ל-ACK ולמזג אותו.