Dexpreopt 및 검사

Android 12에서는 <uses-library> 종속 항목이 있는 Java 모듈의 DEX 파일(dexpreopt)의 AOT 컴파일에 빌드 시스템 변경사항이 적용되었습니다 이러한 빌드 시스템은 특정 경우에 빌드의 손상을 야기할 수 있습니다. 이 페이지를 사용하여 손상에 대비하고 이 페이지의 레시피에 따라 문제를 해결하고 완화하세요.

Dexpreopt는 Java 라이브러리 및 앱의 사전 컴파일 프로세스입니다. Dexpreopt는 빌드 시 호스트에서 발생합니다(dexopt의 경우 기기 내에서 발생). Java 모듈(라이브러리 또는 앱)에서 사용되는 공유 라이브러리 종속 항목의 구조를 클래스 로더 컨텍스트(CLC)라고 합니다. dexpreopt의 정확성을 보장하려면 빌드 시 CLC와 런타임 CLC가 일치해야 합니다. 빌드 시 CLC는 dex2oat 컴파일러가 dexpreopt 시에 사용하는 컨텍스트(ODEX 파일에 기록됨)이고, 런타임 CLC는 사전 컴파일된 코드가 기기에 로드되는 컨텍스트입니다.

정확성과 성능 모두를 이유로 빌드 시 CLC와 런타임 CLC는 일치해야 합니다. 정확성을 위해 중복 클래스를 처리해야 합니다. 런타임 시의 공유 라이브러리 종속 항목이 컴파일에 사용되는 항목과 다른 경우 일부 클래스가 다르게 해결되어 미세한 런타임 버그가 발생할 수 있습니다. 성능은 중복 클래스의 런타임 검사에 의해서도 영향을 받습니다.

영향을 받는 사용 사례

첫 번째 부팅은 이러한 변경의 영향을 받는 주요 사용 사례입니다. ART가 빌드 시 CLC와 런타임 CLC 사이의 불일치를 감지하면 dexpreopt 아티팩트를 거부하고 대신 dexopt를 실행합니다. 후속 부팅이라면 괜찮습니다. 앱이 백그라운드에서 dexopt 처리되어 디스크에 저장될 수 있기 때문입니다.

영향을 받는 Android 영역

다른 Java 라이브러리에 런타임 종속 항목이 있는 모든 Java 앱과 라이브러리에 영향을 미칩니다. Android에는 수천 개의 앱이 있으며 그중 수백 개의 앱이 공유 라이브러리를 사용합니다. 파트너도 자체 라이브러리와 앱이 있기 때문에 영향을 받습니다.

변경사항 중단

빌드 시스템은 dexpreopt 빌드 규칙을 생성하기 전에 <uses-library> 종속 항목을 알아야 합니다. 그러나 빌드 시스템은 빌드 규칙을 생성할 때 임의의 파일을 읽을 수 없으므로(성능의 이유로) 매니페스트에 직접 액세스하여 매니페스트에 있는 <uses-library> 태그를 읽을 수 없습니다. 또한 매니페스트가 APK 내부에 패키징되거나 사전 빌드될 수도 있습니다. 따라서 빌드 파일(Android.bp 또는 Android.mk)에 <uses-library> 정보가 있어야 합니다.

이전에는 ART가 공유 라이브러리 종속 항목(&-classpath라고 함)을 무시하는 해결 방법을 사용했습니다. 이 방법은 안전하지 않으며 미세한 버그를 야기하므로 Android 12에서는 이 해결 방법이 삭제되었습니다.

결과적으로 빌드 파일에 올바른 <uses-library> 정보를 제공하지 않는 Java 모듈은 빌드 손상(빌드 시 CLC 불일치로 인해 발생)이나 첫 부팅 시 회귀(dexopt 이후 부팅 시 CLC 불일치로 인해 발생)를 야기할 수 있습니다.

이전 경로

손상된 빌드를 수정하려면 다음 단계를 따릅니다.

  1. 다음을 설정하여 특정 제품의 빌드 시 검사를 전역적으로 사용 중지합니다.

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    제품 makefile에서 설정합니다. 이렇게 하면 빌드 오류가 해결됩니다(손상 해결 섹션에 나오는 특수한 경우는 제외). 하지만 일시적인 해결 방법이며 dexopt 이후 부팅 시 CLC 불일치가 발생할 수 있습니다.

  2. 빌드 파일에 필수 <uses-library> 정보를 추가하여 빌드 시 검사를 전역적으로 사용 중지하기 전에 실패한 모듈을 수정합니다. 자세한 내용은 손상 해결을 참고하세요. 대부분의 모듈의 경우 Android.bp 또는 Android.mk에 몇 개의 줄을 추가해야 합니다.

  3. 문제가 되는 사례에 관해 빌드 시 검사와 dexpreopt를 모듈별로 사용 중지합니다. 부팅 시 거부되는 아티팩트에서 빌드 시간과 저장용량을 낭비하지 않도록 dexpreopt를 사용 중지합니다.

  4. 1단계에서 설정한 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES를 설정 해제하여 빌드 시 검사를 전역적으로 다시 사용 설정합니다. 이 변경 후에 빌드가 실패해서는 안 됩니다(2단계 및 3단계로 인해).

  5. 3단계에서 사용 중지한 모듈을 한 번에 하나씩 수정한 후 dexpreopt와 <uses-library> 검사를 다시 사용 설정합니다. 필요한 경우 버그를 신고합니다.

Android 12에서는 빌드 시 <uses-library> 검사가 강제 적용됩니다.

손상 해결

다음 섹션에서는 특정 유형의 손상을 해결하는 방법을 설명합니다.

빌드 오류: CLC 불일치

빌드 시스템은 빌드 시에 Android.bp 또는 Android.mk 파일의 정보와 매니페스트 간에 일관성을 검사합니다. 빌드 시스템은 매니페스트를 읽을 수 없지만 매니페스트를 읽기 위한 빌드 규칙을 생성할 수 있고(필요한 경우 APK에서 추출) 매니페스트의 <uses-library> 태그를 빌드 파일의 <uses-library> 정보와 비교할 수 있습니다. 검사가 실패하는 경우 오류는 다음과 같습니다.

error: mismatch in the <uses-library> tags between the build system and the manifest:
    - required libraries in build system: []
                     vs. in the manifest: [org.apache.http.legacy]
    - optional libraries in build system: []
                     vs. in the manifest: [com.x.y.z]
    - tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
        <uses-library android:name="com.x.y.z"/>
        <uses-library android:name="org.apache.http.legacy"/>

note: the following options are available:
    - to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
    - to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
    - to fix the check, make build system properties coherent with the manifest
    - see build/make/Changes.md for details

오류 메시지에서 알 수 있듯이 긴급도에 따라 여러 솔루션이 있습니다.

  • 임시 제품 일괄 수정의 경우 제품 makefile에서 PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true를 설정합니다. 빌드 시 일관성 검사는 여전히 실행되지만 검사 실패가 빌드 실패를 의미하지는 않습니다. 대신, 검사에 실패하면 빌드 시스템이 dex2oat 컴파일러 필터를 dexpreopt의 verify로 다운그레이드하여 이 모듈의 AOT 컴파일이 완전히 사용 중지됩니다.
  • 빠른 전역 명령줄 수정의 경우 환경 변수 RELAX_USES_LIBRARY_CHECK=true를 사용합니다. PRODUCT_BROKEN_VERIFY_USES_LIBRARIES와 동일한 효과를 갖지만 명령줄에서 사용할 수 있습니다. 환경 변수가 제품 변수를 재정의합니다.
  • 오류의 근본 원인 수정을 위한 솔루션의 경우 빌드 시스템이 매니페스트의 <uses-library> 태그를 인식하게 합니다. 오류 메시지를 검사하여 문제를 일으키는 라이브러리를 알 수 있습니다(예: AndroidManifest.xml 검사 또는 `aapt dump badging $APK | grep uses-library`를 사용해 확인할 수 있는 APK 내부의 매니페스트 검사).

Android.bp 모듈의 경우:

  1. 모듈의 libs 속성에서 누락된 라이브러리를 찾습니다. 라이브러리 있는 경우 Soong은 일반적으로 다음과 같은 특별한 경우를 제외하고 이 라이브러리를 자동으로 추가합니다.

    • 라이브러리가 SDK 라이브러리가 아님(java_sdk_library가 아닌 java_library로 정의됨)
    • 매니페스트에 있는 라이브러리 이름이 빌드 시스템의 모듈 이름과 다름

    이 문제를 일시적으로 해결하려면 Android.bp 라이브러리 정의에 provides_uses_lib: "<library-name>"을 추가합니다. 장기적인 솔루션을 위해서는 라이브러리를 SDK 라이브러리로 변환하거나 모듈의 이름을 바꾸는 등 근본적인 문제를 해결합니다.

  2. 이전 단계에서 해결 방법을 제공하지 않았다면 필수 라이브러리의 경우 uses_libs: ["<library-module-name>"]을, 선택적 라이브러리의 경우 optional_uses_libs: ["<library-module-name>"]을 모듈의 Android.bp 정의에 추가합니다. 이러한 속성은 모듈 이름 목록을 허용합니다. 목록에 있는 라이브러리의 상대적 순서는 매니페스트의 순서와 동일해야 합니다.

Android.mk 모듈의 경우:

  1. 매니페스트에 있는 라이브러리 이름이 빌드 시스템의 모듈 이름과 다른지 여부를 확인합니다. 다른 경우에는 라이브러리의 Android.mk 파일에 LOCAL_PROVIDES_USES_LIBRARY := <library-name>을 추가하여 일시적으로 해결하거나 라이브러리의 Android.bp 파일에 provides_uses_lib: "<library-name>"을 추가합니다(Android.mk 모듈이 Android.bp 라이브러리에 종속될 수 있으므로 두 경우 모두 가능함). 장기적인 솔루션을 위해서는 라이브러리 모듈 이름을 바꾸는 등 근본적인 문제를 해결합니다.

  2. 필수 라이브러리의 경우 LOCAL_USES_LIBRARIES := <library-module-name>을, 선택적 라이브러리의 경우 LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>을 모듈의 Android.mk 정의에 추가합니다. 이러한 속성은 모듈 이름 목록을 허용합니다. 목록에 있는 라이브러리의 상대적 순서는 매니페스트의 순서와 동일해야 합니다.

빌드 오류: 알 수 없는 라이브러리 경로

빌드 시스템이 <uses-library> DEX jar의 경로(호스트의 빌드 시 경로 또는 기기 내 설치 경로)를 찾을 수 없는 경우 일반적으로 빌드에 실패합니다. 경로를 찾지 못하는 경우는 라이브러리가 예기치 않은 방식으로 구성되었음을 나타낼 수 있습니다. 문제가 있는 모듈에 dexpreopt를 사용 중지하면 빌드가 일시적으로 수정됩니다.

Android.bp(모듈 속성):

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk(모듈 변수):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

지원되지 않는 시나리오를 조사하려면 버그를 신고하세요.

빌드 오류: 라이브러리 종속 항목 누락

모듈 Y의 매니페스트에서 Y용 빌드 파일에 <uses-library> X를 추가하려고 하면 누락된 종속 항목 X로 인해 빌드 오류가 발생할 수도 있습니다.

다음은 Android.bp 모듈에 관한 오류 메시지 샘플입니다.

"Y" depends on undefined module "X"

다음은 Android.mk 모듈에 관한 오류 메시지 샘플입니다.

'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it

라이브러리 이름이 빌드 시스템 내의 상응하는 모듈과 다르게 지정되었을 때 이러한 오류의 일반적인 원인이 됩니다. 예를 들어 매니페스트 <uses-library> 항목이 com.android.X이지만 라이브러리 모듈의 이름이 X이면 오류가 발생합니다. 이 문제를 해결하려면 X라는 모듈이 com.android.X라는 <uses-library>를 제공한다는 것을 빌드 시스템에 알립니다.

