Android 运行时 (ART) 深度解析

Android 运行时 (ART) 深度解析

ART (Android Runtime) 是 Android 5.0 (Lollipop) 之后默认的应用程序运行时环境,取代了旧的 Dalvik 虚拟机 (DVM)。ART 的引入显著改变了 Android 应用的执行方式,旨在提高应用的性能、启动速度和电池续航。

核心机制:AOT vs JIT

思考:为什么逆向工程师必须理解 ART?

你可能会想:“我只关心应用的 Java 代码和加密算法,ART 的编译机制与我有什么关系?”

实际场景告诉你答案

  • Frida Hook 失败:你写的 Hook 脚本在 Android 4.x 上好用,在 8.0+ 上就不工作了——因为 ART 的 AOT 编译改变了方法的执行方式
  • 脱壳困境:你用传统方法 dump DEX,结果发现关键类根本不在 DEX 里——它们在运行时被解密后直接编译成了 OAT
  • 性能分析:为什么同样的代码在不同 Android 版本上性能差异巨大?混合编译模式是关键
  • 反调试对抗:某些 App 会检测 OAT 文件的完整性,或者利用 dex2oat 的时机来进行反调试

核心要点

  • Android 5.0+ 的应用不再是简单的"DEX 字节码"执行
  • 真正执行的是 本地机器码(OAT 文件)
  • 理解 DEX → VDEX → OAT 的转换流程,才能应对现代 Android 逆向

Dalvik 的 JIT (Just-In-Time)

在 Android 4.4 及更早版本中,Dalvik 虚拟机使用 JIT 编译。

  • 工作方式: 应用每次运行时,Dalvik 会解释执行 DEX 字节码。对于频繁执行的"热点代码" (hotspot),JIT 编译器会将其动态地编译成本地机器码并缓存。

  • 优点: 安装速度快,不占用额外存储空间。

  • 缺点: 应用启动和运行期间需要持续进行解释和编译,导致启动慢、耗电多。

ART 的 AOT (Ahead-Of-Time)

ART 最初的设计是纯 AOT 编译。

  • 工作方式: 在应用安装时,系统会调用 dex2oat 工具,将 APK 中的 classes.dex 文件完整地编译成本地机器码,并以 OAT 文件的形式存储。

  • 优点:

    • 运行速度快: 应用直接执行本地机器码,无需实时编译,性能和启动速度都大大提升。
    • 更省电: CPU 在运行时负担更轻。
  • 缺点:

    • 安装时间长: 应用安装过程需要额外的编译时间。
    • 占用空间大: 预编译的 OAT 文件会占用更多的存储空间。

混合编译 (AOT + JIT + Profile-Guided)

从 Android 7.0 (Nougat) 开始,ART 引入了结合 JIT 的混合编译模式,以平衡上述优缺点。

工作流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────────────────┐
│                        ART 混合编译执行流程                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐      ┌──────────┐   │
│   │ 应用安装  │ ───> │ 首次运行  │ ───> │ 收集 Profile │ ───> │ 后台优化  │   │
│   │ (快速)    │      │ (JIT)    │      │ (热点分析)  │      │ (AOT)    │   │
│   └──────────┘      └──────────┘      └──────────┘      └──────────┘   │
│        │                 │                  │                 │         │
│        ▼                 ▼                  ▼                 ▼         │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐      ┌──────────┐   │
│   │ 无编译    │      │ 解释执行  │      │ .prof 文件│      │ .oat 文件 │   │
│   │          │      │ + JIT 缓存│      │ 热点记录   │      │ 优化代码  │   │
│   └──────────┘      └──────────┘      └──────────┘      └──────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
  1. 初次安装: 应用安装速度很快,不进行 AOT 编译。

  2. 首次运行: 应用代码由解释器执行,同时 JIT 编译器会介入,编译热点代码。在此期间,ART 会生成一份代码执行频率的分析文件 (Profile)

  3. 设备空闲时: 当设备处于空闲状态并正在充电时,Android 系统会启动一个后台优化任务。该任务会根据之前收集的 Profile 信息,只对那些频繁执行的热点方法进行 AOT 编译,并生成新的 OAT 文件。

  4. 后续运行: 直接执行 AOT 编译后的本地代码,速度最快。

Profile 文件位置:

1
2
3
4
5
6
7
8
# 应用级 Profile
/data/misc/profiles/cur/0/<package_name>/primary.prof
/data/misc/profiles/ref/<package_name>/primary.prof

# 查看 Profile 内容
adb shell profman --dump-classes-and-methods \
  --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \
  --apk=/data/app/com.example.app-xxx/base.apk

ART 内部架构

运行时组件总览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────────────┐
│                              ART 运行时架构                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                          应用层 (Application Layer)                   │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                  │   │
│  │  │ Java 代码    │  │ Kotlin 代码 │  │ Android SDK │                  │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘                  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│                                    ▼                                        │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                          ART 核心运行时                               │   │
│  │  ┌───────────────┬───────────────┬───────────────┬───────────────┐  │   │
│  │  │   解释器       │   JIT 编译器  │   AOT 代码    │   GC 系统     │  │   │
│  │  │  (Interpreter) │  (JIT Compiler)│  (OAT Code)   │  (Garbage     │  │   │
│  │  │               │               │               │   Collector)  │  │   │
│  │  └───────────────┴───────────────┴───────────────┴───────────────┘  │   │
│  │  ┌───────────────┬───────────────┬───────────────┬───────────────┐  │   │
│  │  │  类加载器      │   线程管理    │   内存管理    │   JNI 桥接    │  │   │
│  │  │ (ClassLoader) │(Thread Manager)│(Heap Manager) │  (JNI Bridge) │  │   │
│  │  └───────────────┴───────────────┴───────────────┴───────────────┘  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│                                    ▼                                        │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                          Native 层                                   │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                  │   │
│  │  │ libart.so   │  │ libc.so     │  │ Native Libs │                  │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘                  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

核心源码结构

ART 源码位于 AOSP 的 art/ 目录下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
art/
├── compiler/           # AOT/JIT 编译器
│   ├── optimizing/     # 优化编译器 (主要)
│   ├── driver/         # 编译驱动
│   └── jni/            # JNI 编译支持
├── runtime/            # 运行时核心
│   ├── gc/             # 垃圾回收
│   ├── jni/            # JNI 实现
│   ├── interpreter/    # 解释器
│   ├── mirror/         # Java 对象的 C++ 镜像
│   ├── entrypoints/    # 入口点 (quick/portable)
│   └── thread.cc       # 线程管理
├── dex2oat/            # DEX 到 OAT 编译工具
├── oatdump/            # OAT 文件分析工具
└── libdexfile/         # DEX 文件解析库

关键数据结构

Runtime 类

Runtime 是 ART 的核心单例类,管理整个运行时环境:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// art/runtime/runtime.h
class Runtime {
 public:
  // 获取单例
  static Runtime* Current() { return instance_; }

  // 核心组件
  gc::Heap* GetHeap() const { return heap_; }
  ClassLinker* GetClassLinker() const { return class_linker_; }
  InternTable* GetInternTable() const { return intern_table_; }
  JavaVMExt* GetJavaVM() const { return java_vm_; }
  ThreadList* GetThreadList() const { return thread_list_; }
  jit::Jit* GetJit() const { return jit_.get(); }

 private:
  static Runtime* instance_;
  gc::Heap* heap_;                    // 堆管理
  ClassLinker* class_linker_;         // 类链接器
  InternTable* intern_table_;         // 字符串池
  JavaVMExt* java_vm_;               // JavaVM 实例
  ThreadList* thread_list_;          // 线程列表
  std::unique_ptr<jit::Jit> jit_;    // JIT 编译器
  // ...
};

Thread 类

每个 Java 线程对应一个 Thread 对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// art/runtime/thread.h
class Thread {
 public:
  // TLS (Thread Local Storage) 入口
  static Thread* Current();

  // 获取 JNI 环境
  JNIEnvExt* GetJniEnv() const { return tlsPtr_.jni_env; }

  // 获取托管栈顶
  ManagedStack* GetManagedStack() { return &tlsPtr_.managed_stack; }

  // 线程状态
  ThreadState GetState() const { return tls32_.state_and_flags.as_struct.state; }

 private:
  struct PACKED(4) tls_32bit_sized_values {
    union StateAndFlags state_and_flags;
    int suspend_count;
    int debug_suspend_count;
    // ...
  } tls32_;

  struct PACKED(8) tls_ptr_sized_values {
    JNIEnvExt* jni_env;
    ManagedStack managed_stack;
    mirror::Object* exception;
    // ...
  } tlsPtr_;
};

内存布局

ART 进程的内存布局:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
┌─────────────────────────────────────────────────────────────────────────┐
│                         ART 进程内存布局                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  高地址 ─────────────────────────────────────────────────────────────   │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                        内核空间 (Kernel)                           │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                        栈 (Stack)                                  │  │
│  │  - 每个线程一个栈                                                   │  │
│  │  - 包含 Java 栈帧和 Native 栈帧                                    │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                        内存映射 (mmap)                             │  │
│  │  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐   │  │
│  │  │  boot.art       │  │  boot.oat       │  │  app.oat        │   │  │
│  │  │  (Image Heap)   │  │  (Boot Code)    │  │  (App Code)     │   │  │
│  │  └─────────────────┘  └─────────────────┘  └─────────────────┘   │  │
│  │  ┌─────────────────┐  ┌─────────────────┐                        │  │
│  │  │  libart.so      │  │  其他 .so 文件  │                        │  │
│  │  └─────────────────┘  └─────────────────┘                        │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                        ART 托管堆 (Managed Heap)                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐               │  │
│  │  │ Image Space │  │ Zygote Space│  │ Alloc Space │               │  │
│  │  │ (只读)       │  │ (共享)      │  │ (App 对象)  │               │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘               │  │
│  │  ┌─────────────┐  ┌─────────────┐                                │  │
│  │  │ Large Object│  │ Non-Moving  │                                │  │
│  │  │   Space     │  │   Space     │                                │  │
│  │  └─────────────┘  └─────────────┘                                │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                        程序段 (Text/Data/BSS)                      │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  低地址 ─────────────────────────────────────────────────────────────   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

ART 方法调用机制

方法执行模式

ART 支持三种方法执行模式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────────────────┐
│                        ART 方法执行模式                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐     │
│  │   解释执行       │    │   JIT 编译执行   │    │   AOT 编译执行   │     │
│  │  (Interpreter)  │    │  (JIT Compiled) │    │  (AOT Compiled) │     │
│  └────────┬────────┘    └────────┬────────┘    └────────┬────────┘     │
│           │                      │                      │              │
│           ▼                      ▼                      ▼              │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐     │
│  │ 逐条解释 DEX     │    │ 运行时编译为     │    │ 直接执行预编译   │     │
│  │ 字节码指令       │    │ 本地代码并缓存   │    │ 的本地机器码     │     │
│  └─────────────────┘    └─────────────────┘    └─────────────────┘     │
│                                                                         │
│  速度:慢           │    速度:中等         │    速度:快              │
│  内存:小           │    内存:动态增长     │    内存:预分配          │
│  用途:调试/首次运行 │    用途:热点代码     │    用途:核心路径        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

ArtMethod 结构

ArtMethod 是 ART 中表示 Java 方法的核心结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// art/runtime/art_method.h
class ArtMethod {
 protected:
  // 声明该方法的类
  GcRoot<mirror::Class> declaring_class_;

  // 访问标志 (public, static, native 等)
  std::atomic<uint32_t> access_flags_;

  // 方法在 DEX 文件中的索引
  uint32_t dex_method_index_;

  // 方法在虚表中的索引
  uint16_t method_index_;

  // 热度计数器 (用于 JIT 决策)
  uint16_t hotness_count_;

  // 指向数据的指针 (根据编译状态不同含义不同)
  struct PtrSizedFields {
    // 方法入口点
    void* entry_point_from_quick_compiled_code_;

    // JNI 代码 (native 方法) 或 DEX 代码指针
    void* data_;
  } ptr_sized_fields_;
};

方法入口点 (Entry Point)

每个 ArtMethod 有一个入口点,决定了方法如何被执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─────────────────────────────────────────────────────────────────────────┐
│                        方法入口点类型                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ArtMethod                                                              │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │  entry_point_from_quick_compiled_code_  ───────────┐           │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                       │                 │
│                                                       ▼                 │
│                           ┌───────────────────────────────────────┐    │
│                           │        入口点类型                      │    │
│                           ├───────────────────────────────────────┤    │
│                           │                                       │    │
│  ┌────────────────────┐   │  1. 解释器入口                        │    │
│  │ art_quick_to_      │◄──│     art_quick_to_interpreter_bridge  │    │
│  │ interpreter_bridge │   │                                       │    │
│  └────────────────────┘   │  2. JNI 入口 (native 方法)            │    │
│                           │     art_quick_generic_jni_trampoline │    │
│  ┌────────────────────┐   │                                       │    │
│  │ Compiled Code      │◄──│  3. 编译后的代码地址                  │    │
│  │ (OAT/JIT)          │   │     直接指向 OAT 或 JIT 编译的代码    │    │
│  └────────────────────┘   │                                       │    │
│                           │  4. 抽象方法蹦床                      │    │
│  ┌────────────────────┐   │     art_quick_invoke_stub             │    │
│  │ Resolution         │◄──│                                       │    │
│  │ Trampoline         │   │  5. 分辨蹦床 (未链接方法)             │    │
│  └────────────────────┘   │     art_quick_resolution_trampoline   │    │
│                           └───────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

方法调用流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 伪代码:方法调用流程
void InvokeMethod(ArtMethod* method, Object* receiver, args...) {
    // 1. 获取入口点
    void* entry_point = method->GetEntryPointFromQuickCompiledCode();

    // 2. 根据入口点类型执行
    if (entry_point == art_quick_to_interpreter_bridge) {
        // 解释执行
        InterpretMethod(method, receiver, args);
    } else if (entry_point == art_quick_generic_jni_trampoline) {
        // JNI 调用
        CallJniMethod(method, receiver, args);
    } else {
        // 直接执行编译后的代码
        typedef void (*CompiledCode)(Object*, ...);
        ((CompiledCode)entry_point)(receiver, args);
    }
}

Quick vs Portable 编译

ART 早期支持两种编译后端:

特性QuickPortable
实现方式直接生成目标架构汇编使用 LLVM 后端
性能更快的编译速度更好的运行时性能
代码大小较小较大
平台支持需要为每个架构单独实现LLVM 自动支持多平台
现状主要使用已在 Android 6.0 移除

现代 ART 主要使用 Optimizing Compiler,它是 Quick 编译器的演进版本,具有更好的优化能力。


ART 对象模型与类结构

对象头 (Object Header)

每个 Java 对象在内存中都有一个对象头:

1
2
3
4
5
6
7
8
9
// art/runtime/mirror/object.h
class Object {
 protected:
  // 对象头只有一个字段:类指针 + 锁/GC 状态
  HeapReference<Class> klass_;

  // Monitor (锁) 或 HashCode 或 GC 标记
  uint32_t monitor_;
};

32 位系统对象布局:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────────┐
│                        32 位对象内存布局                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │ Object Header (8 bytes)                                        │    │
│  ├────────────────────────────────────────────────────────────────┤    │
│  │  ┌─────────────────────────┐  ┌─────────────────────────┐      │    │
│  │  │     klass_ (4 bytes)    │  │    monitor_ (4 bytes)   │      │    │
│  │  │   指向 Class 对象       │  │  锁状态/HashCode/GC标记  │      │    │
│  │  └─────────────────────────┘  └─────────────────────────┘      │    │
│  ├────────────────────────────────────────────────────────────────┤    │
│  │ Instance Fields (变长)                                         │    │
│  │  ┌─────────────────────────────────────────────────────┐       │    │
│  │  │  字段1  │  字段2  │  字段3  │  ...  │  字段N        │       │    │
│  │  └─────────────────────────────────────────────────────┘       │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Class 结构

Class 对象包含类的所有元信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// art/runtime/mirror/class.h (简化)
class Class : public Object {
 protected:
  // 类加载器
  HeapReference<ClassLoader> class_loader_;

  // 组件类型 (数组类才有)
  HeapReference<Class> component_type_;

  // DexCache 缓存
  HeapReference<DexCache> dex_cache_;

  // 接口表
  HeapReference<IfTable> iftable_;

  // 类名
  HeapReference<String> name_;

  // 父类
  HeapReference<Class> super_class_;

  // 虚方法表 (vtable)
  HeapReference<PointerArray> vtable_;

  // 静态字段值
  uint64_t sfields_[0];

  // 实例字段偏移
  uint32_t ifields_[0];

  // 访问标志
  uint32_t access_flags_;

  // 类状态
  ClassStatus status_;

  // 对象大小
  uint32_t object_size_;

  // ... 更多字段
};

类结构关系图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
┌─────────────────────────────────────────────────────────────────────────┐
│                        ART 类结构关系                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│        ┌─────────────────────────────────────────────────────────┐     │
│        │                    Class 对象                            │     │
│        ├─────────────────────────────────────────────────────────┤     │
│        │  super_class_ ────────────────► [父类 Class]            │     │
│        │  class_loader_ ───────────────► [ClassLoader]           │     │
│        │  dex_cache_ ──────────────────► [DexCache]              │     │
│        │  vtable_ ─────────────────────► [虚方法表]              │     │
│        │  iftable_ ────────────────────► [接口表]                │     │
│        │  methods_ ────────────────────► [方法数组]              │     │
│        │  ifields_ ────────────────────► [实例字段]              │     │
│        │  sfields_ ────────────────────► [静态字段]              │     │
│        └─────────────────────────────────────────────────────────┘     │
│                                    │                                    │
│                                    ▼                                    │
│        ┌─────────────────────────────────────────────────────────┐     │
│        │                    DexCache                              │     │
│        ├─────────────────────────────────────────────────────────┤     │
│        │  resolved_types_[] ───────────► [已解析的类型]          │     │
│        │  resolved_methods_[] ─────────► [已解析的方法]          │     │
│        │  resolved_fields_[] ──────────► [已解析的字段]          │     │
│        │  strings_[] ──────────────────► [字符串常量池]          │     │
│        │  dex_file_ ───────────────────► [DEX 文件指针]          │     │
│        └─────────────────────────────────────────────────────────┘     │
│                                    │                                    │
│                                    ▼                                    │
│        ┌─────────────────────────────────────────────────────────┐     │
│        │                    DexFile (Native)                      │     │
│        ├─────────────────────────────────────────────────────────┤     │
│        │  begin_ ──────────────────────► [DEX 数据起始]          │     │
│        │  size_ ───────────────────────► [DEX 大小]              │     │
│        │  header_ ─────────────────────► [DEX 头]                │     │
│        │  string_ids_ ─────────────────► [字符串 ID 表]          │     │
│        │  type_ids_ ───────────────────► [类型 ID 表]            │     │
│        │  method_ids_ ─────────────────► [方法 ID 表]            │     │
│        └─────────────────────────────────────────────────────────┘     │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

虚方法表 (VTable)

Java 虚方法调用通过 VTable 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────────┐
│                        虚方法表 (VTable) 结构                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  class Base {                         class Derived extends Base {      │
│    void method1() {}                    @Override                       │
│    void method2() {}                    void method1() {}  // 重写      │
│  }                                      void method3() {} // 新增       │
│                                       }                                 │
│                                                                         │
│  ┌─────────────────────────┐        ┌─────────────────────────┐        │
│  │     Base VTable         │        │    Derived VTable       │        │
│  ├─────────────────────────┤        ├─────────────────────────┤        │
│  │ [0] Object.toString     │        │ [0] Object.toString     │        │
│  │ [1] Object.hashCode     │        │ [1] Object.hashCode     │        │
│  │ [2] Object.equals       │        │ [2] Object.equals       │        │
│  │ [3] Base.method1    ─────────────│ [3] Derived.method1 ◄───│ 覆盖   │
│  │ [4] Base.method2        │        │ [4] Base.method2        │        │
│  └─────────────────────────┘        │ [5] Derived.method3 ◄───│ 新增   │
│                                     └─────────────────────────┘        │
│                                                                         │
│  虚方法调用过程:                                                        │
│  obj.method1()                                                          │
│    1. 获取 obj 的 klass_                                                │
│    2. 从 klass_->vtable_ 中读取索引 3 的 ArtMethod*                     │
│    3. 调用该 ArtMethod 的 entry_point                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

ART 垃圾回收 (GC) 机制

GC 类型

ART 支持多种 GC 策略:

GC 类型描述暂停时间使用场景
CMS (Concurrent Mark Sweep)并发标记清除短暂停默认 GC,低延迟
SS (Semi-Space)半空间复制长暂停内存紧张时
GSS (Generational Semi-Space)分代半空间中等暂停分代回收
CC (Concurrent Copying)并发复制极短暂停Android 8.0+ 默认
CMC (Concurrent Mark Compact)并发标记整理短暂停减少碎片

堆空间划分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌─────────────────────────────────────────────────────────────────────────┐
│                        ART 堆空间划分                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                        Managed Heap                              │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │                                                                  │   │
│  │  ┌───────────────┐   ┌───────────────┐   ┌───────────────┐     │   │
│  │  │  Image Space  │   │  Zygote Space │   │ Allocation    │     │   │
│  │  │  (只读映射)    │   │  (COW 共享)   │   │    Space      │     │   │
│  │  │               │   │               │   │               │     │   │
│  │  │ • boot.art    │   │ • Zygote 对象 │   │ • App 对象    │     │   │
│  │  │ • 系统类      │   │ • 预加载类    │   │ • 新分配      │     │   │
│  │  │ • 不可 GC     │   │ • 进程间共享  │   │ • GC 目标     │     │   │
│  │  └───────────────┘   └───────────────┘   └───────────────┘     │   │
│  │                                                                  │   │
│  │  ┌───────────────┐   ┌───────────────┐   ┌───────────────┐     │   │
│  │  │ Large Object  │   │  Non-Moving   │   │   Region      │     │   │
│  │  │    Space      │   │    Space      │   │    Space      │     │   │
│  │  │               │   │               │   │ (CC GC 专用)  │     │   │
│  │  │ • 大对象分配  │   │ • 不移动对象  │   │               │     │   │
│  │  │ • > 12KB      │   │ • JNI 引用    │   │ • 256KB 区域  │     │   │
│  │  │ • 独立 GC     │   │ • 类对象      │   │ • 并发复制    │     │   │
│  │  └───────────────┘   └───────────────┘   └───────────────┘     │   │
│  │                                                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

并发复制 GC (CC) 流程

Android 8.0+ 默认使用的 CC (Concurrent Copying) GC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────────────────┐
│                        CC GC 执行流程                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  时间 ────────────────────────────────────────────────────────────────► │
│                                                                         │
│  应用线程:                                                              │
│  ████████████░░████████████████████████████████████████████████████████ │
│             ▲                                                           │
│             │ 短暂停 (< 1ms)                                            │
│                                                                         │
│  GC 线程:                                                               │
│  ░░░░░░░░░░░█████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│             │                       │                                   │
│             ├───────────────────────┤                                   │
│             │   并发标记 + 复制      │                                   │
│                                                                         │
│  阶段说明:                                                              │
│  1. 初始标记 (STW) - 标记 GC Roots,暂停 < 1ms                          │
│  2. 并发标记       - 遍历对象图,与应用并行                              │
│  3. 并发复制       - 将存活对象复制到新区域                              │
│  4. 引用更新       - 使用 read barrier 更新引用                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Read Barrier (读屏障)

CC GC 使用读屏障来保证并发安全:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 读屏障伪代码
Object* ReadBarrier(Object** field) {
    Object* obj = *field;
    if (obj != nullptr && IsInFromSpace(obj)) {
        // 对象正在被复制,获取新地址
        Object* forward = GetForwardingAddress(obj);
        if (forward != nullptr) {
            // 更新引用指向新地址
            *field = forward;
            return forward;
        }
    }
    return obj;
}

GC 触发条件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// GC 触发条件
void MaybeGC() {
    // 1. 分配失败
    if (allocation_failed) {
        TriggerGC(kGcCauseForAlloc);
    }

    // 2. 达到堆阈值
    if (bytes_allocated > growth_limit_) {
        TriggerGC(kGcCauseHeapTrim);
    }

    // 3. 显式调用 System.gc()
    if (explicit_gc_requested) {
        TriggerGC(kGcCauseExplicit);
    }

    // 4. 后台 GC
    if (app_in_background && idle_time > threshold) {
        TriggerGC(kGcCauseBackground);
    }
}

dex2oat 编译流程详解

编译器架构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────────────────┐
│                        dex2oat 编译流程                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  输入                       编译器                        输出           │
│  ┌─────────┐              ┌─────────────────────────┐   ┌─────────┐    │
│  │         │              │                         │   │         │    │
│  │ APK/DEX │─────────────►│      Optimizing         │──►│   OAT   │    │
│  │         │              │       Compiler          │   │         │    │
│  └─────────┘              │                         │   └─────────┘    │
│                           │  ┌─────────────────┐    │                  │
│                           │  │   Frontend      │    │   ┌─────────┐    │
│                           │  │ (DEX → HIR)     │    │──►│  VDEX   │    │
│                           │  └────────┬────────┘    │   └─────────┘    │
│                           │           ▼             │                  │
│                           │  ┌─────────────────┐    │   ┌─────────┐    │
│                           │  │  Optimization   │    │──►│   ART   │    │
│                           │  │  (HIR → HIR)    │    │   │ (Image) │    │
│                           │  └────────┬────────┘    │   └─────────┘    │
│                           │           ▼             │                  │
│                           │  ┌─────────────────┐    │                  │
│                           │  │   Backend       │    │                  │
│                           │  │ (HIR → 机器码)  │    │                  │
│                           │  └─────────────────┘    │                  │
│                           └─────────────────────────┘                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

HIR (High-level IR) 优化

优化编译器执行多种优化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌─────────────────────────────────────────────────────────────────────────┐
│                        HIR 优化 Pass                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 构建阶段                                                            │
│     ├── SsaBuilder              构建 SSA 形式                          │
│     └── ReferenceTypePropagation 类型传播                               │
│                                                                         │
│  2. 分析阶段                                                            │
│     ├── InductionVarAnalysis    归纳变量分析                            │
│     ├── LoadStoreAnalysis       加载存储分析                            │
│     └── SideEffectsAnalysis     副作用分析                              │
│                                                                         │
│  3. 优化阶段                                                            │
│     ├── InstructionSimplifier   指令简化                                │
│     ├── ConstantFolding         常量折叠                                │
│     ├── DeadCodeElimination     死代码消除                              │
│     ├── InlinerForTest          内联                                    │
│     ├── BoundsCheckElimination  边界检查消除                            │
│     ├── LoopOptimization        循环优化                                │
│     ├── LoadStoreElimination    加载存储消除                            │
│     └── GVN                     全局值编号                              │
│                                                                         │
│  4. 代码生成                                                            │
│     ├── RegisterAllocator       寄存器分配                              │
│     ├── CodeGenerator           代码生成                                │
│     └── SlowPathGenerator       慢路径生成                              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

dex2oat 命令行参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 完整的 dex2oat 调用示例
dex2oat \
    --dex-file=/data/app/com.example.app/base.apk \
    --oat-file=/data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.dex \
    --instruction-set=arm64 \
    --instruction-set-features=default \
    --runtime-arg -Xms64m \
    --runtime-arg -Xmx512m \
    --compiler-filter=speed-profile \
    --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \
    --generate-mini-debug-info \
    --app-image-file=/data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.art

# 常用编译过滤器
# --compiler-filter=<filter>
#   verify        - 仅验证 DEX
#   quicken       - 快速优化,无编译
#   speed         - 编译所有方法
#   speed-profile - 基于 profile 编译热点方法
#   everything    - 编译所有,包括调试信息

