إنّ ثبات واجهة التطبيق الثنائية (ABI) هو شرط أساسي لتطبيق التحديثات المتعلقة بالإطار فقط، لأنّ وحدات المورّدين قد تعتمد على مكتبات Vendor Native Development Kit (VNDK) المشترَكة التي تقع في قسم النظام. ضمن إصدار Android، يجب أن تكون مكتبات VNDK المشترَكة المنشأة حديثًا متوافقة مع ABI مع مكتبات VNDK المشترَكة التي تم إصدارها سابقًا حتى تتمكّن وحدات المورّدين من العمل مع هذه المكتبات بدون إعادة الترجمة وبدون أخطاء وقت التشغيل. يمكن تغيير مكتبات VNDK بين إصدارات Android، ولا تتوفّر أي ضمانات بشأن ABI.
للمساعدة في ضمان توافق ABI، يتضمّن الإصدار 9 من Android أداة تحقّق من ABI للعناوين، كما هو موضّح في الأقسام التالية.
لمحة عن امتثال VNDK وABI
VNDK هي مجموعة مفروض عليها قيود من المكتبات التي يمكن أن ترتبط بها وحدات المورّدين، والتي تتيح التحديثات للإطار الأساسي فقط. يشير التوافق مع ABI إلى قدرة إصدار أحدث من مكتبة مشتركة على العمل على النحو المتوقّع مع ملف برمجي مرتبط به ديناميكيًا (أي يعمل بالطريقة نفسها التي يعمل بها إصدار أقدم من مكتبة ).
لمحة عن الرموز التي تم تصديرها
يشير الرمز الذي تم تصديره (المعروف أيضًا باسم الرمز الشامل) إلى رمز يستوفي جميع الشروط التالية:
- يتم تصديرها من خلال الرؤوس العامة لمكتبة مشتركة.
- يظهر في جدول
.dynsym
من ملف.so
المرتبط بالمكتبة المشتركة. - أن يكون لها ربط WEAK أو GLOBAL
- يكون مستوى العرض "تلقائي" أو "محمي".
- فهرس القسم ليس UNDEFINED.
- يكون النوع إما 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" ], } |
جدول .dynsym | |||||||
---|---|---|---|---|---|---|---|
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:
نوع البيانات | الوصف |
---|---|
الهياكل والفئات |
|
الاتحادات |
|
التعدادات |
|
الرموز الشاملة |
|
* يجب عدم تغيير وظائف الأعضاء العامة والخاصة أو إزالتها لأنّ الوظائف المضمّنة العامة يمكن أن تشير إلى وظائف الأعضاء الخاصة. يمكن الاحتفاظ بمراجع الرموز إلى الدوال الخاصة للأعضاء في ملفات الثنائيات للمتصل. إنّ تغيير وظائف الأعضاء الخاصة أو إزالتها من المكتبات المشتركة قد يؤدي إلى إنشاء ملفات ثنائية غير متوافقة مع الإصدارات السابقة.
** يجب عدم تغيير العناصر المرجعية لأعضاء البيانات العامة أو الخاصة لأنّ الدوالّ المضمّنة يمكنها الإشارة إلى أعضاء البيانات هؤلاء في نص الدالة. يمكن أن يؤدي تغيير Offsets لأعضاء البيانات إلى توليد ملفَي binary غير متوافقَين مع الإصدارات القديمة.
*** على الرغم من أنّ هذه التغييرات لا تغيّر تنسيق الذاكرة للنوع، إلا أنّ هناك اختلافات دلالية قد تؤدي إلى عدم عمل المكتبات على النحو المتوقّع.
استخدام أدوات الامتثال لـ ABI
عند إنشاء مكتبة VNDK، تتم مقارنة واجهة ABI للمكتبة مع مرجع ABI المقابل لإصدار VNDK الذي يتم إنشاؤه. مرجع يمكن العثور على عمليات تفريغ ABI في:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
على سبيل المثال، عند إنشاء الإصدار libfoo
لنظام التشغيل x86 على مستوى واجهة برمجة التطبيقات 27،
تتم مقارنة ABI المستنتجة لإصدار libfoo
بمرجعها على الرابط التالي:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
خطأ في واجهة ABI
في حال حدوث مشاكل في ABI، يعرض سجلّ الإنشاء تحذيرات تتضمّن نوع التحذير و
مسارًا إلى تقرير abi-diff. على سبيل المثال، إذا كان ABI في 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
مرجعي لإنشاء تقرير اختلاف يوضّح الاختلافات في واجهات برمجة التطبيقات للمكتبتَين.
الشكل 3. إنشاء تقرير الاختلافات
header-abi-dumper
تُحلِّل أداة header-abi-dumper
ملف مصدر C/C++ وتُسجِّل
ABI المستنِدة من ملف المصدر هذا في ملف وسيط. يُشغِّل نظام الإنشاءheader-abi-dumper
على جميع الملفات المصدر المجمَّعة، وينشئ أيضًا مكتبة تتضمّن الملفات المصدر من الملحقات
غير المباشرة.
مداخل |
|
---|---|
الإخراج | ملف يصف واجهة برمجة التطبيقات لملف المصدر (على سبيل المثال،
foo.sdump يمثّل واجهة برمجة التطبيقات لنظام التشغيل foo.cpp ).
|
في الوقت الحالي، تكون ملفات .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 التي تصدّرها الرؤوس العامة في directory
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-linker
الملفات الوسيطة التي أنشأهاheader-abi-dumper
كإدخال، ثم تربط هذه الملفات:
مداخل |
|
---|---|
الإخراج | ملف يصف واجهة برمجة التطبيقات لنظام التشغيل لإحدى المكتبات المشتركة (على سبيل المثال،
libfoo.so.lsdump يمثّل واجهة برمجة التطبيقات لنظام التشغيل libfoo ).
|
تدمج الأداة الرسوم البيانية للأنواع في جميع الملفات الوسيطة التي يتم تقديمها إليها،
مع الأخذ في الاعتبار الاختلافات في التعريف الواحد (الأنواع التي يحدّدها المستخدم في وحدات ترجمة مختلفة
تحمل الاسم المحدَّد بالكامل نفسه، وقد تكون مختلفة من الناحية الدلالية
) في جميع وحدات الترجمة. بعد ذلك، تُحلِّل الأداة إما
نصًا برمجيًا للإصدار أو جدول .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
تقارن أداة header-abi-diff
بين ملفي .lsdump
يمثّلان ABI لمكتبتَين، وتُنشئ تقريرًا يوضّح اختلافات ABI بين المكتبتَين.
مداخل |
|
---|---|
الإخراج | تقرير اختلاف يوضّح الاختلافات في واجهات برمجة التطبيقات الأساسية التي تقدّمها المَرحلتَان المشترَكتَان المُقارَنتان |
ملف الاختلافات في ABI بتنسيق protobuf text format يخضع التنسيق للتغيير في الإصدارات المستقبلية.
على سبيل المثال، لديك نسختان من
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 *
(يتمّ إزالة جميع أنواع البيانات المحدّدة مسبقًا)
يشير الحقل type_stack
إلى كيفية وصول header-abi-diff
إلى النوع الذي تغيّر (bar
). قد تتم معالجة هذا الحقل
على أنّ Foo
هي دالة تم تصديرها وتستخدِم
bar *
كمَعلمة تشير إلى bar
التي تم
تصديرها وتغييرها.
فرض ABI وواجهة برمجة التطبيقات
لفرض ABI وواجهة برمجة التطبيقات في مكتبات 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