پایداری رابط دودویی برنامه (ABI) پیش نیاز بهروزرسانیهای فقط چارچوب است، زیرا ماژولهای فروشنده ممکن است به کتابخانههای اشتراکگذاری شده کیت توسعه بومی Vendor (VNDK) وابسته باشند که در پارتیشن سیستم قرار دارند. در نسخه اندروید، کتابخانه های مشترک VNDK تازه ساخته شده باید با کتابخانه های مشترک VNDK قبلا منتشر شده سازگار باشد تا ماژول های فروشنده بتوانند بدون کامپایل مجدد و بدون خطاهای زمان اجرا با آن کتابخانه ها کار کنند. بین نسخههای اندروید، کتابخانههای 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 : [ "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-breaking طبقهبندی میشوند:
نوع داده | شرح |
---|---|
سازه ها و کلاس ها |
|
اتحادیه ها |
|
شمارش ها |
|
نمادهای جهانی |
|
* توابع عضو عمومی و خصوصی نباید تغییر یا حذف شوند زیرا توابع درون خطی عمومی می توانند به توابع عضو خصوصی اشاره کنند. ارجاع نمادها به توابع اعضای خصوصی را می توان در باینری های تماس گیرنده نگه داشت. تغییر یا حذف توابع اعضای خصوصی از کتابخانه های مشترک می تواند منجر به باینری های ناسازگار با عقب گرد شود.
** جایگزینی اعضای داده عمومی یا خصوصی نباید تغییر کند زیرا توابع درون خطی می توانند به این اعضای داده در بدنه عملکرد خود اشاره کنند. تغییر آفست اعضای داده می تواند منجر به باینری های ناسازگار با عقب شود.
*** در حالی که اینها چیدمان حافظه نوع را تغییر نمیدهند، تفاوتهای معنایی وجود دارد که میتواند منجر به کار نکردن کتابخانهها به صورت مورد انتظار شود.
استفاده از ابزارهای سازگاری 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
ابزار 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-linker
فایل های میانی تولید شده توسط header-abi-dumper
را به عنوان ورودی می گیرد و سپس آن فایل ها را پیوند می دهد:
ورودی ها |
|
---|---|
خروجی | فایلی که ABI یک کتابخانه مشترک را ثبت می کند (به عنوان مثال libfoo.so.lsdump نشان دهنده ABI libfoo است). |
این ابزار با در نظر گرفتن تفاوتهای یک تعریفی (انواع تعریف شده توسط کاربر در واحدهای ترجمه مختلف با یک نام کاملاً واجد شرایط یکسان، ممکن است از نظر معنایی متفاوت باشند) در بین واحدهای ترجمه، انواع نمودارها را در همه فایلهای میانی دادهشده به آن ادغام میکند. سپس این ابزار یک اسکریپت نسخه یا جدول .dynsym
کتابخانه مشترک (فایل .so
) را تجزیه می کند تا لیستی از نمادهای صادر شده ایجاد کند.
به عنوان مثال، زمانی که libfoo
فایل bar.cpp
(که bar
تابع C را در معرض نمایش میگذارد) به کامپایل خود اضافه میکند، header-abi-linker
میتواند برای ایجاد کامل ABI dump پیوندی 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
است. -
libfoo.so
تجزیه می کند و اطلاعات مربوط به نمادهای صادر شده توسط کتابخانه را از طریق جدول.dynsym
آن جمع آوری می کند. -
_Z3FooiP3bar
وBar
را اضافه می کند.
libfoo.so.lsdump
آخرین دامپ ABI تولید شده libfoo.so
است.
header-abi-diff
ابزار 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>
به عنوان مثال، برای به روز رسانی مراجع ABI libbinder
، اجرا کنید:
${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