ABI 안정성

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_tfoo_exported.h를 통해 내보내기됩니다.

bar_t에는 foo_t 유형의 멤버 mfoo가 포함되며 이는 foo_exported.h를 통해 내보내기되고 결과적으로는 추가 유형이 내보내기됩니다.
  • int :m1의 유형입니다.
  • int * :m2의 유형입니다.
  • foo_private_t * : mPfoo의 유형입니다.

하지만 foo_private_tfoo_exported.h를 통해 내보내기되지 않으므로 연결할 수 없습니다. foot_private_t *는 불투명이므로 foo_private_t의 변경사항이 허용됩니다.

기본 클래스 지정자 및 템플릿 매개변수를 통해 연결 가능한 유형도 비슷하게 설명할 수 있습니다.

ABI 준수 확인

해당하는 Android.bp 파일에서 vendor_available: truevndk.enabled: true로 표시된 라이브러리의 경우 ABI를 준수하는지 확인해야 합니다. 예를 들면 다음과 같습니다.

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

내보낸 함수에 의해 직접적 또는 간접적으로 연결 가능한 데이터 유형의 경우 다음과 같은 라이브러리에 대한 변경사항이 ABI 중단으로 분류됩니다.

데이터 유형 설명
구조 및 클래스
  • 클래스 유형 또는 구조체 유형 크기를 변경
  • 기본 클래스
    • 기본 클래스를 추가 또는 삭제
    • 가상으로 상속된 기본 클래스를 추가 또는 제거
    • 기본 클래스의 순서 변경
  • 멤버 함수
    • 멤버 함수 삭제*
    • 멤버 함수에서 인수를 추가 또는 제거
    • 멤버 함수의 인수 유형 또는 반환 유형 변경*
    • 가상 테이블 레이아웃 변경
  • 데이터 멤버
    • 정적 데이터 멤버 삭제
    • 비정적 데이터 멤버 추가 또는 제거
    • 데이터 멤버의 유형 변경
    • 오프셋을 비정적 데이터 멤버로 변경**
    • 데이터 멤버의 const, volatile 또는 restricted 한정자 변경***
    • 데이터 멤버의 액세스 지정자 다운그레이드***
  • 템플릿 인수 변경
유니온
  • 데이터 멤버 추가 또는 삭제
  • 유니온 유형의 크기 변경
  • 데이터 멤버의 유형 변경
  • 데이터 멤버의 순서 변경
열거
  • 기본 유형 변경
  • 열거자 이름 변경
  • 열거자 값 변경
전역 기호
  • 공개 헤더에 의해 내보내기된 기호 삭제
  • 유형 FUNC의 전역 기호
    • 인수 추가 또는 삭제
    • 인수 유형 변경
    • 반환 유형 변경
    • 액세스 지정자 다운그레이드***
  • 유형 OBJECT의 전역 기호
    • 해당하는 C/C++ 유형 변경
    • 액세스 지정자 다운그레이드***

* 공개 인라인 함수가 비공개 멤버 함수를 참조하므로 공개 및 비공개 멤버 함수를 변경되거나 제거되면 안 됩니다. 비공개 멤버 함수에 대한 기호 참조는 호출자 바이너리에 보관할 수 있습니다. 공유 라이브러리의 비공개 멤버 함수를 변경하거나 제거하면 역호환되지 않는 바이너리가 생성될 수 있습니다.

** 공개 또는 비공개 멤버에 대한 오프셋은 변경하면 안 됩니다. 이는 인라인 함수가 함수 본문에서 이러한 데이터 멤버를 참조할 수 있기 때문입니다. 데이터 멤버 오프셋을 변경하면 역호환되지 않는 바이너리가 생성될 수 있습니다.

