يعد استقرار الواجهة الثنائية للتطبيق (ABI) شرطًا أساسيًا لتحديثات إطار العمل فقط لأن وحدات البائع قد تعتمد على المكتبات المشتركة لمجموعة تطوير البائع الأصلية (VNDK) الموجودة في قسم النظام. ضمن إصدار Android ، يجب أن تكون مكتبات VNDK المشتركة المبنية حديثًا متوافقة مع ABI مع مكتبات VNDK المشتركة التي تم إصدارها مسبقًا حتى تتمكن وحدات البائعين من العمل مع تلك المكتبات دون إعادة تجميع وبدون أخطاء في وقت التشغيل. بين إصدارات Android ، يمكن تغيير مكتبات VNDK ولا توجد ضمانات من ABI.
للمساعدة في ضمان توافق ABI ، يشتمل Android 9 على مدقق ABI للرأس ، كما هو موضح في الأقسام التالية.
حول الامتثال VNDK و ABI
VNDK عبارة عن مجموعة مقيدة من المكتبات التي قد ترتبط بها وحدات البائعين والتي تمكّن تحديثات إطار العمل فقط. يشير توافق ABI إلى قدرة إصدار أحدث من مكتبة مشتركة على العمل كما هو متوقع مع وحدة نمطية مرتبطة بها ديناميكيًا (أي يعمل كإصدار أقدم من المكتبة).
حول الرموز المصدرة
يشير الرمز الذي تم تصديره (المعروف أيضًا باسم الرمز العام) إلى رمز يلبي كل ما يلي:
- تم تصديره بواسطة الرؤوس العامة لمكتبة مشتركة.
- يظهر في جدول
.dynsym
لملف.so
المطابق للمكتبة المشتركة. - لديه ربط ضعيف أو عالمي.
- الرؤية معيبة أو محمية.
- فهرس القسم غير محدد.
- النوع هو إما FUNC أو كائن.
يتم تعريف الرؤوس العامة للمكتبة المشتركة على أنها الرؤوس المتاحة للمكتبات / الثنائيات الأخرى من خلال 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 : [ "include" ], } |
جدول .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 . ( foot_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/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
على سبيل المثال ، عند بناء libfoo
لمستوى API 27 من libfoo
، تتم مقارنة ABI المستنتج من libfoo مع مرجعها في:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/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 ----
بناء مكتبة VNDK يتحقق ABI
عندما يتم إنشاء مكتبة 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
على جميع ملفات المصدر المترجمة أثناء إنشاء مكتبة تتضمن ملفات المصدر من التبعيات متعدية.
حاليًا ، يتم تنسيق ملفات .sdump
على هيئة Protobuf TextFormatted ، ولا يمكن ضمان استقرارها عبر الإصدارات المستقبلية. على هذا النحو ، يجب اعتبار تنسيق ملف .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 -- -x c++
يخبر هذا الأمر header-abi-dumper
foo.cpp
وإرسال معلومات ABI التي يتم عرضها في الرؤوس العامة في الدليل exported
. هذا مقتطف (ليس تمثيلاً كاملاً) من foo.sdump
إنشاؤه بواسطة header-abi-dumper
:
record_types { type_info { name: "foo" size: 12 alignment: 4 referenced_type: "type-1" source_file: "foo/include/foo_exported.h" linker_set_key: "foo" self_type: "type-1" } fields { referenced_type: "type-2" field_offset: 0 field_name: "m1" access: public_access } fields { referenced_type: "type-3" field_offset: 32 field_name: "m2" access: public_access } fields { referenced_type: "type-5" field_offset: 64 field_name: "mPfoo" access: public_access } access: public_access record_kind: struct_kind tag_info { unique_id: "_ZTS3foo" } } record_types { type_info { name: "bar" size: 12 alignment: 4 referenced_type: "type-6" … pointer_types { type_info { name: "bar *" size: 4 alignment: 4 referenced_type: "type-6" source_file: "foo/include/foo_exported.h" linker_set_key: "bar *" self_type: "type-8" } } builtin_types { type_info { name: "int" size: 4 alignment: 4 referenced_type: "type-2" source_file: "" linker_set_key: "int" self_type: "type-2" } is_unsigned: false is_integral: true } functions { return_type: "type-7" function_name: "Foo" source_file: "foo/include/foo_exported.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-8" default_arg: false } linker_set_key: "_Z3FooiP3bar" access: public_access }
يحتوي 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 يمثل ABI libfoo ). |
تقوم الأداة بدمج الرسوم البيانية للأنواع في جميع الملفات الوسيطة المعطاة لها ، مع مراعاة تعريف واحد (الأنواع المحددة من قبل المستخدم في وحدات الترجمة المختلفة التي تحمل نفس الاسم المؤهل بالكامل ، قد تكون مختلفة بشكل جوهري) عبر وحدات الترجمة. تقوم الأداة بعد ذلك بتوزيع برنامج نصي للإصدار أو جدول .dynsym
للمكتبة المشتركة (ملف .so
) لعمل قائمة بالرموز التي تم تصديرها.
على سبيل المثال ، عندما يضيف libfoo
ملف bar.cpp
(الذي يعرض bar
دالة C) إلى تجميعه ، يمكن استدعاء 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
:
record_types { type_info { name: "foo" size: 24 alignment: 8 referenced_type: "type-1" source_file: "foo/include/foo_exported.h" linker_set_key: "foo" self_type: "type-1" } fields { referenced_type: "type-2" field_offset: 0 field_name: "m1" access: public_access } fields { referenced_type: "type-3" field_offset: 64 field_name: "m2" access: public_access } fields { referenced_type: "type-4" field_offset: 128 field_name: "mPfoo" access: public_access } access: public_access record_kind: struct_kind tag_info { unique_id: "_ZTS3foo" } } record_types { type_info { name: "bar" size: 24 alignment: 8 ... builtin_types { type_info { name: "void" size: 0 alignment: 0 referenced_type: "type-6" source_file: "" linker_set_key: "void" self_type: "type-6" } is_unsigned: false is_integral: false } functions { return_type: "type-19" function_name: "Foo" source_file: "foo/include/foo_exported.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-20" default_arg: false } linker_set_key: "_Z3FooiP3bar" access: public_access } functions { return_type: "type-6" function_name: "FooBad" source_file: "foo/include/foo_exported_bad.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-7" default_arg: false } linker_set_key: "_Z6FooBadiP3foo" access: public_access } elf_functions { name: "_Z3FooiP3bar" } elf_functions { name: "_Z6FooBadiP3foo" }
أداة header-abi-linker
:
- يربط ملفات
.sdump
المتوفرة لها (foo.sdump
وbar.sdump
) ، مع تصفية معلومات ABI غير الموجودة في الرؤوس الموجودة في الدليل:exported
. - Parses
libfoo.so
، ويجمع معلومات حول الرموز التي تم تصديرها بواسطة المكتبة من خلال جدول.dynsym
الخاص بها. - يضيف
_Z3FooiP3bar
Bar
libfoo.so.lsdump
هو تفريغ ABI النهائي الذي تم إنشاؤه لـ libfoo.so
.
رأس أبي فرق
تقارن أداة header-abi-diff
ملفين .lsdump
يمثلان ABI لمكتبتين وتنتج تقرير فرق يوضح الاختلافات بين جهازي ABI.
المدخلات |
|
---|---|
انتاج | | تقرير مقارنة يوضح الاختلافات في ABI التي تقدمها المكتبتان المشتركتان مقارنة. |
تم تصميم ملف ABI diff ليكون مطولًا وقابلًا للقراءة قدر الإمكان. التنسيق عرضة للتغيير في الإصدارات المستقبلية. على سبيل المثال ، لديك نسختان من 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 / API
لفرض ABI / API للمكتبات المشتركة VNDK و LLNDK ، يجب التحقق من مراجع ABI في ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/
. لإنشاء هذه المراجع ، قم بتشغيل الأمر التالي:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
بعد إنشاء المراجع ، فإن أي تغيير يتم إجراؤه على الكود المصدري ينتج عنه تغيير ABI / API غير متوافق في مكتبة VNDK أو LLNDK يؤدي الآن إلى حدوث خطأ في البناء.
لتحديث مراجع ABI لمكتبات VNDK الأساسية المحددة ، قم بتشغيل الأمر التالي:
${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
لتحديث مراجع ABI لمكتبات LLNDK معينة ، قم بتشغيل الأمر التالي:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
على سبيل المثال ، لتحديث مراجع libm
ABI ، قم بتشغيل:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk