יציבות של ממשק בינארי של אפליקציה (ABI) היא תנאי מוקדם לעדכונים של מסגרות בלבד, כי יכול להיות שמודולים של ספקים תלויים בספריות משותפות של ערכת פיתוח Native של ספקים (VNDK) שנמצאות במחיצת המערכת. בגרסת Android, ספריות משותפות חדשות של VNDK צריכות להיות תואמות ל-ABI של ספריות משותפות קודמות של VNDK, כדי שמודולים של ספקים יוכלו לפעול עם הספריות האלה בלי קומפילציה מחדש ובלי שגיאות בזמן הריצה. בין גרסאות Android, אפשר לשנות את ספריות ה-VNDK ואין ערבויות ל-ABI.
כדי להבטיח תאימות של ABI, Android 9 כולל בודק ABI של כותרות, כמו שמתואר בקטעים הבאים.
מידע על VNDK ותאימות ל-ABI
VNDK היא קבוצה מגבילה של ספריות שמודולים של ספקים יכולים לקשר אליהן, והיא מאפשרת עדכונים של המסגרת בלבד. תאימות ל-ABI מתייחסת ליכולת של גרסה חדשה יותר של ספרייה משותפת לפעול כמצופה עם מודול שמקושר אליה באופן דינמי (כלומר, לפעול כמו גרסה ישנה יותר של הספרייה).
מידע על סמלים מיוצאים
סמל מיוצא (נקרא גם סמל גלובלי) הוא סמל שעומד בכל הקריטריונים הבאים:
- מיוצאים על ידי הכותרות הציבוריות של ספרייה משותפת.
- מופיע בטבלה
.dynsym
בקובץ.so
שמתאים לספרייה המשותפת. - יש לו קשירה מסוג WEAK או GLOBAL.
- החשיפה היא 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 דרך foo_exported.h מתבצע באמצעות bar_t * .
bar_t מכיל חבר mfoo , מסוג
foo_t , שמיוצא דרך foo_exported.h ,
מה שמוביל לייצוא של סוגים נוספים:
עם זאת, אי אפשר להגיע אל foo_private_t כי הוא לא
יוצא דרך foo_exported.h . (foo_private_t *
הוא אטום, ולכן מותר לבצע שינויים ב-foo_private_t ).
|
אפשר לתת הסבר דומה גם לסוגים שאפשר להגיע אליהם באמצעות מפרטים של מחלקת בסיס ופרמטרים של תבניות.
בדיקת התאימות של ה-ABI
צריך לוודא שהספריות שמסומנות ב-vendor_available: true
וב-vndk.enabled: true
בקבצים המתאימים של Android.bp
תואמות ל-ABI. לדוגמה:
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
כדי ליצור דוח השוואה שמפרט את ההבדלים בממשקי ה-ABI של שתי הספריות.
איור 3. יצירת דוח ההשוואה
header-abi-dumper
הכלי header-abi-dumper
מנתח קובץ מקור של C/C++ ומייצא את ה-ABI שהוסק מקובץ המקור הזה לקובץ ביניים. מערכת ה-build מריצה את header-abi-dumper
על כל קובצי המקור שעברו קומפילציה, וגם יוצרת ספרייה שכוללת את קובצי המקור מיחסי תלות טרנזיטיביים.
כניסות קלט |
|
---|---|
פלט | קובץ שמתאר את ה-ABI של קובץ המקור (לדוגמה,
foo.sdump represents foo.cpp 's ABI).
|
כרגע קובצי .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 ).
|
הכלי ממזג את הגרפים של הטיפוסים בכל הקבצים הזמניים שמועברים אליו, תוך התחשבות בהבדלים של הגדרה אחת (יכול להיות שטיפוסים שהוגדרו על ידי המשתמש ביחידות תרגום שונות עם אותו שם מלא יהיו שונים מבחינה סמנטית) בין יחידות התרגום. הכלי מנתח סקריפט של גרסה או את טבלת הספרייה המשותפת (קובץ .so
) כדי ליצור רשימה של הסמלים המיוצאים..dynsym
לדוגמה, libfoo
מורכב מ-foo.cpp
ומ-bar.cpp
. אפשר להפעיל את 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
:
{ "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 של שתי ספריות, ומפיק דוח השוואה שמציין את ההבדלים בין שני ה-ABI.
כניסות קלט |
|
---|---|
פלט | דוח השוואה שמציין את ההבדלים בממשקי ה-ABI שמוצעים על ידי שתי הספריות המשותפות שהושוו. |
קובץ ה-diff של ה-ABI הוא בפורמט טקסט של protobuf . הפורמט עשוי להשתנות בגרסאות עתידיות.
לדוגמה, יש לכם שתי גרסאות של libfoo
: libfoo_old.so
ו-libfoo_new.so
. ב-libfoo_new.so
, ב-bar_t
, שינית את הסוג של mfoo
מ-foo_t
ל-foo_t *
. מכיוון ש-bar_t
הוא סוג שאפשר להגיע אליו, header-abi-diff
צריך לסמן את השינוי הזה כשינוי שובר תאימות ל-ABI.
כדי להריץ את 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 *
(כל ההגדרות של typedef הוסרו).
השדה 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