مانیتورینگ ABI هسته اندروید

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

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

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

فرآیند

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

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

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

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

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

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

هر شاخه هسته KMI هسته مشترک اندروید (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 و ساختارهای مرتبط به صورت پایدار حفظ می‌شوند (به این معنی که تغییراتی که رابط‌های پایدار را در یک هسته با KMI فریز شده از بین می‌برند، قابل قبول نیستند)، هسته GKI برای توسعه‌ها باز می‌ماند، به طوری که دستگاه‌هایی که در اواخر سال عرضه می‌شوند، نیازی به تعریف تمام وابستگی‌های خود قبل از فریز شدن KMI ندارند. برای گسترش KMI، می‌توانید نمادهای جدیدی را برای توابع هسته جدید یا موجود صادر شده به KMI اضافه کنید، حتی اگر KMI فریز شده باشد. وصله‌های هسته جدید نیز در صورتی که KMI را خراب نکنند، ممکن است پذیرفته شوند.

درباره خرابی‌های KMI

یک هسته دارای منابعی است و فایل‌های باینری از آن منابع ساخته می‌شوند. شاخه‌های هسته تحت نظارت ABI شامل یک نمایش ABI از GKI ABI فعلی (به شکل یک فایل .stg ) هستند. پس از ساخت فایل‌های باینری ( vmlinux ، Image و هر ماژول GKI) می‌توان یک نمایش ABI از فایل‌های باینری استخراج کرد. هر تغییری که در فایل منبع هسته ایجاد شود می‌تواند بر فایل‌های باینری تأثیر بگذارد و به نوبه خود بر فایل .stg استخراج شده نیز تأثیر بگذارد. تجزیه و تحلیل انطباق ABI فایل .stg ثبت شده را با فایل استخراج شده از مصنوعات ساخت مقایسه می‌کند و در صورت یافتن تفاوت معنایی، یک برچسب Lint-1 روی تغییر در Gerrit قرار می‌دهد.

شکستگی‌های ABI را مدیریت کنید

به عنوان مثال، وصله زیر یک نقص بسیار واضح در ABI را نشان می‌دهد:

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
                ANDROID_KABI_RESERVE(1);
        } __randomize_layout;

