ART TI

Android 8.0 이상에서 ART TI(ART Tooling Interface)는 특정 런타임 내부요소를 노출하고 프로파일러와 디버거가 앱의 런타임 동작에 영향을 줄 수 있도록 합니다. ART TI는 다른 플랫폼에서 네이티브 에이전트를 구현하기 위한 용도로 제공되는 최첨단 성능 도구를 구현하는 데 사용할 수 있습니다.

런타임 내부요소는 런타임 프로세스에 로드된 에이전트에 노출됩니다. 이러한 항목은 직접 호출과 콜백을 통해 ART와 통신합니다. 런타임은 서로 다른 직교 프로파일링 문제를 분리할 수 있도록 여러 에이전트를 지원합니다. 에이전트는 런타임 시작 시 제공되거나(dalvikvm 또는 app_process가 호출된 경우) 이미 실행 중인 프로세스에 연결될 수 있습니다.

앱 및 런타임 동작을 계측하고 수정하는 기능이 매우 강력하기 때문에 다음 두 가지 안전 조치가 ART TI에 통합되었습니다.

  • 첫째, 에이전트 인터페이스 JVMTI를 노출하는 코드가 런타임의 핵심 구성요소가 아닌 런타임 플러그인으로 구현되었습니다. 에이전트가 인터페이스 지점을 찾지 못하게 차단될 수 있도록 플러그인 로드가 제한될 수 있습니다.
  • 둘째, ActivityManager 클래스와 런타임 프로세스는 에이전트가 디버그 가능한 앱에만 연결되도록 허용합니다. 디버그 가능한 앱은 분석 및 계측을 위해 개발자가 승인한 것이며 최종 사용자에게는 배포되지 않습니다. Google Play 스토어에서는 디버그 가능한 앱의 배포를 허용하지 않습니다. 따라서 일반 앱(핵심 구성요소 포함)이 계측되거나 조작되지 않습니다.

디자인

그림 1은 계측된 앱의 일반 흐름과 상호 연결을 보여 줍니다.

계측된 앱의 흐름 및 상호 연결
그림 1. 계측된 앱의 흐름 및 상호 연결

ART 플러그인 libopenjdkjvmti는 플랫폼의 요구사항과 제약 조건을 충족하도록 설계된 ART TI를 노출합니다.

  • 클래스 재정의는 클래스 파일 대신 단일 클래스 정의만 포함된 Dex 파일을 기반으로 합니다.
  • 계측 및 재정의를 위한 Java 언어 API는 노출되지 않습니다.

ART TI는 Android 스튜디오 프로파일러도 지원합니다.

에이전트 로드 또는 연결

런타임 시작 시 에이전트를 연결하려면 다음 명령어를 사용하여 JVMTI 플러그인과 지정된 에이전트를 모두 로드합니다.

dalvikvm -Xplugin:libopenjdkjvmti.so -agentpath:/path/to/agent/libagent.so …

에이전트가 런타임 시작 시 로드될 경우 적용되는 안전 조치가 없으므로, 수동으로 시작한 런타임은 안전 조치 없이 전체 수정을 허용하게 됩니다. 이로 인해 ART 테스트가 허용됩니다.

참고: 이 점은 기기상의 일반 앱(시스템 서버 포함)에는 적용되지 않습니다. 앱은 이미 실행 중인 zygote에서 포크되고, zygote 프로세스에서는 에이전트를 로드할 수 없습니다.

이미 실행 중인 앱에 에이전트를 연결하려면 다음 명령어를 사용합니다.

adb shell cmd activity attach-agent [process]
/path/to/agent/libagent.so[=agent-options]

JVMTI 플러그인이 아직 로드되지 않은 경우 에이전트를 연결하면 플러그인과 에이전트 라이브러리가 모두 로드됩니다.

에이전트는 디버그 가능(앱 노드에서 속성 android:debuggabletrue로 설정된 앱 매니페스트의 일부)으로 표시된 실행 중인 앱에만 연결될 수 있습니다. ActivityManager 클래스와 ART는 모두 에이전트 연결을 허용하기 전에 검사를 진행합니다. ActivityManager 클래스는 현재 앱 정보(PackageManager 클래스 데이터에서 가져옴)에서 디버그 가능 상태를 확인하고, 런타임은 앱이 시작될 때 설정된 현재 상태를 확인합니다.

에이전트 위치

에이전트가 현재 프로세스에 직접 바인딩되어 통신할 수 있도록 런타임은 에이전트를 현재 프로세스로 로드해야 합니다. ART 자체는 에이전트의 위치에 구속받지 않습니다. 문자열은 dlopen 호출에 사용됩니다. 파일 시스템 권한과 SELinux 정책은 실제 로드를 제한합니다.

디버그 가능한 앱에서 실행할 수 있는 에이전트를 제공하려면 다음 작업을 진행합니다.

  • 앱의 APK 라이브러리 디렉터리에 에이전트를 삽입합니다.
  • run-as를 사용하여 에이전트를 앱의 데이터 디렉터리에 복사합니다.

API

다음 메서드가 android.os.Debug에 추가되었습니다.

/**
     * Attach a library as a jvmti agent to the current runtime, with the given classloader
     * determining the library search path.
     * Note: agents may only be attached to debuggable apps. Otherwise, this function will
     * throw a SecurityException.
     *
     * @param library the library containing the agent.
     * @param options the options passed to the agent.
     * @param classLoader the classloader determining the library search path.
     *
     * @throws IOException if the agent could not be attached.
     * @throws a SecurityException if the app is not debuggable.
     */
    public static void attachJvmtiAgent(@NonNull String library, @Nullable String options,
            @Nullable ClassLoader classLoader) throws IOException {

기타 Android API

attach-agent 명령어는 공개적으로 표시되어 있습니다. 이 명령어는 JVMTI 에이전트를 실행 중인 프로세스에 연결합니다.

adb shell 'am attach-agent com.example.android.displayingbitmaps
\'/data/data/com.example.android.displayingbitmaps/code_cache/libfieldnulls.so=Ljava/lang/Class;.name:Ljava/lang/String;\''

am start -Pam start-profiler/stop-profiler 명령어는 attach-agent 명령어와 유사합니다.

JVMTI

이 기능은 JVMTI API를 에이전트(네이티브 코드)에 노출합니다. 중요한 기능은 다음과 같습니다.

  • 클래스 재정의
  • 객체 할당 및 가비지 컬렉션 추적
  • 객체의 참조 트리를 따라 힙의 모든 객체 반복 처리
  • 자바 호출 스택 검사
  • 모든 스레드 정지 및 다시 시작

Android 버전마다 사용 가능한 기능이 다를 수 있습니다.

호환성

이 기능을 사용하려면 Android 8.0 이상에서만 제공되는 핵심 런타임 지원이 필요합니다. 기기 제조업체는 이 기능을 구현하기 위해 어떠한 변경사항도 적용할 필요가 없습니다. 이는 AOSP의 일부입니다.

유효성 검사

CTS는 Android 8 이상에서 다음을 테스트합니다.

  • 에이전트가 디버그 가능한 앱에 연결되고 디버그 가능하지 않은 앱에는 연결이 실패하는지 테스트
  • 구현된 모든 JVMTI API 테스트
  • 에이전트의 바이너리 인터페이스가 안정적인지 테스트

Android 9 이상에는 테스트가 추가되었고 추가된 테스트는 이러한 버전의 CTS 테스트에 포함되어 있습니다.