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 : [ "include" ], } |
.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 를 통해 내보내기되지 않으므로 연결할 수 없습니다. foot_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/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
예를 들어 VNDK API 수준 27의 libfoo
빌드 중에는 libfoo
의 추론된 ABI가 아래의 참조와 비교됩니다.
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/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
를 실행하는 동시에 전이 종속성의 소스 파일을 포함하는 라이브러리를 빌드합니다.
현재는 .sdump
파일의 형식이 Protobuf TextFormatted로 지정되지만 향후 버전 전체에 걸친 안정성은 보장되지 않습니다. 따라서 .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 -- -x c++
이 명령어는 foo.cpp
를 파싱하고 exported
디렉터리의 공개 헤더에 노출된 ABI 정보를 방출하도록 header-abi-dumper
에 지시합니다. 다음은 header-abi-dumper
에 의해 생성된 foo.sdump
에서 발췌한 일부입니다.
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
에는 소스 파일 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를 나타냄) |
도구는 주어진 모든 중간 파일의 유형 그래프를 병합하며, 이때 여러 해석 단위에 걸친 단일 정의(정규화된 같은 이름을 가진 다른 해석 단위의 사용자 정의 유형은 의미적으로 다를 수 있음) 차이를 고려합니다. 이어서 도구는 공유 라이브러리(.dynsym
파일)의 버전 스크립트 또는 .so
테이블을 파싱하여 내보낸 기호 목록을 생성합니다.
예를 들어 libfoo
가 bar.cpp
파일(C 함수 bar
를 노출)을 컴파일에 추가하면 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
의 명령어 결과는 예를 들어 다음과 같습니다.
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" }
header-abi-linker
도구는 다음과 같습니다.
- 제공받은
.sdump
파일(foo.sdump
및bar.sdump
)을 링크하여exported
디렉터리에 상주하는 헤더에 없는 ABI 정보를 필터링합니다. libfoo.so
를 파싱하고.dynsym
테이블을 통해 라이브러리에 의해 내보내기된 기호에 관한 정보를 수집합니다._Z3FooiP3bar
및Bar
를 추가합니다.
libfoo.so.lsdump
는 libfoo.so
의 마지막으로 생성된 ABI 덤프입니다.
header-abi-diff
header-abi-diff
도구는 두 라이브러리의 ABI를 나타내는 2개의 .lsdump
파일을 비교하고 두 ABI 간의 차이점을 언급하는 diff 보고서를 생성합니다.
입력 |
|
---|---|
출력 | 비교된 두 공유 라이브러리에서 제공하는 ABI의 차이점을 언급하는 diff 보고서 |
ABI diff 파일은 최대한 간결하고 가독성이 좋아야 합니다. 형식은 향후 버전에서 변경될 수 있습니다. 예를 들어 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 및 LLNDK 공유 라이브러리의 ABI/API를 적용하려면 ABI 참조를 ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/
에 체크인해야 합니다.
이러한 참조를 생성하려면 다음 명령어를 실행합니다.
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
참조를 생성한 후에는 VNDK 또는 LLNDK 라이브러리의 호환되지 않는 ABI/API 변경사항으로 이어지는 소스 코드에 적용된 모든 변경사항이 이제 빌드 오류를 생성합니다.
특정 VNDK 핵심 라이브러리의 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
특정 LLNDK 라이브러리의 ABI 참조를 업데이트하려면 다음 명령어를 실행합니다.
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
예를 들어 libm
ABI 참조를 업데이트하려면 다음을 실행합니다.
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk