يعد استقرار الواجهة الثنائية للتطبيق (ABI) شرطًا أساسيًا لتحديثات إطار العمل فقط لأن وحدات البائع قد تعتمد على المكتبات المشتركة Vendor Native Development Kit (VNDK) الموجودة في قسم النظام. ضمن إصدار Android، يجب أن تكون مكتبات VNDK المشتركة المبنية حديثًا متوافقة مع ABI لمكتبات VNDK المشتركة التي تم إصدارها مسبقًا حتى تتمكن وحدات البائع من العمل مع تلك المكتبات دون إعادة الترجمة ودون أخطاء وقت التشغيل. بين إصدارات Android، يمكن تغيير مكتبات VNDK ولا توجد ضمانات لـ ABI.
للمساعدة في ضمان توافق ABI، يتضمن Android 9 مدقق ABI للرأس، كما هو موضح في الأقسام التالية.
حول امتثال VNDK وABI
VNDK عبارة عن مجموعة مقيدة من المكتبات التي قد ترتبط بها وحدات البائع والتي تتيح تحديثات إطار العمل فقط. يشير توافق ABI إلى قدرة الإصدار الأحدث من المكتبة المشتركة على العمل كما هو متوقع مع وحدة مرتبطة به ديناميكيًا (أي يعمل كإصدار أقدم من المكتبة).
حول الرموز المصدرة
يشير الرمز الذي تم تصديره (المعروف أيضًا بالرمز العالمي ) إلى رمز يلبي جميع ما يلي:
- تم التصدير بواسطة الرؤوس العامة للمكتبة المشتركة.
- يظهر في جدول
.dynsym
لملف.so
المطابق للمكتبة المشتركة. - لديه ربط ضعيف أو عالمي.
- الرؤية افتراضية أو محمية.
- فهرس القسم ليس غير محدد.
- النوع إما FUNC أو OBJECT.
يتم تعريف الرؤوس العامة للمكتبة المشتركة على أنها الرؤوس المتاحة للمكتبات/الثنائيات الأخرى من خلال سمات export_include_dirs
و export_header_lib_headers
و export_static_lib_headers
و export_shared_lib_headers
و export_generated_headers
في تعريفات Android.bp
للوحدة المقابلة للمكتبة المشتركة.
حول الأنواع التي يمكن الوصول إليها
النوع القابل للوصول هو أي نوع مدمج في C/C++ أو نوع محدد من قبل المستخدم يمكن الوصول إليه بشكل مباشر أو غير مباشر من خلال رمز مُصدَّر ويتم تصديره من خلال الرؤوس العامة. على سبيل المثال، يحتوي libfoo.so
على الدالة Foo
، وهي عبارة عن رمز مُصدَّر موجود في جدول .dynsym
. تتضمن مكتبة libfoo.so
ما يلي:
foo_exported.h | foo.private.h |
---|---|
typedef struct foo_private foo_private_t; typedef struct foo { int m1; int *m2; foo_private_t *mPfoo; } foo_t; typedef struct bar { foo_t mfoo; } bar_t; bool Foo(int id, bar_t *bar_ptr); | typedef struct foo_private { int m1; float mbar; } foo_private_t; |
Android.bp |
---|
cc_library { name : libfoo, vendor_available: true, vndk { enabled : true, } srcs : ["src/*.cpp"], export_include_dirs : [ "exported" ], } |
.جدول الديناميكية | |||||||
---|---|---|---|---|---|---|---|
Num | Value | Size | Type | Bind | Vis | Ndx | Name |
1 | 0 | 0 | FUNC | GLOB | DEF | UND | dlerror@libc |
2 | 1ce0 | 20 | FUNC | GLOB | DEF | 12 | Foo |
بالنظر إلى Foo
، تتضمن الأنواع التي يمكن الوصول إليها بشكل مباشر/غير مباشر ما يلي:
يكتب | وصف |
---|---|
bool | نوع الإرجاع من Foo . |
int | نوع المعلمة Foo الأولى. |
bar_t * | نوع المعلمة Foo الثانية. عن طريق bar_t * ، يتم تصدير bar_t من خلال foo_exported.h .يحتوي bar_t على عضو mfoo من النوع foo_t ، والذي يتم تصديره من خلال foo_exported.h ، مما يؤدي إلى تصدير المزيد من الأنواع:
ومع ذلك، لا يمكن الوصول إلى foo_private_t لأنه لا يتم تصديره من خلال foo_exported.h . ( foo_private_t * غير شفاف، لذلك يُسمح بالتغييرات التي تم إجراؤها على foo_private_t .) |
يمكن تقديم تفسير مماثل للأنواع التي يمكن الوصول إليها من خلال محددات الفئة الأساسية ومعلمات القالب أيضًا.
ضمان الامتثال لـ ABI
يجب التأكد من توافق ABI للمكتبات التي تم وضع علامة vendor_available: true
و vndk.enabled: true
في ملفات Android.bp
المقابلة. على سبيل المثال:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
بالنسبة لأنواع البيانات التي يمكن الوصول إليها بشكل مباشر أو غير مباشر بواسطة دالة مُصدَّرة، يتم تصنيف التغييرات التالية على المكتبة على أنها تغييرات في ABI:
نوع البيانات | وصف |
---|---|
الهياكل والطبقات |
|
النقابات |
|
التعدادات |
|
الرموز العالمية |
|
* يجب عدم تغيير أو إزالة وظائف الأعضاء العامة والخاصة لأن الوظائف المضمنة العامة يمكن أن تشير إلى وظائف الأعضاء الخاصة. يمكن الاحتفاظ بمراجع الرمز لوظائف الأعضاء الخاصة في ثنائيات المتصل. يمكن أن يؤدي تغيير وظائف الأعضاء الخاصة أو إزالتها من المكتبات المشتركة إلى ظهور ثنائيات غير متوافقة مع الإصدارات السابقة.
** لا يجب تغيير الإزاحات لأعضاء البيانات العامة أو الخاصة لأن الوظائف المضمنة يمكن أن تشير إلى أعضاء البيانات هؤلاء في نص وظائفهم. يمكن أن يؤدي تغيير إزاحات أعضاء البيانات إلى ظهور ثنائيات غير متوافقة مع الإصدارات السابقة.
*** على الرغم من أن هذه لا تغير تخطيط الذاكرة للنوع، إلا أن هناك اختلافات دلالية قد تؤدي إلى عدم عمل المكتبات كما هو متوقع.
استخدام أدوات الامتثال لـ ABI
عند إنشاء مكتبة VNDK، تتم مقارنة ABI للمكتبة مع مرجع ABI المقابل لإصدار VNDK الذي يتم إنشاؤه. توجد تفريغات ABI المرجعية في:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
على سبيل المثال، عند بناء libfoo
لـ x86 على مستوى واجهة برمجة التطبيقات (API) 27، تتم مقارنة واجهة برمجة التطبيقات (ABI) المستنتجة من libfoo
مع مرجعها في:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
خطأ في كسر ABI
في حالات كسر ABI، يعرض سجل البناء تحذيرات بنوع التحذير ومسارًا إلى تقرير abi-diff. على سبيل المثال، إذا كان لدى واجهة برمجة التطبيقات الخاصة بـ libbinder
تغيير غير متوافق، فسيقوم نظام الإنشاء بإلقاء خطأ مع رسالة مشابهة لما يلي:
***************************************************** error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES Please check compatibility report at: out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff ****************************************************** ---- Please update abi references by running platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ----
بناء اختبارات ABI لمكتبة VNDK
عندما يتم إنشاء مكتبة VNDK:
- يعالج
header-abi-dumper
الملفات المصدر المجمعة لبناء مكتبة VNDK (ملفات المصدر الخاصة بالمكتبة وكذلك الملفات المصدر الموروثة من خلال التبعيات المتعدية الثابتة)، لإنتاج ملفات.sdump
التي تتوافق مع كل مصدر.الشكل 1. إنشاء ملفات .sdump
- يقوم
header-abi-linker
بعد ذلك بمعالجة ملفات.sdump
(باستخدام إما برنامج نصي للإصدار المقدم له أو ملف.so
المطابق للمكتبة المشتركة) لإنتاج ملف.lsdump
الذي يسجل جميع معلومات ABI المقابلة للمكتبة المشتركة.الشكل 2. إنشاء ملف .lsdump
- يقارن
header-abi-diff
ملف.lsdump
بملف.lsdump
مرجعي لإنتاج تقرير فرق يوضح الاختلافات في واجهات ABI للمكتبتين.الشكل 3. إنشاء تقرير الفرق
رأس أبي قلابة
تقوم أداة header-abi-dumper
بتوزيع ملف مصدر C/C++ وتفريغ ABI المستنتج من هذا الملف المصدر إلى ملف وسيط. يقوم نظام البناء بتشغيل header-abi-dumper
على جميع ملفات المصدر المترجمة بينما يقوم أيضًا ببناء مكتبة تتضمن الملفات المصدر من التبعيات المتعدية.
المدخلات |
|
---|---|
انتاج | | ملف يصف ABI للملف المصدر (على سبيل المثال، foo.sdump يمثل foo.cpp 's ABI). |
تتوفر حاليًا ملفات .sdump
بتنسيق JSON، وهو أمر غير مضمون أن يكون مستقرًا عبر الإصدارات المستقبلية. على هذا النحو، ينبغي اعتبار تنسيق الملف .sdump
أحد تفاصيل تنفيذ نظام البناء.
على سبيل المثال، يحتوي libfoo.so
على الملف المصدر التالي foo.cpp
:
#include <stdio.h> #include <foo_exported.h> bool Foo(int id, bar_t *bar_ptr) { if (id > 0 && bar_ptr->mfoo.m1 > 0) { return true; } return false; }
يمكنك استخدام header-abi-dumper
لإنشاء ملف .sdump
متوسط يمثل ABI المقدم بواسطة الملف المصدر باستخدام:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
يخبر هذا الأمر header-abi-dumper
بتحليل foo.cpp
مع إشارات المترجم التالية --
وإصدار معلومات ABI التي يتم تصديرها بواسطة الرؤوس العامة في الدليل exported
. ما يلي هو foo.sdump
الذي تم إنشاؤه بواسطة header-abi-dumper
:
{ "array_types" : [], "builtin_types" : [ { "alignment" : 4, "is_integral" : true, "linker_set_key" : "_ZTIi", "name" : "int", "referenced_type" : "_ZTIi", "self_type" : "_ZTIi", "size" : 4 } ], "elf_functions" : [], "elf_objects" : [], "enum_types" : [], "function_types" : [], "functions" : [ { "function_name" : "FooBad", "linker_set_key" : "_Z6FooBadiP3foo", "parameters" : [ { "referenced_type" : "_ZTIi" }, { "referenced_type" : "_ZTIP3foo" } ], "return_type" : "_ZTI3bar", "source_file" : "exported/foo_exported.h" } ], "global_vars" : [], "lvalue_reference_types" : [], "pointer_types" : [ { "alignment" : 8, "linker_set_key" : "_ZTIP11foo_private", "name" : "foo_private *", "referenced_type" : "_ZTI11foo_private", "self_type" : "_ZTIP11foo_private", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIP3foo", "name" : "foo *", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTIP3foo", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIPi", "name" : "int *", "referenced_type" : "_ZTIi", "self_type" : "_ZTIPi", "size" : 8, "source_file" : "exported/foo_exported.h" } ], "qualified_types" : [], "record_types" : [ { "alignment" : 8, "fields" : [ { "field_name" : "mfoo", "referenced_type" : "_ZTI3foo" } ], "linker_set_key" : "_ZTI3bar", "name" : "bar", "referenced_type" : "_ZTI3bar", "self_type" : "_ZTI3bar", "size" : 24, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "fields" : [ { "field_name" : "m1", "referenced_type" : "_ZTIi" }, { "field_name" : "m2", "field_offset" : 64, "referenced_type" : "_ZTIPi" }, { "field_name" : "mPfoo", "field_offset" : 128, "referenced_type" : "_ZTIP11foo_private" } ], "linker_set_key" : "_ZTI3foo", "name" : "foo", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTI3foo", "size" : 24, "source_file" : "exported/foo_exported.h" } ], "rvalue_reference_types" : [] }
يحتوي foo.sdump
على معلومات ABI التي تم تصديرها بواسطة الملف المصدر foo.cpp
والرؤوس العامة، على سبيل المثال،
-
record_types
. ارجع إلى الهياكل أو الاتحادات أو الفئات المحددة في الرؤوس العامة. يحتوي كل نوع سجل على معلومات حول حقوله وحجمه ومحدد الوصول وملف الرأس الذي تم تعريفه فيه والسمات الأخرى. -
pointer_types
. قم بالإشارة إلى أنواع المؤشر المشار إليها بشكل مباشر/غير مباشر بواسطة السجلات/الوظائف المصدرة في الرؤوس العامة، بالإضافة إلى النوع الذي يشير إليه المؤشر (من خلال حقلreferenced_type
فيtype_info
). يتم تسجيل معلومات مماثلة في ملف.sdump
للأنواع المؤهلة، وأنواع C/C++ المضمنة، وأنواع المصفوفات، وأنواع مرجع lvalue وrvalue. تسمح هذه المعلومات بالتمييز العودي. -
functions
. تمثيل الوظائف المصدرة بواسطة الرؤوس العامة. لديهم أيضًا معلومات حول اسم الوظيفة المشوه، ونوع الإرجاع، وأنواع المعلمات، ومحدد الوصول، والسمات الأخرى.
رأس أبي رابط
تأخذ أداة header-abi-linker
الملفات المتوسطة التي تنتجها header-abi-dumper
كمدخلات ثم تربط تلك الملفات:
المدخلات |
|
---|---|
انتاج | | ملف يصف ABI لمكتبة مشتركة (على سبيل المثال، libfoo.so.lsdump يمثل libfoo 's ABI). |
تقوم الأداة بدمج الرسوم البيانية للنوع في جميع الملفات الوسيطة المعطاة لها، مع الأخذ في الاعتبار اختلافات التعريف الواحد (الأنواع المحددة بواسطة المستخدم في وحدات ترجمة مختلفة بنفس الاسم المؤهل بالكامل، قد تكون مختلفة لغويًا) عبر وحدات الترجمة. تقوم الأداة بعد ذلك بتوزيع البرنامج النصي للإصدار أو جدول .dynsym
الخاص بالمكتبة المشتركة (ملف .so
) لإنشاء قائمة بالرموز المصدرة.
على سبيل المثال، يتكون libfoo
من foo.cpp
و bar.cpp
. يمكن استدعاء header-abi-linker
لإنشاء تفريغ ABI المرتبط الكامل لـ libfoo
كما يلي:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
مثال على إخراج الأمر في libfoo.so.lsdump
:
{ "array_types" : [], "builtin_types" : [ { "alignment" : 1, "is_integral" : true, "is_unsigned" : true, "linker_set_key" : "_ZTIb", "name" : "bool", "referenced_type" : "_ZTIb", "self_type" : "_ZTIb", "size" : 1 }, { "alignment" : 4, "is_integral" : true, "linker_set_key" : "_ZTIi", "name" : "int", "referenced_type" : "_ZTIi", "self_type" : "_ZTIi", "size" : 4 } ], "elf_functions" : [ { "name" : "_Z3FooiP3bar" }, { "name" : "_Z6FooBadiP3foo" } ], "elf_objects" : [], "enum_types" : [], "function_types" : [], "functions" : [ { "function_name" : "Foo", "linker_set_key" : "_Z3FooiP3bar", "parameters" : [ { "referenced_type" : "_ZTIi" }, { "referenced_type" : "_ZTIP3bar" } ], "return_type" : "_ZTIb", "source_file" : "exported/foo_exported.h" }, { "function_name" : "FooBad", "linker_set_key" : "_Z6FooBadiP3foo", "parameters" : [ { "referenced_type" : "_ZTIi" }, { "referenced_type" : "_ZTIP3foo" } ], "return_type" : "_ZTI3bar", "source_file" : "exported/foo_exported.h" } ], "global_vars" : [], "lvalue_reference_types" : [], "pointer_types" : [ { "alignment" : 8, "linker_set_key" : "_ZTIP11foo_private", "name" : "foo_private *", "referenced_type" : "_ZTI11foo_private", "self_type" : "_ZTIP11foo_private", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIP3bar", "name" : "bar *", "referenced_type" : "_ZTI3bar", "self_type" : "_ZTIP3bar", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIP3foo", "name" : "foo *", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTIP3foo", "size" : 8, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "linker_set_key" : "_ZTIPi", "name" : "int *", "referenced_type" : "_ZTIi", "self_type" : "_ZTIPi", "size" : 8, "source_file" : "exported/foo_exported.h" } ], "qualified_types" : [], "record_types" : [ { "alignment" : 8, "fields" : [ { "field_name" : "mfoo", "referenced_type" : "_ZTI3foo" } ], "linker_set_key" : "_ZTI3bar", "name" : "bar", "referenced_type" : "_ZTI3bar", "self_type" : "_ZTI3bar", "size" : 24, "source_file" : "exported/foo_exported.h" }, { "alignment" : 8, "fields" : [ { "field_name" : "m1", "referenced_type" : "_ZTIi" }, { "field_name" : "m2", "field_offset" : 64, "referenced_type" : "_ZTIPi" }, { "field_name" : "mPfoo", "field_offset" : 128, "referenced_type" : "_ZTIP11foo_private" } ], "linker_set_key" : "_ZTI3foo", "name" : "foo", "referenced_type" : "_ZTI3foo", "self_type" : "_ZTI3foo", "size" : 24, "source_file" : "exported/foo_exported.h" } ], "rvalue_reference_types" : [] }
أداة header-abi-linker
:
- يربط ملفات
.sdump
المقدمة إليه (foo.sdump
وbar.sdump
)، ويقوم بتصفية معلومات ABI غير الموجودة في الرؤوس الموجودة في الدليل:exported
. - يوزع
libfoo.so
ويجمع معلومات حول الرموز التي تصدرها المكتبة من خلال جدول.dynsym
الخاص بها. - يضيف
_Z3FooiP3bar
و_Z6FooBadiP3foo
.
libfoo.so.lsdump
هو تفريغ ABI النهائي الذي تم إنشاؤه لـ libfoo.so
.
رأس أبي فرق
تقوم أداة header-abi-diff
بمقارنة ملفين .lsdump
يمثلان واجهة التطبيق الثنائية (ABI) لمكتبتين وتنتج تقريرًا مختلفًا يوضح الاختلافات بين واجهتي ABI.
المدخلات |
|
---|---|
انتاج | | تقرير فرق يوضح الاختلافات في واجهات التطبيقات الثنائية التي تقدمها المكتبتان المشتركتان مقارنة. |
ملف ABI diff موجود بتنسيق نص protobuf . الشكل عرضة للتغيير في الإصدارات المستقبلية.
على سبيل المثال، لديك إصداران من libfoo
: libfoo_old.so
و libfoo_new.so
. في libfoo_new.so
، في bar_t
، يمكنك تغيير نوع mfoo
من foo_t
إلى foo_t *
. نظرًا لأن bar_t
هو نوع يمكن الوصول إليه، فيجب وضع علامة عليه كتغيير كسر ABI بواسطة header-abi-diff
.
لتشغيل header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
مثال على إخراج الأمر في libfoo.so.abidiff
:
lib_name: "libfoo" arch: "arm64" record_type_diffs { name: "bar" type_stack: "Foo-> bar *->bar " type_info_diff { old_type_info { size: 24 alignment: 8 } new_type_info { size: 8 alignment: 8 } } fields_diff { old_field { referenced_type: "foo" field_offset: 0 field_name: "mfoo" access: public_access } new_field { referenced_type: "foo *" field_offset: 0 field_name: "mfoo" access: public_access } } }
يحتوي libfoo.so.abidiff
على تقرير بجميع تغييرات كسر ABI في libfoo
. تشير رسالة record_type_diffs
إلى أن السجل قد تغير وتسرد التغييرات غير المتوافقة، والتي تتضمن:
- يتغير حجم السجل من
24
بايت إلى8
بايت. - يتغير نوع حقل
mfoo
منfoo
إلىfoo *
(يتم تجريد كافة أنواع typedefs).
يشير الحقل type_stack
إلى كيفية وصول header-abi-diff
إلى النوع الذي تغير ( bar
). يمكن تفسير هذا الحقل على أنه Foo
هي دالة مُصدَّرة تأخذ bar *
كمعلمة، وتشير إلى bar
، الذي تم تصديره وتغييره.
فرض ABI/API
لفرض ABI/API لمكتبات VNDK المشتركة، يجب التحقق من مراجع ABI في ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
. لإنشاء هذه المراجع، قم بتشغيل الأمر التالي:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
بعد إنشاء المراجع، أي تغيير يتم إجراؤه على التعليمات البرمجية المصدر والذي يؤدي إلى تغيير غير متوافق في ABI/API في مكتبة VNDK يؤدي الآن إلى خطأ في البناء.
لتحديث مراجع ABI لمكتبات محددة، قم بتشغيل الأمر التالي:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
على سبيل المثال، لتحديث مراجع libbinder
ABI، قم بتشغيل:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder