Google cam kết thúc đẩy công bằng chủng tộc cho Cộng đồng người da đen. Xem cách thực hiện.

ABI ổn định

Tính ổn định của Application Binary Interface (ABI) là điều kiện tiên quyết của các bản cập nhật chỉ dành cho khung công tác vì các mô-đun của nhà cung cấp có thể phụ thuộc vào các thư viện được chia sẻ của Vendor Native Development Kit (VNDK) nằm trong phân vùng hệ thống. Trong bản phát hành Android, các thư viện chia sẻ VNDK mới được xây dựng phải tương thích ABI với các thư viện chia sẻ VNDK đã phát hành trước đó để các mô-đun của nhà cung cấp có thể hoạt động với các thư viện đó mà không cần biên dịch lại và không có lỗi thời gian chạy. Giữa các bản phát hành Android, thư viện VNDK có thể được thay đổi và không có đảm bảo ABI.

Để giúp đảm bảo khả năng tương thích ABI, Android 9 bao gồm trình kiểm tra ABI tiêu đề, như được mô tả trong các phần sau.

Về tuân thủ VNDK và ABI

VNDK là một bộ thư viện hạn chế mà các mô-đun của nhà cung cấp có thể liên kết đến và cho phép cập nhật chỉ dành cho khung. Tuân thủ ABI đề cập đến khả năng của phiên bản mới hơn của thư viện dùng chung để hoạt động như mong đợi với một mô-đun được liên kết động với nó (nghĩa là hoạt động như một phiên bản thư viện cũ hơn).

Giới thiệu về các ký hiệu đã xuất

Một biểu tượng được xuất (còn được gọi là biểu tượng toàn cầu ) đề cập đến một biểu tượng đáp ứng tất cả những điều sau:

  • Được xuất bởi các tiêu đề công cộng của một thư viện được chia sẻ.
  • Xuất hiện trong bảng .dynsym của tệp .so tương ứng với thư viện được chia sẻ.
  • Có ràng buộc ĐIỂM YẾU hoặc TOÀN CẦU.
  • Khả năng hiển thị là DEFAULT hoặc được BẢO VỆ.
  • Chỉ mục phần không được UNDEFINED.
  • Loại là FUNC hoặc OBJECT.

Tiêu đề công khai của thư viện được chia sẻ được định nghĩa là tiêu đề có sẵn cho các thư viện / tệp nhị phân khác thông qua các thuộc tính export_include_dirs , export_header_lib_headers , export_static_lib_headers , export_shared_lib_headersexport_generated_headers trong định nghĩa Android.bp của mô-đun tương ứng với thư viện được chia sẻ.

Giới thiệu về các loại có thể truy cập

Loại có thể truy cập là bất kỳ loại C / C ++ nào được tích hợp sẵn hoặc do người dùng xác định có thể truy cập trực tiếp hoặc gián tiếp thông qua một ký hiệu được xuất VÀ được xuất qua các tiêu đề công khai. Ví dụ: libfoo.so có hàm Foo , là một biểu tượng đã xuất được tìm thấy trong bảng .dynsym . Thư viện libfoo.so bao gồm những thứ sau:

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 : [
    "include"
  ],
}
bảng .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

Nhìn vào Foo , các loại có thể truy cập trực tiếp / gián tiếp bao gồm:

Thể loại Sự miêu tả
bool Loại trả lại của Foo .
int Loại tham số Foo đầu tiên.
bar_t * Loại tham số Foo thứ hai. Bằng cách sử dụng bar_t * , bar_t được xuất thông qua foo_exported.h .

bar_t chứa một thành viên mfoo , kiểu foo_t , được xuất qua foo_exported.h , dẫn đến nhiều kiểu được xuất hơn:
  • int : là kiểu của m1 .
  • int * : là kiểu m2 .
  • foo_private_t * : là loại mPfoo .

Tuy nhiên, KHÔNG thể truy cập foo_private_t vì nó không được xuất qua foo_exported.h . ( foot_private_t * là không rõ ràng, do đó các thay đổi được thực hiện đối với foo_private_t được cho phép.)

Một lời giải thích tương tự cũng có thể được đưa ra cho các loại có thể truy cập thông qua các chỉ định của lớp cơ sở và các tham số mẫu.

Đảm bảo tuân thủ ABI

Việc tuân thủ ABI phải được đảm bảo đối với các thư viện được đánh dấu vendor_available: truevndk.enabled: true trong các tệp Android.bp tương ứng. Ví dụ:

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

Đối với các loại dữ liệu có thể truy cập trực tiếp hoặc gián tiếp bằng một hàm được xuất, những thay đổi sau đây đối với thư viện được phân loại là phá vỡ ABI:

Loại dữ liệu Sự miêu tả
Cấu trúc và lớp
  • Thay đổi kích thước của kiểu lớp hoặc kiểu cấu trúc.
  • Các lớp cơ bản
    • Thêm hoặc bớt các lớp cơ sở.
    • Thêm hoặc xóa các lớp cơ sở hầu như được kế thừa.
    • Thay đổi thứ tự của các lớp cơ sở.
  • Chức năng thành viên
    • Bỏ chức năng thành viên *.
    • Thêm hoặc xóa đối số khỏi các hàm thành viên.
    • Thay đổi kiểu đối số hoặc kiểu trả về của các hàm thành viên *.
    • Thay đổi bố cục bảng ảo.
  • Thành viên dữ liệu
    • Loại bỏ các thành viên dữ liệu tĩnh.
    • Thêm hoặc loại bỏ các thành viên dữ liệu không tĩnh.
    • Thay đổi các loại thành viên dữ liệu.
    • Thay đổi giá trị thành phần dữ liệu không tĩnh **.
    • Thay đổi const số, volatile và / hoặc định tính restricted của các thành viên dữ liệu ***.
    • Hạ cấp các chỉ định truy cập của các thành viên dữ liệu ***.
  • Thay đổi các đối số mẫu.
Đoàn thể
  • Thêm hoặc xóa thành viên dữ liệu.
  • Thay đổi kích thước của loại liên hợp.
  • Thay đổi các loại dữ liệu thành viên.
  • Thay đổi thứ tự của các thành viên dữ liệu.
Bảng kê
  • Thay đổi loại cơ bản.
  • Thay đổi tên của điều tra viên.
  • Thay đổi giá trị của các điều tra viên.
Biểu tượng toàn cầu
  • Loại bỏ các ký hiệu được xuất bởi các tiêu đề công khai.
  • Đối với các ký hiệu toàn cầu của loại FUNC
    • Thêm hoặc bớt các đối số.
    • Thay đổi các loại đối số.
    • Thay đổi loại trả lại.
    • Hạ cấp công cụ xác định quyền truy cập ***.
  • Đối với các ký hiệu chung của loại OBJECT
    • Thay đổi loại C / C ++ tương ứng.
    • Hạ cấp công cụ xác định quyền truy cập ***.

* Cả hai chức năng thành viên công cộng và riêng tư không được thay đổi hoặc loại bỏ vì các chức năng nội tuyến công cộng có thể tham chiếu đến các chức năng thành viên riêng. Các tham chiếu ký hiệu đến các hàm thành viên riêng có thể được giữ trong các mã nhị phân của người gọi. Thay đổi hoặc xóa các chức năng thành viên riêng tư khỏi các thư viện được chia sẻ có thể dẫn đến các tệp nhị phân không tương thích ngược.

** Không được thay đổi các hiệu số đối với thành viên dữ liệu công khai hoặc riêng tư vì các hàm nội tuyến có thể tham chiếu đến các thành viên dữ liệu này trong thân hàm của chúng. Thay đổi hiệu số thành viên dữ liệu có thể dẫn đến các tệp nhị phân không tương thích ngược.

*** Mặc dù những điều này không thay đổi bố cục bộ nhớ của loại, nhưng có sự khác biệt về ngữ nghĩa có thể dẫn đến các thư viện không hoạt động như mong đợi.

Sử dụng các công cụ tuân thủ ABI

Khi một thư viện VNDK được xây dựng, ABI của thư viện được so sánh với tài liệu tham khảo ABI tương ứng cho phiên bản VNDK đang được xây dựng. Các bãi chứa tham chiếu ABI được đặt tại:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based

Ví dụ: khi xây dựng libfoo cho API cấp 27 của VNDK, ABI được suy ra của libfoo được so sánh với tham chiếu của nó tại:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump

Lỗi đứt ABI

Trên các sự cố ABI, nhật ký bản dựng hiển thị các cảnh báo với loại cảnh báo và đường dẫn đến báo cáo abi-diff. Ví dụ: nếu ABI của libbinder có thay đổi không tương thích, hệ thống xây dựng sẽ thông báo lỗi với thông báo tương tự như sau:

*****************************************************
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 ----

Xây dựng thư viện VNDK séc ABI

Khi một thư viện VNDK được xây dựng:

  1. header-abi-dumper xử lý các tệp nguồn được biên dịch để xây dựng thư viện VNDK (các tệp nguồn riêng của thư viện cũng như các tệp nguồn được kế thừa thông qua các phụ thuộc bắc cầu tĩnh), để tạo ra các tệp .sdump tương ứng với từng nguồn.
    sdump creation
    Hình 1. Tạo tệp .sdump
  2. header-abi-linker sau đó xử lý các tệp .sdump (sử dụng tập lệnh phiên bản được cung cấp cho nó hoặc tệp .so tương ứng với thư viện được chia sẻ) để tạo ra tệp .lsdump ghi lại tất cả thông tin ABI tương ứng với thư viện được chia sẻ.
    lsdump creation
    Hình 2. Tạo tệp .lsdump
  3. header-abi-diff so sánh tệp .lsdump với tệp .lsdump tham chiếu để tạo báo cáo khác biệt nêu rõ sự khác biệt trong ABI của hai thư viện.
    abi diff creation
    Hình 3. Tạo báo cáo khác biệt

header-abi-dumper

Công cụ header-abi-dumper phân tích cú pháp tệp nguồn C / C ++ và kết xuất ABI được suy ra từ tệp nguồn đó vào tệp trung gian. Hệ thống xây dựng chạy header-abi-dumper trên tất cả các tệp nguồn đã biên dịch trong khi cũng xây dựng một thư viện bao gồm các tệp nguồn từ các tệp phụ thuộc bắc cầu.

Hiện tại, các tệp .sdump được định dạng là Protobuf TextFormatted , không được đảm bảo sẽ ổn định trên các bản phát hành trong tương lai. Do đó, định dạng tệp .sdump nên được coi là một chi tiết triển khai hệ thống xây dựng.

Ví dụ: libfoo.so có tệp nguồn foo.cpp sau:

#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;
}

Bạn có thể sử dụng header-abi-dumper để tạo tệp .sdump trung gian đại diện cho ABI được trình bày bởi tệp nguồn bằng cách sử dụng:

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++

Lệnh này yêu header-abi-dumper phân tích cú pháp foo.cpp và phát ra thông tin ABI được hiển thị trong tiêu đề công khai trong thư mục đã exported . Đây là một đoạn trích (không phải là đại diện hoàn chỉnh) từ foo.sdump được tạo bởi header-abi-dumper :

record_types {
  type_info {
    name: "foo"
    size: 12
    alignment: 4
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 32
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-5"
    field_offset: 64
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 12
    alignment: 4
    referenced_type: "type-6"
…
pointer_types {
  type_info {
    name: "bar *"
    size: 4
    alignment: 4
    referenced_type: "type-6"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "bar *"
    self_type: "type-8"
  }
}
builtin_types {
  type_info {
    name: "int"
    size: 4
    alignment: 4
    referenced_type: "type-2"
    source_file: ""
    linker_set_key: "int"
    self_type: "type-2"
  }
  is_unsigned: false
  is_integral: true
}
functions {
  return_type: "type-7"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-8"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}

foo.sdump chứa thông tin ABI được hiển thị bởi tệp nguồn foo.cpp , ví dụ:

  • record_types . Tham khảo các cấu trúc, liên hiệp hoặc các lớp được hiển thị bởi các tiêu đề công khai. Mỗi loại bản ghi có thông tin về các trường của nó, kích thước của nó, mã xác định quyền truy cập, tệp tiêu đề mà nó được hiển thị trong đó, v.v.
  • pointer_types . Tham chiếu đến các loại con trỏ được tham chiếu trực tiếp / gián tiếp bởi các bản ghi / chức năng được hiển thị bởi các tiêu đề công khai, cùng với loại con trỏ trỏ đến (thông qua trường referenced_type trong type_info ). Thông tin tương tự được ghi vào tệp .sdump cho các kiểu đủ điều kiện, kiểu C / C ++ tích hợp sẵn, kiểu mảng và kiểu tham chiếu lvalue và rvalue (thông tin ghi về kiểu như vậy cho phép khác biệt đệ quy).
  • functions . Đại diện cho các chức năng được hiển thị bởi tiêu đề công khai. Chúng cũng có thông tin về tên bị xáo trộn của hàm, kiểu trả về, loại tham số, mã xác định truy cập, v.v.

header-abi-linker

Công cụ header-abi-linker lấy các tệp trung gian do header-abi-dumper tạo ra làm đầu vào, sau đó liên kết các tệp đó:

Đầu vào
  • Các tệp trung gian được tạo bởi header-abi-dumper
  • Tập lệnh phiên bản / Tệp bản đồ (tùy chọn)
  • .so tệp của thư viện được chia sẻ
Đầu ra Một tệp ghi ABI của thư viện được chia sẻ (ví dụ: libfoo.so.lsdump đại diện cho ABI của libfoo ).

Công cụ hợp nhất các biểu đồ loại trong tất cả các tệp trung gian được cung cấp cho nó, có tính đến một định nghĩa (các loại do người dùng xác định trong các đơn vị dịch khác nhau có cùng tên đủ điều kiện, có thể khác nhau về mặt ngữ nghĩa) sự khác biệt giữa các đơn vị dịch. Sau đó, công cụ phân tích cú pháp kịch bản phiên bản hoặc bảng .dynsym của thư viện được chia sẻ (tệp .so ) để tạo danh sách các ký hiệu đã xuất.

Ví dụ: khi libfoo thêm tệp bar.cpp (hiển thị bar chức năng C) vào quá trình biên dịch của nó, header-abi-linker có thể được gọi để tạo kết xuất ABI được liên kết hoàn chỉnh của libfoo như sau:

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current

Ví dụ về đầu ra lệnh trong libfoo.so.lsdump :

record_types {
  type_info {
    name: "foo"
    size: 24
    alignment: 8
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 64
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-4"
    field_offset: 128
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 24
    alignment: 8
...
builtin_types {
  type_info {
    name: "void"
    size: 0
    alignment: 0
    referenced_type: "type-6"
    source_file: ""
    linker_set_key: "void"
    self_type: "type-6"
  }
  is_unsigned: false
  is_integral: false
}
functions {
  return_type: "type-19"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-20"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}
functions {
  return_type: "type-6"
  function_name: "FooBad"
  source_file: "foo/include/foo_exported_bad.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
parameters {
    referenced_type: "type-7"
    default_arg: false
  }
  linker_set_key: "_Z6FooBadiP3foo"
  access: public_access
}
elf_functions {
  name: "_Z3FooiP3bar"
}
elf_functions {
  name: "_Z6FooBadiP3foo"
}

Công cụ header-abi-linker :

  • Liên kết các tệp .sdump được cung cấp cho nó ( foo.sdumpbar.sdump ), lọc ra thông tin ABI không có trong các tiêu đề nằm trong thư mục: exported .
  • Phân tích libfoo.so và thu thập thông tin về các ký hiệu được thư viện xuất ra thông qua bảng .dynsym của nó.
  • Thêm _Z3FooiP3barBar .

libfoo.so.lsdump là kết xuất ABI được tạo cuối cùng của libfoo.so .

header-abi-diff

Công cụ header-abi-diff so sánh hai tệp .lsdump đại diện cho ABI của hai thư viện và tạo báo cáo khác biệt nêu rõ sự khác biệt giữa hai ABI.

Đầu vào
  • .lsdump tệp đại diện cho ABI của một thư viện chia sẻ cũ.
  • .lsdump tệp đại diện cho ABI của một thư viện được chia sẻ mới.
Đầu ra Một báo cáo khác nêu rõ sự khác biệt về ABI do hai thư viện chia sẻ so sánh.

Tệp khác ABI được thiết kế để càng dài và dễ đọc càng tốt. Định dạng có thể thay đổi trong các bản phát hành trong tương lai. Ví dụ: bạn có hai phiên bản libfoo : libfoo_old.solibfoo_new.so . Trong libfoo_new.so , trong bar_t , bạn thay đổi loại mfoo từ foo_t thành foo_t * . Vì bar_t là một loại có thể truy cập trực tiếp, điều này sẽ được gắn cờ là một thay đổi phá vỡ ABI bởi header-abi-diff .

Để chạy header-abi-diff :

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo

Ví dụ về đầu ra lệnh trong 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 chứa một báo cáo về tất cả các thay đổi vi phạm ABI trong libfoo . Thông báo record_type_diffs cho biết một bản ghi đã thay đổi và liệt kê các thay đổi không tương thích, bao gồm:

  • Kích thước của bản ghi thay đổi từ 24 byte thành 8 byte.
  • Loại trường của mfoo thay đổi từ foo thành foo * (tất cả các typedef đều bị loại bỏ).

Trường type_stack cho biết cách header-abi-diff đạt đến loại đã thay đổi ( bar ). Trường này có thể được hiểu là Foo là một hàm được xuất lấy tham số là bar * , trỏ đến bar , được xuất và thay đổi.

Thực thi ABI / API

Để thực thi ABI / API của các thư viện dùng chung VNDK và LLNDK, các tham chiếu ABI phải được kiểm tra trong ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ . Để tạo các tham chiếu này, hãy chạy lệnh sau:

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

Sau khi tạo tham chiếu, bất kỳ thay đổi nào được thực hiện đối với mã nguồn dẫn đến thay đổi ABI / API không tương thích trong thư viện VNDK hoặc LLNDK hiện đều dẫn đến lỗi bản dựng.

Để cập nhật tham chiếu ABI cho các thư viện lõi VNDK cụ thể, hãy chạy lệnh sau:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>

Ví dụ: để cập nhật tham chiếu libbinder ABI, hãy chạy:

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

Để cập nhật tham chiếu ABI cho các thư viện LLNDK cụ thể, hãy chạy lệnh sau:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk

Ví dụ: để cập nhật tham chiếu libm ABI, hãy chạy:

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