一 什么是dalvik虚拟机
可以把它看成是一个进程,因为它是由Zygote进程Fork出来的
二 dalvik虚拟机特点
- 体积小,主要针对jvm来对比,因为jvm生成的class文件,每个class都有一个常量值,会导致信息冗余,体积过大;而dvm生成的dex文件共享一个常量池,这样相比jvm来说,体积就会减少很多
- 常量池采用32位索引值,对类方法名、字段名、常量的寻值速度快,因为寄存器都是32位的,这样就简化了解释器
- 每一个进程都与一个dalvik虚拟机实例对应
- 基于寄存器架构,数据的访问直接在寄存器之间传递,比基于栈的访问方式速度快很多
三 dalvik语言基础
设计准则
- 采用基于寄存器的设计
- 普通数据类型采用32位寄存器进行存储,如果是大数据类型,则采用2个寄存器进行存储
- 如果用来保存对象引用,寄存器必须能容纳引用类型
- 在调用约定上,使用N个寄存器来表示N个参数
3.1 认识dalvik语言
demo示例
public class Hello {
private int result;
public int foo(int a, int b) {
return (a + b) * (a - b);
}
public static void name(String[] args) {
Hello hello = new Hello();
hello.result = hello.foo(5, 3);
System.out.println(hello.result);
}
}
生成class文件
javac -source 1.7 -target 1.7 Hello.java
生成dex文件
dx --dex --output=Hello.dex Hello.class
通过反汇编,生成的smali文件,代码如下:
###### Class defpackage.Hello (Hello)
.class public LHello;
.super Ljava/lang/Object;
.source "Hello.java"
# instance fields
.field private result:I
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static name([Ljava/lang/String;)V
.registers 4
.prologue
.line 10
new-instance v0, LHello;
invoke-direct {v0}, LHello;-><init>()V
.line 11
const/4 v1, 0x5
const/4 v2, 0x3
invoke-virtual {v0, v1, v2}, LHello;->foo(II)I
move-result v1
iput v1, v0, LHello;->result:I
.line 12
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
iget v0, v0, LHello;->result:I
invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V
.line 13
return-void
.end method
# virtual methods
.method public foo(II)I
.registers 5
.prologue
.line 6
add-int v0, p1, p2
sub-int v1, p1, p2
mul-int/2addr v0, v1
return v0
.end method
dalvik虚拟机是如何虚拟的使用寄存器的?
dalvik为每个进程维护一个调用栈,而这个调用栈的作用之一就是‘虚拟’寄存器
在每个函数的头部,通过 .register 指令来指定它所使用的寄存器个数,虚拟机执行到这个函数时就会根据寄存器的个数分配栈空间,这些栈空间就是用来存放寄存器的实际值
虚拟机通过处理字节码对寄存器进行读操作和写操作,实际上都是对栈空间进行写操作
示例:
http://androidxref.com/4.4.4_r1/xref/dalvik/vm/mterp/c/OP_MOVE.cpp
http://androidxref.com/4.4.4_r1/xref/dalvik/vm/mterp/c/header.cpp
3.1.1 寄存器命名法
主要分为v命名法和p命名法,但是以后反汇编中看到的基本都是p命名法,所以主要是记录p命名法
命名规则:
函数中引入的参数名,从p0开始递增,局部变量从v0开始递增
比如在foo函数中,v0,v1表示局部变量寄存器,p1和p2表示传入的整型参数,p0隐藏了,它表示被引入Hello对象的引用
3.1.2 字节码
- 类型
只有两种类型:基本类型和引用类型
- 方法
表现形式是:方法名 + 类型参数 + 返回值,如:foo(II)I,foo是方法名,括号里的II表示两个int类型的参数,最右边的I表示返回int类型的值
.method public foo(II)I
- 字段
表现形式:字段名 + 字段类型,如:
.field private result:I
3.1.3 指令集
- 空操作指令:nop,值为0,主要用来对齐代码
- 数据操作指令:move,格式:move destination,source
- 返回指令:return
- 数据定义指令:const
- 锁指令:monitor,多用在多线程程序对同一个对象进行操作
- 实例操作指令:与实例相关的操作包括实例的类型转换、检查及创建,常见的check-cast,instance-of,new-instance
- 数组操作指令:包括获取数组长度、新建数组、数组赋值、数组元素取值等,常见的array-length,new-array,field-new-array,fill-array-data,filled-new-array,arrayop(aget、aput等)
- 异常指令:throw
- 跳转指令:从当前地址跳到指定的偏移处,主要有goto(无条件跳转)、switch、if
- 比较指令:格式为cmpkind vAA,vBB,vCC
- 字段操作指令:用于对对象实例的字段进行读写操作,普通字段的指令前缀为i,如iget,静态字段的指令前缀为s,如sget
- 方法调用指令:负责调用实例类的方法,基础指令为invoke
- 数据转换指令:将一种数据类型转换为另一种数据类型,格式为: unop vA,vB,常见:
neg-xx: 求补
not-xx: 求反
xx-to-yy: 转换
- 数据运算指令:包括算术运算指令和逻辑运算指令,常见:add-type,sub-type,mul-type,div-type,rem-type(模运算),and-type,or-type,xor-type,shl-type,shr-type,ushr-type
扩展阅读: