پایداری «میانای دودویی کاربردی» (ABI) پیشنیاز بهروزرسانیهای فقط چارچوب است، زیرا ممکن است ماژولهای فروشنده به کتابخانههای مشترک «کیت توسعه بومی فروشنده» (VNDK) که در بخش سیستم قرار دارند وابسته باشند. در نسخه Android، کتابخانههای مشترک VNDK جدیداً ساختهشده باید با کتابخانههای مشترک VNDK قبلاً منتشرشده سازگار با ABI باشند تا ماژولهای فروشنده بتوانند بدون بازهمگردانی و بدون خطاهای زمان اجرا با آن کتابخانهها کار کنند. بین نسخههای Android، کتابخانههای VNDK میتوانند تغییر کنند و هیچ تضمینی برای ABI وجود ندارد.
برای کمک به اطمینان از سازگاری ABI، Android 9 شامل یک بررسیکننده 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 طبقهبندی میشوند:
| نوع داده | شرح |
|---|---|
| ساختارها و کلاسها |
|
| اتحادیهها |
|
| فهرستها |
|
| نمادهای جهانی |
|
* توابع عضو عمومی و خصوصی نباید تغییر کنند یا حذف شوند زیرا توابع درونخطی عمومی میتوانند به توابع عضو خصوصی ارجاع دهند. ارجاعهای نماد به توابع عضو خصوصی میتواند در باینریهای تماسگیرنده حفظ شود. تغییر یا برداشتن کارکردهای عضو خصوصی از کتابخانههای مشترک میتواند منجر به باینریهای ناسازگار با عقب شود.
** آفستهای اعضای دادههای عمومی یا خصوصی نباید تغییر کند زیرا توابع درونخطی میتوانند به این اعضای داده در بدنه تابع خود ارجاع دهند. تغییر دادن افستهای عضو داده میتواند منجر به دودوییهای ناسازگار با نسخه قدیمی شود.
*** اگرچه این موارد چیدمان حافظه نوع را تغییر نمیدهند، تفاوتهای معنایی وجود دارد که میتواند منجر به عملکرد غیرمنتظره کتابخانهها شود.
استفاده از ابزارهای رعایت ABI
وقتی کتابخانه VNDK ساخته میشود، ABI کتابخانه با مرجع ABI مربوط به نسخه VNDK که درحال ساخت است مقایسه میشود. مرجع رونوشتهای ABI در اینجا قرار دارد:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
برای مثال، در ساخت libfoo برای x86 در سطح میانای برنامهسازی کاربردی ۲۷،
میانای دودویی کاربردی استنباطشده 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را که با هر منبع مطابقت دارد تولید کند.
شکل ۱. درحال ایجاد .sdumpفایلheader-abi-linkerسپس فایلهای.sdumpرا پردازش میکند (بااستفاده از اسکریپت نسخهای که به آن ارائه شده است یا فایل.soمربوط به کتابخانه همرسانیشده) تا فایل.lsdumpرا تولید کند که همه اطلاعات ABI مربوط به کتابخانه همرسانیشده را ثبت میکند.
شکل ۲. درحال ایجاد فایل .lsdumpheader-abi-diffفایل.lsdumpرا با فایل مرجع.lsdumpمقایسه میکند تا گزارش تفاوت تولید کند که تفاوتهای موجود در ABI دو کتابخانه را مشخص میکند.
شکل ۳. درحال ایجاد گزارش تفاوت
header-abi-dumper
ابزار header-abi-dumper فایل منبع C/C++ را تجزیه میکند و
«میانای کاربردی باینری» استنباطشده از آن فایل منبع را در یک فایل واسطه تخلیه میکند. سیستم ساخت
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 را که توسط سرایندهای عمومی در
دایرکتوری 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 را بهعنوان ورودی میگیرد و سپس آن فایلها را پیوند میدهد:
| ورودیها |
|
|---|---|
| خروجی | فایلی که ABI کتابخانه مشترک را توصیف میکند (برای مثال،
libfoo.so.lsdump نشاندهنده ABI 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 است.
تفاوت سرایند ABI
ابزار header-abi-diff دو فایل .lsdump را که نشاندهنده ABI دو کتابخانه هستند مقایسه میکند و گزارش تفاوتی تولید میکند که تفاوتهای بین دو ABI را بیان میکند.
| ورودیها |
|
|---|---|
| خروجی | گزارش diff که تفاوتهای ABI ارائهشده توسط دو کتابخانه مشترک مقایسهشده را بیان میکند. |
فایل تفاوت ABI در قالب نوشتاری protobuf است. قالب ممکن است در نسخههای پخش آینده تغییر کند.
برای مثال، دو نسخه از
libfoo دارید: libfoo_old.so و
libfoo_new.so. در libfoo_new.so، در
bar_t، نوع mfoo را از
foo_t به foo_t * تغییر میدهید. ازآنجاییکه bar_t نوعی
دسترسیپذیر است، header-abi-diff باید این مورد را بهعنوان تغییر ناسازگار ABI علامتگذاری کند.
برای اجرای 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 breaking در libfoo است. پیام record_type_diffs
نشان میدهد که گزارشی تغییر کرده است و تغییرات ناسازگار را فهرست میکند، که
شامل موارد زیر میشود:
- اندازه گزارش از
24بایت به8بایت تغییر میکند. - نوع فیلد
mfooازfooبهfoo *تغییر میکند (همه typedefها حذف میشوند).
فیلد 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