다음은 Android.bp 라이브러리(모듈 속성)의 예입니다.

provides_uses_lib: “com.android.X”,

다음은 Android.mk 라이브러리(모듈 변수)의 예입니다.

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

부팅 시 CLC 불일치

처음 부팅할 때 아래와 같이 logcat에서 CLC 불일치와 관련된 메시지를 검색합니다.

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

출력에 다음과 같은 형식의 메시지가 포함될 수 있습니다.

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

CLC 불일치 경고가 표시되면 결함이 있는 모듈에 관한 dexopt 명령어를 찾습니다. 해결하려면 모듈의 빌드 시 검사를 통과해야 합니다. 그래도 해결되지 않으면 빌드 시스템에서 지원되지 않는 특수한 사례일 수 있습니다(예: 라이브러리가 아닌 다른 APK를 로드하는 앱). 빌드 시스템이 모든 사례를 처리하지는 않습니다. 빌드 시에는 앱이 런타임에 무엇을 로드하는지 정확히 알 수 없기 때문입니다.

클래스 로더 컨텍스트

CLC는 클래스 로더 계층 구조를 설명하는 트리 구조입니다. 빌드 시스템은 좁은 의미에서 CLC를 사용합니다(APK 또는 맞춤 클래스 로더가 아닌 라이브러리만 포함). 라이브러리나 앱의 모든 <uses-library> 종속 항목의 전이적 폐쇄를 나타내는 라이브러리 트리입니다. CLC의 최상위 수준 요소는 매니페스트(클래스 경로)에 지정된 직접 <uses-library> 종속 항목입니다. CLC 트리의 각 노드는 자체 <uses-library> 하위 노드가 있을 수도 있는 <uses-library> 노드입니다.

<uses-library> 종속 항목은 트리가 아닌 방향성 비순환 그래프이므로 CLC에 동일한 라이브러리의 여러 하위 트리가 포함될 수 있습니다. 즉, CLC는 트리로 '펼쳐진' 종속 항목 그래프입니다. 중복은 논리적 수준에서만 이루어지며 실제 기본 클래스 로더는 중복되지 않습니다(런타임에 라이브러리마다 단일 클래스 로더 인스턴스가 있음).

CLC는 라이브러리나 앱에서 사용되는 Java 클래스를 결정할 때 라이브러리의 조회 순서를 정의합니다. 라이브러리가 중복 클래스를 포함할 수 있고 클래스가 첫 번째 일치 항목으로 확인되기 때문에 조회 순서가 중요합니다.

기기 내(런타임) CLC

frameworks/basePackageManager는 CLC를 만들어 기기 내에 Java 모듈을 로드합니다. 모듈의 매니페스트에 있는 <uses-library> 태그에 나열된 라이브러리를 최상위 CLC 요소로 추가합니다.

PackageManager는 사용되는 라이브러리마다 모든 <uses-library> 종속 항목(라이브러리의 매니페스트에 태그로 지정됨)을 가져오고 각 종속 항목의 중첩된 CLC를 추가합니다. 이 프로세스는 구성된 CLC 트리의 모든 리프 노드가 <uses-library> 종속 항목이 없는 라이브러리일 때까지 반복적으로 계속됩니다.

PackageManager는 공유 라이브러리만 인식합니다. 이러한 사용에서 공유의 정의는 일반적인 의미(공유 대 정적)와 다릅니다. Android에서 자바 공유 라이브러리는 기기 내에 설치된 XML 구성(/system/etc/permissions/platform.xml)에 나열된 라이브러리입니다. 각 항목에는 공유 라이브러리 이름, DEX jar 파일 경로, 종속 항목 목록(런타임에 사용하며 매니페스트의 <uses-library> 태그에서 지정된 다른 공유 라이브러리)이 포함됩니다.

즉, 런타임에 PackageManager가 CLC를 구성할 수 있게 하는 두 가지 정보 소스, 즉 매니페스트의 <uses-library> 태그와 XML 구성의 공유 라이브러리 종속 항목이 있습니다.

호스트(빌드 시) CLC

