يمكنك استخدام أدوات مراقبة واجهة التطبيق الثنائية (ABI) المتوفّرة في نظام التشغيل Android 11 والإصدارات الأحدث لتثبيت واجهة التطبيق الثنائية (ABI) لنواة Android داخل النواة. تجمع الأدوات تمثيلات ABI وتقارنها
من ملفات ثنائية حالية للنواة (vmlinux
ووحدات GKI). تمثيلات ABI هذه هي ملفات .stg
وقوائم الرموز. تُسمى الواجهة التي يوفّر التمثيل من خلالها عرضًا واجهة وحدة النواة (KMI). يمكنك استخدام الأدوات لتتبُّع التغييرات التي تطرأ على معلومات إدارة المفاتيح والحدّ من تأثيرها.
تم تطوير أدوات مراقبة واجهة التطبيق الثنائية (ABI) في مشروع AOSP، وهي تستخدم STG (أو libabigail
في Android 13 والإصدارات الأقدم) لإنشاء تمثيلات ومقارنتها.
توضّح هذه الصفحة الأدوات وعملية جمع وتحليل تمثيلات واجهة التطبيق الثنائية (ABI) واستخدام هذه التمثيلات لتوفير ثبات واجهة التطبيق الثنائية (ABI) في النواة. تقدّم هذه الصفحة أيضًا معلومات حول المساهمة في إجراء تغييرات على نوى Android.
معالجة
يتطلّب تحليل واجهة ABI للنواة عدة خطوات، يمكن تنفيذ معظمها تلقائيًا:
- إنشاء النواة وتمثيل واجهة التطبيق الثنائية (ABI)
- تحليل الاختلافات في واجهة التطبيق الثنائية (ABI) بين الإصدار ومرجع
- تعديل تمثيل واجهة التطبيق الثنائية (إذا لزم الأمر)
- العمل باستخدام قوائم الرموز
تعمل التعليمات التالية مع أي نواة يمكنك إنشاؤها باستخدام مجموعة أدوات متوافقة (مثل مجموعة أدوات Clang المُنشأة مسبقًا). تتوفّر repo manifests
لجميع فروع نواة Android الشائعة ولعدة نُوى خاصة بالأجهزة، وتتحقّق من استخدام سلسلة الأدوات الصحيحة عند إنشاء توزيعة نواة للتحليل.
قوائم الرموز
لا تتضمّن واجهة KMI جميع الرموز في النواة أو حتى جميع الرموز التي يزيد عددها عن 30,000 رمز تم تصديرها. بدلاً من ذلك، يتم إدراج الرموز التي يمكن أن تستخدمها وحدات المورّد بشكل صريح في مجموعة من ملفات قوائم الرموز التي يتم الاحتفاظ بها بشكل علني في شجرة النواة (gki/{ARCH}/symbols/*
أو android/abi_gki_{ARCH}_*
في Android 15 والإصدارات الأقدم). يحدّد اتحاد جميع الرموز في جميع ملفات قائمة الرموز مجموعة رموز KMI التي يتم الحفاظ عليها على أنّها ثابتة. مثال على ملف قائمة الرموز هو
gki/aarch64/symbols/db845c
،
الذي يحدّد الرموز المطلوبة
لجهاز DragonBoard 845c.
لا تُعتبر سوى الرموز المدرَجة في قائمة الرموز والبِنى والتعريفات ذات الصلة جزءًا من KMI. يمكنك نشر تغييرات على قوائم الرموز إذا لم تكن الرموز التي تحتاج إليها متوفّرة. بعد إضافة واجهات جديدة إلى قائمة الرموز، وإدراجها ضمن وصف KMI، يتم الحفاظ على ثباتها ويجب عدم إزالتها من قائمة الرموز أو تعديلها بعد تجميد الفرع.
يحتوي كل فرع من فروع نواة KMI في "نواة Android الشائعة" (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 (في شكل ملف .stg
). بعد إنشاء الملفات الثنائية (vmlinux
وImage
وأي وحدات GKI)، يمكن استخراج تمثيل لواجهة التطبيق الثنائية من الملفات الثنائية. يمكن أن يؤثر أي تغيير يتم إجراؤه على ملف مصدر kernel في الملفات الثنائية، وبالتالي يؤثر أيضًا في .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.
عند تشغيل 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).
- يمكن الاطّلاع على مثال على تغيير الميزة في 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 في ACK، يُرجى الرجوع إلى aosp/1367601.
حلّ المشاكل في توافق واجهة التطبيق الثنائية (ABI) لنواة النظام
يمكنك التعامل مع حالات عدم توافق واجهة التطبيق الثنائية (ABI) لنواة النظام من خلال إعادة تصميم الرمز البرمجي بدون تغيير واجهة التطبيق الثنائية أو تعديل تمثيل واجهة التطبيق الثنائية. استخدِم المخطّط التالي لتحديد الطريقة الأنسب لحالتك.
الشكل 1. حلّ مشاكل عدم توافق واجهة التطبيق الثنائية (ABI)
إعادة تصميم الرمز البرمجي لتجنُّب تغييرات واجهة التطبيق الثنائية (ABI)
يجب بذل كل جهد ممكن لتجنُّب تعديل واجهة التطبيق الثنائية الحالية. في كثير من الحالات، يمكنك إعادة تصميم الرمز البرمجي لإزالة التغييرات التي تؤثر في واجهة التطبيق الثنائية (ABI).
إعادة تصميم تغييرات حقول البنية إذا كان التغيير يعدّل واجهة التطبيق الثنائية (ABI) لإحدى ميزات تصحيح الأخطاء، أضِف
#ifdef
حول الحقول (في البُنى ومراجع المصدر) وتأكَّد من إيقافCONFIG
المستخدَم في#ifdef
لإعدادات defconfig الخاصة بالإنتاج وgki_defconfig
. للاطّلاع على مثال حول كيفية إضافة إعداد تصحيح الأخطاء إلى بنية بدون إيقاف توافق التطبيق الثنائي، يُرجى الرجوع إلى مجموعة التصحيحات هذه.إعادة تصميم الميزات بدون تغيير النواة الأساسية: إذا كان يجب إضافة ميزات جديدة إلى ACK لتتوافق مع وحدات الشريك، حاوِل إعادة تصميم جزء ABI من التغيير لتجنُّب تعديل ABI للنواة. للاطّلاع على مثال حول استخدام واجهة التطبيق الثنائية (ABI) الحالية لنواة النظام من أجل إضافة إمكانات إضافية بدون تغيير واجهة التطبيق الثنائية (ABI) لنواة النظام، يُرجى الرجوع إلى aosp/1312213.
إصلاح واجهة تطبيق ثنائية (ABI) معطَّلة على Android Gerrit
إذا لم تتعطّل واجهة التطبيق الثنائية للنواة عن غير قصد، عليك التحقيق في المشكلة باستخدام الإرشادات التي توفّرها أدوات مراقبة واجهة التطبيق الثنائية. تشمل الأسباب الأكثر شيوعًا لحدوث أخطاء تغييرات في بنى البيانات وتغييرات في رمز CRC المرتبط، أو تغييرات في خيارات الإعدادات تؤدي إلى أي من الأسباب المذكورة أعلاه. ابدأ بمعالجة المشاكل التي رصدتها الأداة.
يمكنك إعادة إنتاج نتائج ABI محليًا، راجِع إنشاء النواة وتمثيل ABI الخاص بها.
لمحة عن تصنيفات Lint-1
في حال تحميل تغييرات إلى فرع يحتوي على KMI مجمدة أو نهائية، يجب أن تجتاز التغييرات تحليلات التوافق مع واجهة التطبيق الثنائية (ABI) والتوافق لضمان أنّ التغييرات في تمثيل واجهة التطبيق الثنائية (ABI) تعكس واجهة التطبيق الثنائية (ABI) الفعلية ولا تحتوي على أي حالات عدم توافق (عمليات إزالة الرموز أو تغييرات الأنواع).
قد يؤدي كل تحليل من تحليلات واجهة التطبيق الثنائية إلى ضبط التصنيف Lint-1 وحظر إرسال التغييرات إلى أن يتم حل جميع المشاكل أو إلغاء التصنيف.
تعديل واجهة ABI لنواة النظام
إذا كان تعديل واجهة ABI أمرًا لا يمكن تجنُّبه، عليك تطبيق تغييرات الرمز وتمثيل واجهة ABI وقائمة الرموز على حزمة ACK. لإزالة القيمة -1 من خلال أداة Lint بدون التأثير في توافق GKI، اتّبِع الخطوات التالية:
انتظِر إلى أن تتلقّى Code-Review +2 لمجموعة التصحيحات.
ادمج تغييرات الرمز وتغيير تحديث ABI.
تحميل تغييرات رمز واجهة ABI إلى ACK
تعتمد عملية تعديل ACK ABI على نوع التغيير الذي يتم إجراؤه.
إذا كان تغيير ABI مرتبطًا بميزة تؤثر في اختبارات CTS أو VTS، يمكن عادةً اختيار التغيير وإضافته إلى ACK كما هو. على سبيل المثال:
- يجب توفّر aosp/1289677 لكي يعمل الصوت.
- يجب توفير aosp/1295945 حتى يعمل منفذ USB.
إذا كان تغيير ABI مخصّصًا لميزة يمكن مشاركتها مع حزمة ACK، يمكن اختيار هذا التغيير ونقله إلى حزمة ACK كما هو. على سبيل المثال، لا يلزم إجراء التغييرات التالية لاختبار CTS أو VTS، ولكن يمكن مشاركتها مع حزمة ACK:
- aosp/1250412 هو تغيير في إحدى ميزات إدارة درجة الحرارة.
- aosp/1288857
هو تغيير
EXPORT_SYMBOL_GPL
.
إذا كان تغيير واجهة التطبيق الثنائية (ABI) يقدّم ميزة جديدة لا يلزم تضمينها في ACK، يمكنك تقديم الرموز إلى ACK باستخدام رمز تجريبي كما هو موضّح في القسم التالي.
استخدام رموز ACK
يجب أن تكون الرموز الصورية ضرورية فقط لإجراء تغييرات أساسية في النواة لا تستفيد من ACK، مثل تغييرات الأداء والطاقة. توضّح القائمة التالية أمثلة على عمليات نقل جزئية لرموز برمجية في ACK لنواة GKI.
الرمز الزائف لميزة Core-isolate (aosp/1284493). لا تكون الإمكانات في ACK ضرورية، ولكن يجب أن تكون الرموز متوفّرة في ACK لكي تستخدم الوحدات هذه الرموز.
رمز العنصر النائب لوحدة المورّد (aosp/1288860)
اختيار ميزة تتبُّع أحداث
mm
على مستوى كل عملية، مع توفير واجهة ABI فقط (aosp/1288454). تم اختيار التصحيح الأصلي لدمجه في ACK ثم تم تقليصه ليشمل فقط التغييرات اللازمة لحل اختلاف ABI فيtask_struct
وmm_event_count
. يعمل هذا التصحيح أيضًا على تعديل التعدادmm_event_type
ليحتوي على الأعضاء النهائيين.اختيار جزئي للتغييرات في واجهة التطبيق الثنائية (ABI) الخاصة بالبنية الحرارية التي تطلّبت أكثر من مجرد إضافة حقول واجهة التطبيق الثنائية الجديدة
تم حلّ الاختلافات في واجهة التطبيق الثنائية (ABI) بين نواة الشريك ونواة ACK في التصحيح aosp/1255544.
أصلحت حزمة التصحيح 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 من خلال تحديد إصدارات الوحدات
تستخدم نواة Generic Kernel Image (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
. هذا السيناريو يطرح عدة مشاكل، وهي كما يلي:ويمكن أن يؤدي ذلك إلى إبطال الافتراضات التي يتخذها رمز النواة الأساسي بشأن بنى البيانات الداخلية.
إذا غيّر تحديث مستقبلي لنواة النظام
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 عند تحميل وحدة:
أنشئ نواة 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
ضمنيًا.ابحث عن الملف
.c
الذي تم تصدير الرمز الذي لا يتطابق مع CRC فيه، وذلك باستخدام الأمر التالي:git -C common grep EXPORT_SYMBOL.*module_layout kernel/module/version.c:EXPORT_SYMBOL(module_layout);
يحتوي ملف
.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 * )
قارِن بين الملفَين وأصلِح كل الاختلافات.
من الأفضل إنشاء 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
الإضافية.
دقة الشاشة في الإصدار 16 من نظام التشغيل Android والإصدارات الأحدث
تأكَّد من أنّ ملف المصدر المتأثر يتضمّن عنوان 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
الذي يتسبّب في الاختلاف، اتّبِع الخطوات التالية:
افتح ملف العنوان الذي يحدّد الرمز أو نوع البيانات الذي يتضمّن هذا الاختلاف. على سبيل المثال، عدِّل
include/linux/fwnode.h
لـstruct fwnode_handle
.أضِف الرمز التالي في أعلى ملف العنوان:
#ifdef CRC_CATCH #error "Included from here" #endif
في ملف
.c
الخاص بالوحدة الذي يتضمّن خطأ في تطابق CRC، أضِف ما يلي كسطر أول قبل أي من أسطر#include
.#define CRC_CATCH 1
جمِّع الوحدة. يعرض الخطأ الناتج في وقت الإنشاء سلسلة ملفات العناوين
#include
التي أدّت إلى عدم تطابق رمز التحقّق الدوري (CRC). مثلاً:In file included from .../drivers/clk/XXX.c:16:` In file included from .../include/linux/of_device.h:5: In file included from .../include/linux/cpu.h:17: In file included from .../include/linux/node.h:18: .../include/linux/device.h:16:2: error: "Included from here" #error "Included from here"
أحد الروابط في سلسلة
#include
هذه يعود إلى تغيير تم إجراؤه في النواة، وهو غير متوفّر في نواة GKI.
الحالة 2: الاختلافات بسبب تغييرات نوع البيانات
إذا لم يكن عدم تطابق CRC لرمز أو نوع بيانات ناتجًا عن اختلاف في مستوى الظهور، يكون السبب هو حدوث تغييرات فعلية (إضافات أو عمليات إزالة أو تغييرات) في نوع البيانات نفسه.
على سبيل المثال، يؤدي إجراء التغيير التالي في النواة إلى حدوث عدة أخطاء في تطابق CRC، لأنّ العديد من الرموز تتأثر بشكل غير مباشر بهذا النوع من التغيير:
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -259,7 +259,7 @@ struct iommu_ops {
void (*iotlb_sync)(struct iommu_domain *domain);
phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
- dma_addr_t iova);
+ dma_addr_t iova, unsigned long trans_flag);
int (*add_device)(struct device *dev);
void (*remove_device)(struct device *dev);
struct iommu_group *(*device_group)(struct device *dev);
حدث عدم تطابق واحد في CRC للسمة devm_of_platform_populate()
.
إذا قارنت ملفات .symtypes
لهذا الرمز، قد تبدو على النحو التالي:
$ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
--- <GKI>/drivers/of/platform.symtypes
+++ <your kernel>/drivers/of/platform.symtypes
@@ -399,7 +399,7 @@
...
-s#iommu_ops structure_type iommu_ops { ... ; t#phy
s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
( * add_device ) ( s#device * ) ; ...
+s#iommu_ops structure_type iommu_ops { ... ; t#phy
s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...
لتحديد النوع الذي تم تغييره، اتّبِع الخطوات التالية:
ابحث عن تعريف الرمز في رمز المصدر (عادةً في ملفات
.h
).- للاطّلاع على اختلافات الرموز بين النواة ونواة GKI، ابحث عن عملية الإيداع من خلال تنفيذ الأمر التالي:
git blame
- بالنسبة إلى الرموز المحذوفة (حيث يتم حذف رمز في شجرة وتريد أيضًا حذفه في الشجرة الأخرى)، عليك العثور على التغيير الذي حذف السطر. استخدِم الأمر التالي في الشجرة التي تم حذف السطر منها:
git log -S "copy paste of deleted line/word" -- <file where it was deleted>
راجِع قائمة عمليات الإيداع التي تم إرجاعها للعثور على التغيير أو الحذف. من المرجّح أنّ يكون أول عملية إيداع هي ما تبحث عنه. إذا لم يكن كذلك، انتقِل إلى أسفل القائمة حتى تعثر على عملية الإيداع.
بعد تحديد عملية الإيداع، يمكنك إما التراجع عنها في النواة أو تعديلها لإيقاف تغيير CRC وتحميلها إلى ACK ودمجها. يجب مراجعة كل تغيير غير متوافق في واجهة التطبيق الثنائية (ABI) للتأكّد من أمانه، ويمكن تسجيل تغيير مسموح به إذا لزم الأمر.
تفضيل استهلاك المساحة المتروكة الحالية
يتم توسيع بعض البُنى في 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
للحفاظ على ثبات symtype.
struct data {
u64 handle;
ANDROID_KABI_USE(1, void *sekret);
ANDROID_KABI_RESERVE(2);
};
دقة الشاشة في الإصدار 16 من نظام التشغيل Android والإصدارات الأحدث
يتم احتساب رموز 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
للحفاظ على ثبات 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;
};