پایش ABI هسته Android

برای پایدار کردن ABI درون‌هسته‌ای هسته‌های Android، می‌توانید از ابزارهای پایش میانای باینری برنامه (ABI) که در Android 11 و بالاتر دردسترس است استفاده کنید. ابزارها بازنمایی‌های ABI را از باینری‌های هسته موجود (vmlinux+ واحدهای GKI) جمع‌آوری و مقایسه می‌کنند. این نمایش‌های ABI فایل‌های .stg و فهرست‌های نماد هستند. میانایی که نمایش ازطریق آن ارائه می‌شود میانای واحد هسته (KMI) نامیده می‌شود. می‌توانید از ابزارها برای ردیابی و کاهش تغییرات در KMI استفاده کنید.

ابزار پایش ABI در AOSP توسعه یافته است و از STG (یا libabigail در Android 13 و نسخه‌های پایین‌تر) برای تولید و مقایسه نمایش‌ها استفاده می‌کند.

این صفحه ابزارها، فرایند جمع‌آوری و تجزیه‌وتحلیل نمایش‌های ABI، و استفاده از این نمایش‌ها را برای ارائه پایداری به ABI درون هسته شرح می‌دهد. این صفحه همچنین اطلاعاتی برای همیاری در تغییرات هسته‌های Android ارائه می‌دهد.

پردازش

تجزیه‌وتحلیل ABI هسته چندین مرحله دارد که بیشتر آن‌ها را می‌توان خودکارسازی کرد:

  1. هسته و نمایش ABI آن را بسازید.
  2. تفاوت‌های ABI بین ساخت و مرجع را تحلیل کنید.
  3. نمایش ABI را (درصورت نیاز) به‌روزرسانی کنید.
  4. با فهرست نمادها کار کنید.

دستورالعمل‌های زیر برای هر هسته‌ای که می‌توانید بسازید بااستفاده از زنجیره ابزار پشتیبانی‌شده (مثل زنجیره ابزار Clang پیش‌ساخته) کار می‌کند. repo manifestsبرای همه شاخه‌های هسته مشترک Android و برای چندین هسته ویژه دستگاه دردسترس هستند، آن‌ها درستی‌سنجی می‌کنند که هنگام ساختن توزیع هسته برای تجزیه‌وتحلیل، از زنجیره ابزار صحیح استفاده شود.

فهرست‌های نماد

