استقرار ABI

يعد استقرار الواجهة الثنائية للتطبيق (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 ، مما يؤدي إلى تصدير المزيد من الأنواع:
  • 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:

نوع البيانات وصف
الهياكل والفئات
  • قم بتغيير حجم نوع الفصل أو نوع الهيكل.
  • الفئات الأساسية
    • إضافة أو إزالة الفئات الأساسية.
    • إضافة أو إزالة الفئات الأساسية الموروثة فعليًا.
    • قم بتغيير ترتيب الفئات الأساسية.
  • وظائف الأعضاء
    • إزالة وظائف الأعضاء *.
    • إضافة أو إزالة الوسائط من وظائف الأعضاء.
    • قم بتغيير أنواع الوسيطات أو أنواع الإرجاع لوظائف الأعضاء *.
    • تغيير تخطيط الجدول الافتراضي.
  • أعضاء البيانات
    • إزالة أعضاء البيانات الثابتة.
    • إضافة أو إزالة أعضاء البيانات غير الثابتة.
    • تغيير أنواع أعضاء البيانات.
    • قم بتغيير الإزاحات إلى أعضاء البيانات غير الثابتة **.
    • تغيير المؤهلات 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 بتوزيع ملف مصدر 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 كمدخلات ثم تربط هذه الملفات:

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

المدخلات
  • ملف .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>

على سبيل المثال ، لتحديث مراجع 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