编译过滤器对比

Filter编译内容OAT 大小编译时间运行性能
verify最小最快解释执行
quicken快速优化稍快
speed全部方法最佳
speed-profile热点方法中等中等很好
everything全部+调试最大最慢最佳+可调试

ART 生成的文件格式

当 ART 处理一个 APK 时,会在 /data/dalvik-cache/<arch>/ 目录下生成一些优化后的文件。

文件位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 系统 boot image
/system/framework/arm64/boot.art
/system/framework/arm64/boot.oat
/system/framework/arm64/boot.vdex

# 应用编译产物
/data/dalvik-cache/arm64/data@app@<package>@base.apk@classes.dex
/data/dalvik-cache/arm64/data@app@<package>@base.apk@classes.art
/data/dalvik-cache/arm64/data@app@<package>@base.apk@classes.vdex

# 或者在应用目录 (Android 10+)
/data/app/<package>/oat/arm64/base.odex
/data/app/<package>/oat/arm64/base.vdex
/data/app/<package>/oat/arm64/base.art

OAT 文件 (.oat)

OAT (Optimized Android file format) 文件是核心。它包含了由 dex2oat 从 DEX 字节码编译而来的本地机器码 (ARM 汇编)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
┌─────────────────────────────────────────────────────────────────────────┐
│                        OAT 文件结构                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ ELF Header                                                        │  │
│  │   - e_ident: 0x7f 'E' 'L' 'F'                                    │  │
│  │   - e_type: ET_DYN (共享对象)                                     │  │
│  │   - e_machine: EM_AARCH64 / EM_ARM                               │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ .rodata Section                                                   │  │
│  │   ┌─────────────────────────────────────────────────────────────┐ │  │
│  │   │ OatHeader                                                   │ │  │
│  │   │   - magic: "oat\n"                                          │ │  │
│  │   │   - version: "183" (Android 14)                             │ │  │
│  │   │   - checksum                                                │ │  │
│  │   │   - instruction_set                                         │ │  │
│  │   │   - key_value_store (编译选项)                              │ │  │
│  │   └─────────────────────────────────────────────────────────────┘ │  │
│  │   ┌─────────────────────────────────────────────────────────────┐ │  │
│  │   │ OatDexFile[] (每个 DEX 的元信息)                            │ │  │
│  │   │   - dex_file_location                                       │ │  │
│  │   │   - dex_file_checksum                                       │ │  │
│  │   │   - methods_offsets (方法代码偏移表)                        │ │  │
│  │   └─────────────────────────────────────────────────────────────┘ │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ .text Section (oatexec)                                           │  │
│  │   - 编译后的本地机器码                                            │  │
│  │   - 每个方法的代码紧密排列                                        │  │
│  │   - 包含 GC map、栈 map 等元信息                                  │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ .bss Section                                                      │  │
│  │   - 用于 ART 运行时填充的数据                                     │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

VDEX 文件 (.vdex)

从 Android 8.0 (Oreo) 开始引入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
┌─────────────────────────────────────────────────────────────────────────┐
│                        VDEX 文件结构                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ VdexHeader                                                        │  │
│  │   - magic: "vdex"                                                │  │
│  │   - version: "027" (Android 14)                                  │  │
│  │   - number_of_dex_files                                          │  │
│  │   - verifier_deps_size                                           │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ DEX Files (未压缩的原始 DEX)                                      │  │
│  │   - classes.dex                                                  │  │
│  │   - classes2.dex (如果有)                                        │  │
│  │   - ...                                                          │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ Verifier Dependencies                                             │  │
│  │   - 类/方法/字段的验证依赖信息                                    │  │
│  │   - 用于快速验证 DEX 是否需要重新编译                             │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ Quickening Info (可选)                                            │  │
│  │   - 字节码快速化信息                                              │  │
│  │   - 内联缓存等优化数据                                            │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

ART 文件 (.art) (Image)

这是一个预加载的镜像文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
┌─────────────────────────────────────────────────────────────────────────┐
│                        ART Image 文件结构                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ ImageHeader                                                       │  │
│  │   - magic: "art\n"                                               │  │
│  │   - image_begin: 映射基地址                                       │  │
│  │   - image_size: 镜像大小                                          │  │
│  │   - oat_checksum: 对应 OAT 的校验和                               │  │
│  │   - oat_data_begin: OAT 代码基地址                                │  │
│  │   - boot_image_roots: 根对象数组偏移                              │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ Object Section (预加载的 Java 对象)                               │  │
│  │   ┌─────────────────────────────────────────────────────────────┐ │  │
│  │   │ 核心类对象                                                  │ │  │
│  │   │   - java.lang.Object                                        │ │  │
│  │   │   - java.lang.Class                                         │ │  │
│  │   │   - java.lang.String                                        │ │  │
│  │   │   - java.lang.Thread                                        │ │  │
│  │   │   - ...                                                     │ │  │
│  │   └─────────────────────────────────────────────────────────────┘ │  │
│  │   ┌─────────────────────────────────────────────────────────────┐ │  │
│  │   │ DexCache 对象                                               │ │  │
│  │   │   - 已解析的类型                                            │ │  │
│  │   │   - 已解析的方法                                            │ │  │
│  │   │   - 已解析的字段                                            │ │  │
│  │   └─────────────────────────────────────────────────────────────┘ │  │
│  │   ┌─────────────────────────────────────────────────────────────┐ │  │
│  │   │ Intern Table (字符串池)                                     │ │  │
│  │   │   - 预填充的字符串对象                                       │ │  │
│  │   └─────────────────────────────────────────────────────────────┘ │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │ Bitmap Section                                                    │  │
│  │   - Live bitmap: 标记存活对象                                     │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

总结: 在现代 Android 系统中,执行流程是:classes.dex -> (安装时) .vdex -> (后台优化时) .oat


ART vs. Dalvik

特性ARTDalvik
编译模式AOT + JIT 混合编译JIT
执行单元本地机器码 (主要)DEX 字节码
性能更高较低
启动速度更快较慢
安装时间更快 (混合模式下)
存储占用更高 (因 OAT 文件)较低
垃圾回收 (GC)优化更好,暂停时间更短效率较低
调试支持完善 (JDWP + ART TI)JDWP
64 位支持原生支持不支持

Frida/Xposed 与 ART 交互原理