KMI شامل همه نمادهای هسته یا حتی همه بیش‌از ۳۰٬۰۰۰ نماد صادرشده نیست. درعوض، نمادهایی که می‌توانند توسط پیمان‌ماژول‌ها استفاده شوند به‌طور صریح در مجموعه‌ای از فایل‌های فهرست نماد که به‌طور عمومی در درخت هسته نگهداری می‌شوند (gki/{ARCH}/symbols/* یا android/abi_gki_{ARCH}_* در Android 15 و پایین‌تر) فهرست شده‌اند. اتحاد همه نمادها در همه فایل‌های فهرست نمادها مجموعه نمادهای KMI را که به‌عنوان پایدار نگهداری می‌شوند تعریف می‌کند. فایل فهرست نماد نمونه gki/aarch64/symbols/db845c است که نمادهای موردنیاز برای DragonBoard 845c را اعلام می‌کند.

فقط نمادهای فهرست‌شده در فهرست نمادها و ساختارها و تعاریف مربوط به آن‌ها بخشی از KMI محسوب می‌شوند. اگر نمادهای موردنیازتان وجود ندارد، می‌توانید تغییرات را در فهرست نمادهایتان پست کنید. پس‌از اینکه میاناهای جدید در فهرست نمادها قرار گرفتند و بخشی از شرح KMI شدند، به‌عنوان پایدار حفظ می‌شوند و نباید از فهرست نمادها برداشته شوند یا پس‌از منجمد شدن شاخه اصلاح شوند.

هر شاخه هسته KMI «هسته مشترک Android» (ACK) مجموعه فهرست نمادهای خود را دارد. هیچ تلاشی برای ارائه پایداری ABI بین شاخه‌های مختلف هسته KMI انجام نمی‌شود. برای مثال، «شاخص مدیریت دانش» برای android12-5.10 کاملاً مستقل از «شاخص مدیریت دانش» برای 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 و ساختارهای مرتبط به‌عنوان پایدار حفظ می‌شوند (یعنی تغییراتی که واسط‌های پایدار را در هسته‌ای با 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.

وقتی «رابط دودویی کاربردی» ساخت را با این وصله اعمال‌شده اجرا می‌کنید، ابزار با کد خطای غیرصفر خارج می‌شود و تفاوت «رابط دودویی کاربردی» مشابه این را گزارش می‌کند:

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 است.

اگر نماد صادر شده باشد (توسط شما یا قبلاً صادر شده است) اما هیچ درایور دیگری از آن استفاده نکند، ممکن است خطای ساختمانی مشابه خطای زیر دریافت کنید.

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

برای رفع مشکل، فهرست نمادهای KMI را هم در هسته و هم در ACK به‌روز کنید (به به‌روزرسانی نمایش ABI مراجعه کنید). برای نمونه‌ای از به‌روزرسانی فهرست نماد و نمایش ABI در ACK، به aosp/1367601 مراجعه کنید.

رفع مشکلات ABI هسته

می‌توانید با بازسازی کد به‌طوری‌که ABI تغییر نکند یا به‌روزرسانی نمایش ABI، از خرابی‌های ABI هسته جلوگیری کنید. از نمودار زیر برای تعیین بهترین رویکرد برای وضعیت خود استفاده کنید.

نمودار جریان شکستگی ABI

شکل ۱. حل‌وفصل شکستگی ABI

بازسازی کد برای جلوگیری از تغییرات ABI

تمام تلاش خود را بکنید تا از تغییر ABI موجود اجتناب کنید. در بسیاری از موارد، می‌توانید کدتان را بازسازی کنید تا تغییراتی را که بر ABI تأثیر می‌گذارد بردارید.

  • درحال تغییر دادن فیلد ساختار. اگر تغییری «میان‌آبی برنامه‌های کاربردی» را برای ویژگی اشکال‌زدایی تغییر می‌دهد، در اطراف فیلدها (در ساختارها و ارجاع‌های منبع) #ifdef اضافه کنید و مطمئن شوید که CONFIG استفاده‌شده برای #ifdef برای پیکربندی پیش‌فرض تولید و gki_defconfig غیرفعال است. برای نمونه‌ای از نحوه اضافه شدن پیکربندی اشکال‌زدایی به ساختار بدون شکستن ABI، به این مجموعه تغییرات مراجعه کنید.

  • ویژگی‌های بازسازی‌شده برای تغییر ندادن هسته اصلی. اگر برای پشتیبانی از واحدهای شریک، ویژگی‌های جدیدی باید به ACK اضافه شود، سعی کنید بخش 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 ‫-۱ را بردارد و سازگاری GKI را ازبین نبرد، این مراحل را دنبال کنید:

  1. تغییرات کد را در ACK بارگذاری کنید.

  2. منتظر بمانید تا یک «بازبینی کد +۲» برای مجموعه وصله دریافت کنید.

  3. نمایش ABI مرجع را به‌روز کنید.

  4. تغییرات کد و تغییر به‌روزرسانی ABI را ادغام کنید.

بارگذاری تغییرات کد ABI در ACK

به‌روزرسانی ACK ABI به نوع تغییری که ایجاد می‌شود بستگی دارد.

  • اگر تغییر ABI مربوط به ویژگی‌ای باشد که بر آزمایش‌های CTS یا VTS تأثیر می‌گذارد، تغییر معمولاً می‌تواند همان‌طور که هست به ACK گلچین شود. برای مثال:

    • برای اینکه صدا کار کند، aosp/1289677 لازم است.
    • برای کار کردن USB، به aosp/1295945 نیاز است.
  • اگر تغییر ABI برای ویژگی‌ای باشد که بتواند با ACK هم‌رسانی شود، آن تغییر می‌تواند همان‌طور که هست برای ACK انتخاب شود. برای مثال، تغییرات زیر برای آزمایش CTS یا VTS لازم نیست اما هم‌رسانی آن‌ها با ACK مشکلی ندارد:

  • اگر تغییر ABI ویژگی جدیدی را معرفی کند که نیازی به گنجاندن در ‫ACK ندارد، می‌توانید نمادها را بااستفاده از یک «ته‌برگ» همان‌طور که در بخش زیر توضیح داده شده است به ACK معرفی کنید.

استفاده از ته برگ برای ACK

کدهای موقت باید فقط برای تغییرات هسته اصلی که برای ACK مفید نیستند، مانند تغییرات عملکرد و توان، ضروری باشند. فهرست زیر نمونه‌هایی از استاب‌ها و انتخاب‌های جزئی در ACK برای GKI را شرح می‌دهد.

  • شبه‌کد ویژگی جداسازی هسته (aosp/1284493). قابلیت‌های ACK ضروری نیست، اما نمادها باید در ACK وجود داشته باشند تا واحدهای شما بتوانند از این نمادها استفاده کنند.

  • نماد جای‌بان برای واحد فروشنده (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()) به نمادهای فهرست‌شده در فهرست نمادها. همه نمادهای دیگر صادرنشده هستند و بار کردن ماژولی که به نماد صادرنشده نیاز دارد رد می‌شود. این محدودیت در زمان ساخت اعمال می‌شود و ورودی‌های ازدست‌رفته پرچم‌گذاری می‌شوند.

برای اهداف توسعه، می‌توانید از ساخت هسته GKI استفاده کنید که شامل پیرایش نماد نیست (یعنی همه نمادهایی که معمولاً صادر می‌شوند قابل‌استفاده هستند). برای پیدا کردن این ساخت‌ها، ساخت‌های kernel_debug_aarch64 را در ci.android.com جستجو کنید.

اجرای KMI بااستفاده از نسخه‌بندی واحد

هسته‌های «تصویر هسته عمومی» (GKI) از نسخه‌بندی واحد (CONFIG_MODVERSIONS) به‌عنوان اقدامی اضافی برای اعمال تبعیت از KMI در زمان اجرا استفاده می‌کنند. اگر KMI موردانتظار یک واحد با KMI vmlinux مطابقت نداشته باشد، نسخه‌بندی واحد می‌تواند باعث خرابی‌های عدم‌تطابق بازبینی افزونگی چرخه‌ای (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 و ساختار خودتان مقایسه کنید تا تفاوت‌های CRC را در نمادهای صادرشده توسط vmlinux بررسی کنید. اگر در مقدار CRC هریک از نمادهای صادرشده توسط vmlinux و تفاوتی وجود داشته باشد و آن نماد توسط یکی از واحدهایی که در دستگاهتان بار می‌کنید استفاده شود، آن واحد بار نمی‌شود.

اگر همه مصنوعات ساخت را ندارید، اما فایل‌های vmlinux هسته GKI و هسته خودتان را دارید، می‌توانید با اجرای فرمان زیر در هر دو هسته و مقایسه خروجی، مقادیر CRC را برای نماد خاصی مقایسه کنید:

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

برای مثال، فرمان زیر مقدار CRC را برای نماد module_layout بررسی می‌کند:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

رفع کردن عدم تطابق‌های CRC

برای حل مشکل عدم تطابق CRC هنگام بار کردن واحد، مراحل زیر را دنبال کنید:

  1. هسته GKI و هسته دستگاهتان را بااستفاده از گزینه --kbuild_symtypes همان‌طور که در فرمان زیر نشان داده شده است بسازید:

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

    این فرمان برای هر فایل .o، یک فایل .symtypes تولید می‌کند. برای جزئیات، KBUILD_SYMTYPES در Kleaf را ببینید.

    برای Android 13 و نسخه‌های پایین‌تر، هسته GKI و هسته دستگاهتان را با پیش‌افزودن KBUILD_SYMTYPES=1 به فرمانی که برای ساختن هسته استفاده می‌کنید بسازید، همان‌طور که در فرمان زیر نشان داده شده است:

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

    هنگام استفاده از build_abi.sh,، پرچم KBUILD_SYMTYPES=1 به‌طور ضمنی ازقبل تنظیم شده است.

  2. فایل .c را که نماد با عدم تطابق CRC در آن صادر شده است بااستفاده از فرمان زیر پیدا کنید:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. فایل .c دارای فایل .symtypes متناظر در GKI و مصنوعات ساخت هسته دستگاه شما است. فایل .symtypes را بااستفاده از فرمان‌های زیر پیدا کنید:

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

    در Android 13 و نسخه‌های پایین‌تر، بااستفاده از اسکریپت‌های ساخت قدیمی، مکان احتمالاً out/$BRANCH/common یا out_abi/$BRANCH/common است.

    هر فایل .symtypes یک فایل نوشتار ساده است که شامل شرح نوع و نماد است:

    • هر خط به‌صورت key description است که در آن شرح می‌تواند به کلیدهای دیگر در همان فایل اشاره کند.

    • کلیدهایی مثل [s|u|e|t]#foo به [struct|union|enum|typedef] foo اشاره دارند. برای مثال:

      t#bool typedef _Bool bool
      
    • کلیدهای بدون پیشوند x# فقط نام نماد هستند. برای مثال:

      find_module s#module * find_module ( const char * )
      
  4. این دو فایل را مقایسه کن و همه تفاوت‌ها را برطرف کن.

بهتر است symtypes را با ساختمانی درست قبل‌از تغییر مشکل‌ساز و سپس در تغییر مشکل‌ساز تولید کنید. ذخیره کردن همه فایل‌ها یعنی می‌توان آن‌ها را به‌صورت انبوه مقایسه کرد.

برای مثال،

diff -r -N -U0 good bad

درغیراین‌صورت، فقط فایل‌های موردنظر را مقایسه کنید.

مورد ۱: تفاوت‌های ناشی از نمایان بودن نوع داده

‫#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 استفاده کنید.

در بیشتر موارد، هسته GKI تعریف نوع کامل را در .symtypes ندارد، اما هسته شما به‌دلیل دستورات #include اضافی آن را دارد.

وضوح برای Android 16 و بالاتر

مطمئن شوید که فایل منبع تحت‌تأثیر قرارگرفته شامل سرصفحه تثبیت KABI Android باشد:

#include <linux/android_kabi.h>

برای هر نوع تحت‌تأثیر قرارگرفته، ANDROID_KABI_DECLONLY(name); را در حوزه سراسری به فایل منبع تحت‌تأثیر قرارگرفته اضافه کنید.

برای مثال، اگر تفاوت symtypes این باشد:

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

در این صورت مشکل این است که struct ubuf_info اکنون تعریف کاملی در symtypes دارد. راه‌حل این است که خطی به drivers/android/vendor_hooks.c اضافه کنید:

ANDROID_KABI_DECLONLY(ubuf_info);

این دستور به gendwarfksyms می‌گوید که نوع نام‌گذاری‌شده را در فایل به‌عنوان تعریف‌نشده درنظر بگیرد.

احتمال پیچیده‌تر این است که #include جدید خود در فایل سرصفحه باشد. در این مورد، ممکن است لازم باشد مجموعه‌های مختلفی از ANDROID_KABI_DECLONLY فراخوانی‌های کلان را در فایل‌های منبع توزیع کنید که به‌طور غیرمستقیم تعریف‌های نوع اضافی را فرا می‌خوانند، زیرا ممکن است برخی‌از آن‌ها قبلاً برخی‌از تعریف‌های نوع را داشته باشند.

برای خوانایی، این فراخوانی‌های ماکرو را نزدیک ابتدای فایل منبع قرار دهید.

وضوح برای Android 15 و نسخه‌های پایین‌تر

اغلب، راه‌حل فقط پنهان کردن #include جدید از genksyms است.

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

درغیراین‌صورت، برای شناسایی #include که باعث تفاوت می‌شود، این مراحل را دنبال کنید:

  1. فایل سرصفحه‌ای را که نماد یا نوع داده دارای این تفاوت را تعریف می‌کند باز کنید. برای مثال، include/linux/fwnode.h را برای struct fwnode_handle ویرایش کنید.

  2. کد زیر را به بالای فایل سرایند اضافه کنید:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. در فایل .c واحد که دارای عدم تطابق CRC است، مورد زیر را به‌عنوان اولین خط قبل‌از هریک از #include خط اضافه کنید.

    #define CRC_CATCH 1
    
  4. واحدتان را ترجمه کنید. خطای زمان ساخت حاصل، زنجیره فایل سرصفحه #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 وجود ندارد.

مورد ۲: تفاوت‌های ناشی از تغییرات نوع داده

اگر عدم تطابق CRC برای یک نماد یا نوع داده به‌دلیل تفاوت در رؤیت‌پذیری نباشد، پس به‌دلیل تغییرات واقعی (افزودن، برداشتن، یا تغییر) در خود نوع داده است.

برای مثال، ایجاد تغییر زیر در هسته باعث چندین عدم تطابق CRC می‌شود زیرا بسیاری از نمادها به‌طور غیرمستقیم تحت تأثیر این نوع تغییر قرار می‌گیرند:

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

یک عدم تطابق CRC برای devm_of_platform_populate() وجود دارد.

اگر .symtypes فایل را برای آن نماد مقایسه کنید، ممکن است به این شکل باشد:

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

برای شناسایی نوع تغییریافته، این مراحل را دنبال کنید:

  1. تعریف نماد را در کد منبع (معمولاً در فایل‌های .h) پیدا کنید.

    • برای تفاوت‌های نماد بین هسته شما و هسته GKI، با اجرای فرمان زیر، commit را پیدا کنید:
    git blame
    • برای نمادهای حذف‌شده (جایی که نمادی در یک درخت حذف شده است و شما نیز می‌خواهید آن را در درخت دیگر حذف کنید)، باید تغییری را که خط را حذف کرده است پیدا کنید. از فرمان زیر در درختی که خط حذف شده است استفاده کنید:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. فهرست برگشتی تعهدات را مرور کنید تا تغییر یا حذف را پیدا کنید. اولین ثبت احتمالاً همان چیزی است که به‌دنبالش هستید. اگر این‌طور نیست، فهرست را مرور کنید تا تعهد را پیدا کنید.

  3. پس‌از شناسایی commit، آن را در هسته خود برگردانید یا آن را به‌روز کنید تا تغییر CRC را سرکوب کنید و آن را در ACK بارگذاری کنید و آن را ادغام کنید. هر شکستگی باقی‌مانده ABI باید ازنظر ایمنی بررسی شود و درصورت لزوم شکستگی مجاز می‌تواند ثبت شود.

ترجیح می‌دهد از حاشیه موجود استفاده کند

برخی‌از ساختارهای GKI برای امکان‌پذیر کردن توسعه آن‌ها بدون شکستن واحدهای فروشنده موجود، بالشتک‌گذاری می‌شوند. اگر یک commit بالادستی (به‌عنوان مثال) عضوی را به چنین ساختاری اضافه کند، ممکن است بتوان آن را تغییر داد تا به‌جای آن مقداری از padding را مصرف کند. سپس این تغییر از محاسبه CRC پنهان می‌شود.

ماکرو استانداردشده و خودسندسازی ANDROID_KABI_RESERVE فضایی به ارزش u64 (ترازشده) را رزرو می‌کند. این عبارت به‌جای اعلام عضویت استفاده می‌شود.

برای مثال:

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

لایی را می‌توان بدون تأثیر بر 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 و بالاتر

‫CRCs توسط 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;
};