ABI 穩定性

應用程式二進位檔介面 (ABI) 穩定性 僅針對架構更新,因為供應商模組可能會依附於供應商原生模組 位於系統分區的 Development Kit (VNDK) 共用程式庫。 在 Android 版本中,新建構的 VNDK 共用程式庫必須 與先前發布的 VNDK 共用程式庫相容,以便支援供應商模組 且不需重新編譯和執行階段錯誤即可使用這些程式庫。 在 Android 版本之間,VNDK 程式庫可以變更,且沒有 ABI。 保證。

為確保 ABI 相容性,Android 9 包含以下 設定標頭 ABI 檢查工具,如以下各節所述。

關於 VNDK 與 ABI 法規遵循

VNDK 是一組限制性的程式庫,供應商模組可以連結這些程式庫, 啟用純架構更新ABI 法規遵循是指 新版共用程式庫的功能 動態連結模組 (也就是說,如同舊版的 程式庫)。

關於匯出的符號

「匯出符號」 (也稱為「全域符號」) 是指 滿足以下所有條件的符號:

  • 由共用資料庫的公開標頭匯出。
  • 顯示在 .so 檔案的 .dynsym 資料表中 與共用資料庫對應的
  • 具有 WEAK 或 GLOBAL 繫結。
  • 可見性為「預設」或「PROTECTED」。
  • 版面索引不是「UNDEFINED」(未定義)。
  • 類型為 FUNC 或 OBJECT。

共用資料庫的公開標頭會定義為標頭 提供給其他程式庫/二進位檔 export_include_dirsexport_header_lib_headersexport_static_lib_headers, export_shared_lib_headersAndroid.bp 中的 export_generated_headers 個屬性 每個共用程式庫的模組定義。

關於可連線類型

可連線類型是任何內建或使用者定義類型, 可透過匯出的符號直接或間接聯絡,「以及」 通過公開標頭例如,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 匯出) 這會導致匯出更多類型:
  • int :m1 的類型。
  • int * :m2 的類型。
  • foo_private_t * : mPfoo 的類型。
,瞭解如何調查及移除這項存取權。
不過,由於 foo_private_t 無法使用,因此無法連上 已透過 foo_exported.h 匯出。(foo_private_t *)。 不透明,因此可以對 foo_private_t 進行變更)。

可針對可透過基礎類別可連線的類型提供類似的解釋 指定碼和範本參數

確保符合 ABI 規定

針對標示的程式庫,必須確保其符合 ABI 規定 vendor_available: truevndk.enabled: true 對應的 Android.bp 檔案。例如:

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}

如果是以匯出函式直接或間接存取的資料類型, 下列程式庫變更都會歸類為破壞性 ABI 的內容:

資料類型 說明
結構與類別
  • 變更類別類型或結構體類型的大小。
  • 基礎類別
    • 新增或移除基本類別。
    • 新增或移除虛擬繼承的基本類別。
    • 變更基本類別的順序。
  • 成員職務
    • 移除成員函式*。
    • 在成員函式中新增或移除引數。
    • 變更引數類型或傳回成員類型 函式*。
    • 變更虛擬表格版面配置。
  • 資料成員
    • 移除靜態資料成員。
    • 新增或移除非靜態資料成員。
    • 變更資料成員的類型。
    • 將偏移值變更為非靜態資料成員**。
    • 變更 constvolatile 和/或 restricted 資料成員的限定詞***。
    • 降級資料成員的存取權指定碼***。
  • 變更範本引數。
聯合工會
  • 新增或移除資料成員。
  • 變更聯集類型的大小。
  • 變更資料成員的類型。
列舉
  • 變更基礎類型。
  • 變更列舉器的名稱。
  • 變更列舉器的值。
全域符號
  • 移除公開標頭匯出的符號。
  • 適用於 FUNC 類型的全域符號
    • 新增或移除引數。
    • 變更引數類型。
    • 變更傳回類型。
    • 將存取指定碼降級***。
  • 適用於 OBJECT 類型的全域符號
    • 變更對應的 C/C++ 類型。
    • 將存取指定碼降級***。

* 公開和私人成員函式都必須 因此無法變更或移除,因為公開內嵌函式可能參照 私人成員函式。對私人成員函式的符號參照可以 會保留在呼叫端二進位檔中變更或移除私人成員函式 可能會導致回溯不相容的二進位檔。

** 至公開或私人資料成員的偏移量不得為 因為內嵌函式可參照 函式主體。變更資料成員位移可能會導致 回溯不相容的二進位檔。

*** 這些項目不會影響記憶體配置 語意差異導致程式庫無法擷取文字 正常運作。

使用 ABI 法規遵循工具