Frida Hook 原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─────────────────────────────────────────────────────────────────────────┐
│                        Frida Java Hook 原理                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  原始状态                              Hook 后                          │
│  ┌─────────────────────┐              ┌─────────────────────┐          │
│  │    ArtMethod        │              │    ArtMethod        │          │
│  ├─────────────────────┤              ├─────────────────────┤          │
│  │ entry_point ────────┼──► OAT Code  │ entry_point ────────┼──► Frida │
│  │                     │              │                     │   Stub   │
│  │ access_flags        │              │ access_flags |= N   │          │
│  │                     │              │ (设为 Native)       │          │
│  │ data_ ──────────────┼──► DEX info  │ data_ ──────────────┼──► Hook  │
│  │                     │              │                     │   Info   │
│  └─────────────────────┘              └─────────────────────┘          │
│                                                                         │
│  Frida Hook 步骤:                                                      │
│                                                                         │
│  1. 通过反射获取 java.lang.reflect.Method                               │
│  2. 从 Method 对象获取 ArtMethod 指针                                   │
│  3. 备份原始 entry_point 和 data_                                       │
│  4. 修改 access_flags,添加 kAccNative 标志                             │
│  5. 将 entry_point 替换为 Frida 的 trampoline                           │
│  6. 将 data_ 替换为指向 Hook 上下文的指针                                │
│  7. 当方法被调用时:                                                    │
│     a. 跳转到 Frida trampoline                                         │
│     b. 执行 JavaScript 回调                                             │
│     c. 可选:调用原始方法                                               │
│     d. 返回                                                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Xposed Hook 原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌─────────────────────────────────────────────────────────────────────────┐
│                        Xposed Hook 原理                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Xposed (现代版本如 LSPosed) 使用类似的技术:                           │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 1. 注入 Zygote 进程                                             │   │
│  │    - 通过 Magisk 模块或 SELinux patch                           │   │
│  │    - 加载 libxposed_art.so                                      │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 2. Hook ART 内部函数                                            │   │
│  │    - 替换 ClassLinker::DefineClass                              │   │
│  │    - 在类加载时注入回调                                          │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 3. 方法 Hook                                                    │   │
│  │                                                                 │   │
│  │    原始 ArtMethod            Hook 后                            │   │
│  │    ┌────────────────┐       ┌────────────────┐                 │   │
│  │    │ entry_point    │──────►│ XposedBridge   │                 │   │
│  │    │                │       │   ↓            │                 │   │
│  │    │                │       │ beforeHook()   │                 │   │
│  │    │                │       │   ↓            │                 │   │
│  │    │ [原始代码]     │◄──────│ callOriginal() │                 │   │
│  │    │                │       │   ↓            │                 │   │
│  │    │                │       │ afterHook()    │                 │   │
│  │    └────────────────┘       └────────────────┘                 │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

ART 方法 Hook 的关键地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Frida 获取关键地址的方式 (伪代码)
void* GetArtMethodEntryPoint(void* art_method) {
    // Android 版本不同,偏移不同
    // Android 10 (arm64): offset = 0x20
    // Android 11 (arm64): offset = 0x18
    size_t offset = GetEntryPointOffset();
    return *(void**)((char*)art_method + offset);
}

void SetArtMethodEntryPoint(void* art_method, void* new_entry) {
    size_t offset = GetEntryPointOffset();
    *(void**)((char*)art_method + offset) = new_entry;
}

// ArtMethod 结构体偏移 (Android 14, arm64)
// 注意:这些偏移可能随版本变化
struct ArtMethodOffsets {
    size_t declaring_class;           // 0x00
    size_t access_flags;              // 0x04
    size_t dex_code_item_offset;      // 0x08
    size_t dex_method_index;          // 0x0C
    size_t method_index;              // 0x10
    size_t hotness_count;             // 0x12
    size_t ptr_sized_fields_data;     // 0x18
    size_t ptr_sized_fields_entry;    // 0x20
};

对逆向工程的影响

实战技巧:从 ART 机制找到突破口

理解 ART 的工作原理,能让你找到很多"非常规"的逆向思路:

脱壳新思路

  1. 监控 dex2oat 调用:某些壳会在运行时动态调用 dex2oat,监控其命令行参数能发现隐藏的 DEX
  2. 从 VDEX 提取 DEX:Android 8.0+ 的 VDEX 文件本质上就是 DEX,用 vdexExtractor 可以快速提取
  3. 从 OAT 还原 DEX:使用 oat2dex 等工具从编译后的 OAT 文件反推原始 DEX

Hook 优化策略

  • Java 方法 Hook:优先 Hook Java 层 API,更稳定通用
  • Native Hook:当 Java Hook 失效时,找到 ART 编译后的机器码地址进行 inline hook
  • GOT/PLT Hook:Hook 动态链接库的导入表,绕过代码完整性检查

关键影响点

  • Hook 点的变化: 由于存在 AOT 编译,Frida/Xposed 等框架的 Hook 原理也需要适应。它们不仅仅是 Hook Java 方法,实际上是找到了该方法编译后的本地机器码地址,并对其进行修改(inline hook)。

  • 脱壳的复杂性: 许多加固厂商利用 ART 的 AOT 机制。他们可能会在运行时动态解密并加载 DEX,然后手动调用 dex2oat 生成 OAT 文件来执行。这使得传统的 DEX Dump 方法失效,需要对 OAT 文件格式和 dex2oat 的调用时机进行监控。

  • OAT 文件分析: 高级逆向分析有时需要直接分析 OAT 文件。有一些工具(如 oatdump)可以从 OAT 文件中提取出原始的 DEX 数据或查看编译后的汇编代码。

  • 寻找代码的源头: 即使代码被 AOT 编译,其元数据依然与原始的 DEX 文件相关联。因此,我们的分析起点通常还是从 classes.dex 反编译出的 Java 代码开始,而不是直接一头扎进 OAT 文件的汇编代码中。


实战代码示例

💡 思路一句话: 理解 ART 运行时的关键在于「方法调用链」— 从 Java 方法 → ArtMethod 结构体 → 编译后的机器码入口点,hook 和 dump 都需要在这条链上找到正确的拦截点。

示例 1:获取 ArtMethod 地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Frida 脚本:获取 Java 方法对应的 ArtMethod 地址
function getArtMethod(className, methodName, signature) {
  Java.perform(function () {
    var targetClass = Java.use(className);
    var methods = targetClass.class.getDeclaredMethods();

    for (var i = 0; i < methods.length; i++) {
      var method = methods[i];
      if (method.getName() === methodName) {
        // 获取 ArtMethod 指针
        // Method 对象的 artMethod 字段存储着指针
        var artMethodField = method.getClass().getDeclaredField("artMethod");
        artMethodField.setAccessible(true);
        var artMethodPtr = artMethodField.get(method);

        console.log("[*] Class: " + className);
        console.log("[*] Method: " + methodName);
        console.log("[*] ArtMethod address: " + artMethodPtr);

        // 读取 entry_point
        var entryPointOffset = 0x20; // Android 14, arm64
        var entryPoint = Memory.readPointer(
          ptr(artMethodPtr).add(entryPointOffset)
        );
        console.log("[*] Entry point: " + entryPoint);

        // 判断执行模式
        var toInterpreter = Module.findExportByName(
          "libart.so",
          "art_quick_to_interpreter_bridge"
        );
        if (entryPoint.equals(toInterpreter)) {
          console.log("[*] Mode: Interpreted");
        } else {
          console.log("[*] Mode: Compiled (AOT/JIT)");
          // 打印前几条汇编指令
          console.log("[*] Disassembly:");
          console.log(Instruction.parse(entryPoint));
        }
      }
    }
  });
}

// 使用示例
getArtMethod("com.example.app.MainActivity", "onCreate");

