پایداری رابط دودویی برنامه (ABI) پیشنیاز بهروزرسانیهای صرفاً چارچوب است، زیرا ماژولهای فروشنده ممکن است به کتابخانههای اشتراکی کیت توسعه بومی فروشنده (VNDK) که در پارتیشن سیستم قرار دارند، وابسته باشند. در یک نسخه اندروید، کتابخانههای اشتراکی VNDK که به تازگی ساخته شدهاند باید با کتابخانههای اشتراکی VNDK که قبلاً منتشر شدهاند، از نظر ABI سازگار باشند تا ماژولهای فروشنده بتوانند بدون کامپایل مجدد و بدون خطاهای زمان اجرا با آن کتابخانهها کار کنند. بین نسخههای اندروید، کتابخانههای VNDK میتوانند تغییر کنند و هیچ تضمینی برای ABI وجود ندارد.
برای اطمینان از سازگاری ABI، اندروید ۹ شامل یک بررسیکنندهی ABI هدر است که در بخشهای بعدی توضیح داده شده است.
درباره انطباق با VNDK و ABI
VNDK مجموعهای محدود از کتابخانهها است که ماژولهای فروشنده ممکن است به آنها پیوند داشته باشند و بهروزرسانیهای فقط چارچوب را فعال میکنند. انطباق با ABI به توانایی یک نسخه جدیدتر از یک کتابخانه مشترک برای کار مطابق انتظار با ماژولی که به صورت پویا به آن پیوند داده شده است (یعنی مانند نسخه قدیمیتر کتابخانه کار میکند) اشاره دارد.
درباره نمادهای صادر شده
یک نماد صادر شده (که به عنوان نماد سراسری نیز شناخته میشود) به نمادی اشاره دارد که تمام موارد زیر را برآورده میکند:
- توسط سرآیندهای عمومی یک کتابخانه مشترک صادر میشود.
- در جدول
.dynsym مربوط به کتابخانه مشترک، در فایل.so نمایش داده میشود. - دارای الزام WEAK یا GLOBAL است.
- قابلیت مشاهده پیشفرض یا محافظتشده است.
- فهرست بخش تعریف نشده است.
- نوع یا 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; |
| اندروید.بیپی |
|---|
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 باید برای کتابخانههایی که در فایلهای Android.bp مربوطه با vendor_available: true و vndk.enabled: true علامتگذاری شدهاند، تضمین شود. برای مثال:
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 نمایش میدهد. برای مثال، اگر 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مربوط به هر منبع را تولید کند.
شکل ۱. ایجاد فایلهای .sdump - سپس
header-abi-linkerفایلهای.sdumpرا (با استفاده از اسکریپت نسخه ارائه شده به آن یا فایل.soمربوط به کتابخانه مشترک) پردازش میکند تا یک فایل.lsdumpتولید کند که تمام اطلاعات ABI مربوط به کتابخانه مشترک را ثبت میکند.
شکل ۲. ایجاد فایل .lsdump -
header-abi-diffفایل.lsdumpرا با یک فایل.lsdumpمرجع مقایسه میکند تا یک گزارش diff تولید کند که تفاوتهای ABI های دو کتابخانه را مشخص میکند.
شکل ۳. ایجاد گزارش تفاوت
هدر-ابی-دامپر
ابزار header-abi-dumper یک فایل منبع C/C++ را تجزیه میکند و ABI استنباط شده از آن فایل منبع را در یک فایل میانی ذخیره میکند. سیستم ساخت، header-abi-dumper روی تمام فایلهای منبع کامپایل شده اجرا میکند و در عین حال کتابخانهای میسازد که شامل فایلهای منبع از وابستگیهای انتقالی است.
| ورودیها |
|
|---|---|
| خروجی | فایلی که ABI فایل منبع را توصیف میکند (برای مثال، foo.sdump نشاندهنده ABI مربوط به 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-dumper را به عنوان ورودی دریافت میکند و سپس آن فایلها را به هم پیوند میدهد:
| ورودیها |
|
|---|---|
| خروجی | فایلی که ABI یک کتابخانه مشترک را توصیف میکند (برای مثال، libfoo.so.lsdump نشاندهنده ABI مربوط به libfoo است). |
این ابزار نمودارهای نوع را در تمام فایلهای میانی داده شده به آن ادغام میکند، و تفاوتهای تکتعریفی (انواع تعریفشده توسط کاربر در واحدهای ترجمه مختلف با نام کاملاً یکسان، ممکن است از نظر معنایی متفاوت باشند) را در بین واحدهای ترجمه در نظر میگیرد. سپس این ابزار یا یک اسکریپت نسخه یا جدول .dynsym از کتابخانه مشترک (فایل .so ) را تجزیه میکند تا لیستی از نمادهای صادر شده ایجاد کند.
برای مثال، libfoo از foo.cpp و bar.cpp تشکیل شده است. header-abi-linker میتواند برای ایجاد یک نسخه پشتیبان کامل از 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 دو کتابخانه هستند را مقایسه میکند و یک گزارش diff تولید میکند که تفاوتهای بین دو 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 breaking توسط 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 breaking در libfoo است. پیام record_type_diffs نشان میدهد که یک رکورد تغییر کرده است و تغییرات ناسازگار را فهرست میکند، که شامل موارد زیر است:
- اندازه رکورد از
24بایت به8بایت تغییر میکند. - نوع فیلد
mfooازfooبهfoo *تغییر میکند (تمام typedefها حذف میشوند).
فیلد type_stack نشان میدهد که header-abi-diff چگونه به نوعی که تغییر کرده ( bar ) رسیده است. این فیلد را میتوان به این صورت تفسیر کرد که Foo یک تابع export شده است که bar * را به عنوان پارامتر میگیرد، که به bar اشاره میکند، که export شده و تغییر کرده است.
اعمال 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>
برای مثال، برای بهروزرسانی ارجاعات ABI libbinder ، دستور زیر را اجرا کنید:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder