יציבות של ממשק בינארי של יישומים (ABI) היא תנאי מוקדם לעדכוני מסגרת בלבד מכיוון שמודולי הספק עשויים להיות תלויים בספריות המשותפות של ערכת הפיתוח המקורית של הספק (VNDK) השוכנות במחיצת המערכת. בתוך מהדורת אנדרואיד, ספריות משותפות VNDK שנבנו לאחרונה חייבות להיות תואמות ABI לספריות משותפות VNDK שפורסמו בעבר, כך שמודולי הספק יוכלו לעבוד עם ספריות אלה ללא הידור מחדש וללא שגיאות זמן ריצה. בין מהדורות אנדרואיד, ניתן לשנות ספריות VNDK ואין ערבויות של ABI.
כדי לעזור להבטיח תאימות ABI, אנדרואיד 9 כולל בודק כותרת ABI, כמתואר בסעיפים הבאים.
על תאימות VNDK ו-ABI
ה-VNDK הוא קבוצה מגבילה של ספריות שמודולי הספק עשויים לקשר אליהן ומאפשרות עדכונים למסגרת בלבד. תאימות ABI מתייחסת ליכולת של גרסה חדשה יותר של ספרייה משותפת לעבוד כצפוי עם מודול שמקושר אליה באופן דינמי (כלומר עובד כמו גרסה ישנה יותר של הספרייה).
על סמלים מיוצאים
סמל מיוצא (הידוע גם כסמל גלובלי ) מתייחס לסמל המקיים את כל התנאים הבאים:
- מיוצא על ידי הכותרות הציבוריות של ספרייה משותפת.
- מופיע בטבלת
.dynsym
של קובץ.so
התואם לספרייה המשותפת. - בעל כריכה חלשה או גלובלית.
- הראות היא DeFAULT או PROTECTED.
- אינדקס המדור אינו מוגדר.
- הסוג הוא 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 ברמת 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
יש שינוי לא תואם, מערכת ה-build זורקת שגיאה עם הודעה הדומה להודעה הבאה:
***************************************************** 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
המתאימים לכל מקור.איור 1. יצירת קבצי .sdump
-
header-abi-linker
לאחר מכן מעבד את קבצי.sdump
(באמצעות סקריפט גרסה שסופק לו או קובץ ה-.so
המתאים לספרייה המשותפת) כדי לייצר קובץ.lsdump
שמתעד את כל מידע ה-ABI המתאים לספרייה המשותפת.איור 2. יצירת קובץ .lsdump
-
header-abi-diff
משווה את קובץ.lsdump
עם קובץ.lsdump
רפרנס כדי לייצר דוח diff שמתאר את ההבדלים ב-ABI של שתי הספריות.איור 3. יצירת דוח ההבדל
header-abi-dumper
הכלי header-abi-dumper
מנתח קובץ מקור C/C++ ומשליך את ה-ABI המסיק מקובץ המקור הזה לקובץ ביניים. מערכת ה-build מריצה 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-linker
לוקח את קבצי הביניים המיוצרים על ידי header-abi-dumper
כקלט ואז מקשר את הקבצים האלה:
תשומות |
|
---|---|
תְפוּקָה | קובץ שמתאר את ה-ABI של ספרייה משותפת (לדוגמה, libfoo.so.lsdump מייצג את ה-ABI של libfoo ). |
הכלי ממזג את גרפי הסוג בכל קבצי הביניים שניתנו לו, תוך התחשבות בהבדלים בהגדרה אחת (סוגים המוגדרים על ידי משתמש ביחידות תרגום שונות עם אותו שם מלא, עשויים להיות שונים מבחינה סמנטית) בין יחידות תרגום. לאחר מכן הכלי מנתח סקריפט גרסה או טבלת .dynsym
של הספרייה המשותפת (קובץ .so
) כדי ליצור רשימה של הסמלים המיוצאים.
לדוגמה, libfoo
מורכב מ- foo.cpp
ו- bar.cpp
. ניתן להפעיל header-abi-linker
כדי ליצור את dump 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 ה-dump הסופי שנוצר של libfoo.so
.
header-abi-diff
הכלי header-abi-diff
משווה שני קבצי .lsdump
המייצגים את ה-ABI של שתי ספריות ומפיק דוח diff המציין את ההבדלים בין שני ה-ABIs.
תשומות |
|
---|---|
תְפוּקָה | דוח הבדל המציין את ההבדלים ב-ABIs המוצעים על ידי שתי הספריות המשותפות בהשוואה. |
קובץ ABI diff הוא בפורמט טקסט protobuf . הפורמט נתון לשינויים במהדורות עתידיות.
לדוגמה, יש לך שתי גרסאות של 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 *
(כל ה-typedefs נמחקים).
השדה 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>
לדוגמה, כדי לעדכן הפניות ABI libbinder
, הרץ:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder