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 编译。
ART 的 AOT (Ahead-Of-Time)
ART 最初的设计是纯 AOT 编译。
混合编译 (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 缓存│ │ 热点记录 │ │ 优化代码 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
|
初次安装: 应用安装速度很快,不进行 AOT 编译。
首次运行: 应用代码由解释器执行,同时 JIT 编译器会介入,编译热点代码。在此期间,ART 会生成一份代码执行频率的分析文件 (Profile)。
设备空闲时: 当设备处于空闲状态并正在充电时,Android 系统会启动一个后台优化任务。该任务会根据之前收集的 Profile 信息,只对那些频繁执行的热点方法进行 AOT 编译,并生成新的 OAT 文件。
后续运行: 直接执行 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 早期支持两种编译后端:
| 特性 | Quick | Portable |
|---|
| 实现方式 | 直接生成目标架构汇编 | 使用 LLVM 后端 |
| 性能 | 更快的编译速度 | 更好的运行时性能 |
| 代码大小 | 较小 | 较大 |
| 平台支持 | 需要为每个架构单独实现 | LLVM 自动支持多平台 |
| 现状 | 主要使用 | 已在 Android 6.0 移除 |
现代 ART 主要使用 Optimizing Compiler,它是 Quick 编译器的演进版本,具有更好的优化能力。
ART 对象模型与类结构
每个 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
| 特性 | ART | Dalvik |
|---|
| 编译模式 | 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 的工作原理,能让你找到很多"非常规"的逆向思路:
脱壳新思路
- 监控
dex2oat 调用:某些壳会在运行时动态调用 dex2oat,监控其命令行参数能发现隐藏的 DEX - 从 VDEX 提取 DEX:Android 8.0+ 的 VDEX 文件本质上就是 DEX,用
vdexExtractor 可以快速提取 - 从 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
...
|
从 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;"
|
其他实用工具
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.so | ART 内部错误 | 检查 tombstone 和 logcat |
| 性能下降 | JIT 频繁编译 | 检查热点代码 |
| OOM | 堆空间不足 | 调整 dalvik.vm.heapsize |