יציבות Application Binary Interface (ABI) היא דרישה מוקדמת עדכונים של framework בלבד, כי המודולים של הספק עשויים להיות תלויים ב-Native של הספק ספריות משותפות של ערכת פיתוח (VNDK) שנמצאות במחיצת המערכת. בגרסה של Android, ספריות משותפות של VNDK שנוצרו לאחרונה חייבות להיות תואם ל-ABI לספריות משותפות של VNDK שפורסמו בעבר, כך שמודולים של ספקים יכול לפעול עם הספריות האלה ללא אוסף מחדש ובלי שגיאות זמן ריצה. בין הגרסאות של Android, ניתן לשנות ספריות VNDK ואין ממשק ABI באחריות.
כדי להבטיח תאימות ABI, מערכת Android 9 כוללת בודק ABI כותרת, כפי שמתואר בקטעים הבאים.
מידע על תאימות של VNDK ו-ABI
ה-VNDK היא קבוצה מגבילה של ספריות שמודולים של ספקים יכולים לקשר אליהם שמאפשרים עדכונים של framework בלבד. תאימות ל-ABI מתייחסת את היכולת של גרסה חדשה יותר של ספרייה משותפת לפעול כצפוי עם שמקושר אליו באופן דינמי (כלומר פועל כגרסה ישנה יותר של לספרייה).
מידע על סמלים שיוצאו
סמל מיוצא (שנקרא גם סמל גלובלי) מתייחס סמל שעומד בכל התנאים הבאים:
- מיוצא באמצעות הכותרות הציבוריות של ספרייה משותפת.
- מופיע בטבלה
.dynsym
של הקובץ.so
שתואם לספרייה המשותפת. - יש לו קישור WEAK או GLOBAL.
- הרשאות הגישה הן ברירת המחדל או ההגנה.
- אינדקס הקטע לא מוגדר.
- הסוג הוא 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, ביומן ה-build מוצגות אזהרות עם סוג האזהרה
נתיב לדוח ה-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. יוצרים את דוח ההבדלים
כותרת-abi-dumper
הכלי header-abi-dumper
מנתח קובץ מקור וקובצי מקור מסוג C/C++
את ה-ABI שהמערכת מסיקה מקובץ המקור הזה לקובץ ביניים. גרסת ה-build
המערכת תריץ את header-abi-dumper
בכל קובצי המקור שעברו הידור בזמן
וגם ליצור ספרייה שכוללת את קובצי המקור
של יחסי התלות.
כניסות קלט |
|
---|---|
פלט | קובץ שמתאר את ה-ABI של קובץ המקור (לדוגמה,
foo.sdump היא ה-ABI של foo.cpp ).
|
נכון לעכשיו, .sdump
קבצים הם בפורמט JSON, שאינו
יציבות בגרסאות עתידיות. לכן, .sdump
יש להתייחס לפורמט הקובץ כפרטי ההטמעה של מערכת ה-build.
לדוגמה, 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
ל-
יוצרים את תמונת ה-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 הסופית שנוצרה
libfoo.so
.
כותרת-abi-diff
הכלי header-abi-diff
משווה בין שני קובצי .lsdump
שמייצג את ה-ABI של שתי ספריות ומפיק דוח הבדלים שמציין
בין שני ממשקי ה-ABI.
כניסות קלט |
|
---|---|
פלט | דוח דיפרנציאלי שבו מפורטים ההבדלים בין ממשקי ה-ABI של שתי הפלטפורמות בהשוואה לספריות משותפות. |
קובץ הפרשי ה-ABI נמצא פורמט טקסט של 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 *
(כל הגדרות ה-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, תופיע עכשיו שגיאת build.
כדי לעדכן הפניות ABI לספריות ספציפיות, מריצים את הפקודה הבאה:
${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