示例 2:监控 dex2oat 调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Frida 脚本:监控 dex2oat 相关调用
function hookDex2oat() {
  // Hook execve 来捕获 dex2oat 调用
  var execve = Module.findExportByName("libc.so", "execve");
  Interceptor.attach(execve, {
    onEnter: function (args) {
      var pathname = Memory.readUtf8String(args[0]);
      if (pathname && pathname.indexOf("dex2oat") !== -1) {
        console.log("\n[!] dex2oat called!");
        console.log("[*] Path: " + pathname);

        // 打印参数
        var argv = args[1];
        var i = 0;
        while (true) {
          var arg = Memory.readPointer(argv.add(i * Process.pointerSize));
          if (arg.isNull()) break;
          console.log("[*] arg[" + i + "]: " + Memory.readUtf8String(arg));
          i++;
        }
      }
    },
  });

  // 也可以 Hook OpenDexFilesFromOat
  var artModule = Process.findModuleByName("libart.so");
  if (artModule) {
    var symbols = artModule.enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
      if (symbols[i].name.indexOf("OpenDexFilesFromOat") !== -1) {
        console.log(
          "[*] Found: " + symbols[i].name + " at " + symbols[i].address
        );
        Interceptor.attach(symbols[i].address, {
          onEnter: function (args) {
            console.log("[*] OpenDexFilesFromOat called");
          },
          onLeave: function (retval) {
            console.log("[*] OpenDexFilesFromOat returned: " + retval);
          },
        });
      }
    }
  }
}

hookDex2oat();

示例 3:Dump 解密后的 DEX

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Frida 脚本:从内存中 Dump DEX
function dumpDex() {
  Java.perform(function () {
    // 方法 1:Hook DexFile 构造
    var DexFile = Java.use("dalvik.system.DexFile");

    // 方法 2:遍历已加载的 DexFile
    Java.enumerateClassLoaders({
      onMatch: function (loader) {
        try {
          var pathList = loader.pathList.value;
          var dexElements = pathList.dexElements.value;

          for (var i = 0; i < dexElements.length; i++) {
            var element = dexElements[i];
            var dexFile = element.dexFile.value;
            if (dexFile) {
              console.log("\n[*] Found DexFile in ClassLoader");

              // 获取 cookie (native 层 DexFile 指针)
              var cookie = dexFile.mCookie.value;
              console.log("[*] Cookie: " + cookie);

              // 对于 Android 8.0+,需要解析 cookie 获取多个 DEX
              // cookie 是一个 long 数组
              if (cookie) {
                dumpFromCookie(cookie);
              }
            }
          }
        } catch (e) {
          // ClassLoader 类型可能不是 BaseDexClassLoader
        }
      },
      onComplete: function () {
        console.log("[*] ClassLoader enumeration complete");
      },
    });
  });
}

function dumpFromCookie(cookie) {
  // cookie[0] = oat_file
  // cookie[1..n] = dex_files
  var oatFile = cookie[0];

  for (var i = 1; i < cookie.length; i++) {
    var dexFilePtr = ptr(cookie[i]);

    // 读取 DexFile 结构
    // begin_ 和 size_ 的偏移取决于 Android 版本
    var beginOffset = 0x10; // 示例偏移
    var sizeOffset = 0x18;

    var begin = Memory.readPointer(dexFilePtr.add(beginOffset));
    var size = Memory.readU32(dexFilePtr.add(sizeOffset));

    console.log("[*] DEX " + i + ": begin=" + begin + ", size=" + size);

    // 验证 DEX magic
    var magic = Memory.readByteArray(begin, 4);
    if (arrayToString(magic) === "dex\n") {
      // Dump 到文件
      var fileName = "/data/local/tmp/dump_" + i + ".dex";
      var file = new File(fileName, "wb");
      file.write(Memory.readByteArray(begin, size));
      file.close();
      console.log("[+] Dumped to: " + fileName);
    }
  }
}

function arrayToString(arr) {
  return String.fromCharCode.apply(null, new Uint8Array(arr));
}

dumpDex();

示例 4:绕过 dex2oat 检测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 某些 App 会检测 OAT 文件是否存在或被修改
// 这个脚本用于绕过这种检测

function bypassOatCheck() {
  // Hook access/stat 系列函数
  var access = Module.findExportByName("libc.so", "access");
  Interceptor.attach(access, {
    onEnter: function (args) {
      this.path = Memory.readUtf8String(args[0]);
    },
    onLeave: function (retval) {
      if (
        this.path &&
        (this.path.indexOf(".oat") !== -1 || this.path.indexOf(".vdex") !== -1)
      ) {
        console.log("[*] access(" + this.path + ") = " + retval);
        // 根据需要修改返回值
      }
    },
  });

  // Hook fopen 检测 OAT 读取
  var fopen = Module.findExportByName("libc.so", "fopen");
  Interceptor.attach(fopen, {
    onEnter: function (args) {
      this.path = Memory.readUtf8String(args[0]);
    },
    onLeave: function (retval) {
      if (this.path && this.path.indexOf(".oat") !== -1) {
        console.log("[*] fopen(" + this.path + ") = " + retval);
      }
    },
  });
}

bypassOatCheck();

示例 5:Hook ART 内部函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Hook ART 运行时的内部函数,用于高级分析

function hookArtInternals() {
  var libart = Module.findModuleByName("libart.so");
  if (!libart) {
    console.log("[-] libart.so not found");
    return;
  }

  // 1. Hook 类加载
  var defineClass = null;
  libart.enumerateSymbols().forEach(function (sym) {
    if (
      sym.name.indexOf("ClassLinker") !== -1 &&
      sym.name.indexOf("DefineClass") !== -1
    ) {
      if (!sym.name.includes("Callback")) {
        defineClass = sym.address;
      }
    }
  });

  if (defineClass) {
    Interceptor.attach(defineClass, {
      onEnter: function (args) {
        // 参数取决于具体版本
        console.log("[*] DefineClass called");
      },
    });
  }

  // 2. Hook 方法编译
  libart.enumerateSymbols().forEach(function (sym) {
    if (sym.name.indexOf("JitCompileMethod") !== -1) {
      console.log("[*] Found JitCompileMethod: " + sym.address);
      Interceptor.attach(sym.address, {
        onEnter: function (args) {
          console.log("[*] JIT compiling method...");
        },
        onLeave: function (retval) {
          console.log("[*] JIT compile result: " + retval);
        },
      });
    }
  });

  // 3. Hook GC
  libart.enumerateSymbols().forEach(function (sym) {
    if (
      sym.name.indexOf("CollectGarbage") !== -1 &&
      !sym.name.includes("Internal")
    ) {
      console.log("[*] Found GC function: " + sym.name);
      Interceptor.attach(sym.address, {
        onEnter: function (args) {
          console.log("[*] GC triggered!");
        },
      });
      return;
    }
  });
}

hookArtInternals();

常用工具指南

oatdump

Android 自带的 OAT 分析工具:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 基本用法
oatdump --oat-file=/data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.dex

# 只显示头信息
oatdump --oat-file=base.odex --header-only

