ABI(Application Binary Interface) 안정성이 프레임워크 전용 업데이트의 선행 조건인 이유는 공급업체 모듈이 시스템 파티션에 상주하는 공급업체 네이티브 개발 키트(VNDK) 공유 라이브러리에 종속될 수 있기 때문입니다. Android 버전 내에서 새로 빌드된 VNDK 공유 라이브러리는 이전에 출시된 VNDK 공유 라이브러리와 ABI 호환이 가능해야 하며, 그래야 공급업체 모듈이 재컴파일 및 런타임 오류 없이 이러한 라이브러리와 함께 작동할 수 있습니다. Android 버전 간에 VNDK 라이브러리가 변경될 수 있으며 ABI는 보장되지 않습니다.
ABI 호환성 보장에 도움이 되도록 Android 9에는 아래 섹션에서 설명할 헤더 ABI 검사기가 도입되었습니다.
VNDK 및 ABI 준수 정보
VNDK는 공급업체 모듈이 링크될 수 있고 프레임워크 전용 업데이트를 지원하는 제한적 라이브러리 집합입니다. ABI 준수란 최신 버전의 공유 라이브러리가 (기존 버전의 라이브러리처럼) 동적으로 링크된 모듈과 함께 사용될 때 예상대로 작동하는지 여부를 의미합니다.
내보낸 기호 정보
내보낸 기호(전역 기호라고도 알려짐)란 다음을 모두 충족하는 기호를 지칭합니다.
- 공유 라이브러리의 공개 헤더에 의해 내보내기됩니다.
- 공유 라이브러리에 해당하는
.so
파일의.dynsym
테이블에 표시됩니다. - 바인딩이 WEAK 또는 GLOBAL입니다.
- 가시성이 DEFAULT 또는 PROTECTED입니다.
- 섹션 색인이 UNDEFINED가 아닙니다.
- 유형이 FUNC 또는 OBJECT입니다.
공유 라이브러리의 공개 헤더는 공유 라이브러리에 해당하는 모듈의 Android.bp
정의에 있는 export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
, export_generated_headers
속성을 통해 다른 라이브러리/바이너리에 제공되는 헤더로 정의됩니다.
연결 가능한 유형 정보
연결 가능한 유형은 공개 헤더를 통해 내보낸 AND 기호를 통해 직접적으로 또는 간접적으로 연결 가능한 모든 C/C++ 내장 또는 사용자 정의 유형입니다. 예를 들어 libfoo.so
에는 .dynsym
테이블에서 발견되는 내보낸 기호인 함수 Foo
가 있습니다. 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 에는 foo_t 유형의 멤버 mfoo 가 포함되며 이는 foo_exported.h 를 통해 내보내기되고 결과적으로는 추가 유형이 내보내기됩니다.
하지만 foo_private_t 는 foo_exported.h 를 통해 내보내기되지 않으므로 연결할 수 없습니다. foo_private_t * 는 불투명이므로 foo_private_t 의 변경사항이 허용됩니다.
|
기본 클래스 지정자 및 템플릿 매개변수를 통해 연결 가능한 유형도 비슷하게 설명할 수 있습니다.
ABI 준수 확인
해당하는 Android.bp
파일에서 vendor_available: true
및 vndk.enabled: true
로 표시된 라이브러리의 경우 ABI를 준수하는지 확인해야 합니다. 예:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
내보낸 함수에 의해 직접적 또는 간접적으로 연결 가능한 데이터 유형의 경우 다음과 같은 라이브러리에 대한 변경사항이 ABI 중단으로 분류됩니다.
데이터 유형 | 설명 |
---|---|
구조 및 클래스 |
|
유니온 |
|
열거형 |
|
전역 기호 |
|
* 공개 인라인 함수가 비공개 멤버 함수를 참조하므로 공개 및 비공개 멤버 함수를 변경되거나 제거되면 안 됩니다. 비공개 멤버 함수에 대한 기호 참조는 호출자 바이너리에 보관할 수 있습니다. 공유 라이브러리의 비공개 멤버 함수를 변경하거나 제거하면 이전 버전과 호환되지 않는 바이너리가 생성될 수 있습니다.
** 공개 또는 비공개 멤버에 대한 오프셋은 변경하면 안 됩니다. 이는 인라인 함수가 함수 본문에서 이러한 데이터 멤버를 참조할 수 있기 때문입니다. 데이터 멤버 오프셋을 변경하면 이전 버전과 호환되지 않는 바이너리가 생성될 수 있습니다.
*** 이로 인해 유형의 메모리 레이아웃이 변경되지는 않지만 라이브러리가 예상대로 작동하지 않는 문제로 이어질 수 있는 의미적 차이가 있습니다.
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 라이브러리가 빌드되면 다음과 같은 상황이 발생합니다.
header-abi-dumper
가 VNDK 라이브러리(라이브러리의 자체 소스 파일과 정적 전이 종속 항목을 통해 상속된 소스 파일) 빌드를 위해 컴파일된 소스 파일을 처리하여 각 소스에 해당하는.sdump
파일을 생성합니다.
그림 1. .sdump
파일 만들기- 그러면
header-abi-linker
가.sdump
파일을 처리(제공된 버전 스크립트 또는 공유 라이브러리에 해당하는.so
파일 사용)하여 공유 라이브러리에 해당하는 모든 ABI 정보를 로깅하는.lsdump
파일을 생성합니다.
그림 2. .lsdump
파일 만들기 header-abi-diff
가.lsdump
파일을 참조.lsdump
파일과 비교하여 두 라이브러리의 ABI 관련 차이점을 설명하는 diff 보고서를 생성합니다.
그림 3. diff 보고서 생성
header-abi-dumper
header-abi-dumper
도구는 C/C++ 소스 파일을 파싱하고 이 소스 파일에서 상속된 ABI를 중간 파일에 덤프합니다. 빌드 시스템은 모든 컴파일된 소스 파일에 header-abi-dumper
를 실행하는 동시에 전이 종속 항목의 소스 파일을 포함하는 라이브러리를 빌드합니다.
입력 |
|
---|---|
출력 | 소스 파일의 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
를 파싱하고 exported
디렉터리의 공개 헤더가 내보낸 ABI 정보를 방출하도록 지시합니다. 합니다. 다음은 header-abi-dumper
에 의해 생성된 foo.sdump
입니다.
{ "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
에는 소스 파일 foo.cpp
및 공개 헤더가 내보낸 ABI 정보가 포함됩니다. 예를 들면 다음과 같습니다.
record_types
. 공개 헤더에 정의된 구조체, 공용체 또는 클래스를 참조합니다. 각 기록 유형에는 필드, 크기, 액세스 지정자, 정의된 헤더 파일, 기타 속성에 관한 정보가 포함됩니다.pointer_types
. 공개 헤더에서 내보낸 기록/함수에 의해 직간접적으로 참조되는 포인터 유형과 포인터가type_info
의referenced_type
필드를 통해 가리키는 유형을 지칭합니다. 유사한 정보가 정규화된 유형의.sdump
파일, 내장된 C/C++ 유형, 배열 유형, lvalue 및 rvalue 참조 유형에 로깅됩니다. 이러한 정보는 반복 디핑을 허용합니다.functions
. 공개 헤더에서 내보낸 함수를 나타냅니다. 또한 함수의 손상된 이름, 반환 유형, 매개변수 유형, 액세스 지정자, 기타 속성에 관한 정보도 포함합니다.
header-abi-linker
header-abi-linker
도구는 header-abi-dumper
에 의해 생성된 중간 파일을 입력으로 취한 다음 이러한 파일을 링크합니다.
입력 |
|
---|---|
출력 | 공유 라이브러리의 ABI를 설명하는 파일입니다(예: libfoo.so.lsdump 는 libfoo 의 ABI를 나타냄).
|
도구는 주어진 모든 중간 파일의 유형 그래프를 병합하며, 이때 여러 해석 단위에 걸친 단일 정의(정규화된 같은 이름을 가진 다른 해석 단위의 사용자 정의 유형은 의미적으로 다를 수 있음) 차이를 고려합니다. 이어서 도구는 공유 라이브러리(.so
파일)의 버전 스크립트 또는 .dynsym
테이블을 파싱하여 내보낸 기호 목록을 생성합니다.
예를 들어 libfoo
는 foo.cpp
및 bar.cpp
로 구성됩니다. header-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.sdump
및bar.sdump
)을 링크하여exported
디렉터리에 상주하는 헤더에 없는 ABI 정보를 필터링합니다. libfoo.so
를 파싱하고.dynsym
테이블을 통해 라이브러리에 의해 내보내기된 기호에 관한 정보를 수집합니다._Z3FooiP3bar
및_Z6FooBadiP3foo
를 추가합니다.
libfoo.so.lsdump
는 libfoo.so
의 마지막으로 생성된 ABI 덤프입니다.
header-abi-diff
header-abi-diff
도구는 두 라이브러리의 ABI를 나타내는 2개의 .lsdump
파일을 비교하고 두 ABI 간의 차이점을 언급하는 diff 보고서를 생성합니다.
입력 |
|
---|---|
출력 | 비교된 두 공유 라이브러리에서 제공하는 ABI의 차이점을 언급하는 diff 보고서 |
ABI diff 파일은 protobuf 텍스트 형식입니다. 형식은 향후 버전에서 변경될 수 있습니다.
예를 들어 libfoo_old.so
및 libfoo_new.so
의 두 버전의 libfoo
가 있다고 가정하겠습니다. 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
에는 libfoo
의 모든 ABI 브레이킹 체인지의 보고서가 포함됩니다. record_type_diffs
메시지는 기록이 변경되었음을 나타내며 다음과 같이 호환되지 않는 변경사항을 나열합니다.
- 기록 크기가
24
바이트에서8
바이트로 변경 mfoo
의 필드 유형이foo
에서foo *
로 변경(모든 typedefs가 제거됨)
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