Dalvik指令集是学习Android虚拟机中必不可少的知识点,它是被Android虚拟机所识别且直接执行的指令。
Dalvik是基于寄存器指令集,他的几乎所有指令操作都来自对寄存器的操作,而不像x86/ARM因为寄存器个数不够导致运算结果存不下,需要依靠堆栈来存储情况。Dalvik一共有65536个寄存器,执行一个函数调用后虚拟机内部会保存当前方法中用到的寄存器,并且重置所有寄存器供被调用方法的新环境使用。
由于Dalvik是虚拟机指令,最终依然会被解析成机器指令执行,因此65536个寄存器中大多数寄存器实际上仍然会被存储于堆栈中。少量运算中所使用的寄存器会被放在物理机的寄存器中使用。Dalvik虚拟机内部维护了调用栈,调用新的方法时会将寄存器与返回地址存放于调用栈中,返回时取回。
1. Dalvik寄存器
Dalvik中所有的寄存器均为32位长度。当表示64位数值时则使用相邻的两个32位寄存器存放,在使用时指定相邻的第一个寄存器,第二个寄存器也会参与运算。用.registers伪指令描述当前方法中使用的寄存器个数(包括形参)。
寄存器的命名有两种:
v0~vN+M-1:当前方法中运算所使用的寄存器。(var)
p0~pM-1:当前方法中的形参。(param)
其中N为当前方法的局部变量个数,M为当前方法形参个数。 vN~vN+M-1与p0~pM-1是一样的。
如果被调用的方法不是静态的,则p0为方法所属类的实例。
假设这里有个方法Add:
- class Clazz{
- public int Add(int a, int b)
- {
- int c = a + b;
- return c;
- }
- }
在排除编译器自动生成的临时变量与优化以外,这里使用了四个寄存器,即.registers 4
其中形参p0为Clazz的实例,p1为a, p2为b。也可以使用v1、v2、v3,不过命名通常以pM的居多。
其中局部变量v0为c。
2. 数据类型
java类型 |
dalvik类型 |
void |
V |
boolean |
Z |
byte |
B |
char |
C |
short |
S |
int |
I |
long |
J |
float |
F |
double |
D |
class |
L类全路径; |
int[]、char[][]、... |
[I、[[C、... |
基本数据类型中除了boolean->V、long->J以外,均为首字母大写。
类类型为L加类的全路径,且以/分割,比如有个类的全路径为com.example.MainClass,那么在Dalvik中命名为Lcom/example/MainClass; 注意分号。
3. 类的描述:
有如下类:
- package com.example;
- public class User{
- private String name;
- private String nick;
- public String getName()
- {return name;}
- public void setName(String name)
- {this.name = name;}
- }
注:其中String 的全路径为 java.lang.String。
.class 指令用于描述该类的起始位置:.class public User
.super 紧跟在.class后用于描述该类的父类(没有父类时继承自Object):.super Ljava/lang/Object;
.implements 描述类实现的接口:.implements 接口名
.annotation 描述类注解:
.annotation [注解的属性] <注解类名>
[注解字段=值]
...
.end
.field 指令用于描述类中的字段:.field private name:Ljava/lang/String;
.method 描述方法的开始:.method public getName()Ljava/lang/String;(返回值类型写在最后面)
方法特例:
.method public constructor
.method static constructor
.end method 描述方法的结束
当外部使用类中的方法时将会被描述为
getName:Lcom/example/User;->getName(Ljava/lang/String)V;
setName:Lcom/example/User;->setName()Ljava/lang/String;
jeb Dalvik汇编窗口与Java代码对比样例:
4. 常见指令
v*表示任意寄存器,#+*表示任意常量
其中A、B、C等表示指令的参数,其个数表示它的取值范围。
寄存器:
A、B、C等的个数表示指令能接受的寄存器下标范围。
如vA表示2^4-1内的寄存器可以使用,vAA则表示2^8-1内的寄存器可以使用。
最多为4个,如vAAAA表示2^16-1内的寄存器可以使用(v0~v65535)
常量:
如#+B为4位常量(通常为boolean)。
#+BBBBBBBBBBBBBBBB则为64位常量(long / double)。最多为16个
[下文整合分类自https://source.android.com/docs/core/runtime/dalvik-bytecode]
数据定义指令
用于将一个常量放入寄存器中
opcode |
指令 |
参数 |
描述 |
12 11n |
const/4 vA, #+B |
A: 目标寄存器(4 位) |
将给定的字面量值(符号扩展为 32 位)移到指定的寄存器中。 |
B: 有符号整数(4 位) |
|||
13 21s |
const/16 vAA, #+BBBB |
A: 目标寄存器(8 位) |
将给定的字面量值(符号扩展为 32 位)移到指定的寄存器中。 |
B: 有符号整数(16 位) |
|||
14 31i |
const vAA, #+BBBBBBBB |
A: 目标寄存器(8 位) |
将给定的字面量值移到指定的寄存器中。 |
B: 任意 32 位常量 |
|||
15 21h |
const/high16 vAA, #+BBBB0000 |
A: 目标寄存器(8 位) |
将给定的字面量值(右零扩展为 32 位)移到指定的寄存器中。 |
B: 有符号整数(16 位) |
|||
16 21s |
const-wide/16 vAA, #+BBBB |
A: 目标寄存器(8 位) |
将给定的字面量值(符号扩展为 64 位)移到指定的寄存器对中。 |
B: 有符号整数(16 位) |
|||
17 31i |
const-wide/32 vAA, #+BBBBBBBB |
A: 目标寄存器(8 位) |
将给定的字面量值(符号扩展为 64 位)移到指定的寄存器对中。 |
B: 有符号整数(32 位) |
|||
18 51l |
const-wide vAA, #+BBBBBBBBBBBBBBBB |
A: 目标寄存器(8 位) |
将给定的字面量值移到指定的寄存器对中。 |
B: 任意双字宽度(64 位)常量 |
|||
19 21h |
const-wide/high16 vAA, #+BBBB000000000000 |
A: 目标寄存器(8 位) |
将给定的字面量值(右零扩展为 64 位)移到指定的寄存器对中。 |
B: 有符号整数(16 位) |
|||
1a 21c |
const-string vAA, string@BBBB |
A: 目标寄存器(8 位) |
将通过给定索引指定的字符串的引用移到指定的寄存器中。 |
B: 字符串索引 |
|||
1b 31c |
const-string/jumbo vAA, string@BBBBBBBB |
A: 目标寄存器(8 位) |
将通过给定索引指定的字符串的引用移到指定的寄存器中。 |
B: 字符串索引 |
|||
1c 21c |
const-class vAA, type@BBBB |
A: 目标寄存器(8 位) |
将通过给定索引指定的类的引用移到指定的寄存器中。如果指定的类型是原始类型,则将存储 |
B: 类型索引 |
数据操作指令:
移动数值到新的地方
opcode |
指令 |
参数 |
描述 |
01 12x |
move vA, vB |
A: 目标寄存器(4 位) |
将一个非对象寄存器的内容移到另一个非对象寄存器中。 |
B: 源寄存器(4 位) |
|||
02 22x |
move/from16 vAA, vBBBB |