# 导出特定方法的汇编
oatdump --oat-file=base.odex --method-filter="MainActivity.onCreate"

# 导出所有方法
oatdump --oat-file=base.odex --dump:code

# 验证 OAT 文件
oatdump --oat-file=base.odex --verify-image

# 输出 OAT 文件的 ELF sections
oatdump --oat-file=base.odex --list-methods

典型输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
OAT FILE: /data/dalvik-cache/arm64/data@app@com.example.app@base.apk@classes.dex
LOCATION: /data/app/com.example.app/base.apk
CHECKSUM: 0x12345678
INSTRUCTION SET: ARM64
INSTRUCTION SET FEATURES: ...
DEX FILE COUNT: 1

DEX FILE: base.apk
  LOCATION: base.apk
  CHECKSUM: 0xabcdef01
  DEX FILE SIZE: 1234567

  CLASS: Lcom/example/app/MainActivity; (status=kInitialized)
    0: void com.example.app.MainActivity.onCreate(android.os.Bundle)
       DEX CODE:
         0x0000: ...
       OAT CODE:
         0x00001000: stp x29, x30, [sp, #-16]!
         0x00001004: mov x29, sp
         ...

vdexExtractor

从 VDEX 文件提取 DEX:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 下载
git clone https://github.com/anestisb/vdexExtractor.git
cd vdexExtractor
make

# 提取 DEX
./vdexExtractor -i /path/to/base.vdex -o /output/dir

# 显示 VDEX 信息
./vdexExtractor -i base.vdex --get-api

# 批量提取
./vdexExtractor -i /data/dalvik-cache/arm64/ -o /output/

dex2oat 手动编译

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 查看 dex2oat 帮助
dex2oat --help

# 手动编译 APK
dex2oat \
    --dex-file=/data/local/tmp/test.apk \
    --oat-file=/data/local/tmp/test.oat \
    --instruction-set=arm64 \
    --compiler-filter=speed

# 编译为调试模式 (保留更多信息)
dex2oat \
    --dex-file=test.apk \
    --oat-file=test.oat \
    --compiler-filter=everything \
    --debuggable \
    --generate-debug-info

# 使用 profile 编译
dex2oat \
    --dex-file=test.apk \
    --oat-file=test.oat \
    --compiler-filter=speed-profile \
    --profile-file=/path/to/primary.prof

profman

Profile 分析工具:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 查看 profile 内容
profman --dump-classes-and-methods \
    --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \
    --apk=/data/app/com.example.app/base.apk

# 创建 profile
profman --create-profile-from=method_list.txt \
    --reference-profile-file=/output/primary.prof \
    --apk=/path/to/base.apk

# 合并 profiles
profman --profile-file=profile1.prof \
    --profile-file=profile2.prof \
    --reference-profile-file=/output/merged.prof \
    --apk=/path/to/base.apk

baksmali/smali

DEX 反汇编/汇编工具:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 反汇编 DEX 到 smali
baksmali d classes.dex -o smali_output/

# 从 VDEX 反汇编 (需要先提取)
baksmali d base.vdex -o smali_output/

# 汇编 smali 回 DEX
smali a smali_output/ -o classes_new.dex

# 反汇编特定类
baksmali d classes.dex -o output/ -c "Lcom/example/MainActivity;"

其他实用工具

工具用途链接
JADXDEX/APK 反编译为 Javahttps://github.com/skylot/jadx
GhidraOAT 本地代码分析https://ghidra-sre.org/
IDA ProOAT/SO 逆向分析https://hex-rays.com/ida-pro/
dexdumpAndroid 自带 DEX 分析Android SDK
apktoolAPK 解包/重打包https://ibotpeaches.github.io/Apktool/

ART 版本演进

Android 各版本 ART 特性

Android 版本ART 主要变化
5.0 (L)ART 成为默认运行时,纯 AOT 编译
6.0 (M)移除 Portable 后端,仅保留 Quick
7.0 (N)引入 JIT + AOT 混合模式,Profile-Guided 编译
8.0 (O)引入 VDEX 文件,Concurrent Compacting GC
9.0 (P)改进的 Profile 收集,更好的内联
10 (Q).art 文件优化,更快的应用启动
11 (R)增量 dex2oat,改进的 JIT 缓存
12 (S)进一步优化内存使用,改进 GC
13 (T)更好的启动 Profile,改进的 AOT
14 (U)持续性能优化,更好的电池续航

ArtMethod 结构演进

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 不同版本 ArtMethod 入口点偏移 (arm64)
// 这些偏移会随版本变化,Hook 时需要动态获取

// Android 7.x
// entry_point offset: 0x24

// Android 8.x
// entry_point offset: 0x20

// Android 9.x
// entry_point offset: 0x20

// Android 10.x
// entry_point offset: 0x20

// Android 11+
// entry_point offset: 0x18 (部分版本)

// 获取正确偏移的方法
function getEntryPointOffset() {
    var sdk = Java.use("android.os.Build$VERSION").SDK_INT.value;
    if (sdk >= 30) {
        return 0x18;  // Android 11+
    } else if (sdk >= 26) {
        return 0x20;  // Android 8-10
    } else {
        return 0x24;  // Android 7.x
    }
}

OAT 版本历史

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
OAT 版本号对应关系:

Android 5.0  - OAT 039
Android 5.1  - OAT 045
Android 6.0  - OAT 064
Android 7.0  - OAT 079
Android 7.1  - OAT 088
Android 8.0  - OAT 126
Android 8.1  - OAT 131
Android 9.0  - OAT 138
Android 10   - OAT 170
Android 11   - OAT 183
Android 12   - OAT 195
Android 13   - OAT 220
Android 14   - OAT 225

可以通过读取 OAT 文件头来判断版本:
$ xxd base.oat | head -n 1
00000000: 6f61 740a 3138 3300 ...  oat.183.

调试与诊断

启用 ART 日志

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 启用详细日志
adb shell setprop dalvik.vm.extra-opts "-verbose:class,jit,gc"

# 启用 JIT 日志
adb shell setprop dalvik.vm.usejit true
adb shell setprop dalvik.vm.usejitprofiles true

# 查看 GC 日志
adb logcat -s "art:V"

# 查看类加载日志
adb logcat | grep -E "(Loading class|Loaded class)"

性能分析

1
2
3
4
5
6
# 使用 simpleperf 分析 ART
simpleperf record -p <pid> -g --call-graph fp -o perf.data
simpleperf report -i perf.data

# 生成火焰图
simpleperf report -i perf.data --kallsyms /proc/kallsyms -g --symfs /path/to/symbols

常见问题诊断

问题可能原因解决方法
Hook 不生效方法已被 AOT 编译使用 inline hook 或重新编译
脱壳不完整DEX 动态加载监控 DexFile 创建
崩溃在 libart.soART 内部错误检查 tombstone 和 logcat
性能下降JIT 频繁编译检查热点代码
OOM堆空间不足调整 dalvik.vm.heapsize

相关内容

如果这篇文章对你有帮助,请我喝杯咖啡吧~
+5 支付宝支付宝
+5 微信微信