*** 이로 인해 유형의 메모리 레이아웃이 변경되지는 않지만 라이브러리가 예상대로 작동하지 않는 문제로 이어질 수 있는 의미적 차이가 있습니다.

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 라이브러리가 빌드되면 다음과 같은 상황이 발생합니다.

  1. header-abi-dumper가 VNDK 라이브러리(라이브러리의 자체 소스 파일과 정적 전이 종속성을 통해 상속된 소스 파일) 빌드를 위해 컴파일된 소스 파일을 처리하여 각 소스에 해당하는 .sdump 파일을 생성합니다.
    sdump 생성
    그림 1. .sdump 파일 만들기
  2. 그러면 header-abi-linker.sdump 파일을 처리(제공된 버전 스크립트 또는 공유 라이브러리에 해당하는 .so 파일 사용)하여 공유 라이브러리에 해당하는 모든 ABI 정보를 로깅하는 .lsdump 파일을 생성합니다.
    lsdump 생성
    그림 2. .lsdump 파일 만들기
  3. header-abi-diff.lsdump 파일을 참조 .lsdump 파일과 비교하여 두 라이브러리의 ABI 관련 차이점을 설명하는 diff 보고서를 생성합니다.
    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_inforeferenced_type 필드를 통해 가리키는 유형을 지칭합니다. 유사한 정보가 정규화된 유형의 .sdump 파일, 내장된 C/C++ 유형, 배열 유형, lvalue 및 rvalue 참조 유형에 로깅됩니다(유형에 관한 이러한 로깅 정보는 반복 디핑이 가능하게 함).
  • functions: 공개 헤더에 의해 노출된 함수를 나타냅니다. 또한 함수의 손상된 이름, 반환 유형, 매개변수 유형, 액세스 지정자 등에 관한 정보도 포함합니다.

header-abi-linker

header-abi-linker 도구는 header-abi-dumper에 의해 생성된 중간 파일을 입력으로 취한 다음 이러한 파일을 링크합니다.

입력
  • header-abi-dumper에 의해 생성된 중간 파일
  • 버전 스크립트/맵 파일(선택사항)
  • 공유 라이브러리의 .so 파일
출력 공유 라이브러리의 ABI를 로깅하는 파일(예: libfoo.so.lsdump libfoo의 ABI를 나타냄)

도구는 주어진 모든 중간 파일의 유형 그래프를 병합하며, 이때 여러 해석 단위에 걸친 단일 정의(정규화된 같은 이름을 가진 다른 해석 단위의 사용자 정의 유형은 의미적으로 다를 수 있음) 차이를 고려합니다. 이어서 도구는 공유 라이브러리(.dynsym 파일)의 버전 스크립트 또는 .so 테이블을 파싱하여 내보낸 기호 목록을 생성합니다.

예를 들어 libfoobar.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.sdumpbar.sdump)을 링크하여 exported 디렉터리에 상주하는 헤더에 없는 ABI 정보를 필터링합니다.
  • libfoo.so를 파싱하고 .dynsym 테이블을 통해 라이브러리에 의해 내보내기된 기호에 관한 정보를 수집합니다.
  • _Z3FooiP3barBar를 추가합니다.

libfoo.so.lsdumplibfoo.so의 마지막으로 생성된 ABI 덤프입니다.

header-abi-diff

header-abi-diff 도구는 두 라이브러리의 ABI를 나타내는 2개의 .lsdump 파일을 비교하고 두 ABI 간의 차이점을 언급하는 diff 보고서를 생성합니다.

입력
  • 기존 공유 라이브러리의 ABI를 나타내는 .lsdump 파일
  • 새 공유 라이브러리의 ABI를 나타내는 .lsdump 파일
출력 비교된 두 공유 라이브러리에서 제공하는 ABI의 차이점을 언급하는 diff 보고서

ABI diff 파일은 최대한 간결하고 가독성이 좋아야 합니다. 형식은 향후 버전에서 변경될 수 있습니다. 예를 들어 libfoo_old.solibfoo_new.so의 두 버전의 libfoo가 있다고 가정하겠습니다. libfoo_new.sobar_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-diffbar를 변경한 유형에 어떻게 연결되었는지를 나타냅니다. 이 필드는 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