前面我们说,apk文件可以解包成smali文件。那么smali是什么?这还要从dalvik说起。dalvik是google专门为android操作系统设计的虚拟机,经过深度优化。虽然android程序也是用java语言来编写,不过dalvik与标准java虚拟机JVM还是两码事。前者是基于寄存器的而后者是基于栈的,前者拥有其专属的文件格式dex(dalvik executable)而后者执行的是java字节码,所以dalvik比jvm速度更快,占用空间更小。
我们从apk中解出的smali,其实质是把dex转换为smali,dex文件格式相当紧凑,里面包含着指令,只不过都是16进制的看起来相当不便。介绍了一些背景,总结起来一句话,smali是dalvik虚拟机内部执行的核心代码,他也有自己的语法,下面先来看一下他的基本类型:
- B (byte)
- C (char)
- D (double)
- F (float)
- I (int)
- J (long)
- S (short)
- V (void)
- Z (boolean)
- [XXX (array)
- LXXX (object)
这里需要解释的是数组和对象,数组以[开头,如整数数组[I,对象以L开头,格式为LpackageName/objectName;(分号结尾),如字符串对象Ljava/lang/String;,字符串对象数组[Ljava/lang/String;。知道了这些基本类型,再了解方法时就能一目了然:
function (Z[I[ILjava/lang/String;J)Ljava/lang/String;
有点晕?没关系,这个方法的声明相当于:
String function (boolean, int[], int[], String, long)
在调用的时候要遵循下面的格式:
LpackageName/objectName;->functionName (ZI[I)V
在一个类中我们声明域的方法跟上面的都很类似,只是在前面加上.field,就不再多说了,了解一下即可。
.field LpackageName/objectName;->fieldName:Ljava/lang/String;
之前说过,dalvik是基于寄存器的,所以在函数内部避免不了的就是操作寄存器,那么在dalvik里是如何定义寄存器的呢?目前最流行的一种方法是,本地寄存器用v开头数字结尾表示,如v0、v1、v2...,参数寄存器用p开头数字结尾,如p0、p1、p2...。在使用寄存器之前,要在函数内部用.locals声明本函数内公用了多少个本地寄存器,结合下面的例子就清楚了:
# virtual methods .method public static printfStr(ZLjava/lang/String;)V .locals 1 //声明此函数需要一个本地寄存器v0 if-eqz p0, :cond_0 //判断参数1若为假则跳转到:cond_0 //下面两句相当于System.out.println(String) sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; invoke-virtual {v0, p1}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V :cond_0 return-void //返回 .end method
最后需要做几个额外的说明,上面的例子我把它声明为static,本身没有this方法,所以参数寄存器是从p0开始计数,若非static方法,参数寄存器则是从p1开始对应,而p0则是隐含的this所用。若变量为double或long方法,则此变量需要两个寄存器来存储。smali最基本语法大致就是如此,我们先做一个简单的了解,含有非常多的指令我们没有用到,贴一个官方的介绍,dalvik操作码,先看一下,具体在后面实战时边用边说。