+       int tickle_count;
        /*
         * The mm_cpumask needs to be at the end of mm_struct, because it
         * is dynamically sized based on nr_cpu_ids.

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

function symbol 'struct block_device* I_BDEV(struct inode*)' changed
  CRC changed from 0x8d400dbd to 0xabfc92ad

function symbol 'void* PDE_DATA(const struct inode*)' changed
  CRC changed from 0xc3c38b5c to 0x7ad96c0d

function symbol 'void __ClearPageMovable(struct page*)' changed
  CRC changed from 0xf489e5e8 to 0x92bd005e

... 4492 omitted; 4495 symbols have only CRC changes

type 'struct mm_struct' changed
  byte size changed from 992 to 1000
  member 'int tickle_count' was added
  member 'unsigned long cpu_bitmap[0]' changed
    offset changed by 64

تفاوت‌های ABI در زمان ساخت شناسایی شدند

رایج‌ترین دلیل خطاها زمانی است که یک درایور از نماد جدیدی از هسته استفاده می‌کند که در هیچ یک از فهرست‌های نماد وجود ندارد.

اگر نماد مورد نظر در فهرست نمادهای شما وجود ندارد، ابتدا باید با استفاده از EXPORT_SYMBOL_GPL( symbol_name ) تأیید کنید که صادر شده است و سپس فهرست نمادها و نمایش ABI را به‌روزرسانی کنید. برای مثال، تغییرات زیر ویژگی جدید Incremental FS را به شاخه android-12-5.10 اضافه می‌کند که شامل به‌روزرسانی فهرست نمادها و نمایش ABI می‌شود.

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

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

شکل 1. وضوح شکستگی ABI

کد را برای جلوگیری از تغییرات ABI، ریفکتور کنید

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

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

  • بازسازی ویژگی‌ها به گونه‌ای که هسته اصلی تغییر نکند. اگر لازم است ویژگی‌های جدیدی به ACK اضافه شود تا از ماژول‌های همکار پشتیبانی کند، سعی کنید بخش ABI تغییر را بازسازی کنید تا از تغییر ABI هسته جلوگیری شود. برای مثالی از استفاده از ABI هسته موجود برای افزودن قابلیت‌های اضافی بدون تغییر ABI هسته، به aosp/1312213 مراجعه کنید.

رفع مشکل ABI خراب در اندروید Gerrit

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

شما می‌توانید یافته‌های ABI را به صورت محلی بازتولید کنید، به ساخت هسته و نمایش ABI آن مراجعه کنید.

درباره برچسب‌های Lint-1

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

هر یک از این تحلیل‌های ABI ممکن است برچسب Lint-1 را تنظیم کرده و ارسال تغییر را مسدود کند تا زمانی که همه مشکلات حل شوند یا برچسب لغو شود.

به‌روزرسانی ABI هسته

اگر تغییر ABI اجتناب‌ناپذیر است، باید تغییرات کد، نمایش ABI و لیست نمادها را روی ACK اعمال کنید. برای اینکه Lint مقدار -1 را حذف کند و سازگاری 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 نباشد، می‌توانید نمادها را با استفاده از یک stub همانطور که در بخش بعدی توضیح داده شده است، به ACK معرفی کنید.

از stubها برای ACK استفاده کنید

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

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

  • نماد جایگزین برای ماژول فروشنده ( aosp/1288860 ).

  • انتخاب گزینشیِ فقط ABI از ویژگی ردیابی رویداد mm برای هر فرآیند ( aosp/1288454 ). وصله اصلی به صورت گزینشی انتخاب شده و سپس اصلاح شده است تا فقط تغییرات لازم برای حل تفاوت ABI برای task_struct و mm_event_count را شامل شود. این وصله همچنین شمارشگر mm_event_type را به‌روزرسانی می‌کند تا اعضای نهایی را در بر بگیرد.

  • گلچینی از تغییرات جزئی در ساختار حرارتی 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 در زمان اجرا استفاده می‌کنند. نسخه‌بندی ماژول می‌تواند باعث بروز خطاهای عدم تطابق بررسی افزونگی چرخه‌ای (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 مقایسه و گزارش می‌کند.

علاوه بر این، یک نسخه کامل هسته با فعال بودن CONFIG_MODVERSIONS ، یک فایل Module.symvers را به عنوان بخشی از فرآیند ساخت عادی تولید می‌کند. این فایل برای هر نمادی که توسط هسته ( vmlinux ) و ماژول‌ها صادر می‌شود، یک خط دارد. هر خط شامل مقدار CRC، نام نماد، فضای نام نماد، نام vmlinux یا ماژولی که نماد را صادر می‌کند و نوع صادرات (به عنوان مثال، EXPORT_SYMBOL در مقابل EXPORT_SYMBOL_GPL ) است.

شما می‌توانید فایل‌های Module.symvers را بین نسخه GKI و نسخه خودتان مقایسه کنید تا هرگونه تفاوت CRC در نمادهای صادر شده توسط vmlinux را بررسی کنید. اگر در هر نمادی که توسط vmlinux صادر می‌شود، تفاوت مقدار CRC وجود داشته باشد و آن نماد توسط یکی از ماژول‌هایی که در دستگاه خود بارگذاری می‌کنید استفاده شود، ماژول بارگذاری نمی‌شود.

اگر تمام مصنوعات ساخت را ندارید، اما فایل‌های 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 مراجعه کنید.

    برای اندروید ۱۳ و پایین‌تر، هسته 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

    در اندروید ۱۳ و پایین‌تر، با استفاده از اسکریپت‌های ساخت قدیمی، احتمالاً این مکان یا 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 تعریف نوع کامل را دارد، اما هسته شما آن را ندارد (که بسیار بعید است)، آخرین هسته مشترک اندروید را در هسته خود ادغام کنید تا از آخرین پایه هسته GKI استفاده کنید.

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

وضوح تصویر برای اندروید ۱۶ و بالاتر

مطمئن شوید که فایل منبع آسیب‌دیده شامل هدر تثبیت‌کننده‌ی 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 را در فایل‌های منبع توزیع کنید که به طور غیرمستقیم تعاریف نوع اضافی را دریافت می‌کنند، زیرا برخی از آنها ممکن است از قبل برخی از تعاریف نوع را داشته باشند.

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

وضوح تصویر برای اندروید ۱۵ و پایین‌تر

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

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

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

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

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

  3. پس از شناسایی کامیت، یا آن را در هسته خود برگردانید یا آن را به‌روزرسانی کنید تا تغییر 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 برای حفظ پایداری سیم‌تایپ استفاده می‌شود، گسترش می‌یابد.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
وضوح تصویر برای اندروید ۱۶ و بالاتر

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 عضو، گویی مستقیماً تعریف شده است، در دسترس است، اما ماکرو در واقع به یک عضو ناشناس union که شامل retry و همچنین مواردی است که توسط gendwarfksyms برای حفظ پایداری سیم‌تایپ استفاده می‌شود، گسترش می‌یابد.

گسترش یک ساختار با اعضای جدید

گاهی اوقات اعضا به انتهای یک ساختار اضافه می‌شوند. این موضوع تاثیری بر جابجایی اعضای موجود یا کاربران موجود ساختار که فقط از طریق اشاره‌گر به آن دسترسی دارند، ندارد. اندازه ساختار بر 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");

برای خوانایی بیشتر، این را درست بعد از تعریف نوع یا نماد قرار دهید.

وضوح تصویر برای اندروید ۱۵ و پایین‌تر

تغییرات نوع و نوع نماد باید از genksyms پنهان شوند. این کار را می‌توان با کنترل پیش‌پردازش با __GENKSYMS__ انجام داد.

تبدیل‌های دلخواه کد را می‌توان به این صورت بیان کرد.

برای مثال، برای پنهان کردن یک عضو جدید که حفره‌ای را در یک سازه موجود اشغال کرده است:

struct parcel {
        void *data;
        bool urgent;
#ifndef __GENKSYMS__
        bool retry;
#endif
        void *scratch_space;
};