Dalvik虚拟机的特点
Dalvik虚拟机是Android平台专为应用程序设计的虚拟机,区别于标准的Java虚拟机(JVM)。它的主要目的是提高移动设备的软件运行效率,并规避与Oracle公司的版权纠纷。
Dalvik虚拟机具有体积小、占用内存少的特点,同时使用专有的DEX可执行文件格式,使得文件更小且执行速度更快。其常量池采用32位索引值,提供了更快的类方法名、字段名和常量寻址能力。此外,Dalvik基于寄存器架构,拥有完整的指令系统,支持对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等重要功能。值得一提的是,所有Android程序在Android系统进程中运行,每个进程对应一个Dalvik虚拟机实例。
Dalvik虚拟机与java虚拟机的区别
与Java虚拟机相比,Dalvik虚拟机在字节码格式上有所不同:它运行的是Dalvik字节码,而Java虚拟机则运行Java字节码(class文件)。这种差异使得Dalvik可执行文件体积更小。同时,Java虚拟机使用栈架构,相对开销较大,而Dalvik的寄存器架构不仅访问速度更快,而且生成的指令数量更少,从而提升程序的执行效率。
关于Dalvik虚拟机的执行过程
当Android系统启动后,首先执行init进程进行设备初始化,接着启动Zygote进程。Zygote负责初始化Dalvik虚拟机并进入等待状态。当需要执行某个Android应用时,system_server通过socket向Zygote发送命令,Zygote会fork出自身的实例来运行应用程序的入口函数,从而完成程序的启动。
Zygote 进程能够通过 fork()
创建其他进程,而非 Zygote 进程则不具备此能力。系统服务进程在终止后,其子进程也必须随之终止。
Dalvik 虚拟机的工作流程开始于 loadClassFromDex()
函数,用于加载类。每个成功解析的类会生成一个 ClassObject
类型的数据结构,这些结构存储在运行时环境中。虚拟机使用全局哈希表 gDvm.loadedClasses
来存储和查询已装载的类。接下来,字节码验证器通过 dvmVerifyCodeFlow()
函数对装载的代码进行校验。之后,虚拟机调用 FindClass()
函数查找并装载主方法类,最后通过 dvmInterpret()
函数初始化解释器并执行字节码流。
Dalvik虚拟机 JIT(即时编译)
Trace 方式是一种编译方法,与传统的 method 方式相比,主要用于解决代码执行路径的效率问题。在许多函数中,代码并非顺序执行,而是分为热路径和冷路径。热路径是指被频繁执行的部分,而冷路径则是很少被执行的部分。传统的 method 方式会对整个方法进行编译,这在冷路径上浪费了时间和内存。而 trace 方式则专注于快速获取热路径代码,从而以更短的时间和更少的内存完成编译。Dalvik 虚拟机默认采用 trace 方式编译,同时也支持 method 方式。
Dalvik指令集
.method public myMethod()V
.registers 2 # 声明使用的寄存器数量
const/4 v0, 0x5 # 将常量 5 加载到寄存器 v0
const/4 v1, 0x10 # 将常量 16 加载到寄存器 v1
add-int v0, v0, v1 # 执行加法操作: v0 = v0 + v1 (5 + 16)
return-void # 返回,不带返回值
.end method
条件跳转示例
.method public checkValue(I)V
.registers 2 # 声明使用的寄存器数量
iput v0, p0, Lcom/example/MyClass;->someInt:I # 将参数 p0 存入 someInt
if-eq p0, v0, :equalLabel # 如果 p0 等于 v0,则跳转到 equalLabel
return-void # 否则正常返回
:equalLabel
# 处理相等的情况
return-void # 返回
.end method
数组操作示例
.method public arrayExample([I)V
.registers 3 # 声明使用的寄存器数量
const/4 v0, 0x1 # 将常量 1 加载到寄存器 v0
aput v0, p0, 0 # 将 v0 的值放入数组 p0 的索引 0 处
return-void # 返回,不带返回值
.end method
参考
- 《Android软件安全与逆向分析》