chapter3 dalvik可执行格式与字节码规范

190 阅读4分钟

一 什么是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 字节码
  • 类型

只有两种类型:基本类型和引用类型

image.png

  • 方法

表现形式是:方法名 + 类型参数 + 返回值,如: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

扩展阅读:

blog.csdn.net/zhonglunshu…