پایداری ABI

پایداری رابط دودویی برنامه (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 صادر می‌شود، که منجر به صادرات انواع بیشتری می‌شود:
  • int : نوع m1 است.
  • int * : نوع m2 است.
  • foo_private_t * : نوع mPfoo است.

با این حال، 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 طبقه‌بندی می‌شوند:

نوع داده شرح
سازه ها و کلاس ها
  • اندازه نوع کلاس یا نوع ساختار را تغییر دهید.
  • کلاس های پایه
    • اضافه کردن یا حذف کلاس های پایه
    • کلاس های پایه تقریباً ارثی را اضافه یا حذف کنید.
    • ترتیب کلاس های پایه را تغییر دهید.
  • توابع عضو
    • حذف توابع عضو*.
    • آرگومان ها را از توابع عضو اضافه یا حذف کنید.
    • انواع آرگومان یا نوع بازگشتی توابع عضو* را تغییر دهید.
    • طرح جدول مجازی را تغییر دهید.
  • اعضای داده
    • اعضای داده استاتیک را حذف کنید.
    • اعضای داده غیراستاتیک را اضافه یا حذف کنید.
    • نوع اعضای داده را تغییر دهید.
    • افست ها را به اعضای داده غیر ایستا تغییر دهید**.
    • const ، volatile و/یا restricted اعضای داده را تغییر دهید.
    • مشخصات دسترسی اعضای داده را کاهش دهید***.
  • آرگومان های قالب را تغییر دهید.
اتحادیه ها
  • اعضای داده را اضافه یا حذف کنید.
  • اندازه نوع اتحادیه را تغییر دهید.
  • نوع اعضای داده را تغییر دهید.
  • ترتیب اعضای داده را تغییر دهید.
شمارش ها
  • نوع زیرین را تغییر دهید.
  • نام شمارش کنندگان را تغییر دهید.
  • مقادیر شمارشگرها را تغییر دهید.
نمادهای جهانی
  • نمادهای صادر شده توسط هدرهای عمومی را حذف کنید.
  • برای نمادهای جهانی از نوع FUNC
    • آرگومان ها را اضافه یا حذف کنید.
    • انواع آرگومان را تغییر دهید
    • نوع برگشت را تغییر دهید
    • تعیین کننده دسترسی را کاهش دهید***.
  • برای نمادهای جهانی از نوع OBJECT
    • نوع C/C++ مربوطه را تغییر دهید.
    • تعیین کننده دسترسی را کاهش دهید***.

* توابع عضو عمومی و خصوصی نباید تغییر یا حذف شوند زیرا توابع درون خطی عمومی می توانند به توابع عضو خصوصی اشاره کنند. ارجاع نمادها به توابع اعضای خصوصی را می توان در باینری های تماس گیرنده نگه داشت. تغییر یا حذف توابع اعضای خصوصی از کتابخانه های مشترک می تواند منجر به باینری های ناسازگار با عقب گرد شود.

** جایگزینی اعضای داده عمومی یا خصوصی نباید تغییر کند زیرا توابع درون خطی می توانند به این اعضای داده در بدنه عملکرد خود اشاره کنند. تغییر آفست اعضای داده می تواند منجر به باینری های ناسازگار با عقب شود.

*** در حالی که اینها چیدمان حافظه نوع را تغییر نمی‌دهند، تفاوت‌های معنایی وجود دارد که می‌تواند منجر به کار نکردن کتابخانه‌ها به صورت مورد انتظار شود.

استفاده از ابزارهای سازگاری 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 ساخته می شود:

  1. header-abi-dumper فایل‌های منبع کامپایل‌شده را برای ساخت کتابخانه VNDK (فایل‌های منبع خود کتابخانه و همچنین فایل‌های منبع به ارث رسیده از طریق وابستگی‌های انتقالی استاتیک) پردازش می‌کند تا فایل‌های .sdump را که با هر منبع مطابقت دارند تولید کند.
    sdump creation
    شکل 1. ایجاد فایل های .sdump
  2. سپس header-abi-linker فایل‌های .sdump را پردازش می‌کند (با استفاده از اسکریپت نسخه ارائه شده به آن یا فایل .so مربوط به کتابخانه مشترک) تا یک فایل .lsdump تولید کند که تمام اطلاعات ABI مربوط به کتابخانه مشترک را ثبت می‌کند.
    lsdump creation
    شکل 2. ایجاد فایل .lsdump
  3. header-abi-diff فایل .lsdump را با فایل مرجع .lsdump می کند تا گزارشی از تفاوت ایجاد کند که تفاوت های موجود در ABI های دو کتابخانه را مشخص می کند.
    abi diff creation
    شکل 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 را به عنوان ورودی می گیرد و سپس آن فایل ها را پیوند می دهد:

ورودی ها
  • فایل های میانی تولید شده توسط header-abi-dumper
  • نسخه اسکریپت/فایل نقشه (اختیاری)
  • فایل .so کتابخانه مشترک
خروجی فایلی که 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 را بیان می‌کند.

ورودی ها
  • فایل .lsdump که نشان دهنده ABI یک کتابخانه مشترک قدیمی است.
  • فایل .lsdump که نشان دهنده 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