مراقبة ABI لنظام تشغيل Android

يمكنك استخدام أدوات مراقبة واجهة التطبيق الثنائية (ABI) المتوفّرة في Android 11 والإصدارات الأحدث لتحسين استقرار واجهة ABI المضمّنة في نواة نظام التشغيل Android. تجمع الأدوات تمثيلات 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 جميع الرموز في النواة أو حتى كل الرموز التي تم تصديرها والتي تزيد عن 30,000 رمز. بدلاً من ذلك، يتم إدراج الرموز التي يمكن أن تستخدمها وحدات المورّدين بشكل صريح في مجموعة من ملفات قوائم الرموز التي يتم الاحتفاظ بها بشكل علني في ملف بنية ملف الترميز (gki/{ARCH}/symbols/* أو android/abi_gki_{ARCH}_* في الإصدار 15 من Android والإصدارات الأقدم). يحدِّد تجميع جميع الرموز في جميع ملفات قوائم الرموز مجموعة رموز KMI التي يتم الاحتفاظ بها كمجموعة ثابتة. مثال على ملف قائمة الرموز هو ملف gki/aarch64/symbols/db845c الذي يعرِض الرموز المطلوبة لجهاز DragonBoard 845c.

لا يُعتبَر جزءًا من قاعدة بيانات KMI سوى الرموز المدرَجة في قائمة الرموز والبنى والتعريفات ذات الصلة بها. يمكنك نشر التغييرات على قوائم الرموز إذا لم تكن الرموز التي تحتاج إليها متوفّرة. بعد أن تصبح الواجهات الجديدة في قائمة الرموز، وتصبح جزءًا من وصف KMI، يتم الاحتفاظ بها على أنّها مستقرة ويجب عدم إزالتها من قائمة الرموز أو تعديلها بعد تجميد الفرع.

يحتوي كل فرع من فروع "النواة المشتركة لنظام التشغيل Android" (ACK) على مجموعة خاصة به من قوائم الرموز. لا يتمّ إجراء أيّ محاولة لتوفير ثبات ABI بين مشاريع kernel KMI المختلفة. على سبيل المثال، يكون مقياس KMI الخاص بـ android12-5.10 مستقلاً تمامًا عن مقياس KMI الخاص بـ android13-5.10.

تستخدِم أدوات ABI قوائم رموز KMI للحدّ من الواجهات التي يجب مراقبتها من أجل الثبات. من المتوقّع أن يرسل المورّدون قوائم الرموز الخاصة بهم ويعدّلوها لتأكيد الحفاظ على توافق ABI مع الواجهات التي يعتمدون عليها. على سبيل المثال، للاطّلاع على قائمة بقوائم الرموز لنظام التشغيل android16-6.12، يُرجى الرجوع إلى https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols.

تحتوي قائمة الرموز على الرموز التي تم الإبلاغ عن أنّها مطلوبة لجهة التصنيع أو الجهاز المعيّنين. القائمة الكاملة التي تستخدمها الأدوات هي اتحاد جميع ملفّات قائمة رموز KMI. تحدِّد أدوات ABI تفاصيل كل رمز، بما في ذلك توقيع الدالة وهياكل البيانات المُدمجة.

عند تجميد واجهة مستخدم KMI، لا يُسمح بإجراء أي تغييرات على واجهات KMI الحالية، لأنّها مستقرة. ومع ذلك، يُتاح للمورّدين إضافة رموز إلى KMI في أي وقت ما دامت الإضافات لا تؤثّر في استقرار ABI الحالي. يتم الحفاظ على ثبات العلامات المُضافة حديثًا فور الإشارة إليها في قائمة رموز KMI. يجب عدم إزالة الرموز من قائمة نواة ما ما لم يكن من الممكن تأكيد أنّه لم يتم شحن أي جهاز يتضمّن رمزًا يعتمد على هذا الرمز.

يمكنك إنشاء قائمة رموز KMI لجهاز باستخدام التعليمات الواردة في مقالة كيفية التعامل مع قوائم الرموز. يرسل العديد من الشركاء قائمة رموز واحدة لكلّ رسالة تأكيد، ولكنّ هذا ليس شرطًا صارمًا. يمكنك إرسال قوائم رموز متعددة إذا كان ذلك يساعد في عملية الصيانة.

توسيع نطاق نموذج KMI

على الرغم من أنّ رموز KMI والهياكل ذات الصلة تظلّ ثابتة (أي أنّه لا يمكن قبول التغيُّرات التي تؤدي إلى إيقاف الواجهات الثابتة في نواة تحتوي على KMI مجمّد)، يظلّ نواة GKI مفتوحًا للاستخدام مع الإضافات حتى لا تحتاج الأجهزة التي يتم شحنها في وقت لاحق من العام إلى تحديد جميع العناصر التي تعتمد عليها قبل تجميد KMI. لتوسيع نطاق KMI، يمكنك إضافة رموز جديدة إلى KMI لدوالّ kernel الجديدة أو الحالية التي تم تصديرها، حتى إذا تم تجميد KMI. قد يتم أيضًا قبول تصحيحات ملف التمهيد الجديدة إذا كانت لا تؤدي إلى إيقاف KMI.

لمحة عن حالات تعطُّل ميزة "التجوّل الافتراضي"

تحتوي النواة على مصادر ويتم إنشاء الملفات الثنائية من هذه المصادر. تتضمّن فروع النواة التي يتم تتبُّعها من خلال ABI تمثيلًا لـ ABI الحالي لـ GKI ABI (بتنسيق ملف .stg). بعد إنشاء الملفات الثنائية (vmlinux وImage و أي وحدات GKI)، يمكن استخراج تمثيل ABI من الملفات الثنائية. يمكن أن يؤثر أي تغيير يتم إجراؤه على ملف مصدر نواة النظام في الملفات الثنائية، وبالتالي في .stg المستخرَج. يقارن محلّل AbiAnalyzerملف .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.

عند تشغيل ملف 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. على سبيل المثال، تضيف التغييرات التالية ميزة "التجميع التراكمي للصور" الجديدة إلى الفرع android-12-5.10، بما في ذلك تعديل قائمة الرموز وتمثيل ABI.

  • يمكنك العثور على مثال على تغيير الميزة في aosp/1345659.
  • يمكنك العثور على مثال لقائمة الرموز فيملف aosp/1346742.
  • يمكنك العثور على مثال على تغيير تمثيل ABI في aosp/1349377.

إذا تم تصدير الرمز (إما من قِبلك أو تم تصديره سابقًا) ولكن ليس هناك أي سائق آخر يستخدمه، قد يظهر لك خطأ في الإنشاء مشابه لما يلي.

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

لحلّ هذه المشكلة، عليك تعديل قائمة رموز KMI في كلّ من النواة وACK (راجِع تعديل تمثيل ABI). للحصول على مثال على تعديل قائمة الرموز وتمثيل ABI في الإشعار بتلقّي الرسالة، يُرجى الرجوع إلى aosp/1367601.

حلّ مشاكل تعذُّر استخدام واجهة برمجة التطبيقات للنواة

يمكنك التعامل مع حالات تعطُّل ABI للنواة من خلال إعادة صياغة الرمز البرمجي لتجنُّب تغيير IDE أو تعديل تمثيل ABI. استخدِم الرسم البياني التالي لتحديد أفضل نهج لحالتك.

رسم بياني انسيابي لتعطُّل ABI

الشكل 1: حلّ مشكلة عدم توافق واجهة ABI

إعادة صياغة الرمز لتجنُّب تغييرات ABI

ابذل قصارى جهدك لتجنُّب تعديل ABI الحالي. في كثير من الحالات، يمكنك إعادة صياغة الرمز البرمجي لإزالة التغييرات التي تؤثّر في ABI.

  • إعادة صياغة تغييرات حقل البنية إذا كان التغيير يُعدِّل ABI ميزة debugging ، أضِف #ifdef حول الحقول (في البنى ومراجع المصدر) وتأكَّد من أنّ CONFIG المستخدَم في #ifdef غير مفعَّل لملف gki_defconfig وملف defconfig في وضع الإنتاج. للحصول على مثال على كيفية إضافة ملف برمجي لضبط debugging إلى بنية بدون إيقاف ABI، يُرجى الرجوع إلى مجموعة التعديلات هذه.

  • إعادة صياغة الميزات لتجنُّب تغيير النواة الأساسية إذا كان يجب إضافة ميزات جديدة إلى ACK لتتوافق مع وحدات الشركاء، حاوِل إعادة صياغة ABI كجزء من التغيير لتجنُّب تعديل ABI للنواة. للحصول على مثال على استخدام واجهة تطبيقات ثنائية (ABI) الحالية للنواة لإضافة إمكانات إضافية بدون تغيير واجهة تطبيقات ثنائية (ABI) للنواة، يُرجى الرجوع إلى aosp/1312213.

إصلاح واجهة برمجة تطبيقات معطّلة على Android Gerrit

إذا لم تكن قد اخترقت ABI للنواة عن قصد، عليك التحقيق في الأمر، باستخدام الإرشادات المقدَّمة من خلال أدوات مراقبة ABI. إنّ الأسباب الأكثر شيوعًا للحدوث الأعطال هي تغييرات في بنية البيانات والرموز المرتبطة بها المتعلّقة بـ CRC ، أو بسبب تغييرات في خيارات الضبط تؤدي إلى أيّ من الأسباب المذكورة أعلاه. ابدأ بمعالجة المشاكل التي عثرت عليها الأداة.

يمكنك إعادة إنتاج نتائج ABI محليًا، اطّلِع على إنشاء النواة وتمثيل ABI.

لمحة عن تصنيفات Lint-1

في حال تحميل تغييرات على فرع يتضمّن ملف KMI مجمّدًا أو نهائيًا، يجب أن تمرّ التغييرات بالاختبار AbiAnalyzer لضمان عدم تأثُّر ABI الثابت بطريقة غير متوافقة. خلال هذه العملية، يبحث AbiAnalyzer عن تقرير ABI الذي تم إنشاؤه أثناء عملية الإنشاء (عملية إنشاء موسّعة تُجري عملية الإنشاء العادية ثم بعض خطوات استخراج ABI ومقارنتها).

إذا عثرت أداة AbiAnalyzer على تقرير غير فارغ، يتم ضبط التصنيف Lint-1 ويتم حظر إرسال المحتوى المتغيّر إلى أن يتم حلّ المشكلة، إلى أن تتلقّى مجموعة التصحيحات تصنيف Lint+1.

تعديل واجهة برمجة التطبيقات للنواة

إذا كان لا مفر من تعديل ABI، عليك تطبيق تغييرات الرمز البرمجي، وتمثيل ABI، وقائمة الرموز على ACK. لإزالة القيمة -1 من خلال Lint وعدم إيقاف توافق GKI، اتّبِع الخطوات التالية:

  1. حمِّل التغييرات في الرمز إلى الإشعار بتلقّي الرسالة.

  2. انتظِر تلقّي مراجعة للرمز البرمجي و+2 لمجموعة التصحيحات.

  3. تعديل تمثيل ABI المرجعي

  4. دمج تغييرات الرمز البرمجي وتغيير تحديث ABI

تحميل التغييرات في رمز ABI إلى ACK

يعتمد تعديل ABI لتطبيق ACK على نوع التغيير الذي يتم إجراؤه.

  • إذا كان تغيير ABI مرتبطًا بميزة تؤثّر في اختبارات CTS أو VTS، يمكن عادةً اختيار التغيير لتأكيده كما هو. على سبيل المثال:

    • يجب توفّر حزمة aosp/1289677 لكي يعمل الصوت.
    • يجب تثبيت الإصدار aosp/1295945 لكي يعمل منفذ USB.
  • إذا كان تغيير ABI ناتجًا عن ميزة يمكن مشاركتها مع ACK، يمكن اختيار هذا التغيير وإضافته إلى ACK كما هو. على سبيل المثال، التغييرات التالية غير مطلوبة لاختبار CTS أو VTS، ولكن يمكن مشاركتها مع ACK:

  • إذا كان تغيير ABI يقدّم ميزة جديدة لا يلزم تضمينها في ACK، يمكنك إدخال الرموز إلى ACK باستخدام رمز نائب كما هو موضّح في القسم التالي.

استخدام العناصر النائبة لتأكيد الاستلام

يجب أن تكون العناصر الأساسية ضرورية فقط لتغييرات kernel الأساسية التي لا تعود بالفائدة على ACK، مثل التغييرات في الأداء واستهلاك الطاقة. توضِّح القائمة التالية أمثلة على العناصر النائبة وعمليات الاختيار الجزئي في ACK لـ GKI.

  • ميزة Core-isolate stub (aosp/1284493). ليست الإمكانات في ACK ضرورية، ولكن يجب أن تكون الرموز متوفّرة في ACK لكي تستخدم وحداتك هذه الرموز.

  • رمز العنصر النائب لمكوّن المورّد (aosp/1288860).

  • اختيار ABI فقط لميزة تتبُّع أحداث mm لكل عملية (aosp/1288454) تم اختيار التصحيح الأصلي للموافقة عليه، ثم تم اقتصاصه ليشمل فقط التغييرات اللازمة لحلّ مشكلة اختلاف ABI في task_struct و mm_event_count. يُعدِّل هذا الإصلاح أيضًا قائمة القيم المحدَّدة mm_event_type لتشمل العناصر النهائية.

  • اختيار جزئي لتغييرات ABI في البنية الحرارية التي تتطلّب أكثر من إضافة حقول ABI الجديدة

    • تم حلّ الاختلافات في ABI بين نواة الشريك وACK من خلال التصحيح aosp/1255544.

    • تم حلّ المشاكل الوظيفية التي تم رصدها أثناء اختبار GKI للّصقة السابقة من خلال الإصدار aosp/1291018. يتضمّن الإصلاح إعداد بنية مَعلمات أداة الاستشعار لتسجيل مناطق حرارية متعددة في أداة استشعار واحدة.

  • 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 المتوقّع للوحدة مع معيار vmlinux KMI. على سبيل المثال، في ما يلي خطأ شائع يحدث في وقتتحميل الوحدات بسبب عدم تطابق فحص التكرار الدوري للرمز 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. يشكّل هذا السيناريو مشكلة لعدة أسباب، كما هو موضّح أدناه:

    • ويمكن أن يؤدي ذلك إلى كسر الافتراضات التي يجريها رمز النواة الأساسي بشأن هياكل data الداخلية.

    • إذا غيّر تحديث مستقبلي للنواة struct fwnode_handle (نوع data fwnode)، لن تعمل الوحدة مع النواة الجديدة. بالإضافة إلى ذلك، لن يعرض stgdiff أي اختلافات لأنّ الوحدة تؤدي إلى تعطيل المعيار KMI من خلال التلاعب بهياكل البيانات الداخلية مباشرةً بطرق لا يمكن تسجيلها من خلال فحص التمثيل الثنائي فقط.

  • تُعدّ الوحدة الحالية غير متوافقة مع KMI عند تحميلها في تاريخ لاحق بواسطة نواة جديدة غير متوافقة. تضيف عملية تحديد الإصدار للوحدة عملية تحقّق من وقت التشغيل لتجنُّب تحميل وحدة غير متوافقة مع KMI مع kernel عن طريق الخطأ. يمنع هذا التحقّق حدوث مشاكل وقت التشغيل التي يصعب تصحيحها وتعطلات نظام التشغيل التي قد تنتج عن عدم التوافق غير المكتشف في 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 kernel وkernel، يمكنك مقارنة قيم CRC لرمز معيّن من خلال تنفيذ الأمر التالي على كل من ملفَّي kernel ومقارنة الإخراج:

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

على سبيل المثال، يتحقّق الأمر التالي من قيمة CRC للرمز module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

حلّ مشكلة عدم تطابق أرقام التحقق من صحة البيانات

اتّبِع الخطوات التالية لحلّ مشكلة عدم تطابق رمز التحقّق من صحة البيانات (CRC) عند تحميل وحدة:

  1. أنشئ نواة GKI ونواة جهازك باستخدام الخيار --kbuild_symtypes كما هو موضّح في الأمر التالي:

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

    ينشئ هذا الأمر ملف .symtypes لكل ملف .o. اطّلِع على KBUILD_SYMTYPES في Kleaf للحصول على التفاصيل.

    بالنسبة إلى الإصدار 13 من Android والإصدارات الأقدم، يمكنك إنشاء نواة 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 ومواد عرض إنشاء ملف ‎ kernel للجهاز. حدِّد موقع ملف .symtypes باستخدام الأوامر التالية:

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

    في الإصدار 13 من نظام التشغيل Android والإصدارات الأقدم، باستخدام نصوص الترميز البرمجي القديمة لإنشاء التطبيق، من المرجّح أن يكون الموقع الجغرافي هو 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 باستخدام إصدار قبل التغيُّر المعنيّ مباشرةً ثم عند التغيُّر المعنيّ. يعني حفظ جميع الملفات أنّه يمكن مقارنة الملفّات بشكل مجمّع.

على سبيل المثال:

for f in $(find good bad -name '*.symtypes' | sed -r 's;^(good|bad)/;;' | LANG=C sort -u); do
  diff -N -U0 --label good/"$f" --label bad/"$f" <(LANG=C sort good/"$f") <(LANG=C sort bad/"$f")
done

بخلاف ذلك، ما عليك سوى مقارنة الملفات المحدّدة التي تهمّك.

الحالة 1: الاختلافات بسبب مستوى رؤية نوع البيانات

يمكن أن يسحب #include جديد تعريف نوع جديد (مثل struct foo) إلىملف مصدر، وفي هذه الحالات، سيتغيّر وصفه في ملف.symtypes المقابل من structure_type foo { } فارغ إلى تعريف كامل.

سيؤثّر ذلك في جميع قيم تحقّق صحة الرمز المميّز (CRC) لجميع الرموز في ملف .symtypes التي تعتمد أوصافها بشكل مباشر أو غير مباشر على تعريف النوع.

على سبيل المثال، تؤدي إضافة السطر التالي إلىملف include/linux/device.h في نواة النظام إلى حدوث تعارضات في تسلسل التحقّق من صحة البيانات (CRC)، أحدها يخص module_layout():

 #include <linux/fwnode.h>

عند مقارنة module/version.symtypes لهذا الرمز، تظهر الاختلافات التالية:

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

إذا كان نواة GKI تحتوي على تعريف النوع الكامل، ولكن نواة نظام التشغيل لا تحتوي عليه (من غير المرجّح حدوث ذلك)، يمكنك دمج أحدث نواة Android Common Kernel في نواة نظام التشغيل لكي تتمكّن من استخدام أحدث قاعدة نواة GKI.

في معظم الحالات، لا يتضمّن نواة GKI تعريف النوع الكامل في .symtypes، ولكن تتضمّنه نواتك بسبب توجيهات #include الإضافية.

درجة الدقة لنظام التشغيل Android 16 والإصدارات الأحدث

تأكَّد من أنّ ملف المصدر المتأثر يتضمّن ملف header لاستقرار Android KABI:

#include <linux/android_kabi.h>

لكل نوع متأثر، أضِف ANDROID_KABI_DECLONLY(name); على نطاق شامل إلى ملف المصدر المتأثر.

على سبيل المثال، إذا كان فرق symtypes على النحو التالي:

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

في هذه الحالة، المشكلة هي أنّ struct ubuf_info لديها الآن تعريفًا كاملاً في symtypes. الحلّ هو إضافة سطر إلى drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

يوجّه هذا الإجراء gendwarfksyms إلى التعامل مع النوع المُعنوَن على أنّه غير محدّد في الملف.

من الاحتمالات الأكثر تعقيدًا أن يكون #include الجديد نفسه في ملف عنوان. في هذه الحالة، قد تحتاج إلى توزيع مجموعات مختلفة من طلبات وحدات الماكرو ANDROID_KABI_DECLONLY على مستوى الملفات المصدر التي تؤدي بدورها إلى جلب تعريفات أنواع إضافية بشكل غير مباشر، لأنّ بعض هذه الملفات قد تحتوي على بعض تعريفات الأنواع.

لسهولة القراءة، ضَع عمليات استدعاء الوحدات النمطية هذه بالقرب من بدايةملف المصدر.

درجة الدقة لنظام التشغيل Android 15 والإصدارات الأقدم

غالبًا ما يكفي إخفاء #include الجديد من genksyms لحلّ المشكلة.

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

في حال عدم توفّر هذه الميزة، اتّبِع الخطوات التالية لتحديد #include الذي يتسبب في الاختلاف:

  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 التي أدّت إلى عدم تطابق رمز التحقق من السلامة هذا. مثلاً:

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

الحالة 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 * ) ; ...

لتحديد النوع الذي تم تغييره، اتّبِع الخطوات التالية:

  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 للسماح بتمديدها بدون تعطيل وحدات المورّدين الحالية. إذا أضافت عملية الربط في المصدر (على سبيل المثال) عضوًا إلى هذه البنية، قد يكون من الممكن تغييرها لاستخدام بعض المسافات البادئة بدلاً من ذلك. ويتم بعد ذلك إخفاء هذا التغيير من عملية احتساب "التكلفة المتغيرة لكلّ إجراء".

يحتجز الماكرو العادي الوثائقي الذاتي 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 للحفاظ على ثبات نوع الرمز.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
درجة الدقة لنظام التشغيل Android 16 والإصدارات الأحدث

يتم احتساب القيم المتعلّقة بـ CRC بواسطة gendwarfksyms التي تستخدِم معلومات تصحيح أخطاء DWARF، وبالتالي تتيح استخدام نوعَي C وRust. تختلف درجة الدقة حسب نوع التغيير. وإليك بعض الأمثلة.

العدّادات الجديدة أو المعدَّلة

في بعض الأحيان، تتم إضافة أدوات إحصاء جديدة، وفي بعض الأحيان تتأثر أيضًا قيمة MAX أو قيمة أداة إحصاء مشابهة. تكون هذه التغييرات آمنة إذا لم "تهرب" من GKI أو إذا كان بإمكاننا التأكّد من أنّ وحدات المورّدين لا يمكنها الاهتمام بقيمها.

مثلاً:

 enum outcome {
       SUCCESS,
       FAILURE,
       RETRY,
+      TRY_HARDER,
       OUTCOME_LIMIT
 };

يمكن إخفاء إضافة TRY_HARDER والتغيير إلى OUTCOME_LIMIT من حساب CRC باستخدام عمليات استدعاء الوحدات النمطية على النطاق العام:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

لتسهيل القراءة، ضَع هذه المراجع بعد تعريف enum مباشرةً.

عنصر هيكلي جديد يشغل مساحة فارغة حالية

بسبب المحاذاة، ستظهر وحدات بايت غير مستخدَمة بين urgent وscratch.

        void *data;
        bool urgent;
+       bool retry;
        void *scratch;

لا يتأثّر أيّ إزاحة عضو حالية أو حجم البنية بإضافة retry. ومع ذلك، قد يؤثر ذلك في رموز CRC أو تمثيل ABI أو كليهما.

سيؤدي ذلك إلى إخفاءها من عملية احتساب CRC:

        void *data;
        bool urgent;
+       ANDROID_KABI_IGNORE(1, bool retry);
        void *scratch_space;

يتوفّر العنصر retry كما لو تمّ الإعلان عنه مباشرةً، ولكن يتم توسيع النطاق المبرمَج في الواقع إلى عنصر موحّد مجهول يحتوي على retry بالإضافة إلى العناصر التي يستخدمها gendwarfksyms للحفاظ على ثبات نوع الرمز.

إضافة أعضاء جدد إلى بنية

تتم أحيانًا إضافة الأعضاء إلى نهاية البنية. ولا يؤثّر ذلك في الموضع النسبي للأعضاء الحاليين أو في المستخدمين الحاليين للبنية الذين يدخلون إليها باستخدام المؤشر فقط. يؤثر حجم البنية في معرّف 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;
};