建構 VNDK 程式庫時,會比較程式庫的 ABI 與 所建構 VNDK 版本的對應 ABI 參考資料。參考資料 ABI 傾印位於:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based

以在 API 級別 27 建構 x86 的 libfoo 時, 系統會將 libfoo 推測的 ABI 與參考以下的參考資料進行比較:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

ABI 中斷錯誤

在 ABI 故障時,建構記錄會顯示警告類型和警示類型 abi-diff 報表的路徑。舉例來說,如果 libbinder 的 ABI 不相容的變更時,建構系統會擲回錯誤訊息 類似這樣:

*****************************************************
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 個檔案。
    建立轉儲
    圖 1.建立 .sdump 檔案
  2. header-abi-linker 接著會處理 .sdump 檔案 (使用提供給檔案的版本指令碼,或是 .so 檔案),並產生 .lsdump 檔案,其中記錄了與共用資料庫相關的所有 ABI 資訊。
    lsdump 建立作業
    圖 2.建立 .lsdump 檔案
  3. header-abi-diff 比較.lsdump 內含參考 .lsdump 參考資料的檔案,以便製作差異比較報表 概述這兩種程式庫的 ABI 的差異。
    建立 Abi 差異比較
    圖 3.建立差異比較報表

標頭 -abi-dumper

header-abi-dumper 工具剖析 C/C++ 來源檔案並轉儲 從來源檔案推斷至中繼檔案中的 ABI。建構作業 系統在所有已編譯的來源檔案上執行 header-abi-dumper 也建構了程式庫,內含來自轉換程序的來源檔案 依附元件

輸入裝置
  • C/C++ 來源檔案
  • 匯出的內容包含目錄
  • 編譯器標記
輸出 說明來源檔案 ABI 的檔案 (例如 foo.sdump 代表 foo.cpp 的 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 產生中繼資訊 代表來源檔案提供的 ABI 的 .sdump 檔案 使用:

$ 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)。系統記錄類似資訊時,都會留存在 符合條件的類型、內建 C/C++ 型別、陣列的 .sdump 檔案 type,以及 lvalue 和 rvalue 參照類型。這類資訊 遞迴性差異
  • functions。代表由公開標頭匯出的函式。 同時也會提供函式的破壞名稱、傳回類型、 參數類型、存取指定碼以及其他屬性
,瞭解如何調查及移除這項存取權。

頁首-abi-linker

header-abi-linker 工具會接收 以 header-abi-dumper 做為輸入內容,然後連結這些檔案:

輸入裝置
  • header-abi-dumper 產生的中繼檔案
  • 版本指令碼/對應檔案 (選用)
  • 共用資料庫的 .so 個檔案
  • 匯出的內容包含目錄
輸出 說明共用程式庫 ABI 的檔案 (例如 libfoo.so.lsdump 代表 libfoo 的 ABI)。

這項工具會合併所有已取得的中繼檔案中的類型圖表 考量到使用者定義的 同一個完整名稱的翻譯單位 不同) 翻譯單位的差異接著,工具會剖析 版本指令碼或共用資料庫的 .dynsym 表格 (.so 檔案) 建立匯出符號的清單。

例如,libfoo 包含 foo.cppbar.cppheader-abi-linker 可用於叫用 建立 libfoo 的完整連結 ABI 轉儲,如下所示:

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.sdumpbar.sdump),篩除不包含在 和 exported 目錄中的標頭。
  • 剖析 libfoo.so,並收集符號相關資訊 會透過 .dynsym 資料表匯出。
  • 加入 _Z3FooiP3bar_Z6FooBadiP3foo

libfoo.so.lsdump 是下列最終產生的 ABI 轉儲 libfoo.so

頁首-abi-diff

header-abi-diff 工具會比較兩個 .lsdump 檔案 會呈現兩個程式庫的 ABI 並產生差異比較表,說明 計算差異

輸入裝置
  • 代表舊共用 ABI 的 .lsdump 檔案 資源庫。
  • 代表新共用程式庫 ABI 的 .lsdump 檔案。
輸出 差異比較報表:說明兩者之間提供的 ABI 差異 共用程式庫

ABI 差異檔案位於 protobuf 文字格式。格式可能會變動 。

例如,有兩個版本 libfoolibfoo_old.solibfoo_new.solibfoo_new.sobar_t,您將mfoo類型從 foo_tfoo_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

如要強制執行 VNDK 共用程式庫的 ABI 和 API,ABI 參照必須 目前在「${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/」中簽到。 如要建立這些參照,請執行下列指令:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py

建立參照後,對產生結果的原始碼所做的任何變更 現在 VNDK 程式庫中的不相容 ABI/API 變更會導致建構錯誤。

如要更新特定程式庫的 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