CLC는 라이브러리나 앱을 로드할 때뿐만 아니라 컴파일할 때도 필요합니다. 컴파일은 기기 내(dexopt)에서나 빌드 중에(dexpreopt) 발생할 수 있습니다. dexopt는 기기 내에서 발생하므로 PackageManager (매니페스트 및 공유 라이브러리 종속 항목)와 동일한 정보를 가집니다. 하지만 Dexpreopt는 호스트에서 그리고 완전히 다른 환경에서 발생하며 빌드 시스템에서 동일한 정보를 가져와야 합니다.

따라서 dexpreopt에서 사용하는 빌드 시 CLC와 PackageManager에서 사용하는 런타임 CLC는 동일하지만 서로 다른 두 방식으로 계산됩니다.

빌드 시 CLC와 런타임 CLC는 반드시 일치해야 합니다. 일치하지 않으면 dexpreopt에 의해 생성된 AOT 컴파일 코드가 거부됩니다. 빌드 시 CLC와 런타임 CLC의 동등성을 확인하기 위해 dex2oat 컴파일러는 빌드 시 CLC를 *.odex 파일(OAT 파일 헤더의 classpath 필드)에 기록합니다. 저장된 CLC를 찾으려면 다음 명령어를 사용합니다.

oatdump --oat-file=<FILE> | grep '^classpath = '

부팅 중에 빌드 시 CLC 및 런타임 CLC 불일치가 logcat에서 보고됩니다. 다음 명령어를 사용하여 검색합니다.

logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'

불일치는 라이브러리나 앱을 강제로 dexopt 처리하거나 최적화 없이 실행하므로(예: 앱의 코드가 APK에서 메모리에 추출해야 할 수도 있으며 이는 비용이 매우 많이 드는 작업) 성능에 악영향을 미칩니다.

공유 라이브러리는 선택사항이거나 필수일 수 있습니다. dexpreopt 관점에서는 필수 라이브러리가 빌드 시에 있어야 합니다(없다면 빌드 오류임). 선택적 라이브러리는 빌드 시에 있거나 없을 수 있습니다. 있는 경우 라이브러리는 CLC에 추가되고 dex2oat에 전달되고 *.odex 파일에 기록됩니다. 선택적 라이브러리가 없는 경우에는 건너뛰며 CLC에 추가되지 않습니다. 빌드 시 상태와 런타임 상태 사이에 불일치가 있는 경우(선택적인 라이브러리가 한 경우에는 있고 다른 한 경우에는 없음)에는 빌드 시 CLC와 런타임 CLC가 일치하지 않으며 컴파일된 코드가 거부됩니다.

고급 빌드 시스템 세부정보(매니페스트 수정자)

라이브러리나 앱의 소스 매니페스트에서 <uses-library> 태그가 누락되는 경우가 있습니다. 예를 들어 라이브러리나 앱의 전이적 종속 항목 중 하나가 다른 <uses-library> 태그를 사용하기 시작하는데 라이브러리나 앱의 매니페스트는 이 태그를 포함하도록 업데이트되지 않은 경우 이러한 상황이 발생할 수 있습니다.

Soong은 지정된 라이브러리나 앱의 일부 누락된 <uses-library> 태그를 라이브러리나 앱의 전이적 종속 항목 폐쇄의 SDK 라이브러리로 자동 계산할 수 있습니다. 라이브러리(또는 앱)는 SDK 라이브러리에 종속된 정적 라이브러리에 종속될 수도 있으며 다른 라이브러리를 통해 전이적으로 종속될 수도 있기 때문에 폐쇄가 필요합니다.

이 방식으로 모든 <uses-library> 태그를 계산할 수는 없지만, 가능하다면 Soong으로 하여금 매니페스트 항목을 자동으로 추가하도록 하는 것이 더 좋습니다. 그러면 오류 발생 가능성이 낮아지고 유지보수가 간소화됩니다. 예를 들어 많은 앱이 새로운 <uses-library> 종속 항목을 추가하는 정적 라이브러리를 사용하는 경우 이러한 앱을 모두 업데이트해야 하므로 유지보수가 어렵습니다.