יציבות של ממשק בינארי של אפליקציה (ABI) היא תנאי מוקדם לעדכונים של מסגרות בלבד, כי מודולים של ספקים עשויים להיות תלויים בספריות המשותפות של Vendor Native Development Kit (VNDK) שנמצאות במחיצה של המערכת. במהדורת Android, ספריות VNDK משותפות שנוצרו לאחרונה חייבות להיות תואמות ל-ABI של ספריות VNDK משותפות שפורסמו בעבר, כדי שממשקי ה-vendor יוכלו לפעול עם הספריות האלה בלי הידור מחדש ובלי שגיאות זמן ריצה. בין גרסאות Android, ספריות VNDK עשויות להשתנות ואין ערבויות ל-ABI.
כדי להבטיח תאימות ל-ABI, גרסת Android 9 כוללת בדיקה של כותרות ABI, כפי שמתואר בקטעים הבאים.
מידע על תאימות ל-VNDK ול-ABI
VNDK היא קבוצה מוגבלת של ספריות שמודול של ספק יכול לקשר אליהן, ומאפשרת עדכונים של מסגרת בלבד. תאימות ABI מתייחסת ליכולת של גרסה חדשה יותר של ספרייה משותפת לפעול כצפוי עם מודול שמקושר אליה באופן דינמי (כלומר, פועלת כמו גרסה ישנה יותר של הספרייה).
מידע על סמלים שיוצאו
סמל שיוצא (נקרא גם סמל גלובלי) הוא סמל שעומד בכל הדרישות הבאות:
- מיוצאים על ידי כותרות הציבוריות של ספרייה משותפת.
- מופיע בטבלה
.dynsym
בקובץ.so
שמתאים לספרייה המשותפת. - יש לו קישור WEAK או GLOBAL.
- החשיפה היא 'ברירת מחדל' או 'מוגן'.
- אינדקס הקטע הוא לא UNDEFINED.
- הערך של Type הוא 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
צריך לוודא תאימות 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 שנוצרת. קובצי ה-dump של ABI נמצאים במיקומים הבאים:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
לדוגמה, ב-build 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
כדי ליצור דוח 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
כפרט בהטמעה של מערכת ה-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. המידע הזה מאפשר לבצע השוואה חזרה (recursive diffing).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
הוא קובץ ה-dump הסופי של ABI שנוצר עבור libfoo.so
.
header-abi-diff
הכלי header-abi-diff
משווה בין שני קובצי .lsdump
שמייצגים את ה-ABI של שתי ספריות, ויוצר דוח diff שמציין את ההבדלים בין שני ה-ABI.
כניסות קלט |
|
---|---|
פלט | דוח diff שמציג את ההבדלים ב-ABIs שמוצעים בשתי הספריות המשותפות שנערכת ביניהן השוואה. |
קובץ ההבדלים של 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 *
(כל ה-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 גורם עכשיו לשגיאת build.
כדי לעדכן את ההפניות ל-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