一、实验题目
1.输出“Hello Xidian”字符串
2.编写smali文件,计算(7+5)*(7-5)并输出结果
二、smali文件的编写
1.HelloXidian.smali的编写
smali 是一种宽松的 Jasmin/dedexer 语法,它可以通过 baksmali 将我们已经编译好的 dex 格式的汇编语言,反汇编成 smali 文件,供我们阅读。
HelloXidian.smali的代码如下:
.class public LHelloXidian;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "HelloXidian!"
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
Smali文件的前三行声明了smali文件的头,每个smali文件都会有它们。
.class表示类名,这里定义了一个 public 的类,全类名是HelloXidian。
.super表示它的父类,这里是Object。
.source表示它对应的Java文件的文件名,这只是个标记,实际上在真实反编译的场景下,如果代码被混淆了,.source可能会没有值。在编写的HelloXidian.smali中,这个smali文件是直接手动编写的,没有经历从java代码演化的过程,故没有.source。
第 3-9 行,以一个.method开始,.end结尾,表示它是一个方法。public static表示它是一个公有的静态方法,方法名是 main。而之后紧跟的([LJava/lang/String;])V表示它需要传递一个String数组,并且返回值是void。[ 类型可以表示基本类型的数组。[ 后面紧跟基本类型描述符,如 [I 表示一个整形一维数组,相当于Java中的int[],多个 [ 在一起表示多维数组,如 [[I 表示int[][],[[[I 表示int[][][]。L与 [ 可以同时使用用来表示对象数组,如 [Ljava/lang/String; 就是Java中的字符串数组。Dalvik字节码有两种类型,基本类型和引用类型。对象和数组是引用类型,其它都是基本类型。其数据类型除boolean类型是由大写的“Z”表示之外,剩下都是是由Java基本数据类型的首字母大写表示。 L类型可以表示Java类型中的任何类。在Dalvik中,这些类以Lpackage/name/ObjectName;形式表示,最后有个分号,L表示后面跟着一个Java类。package/name表示对象所在的包,ObjectName表示对象的名称,最后的分号表示对象名结束。例如:Ljava/lang/String相当于java.lang.String。
第4行,.registers表示寄存器的声明,这里声明了 2个寄存器,供后面使用。第5 行,sget-object 表示创建了一个 PrintStream 对象,并存入 v0 寄存器中。第6行,const-string 表示什么了一个字符串 “Hello CxmyDev!”,并存入 v1 寄存器中。第7行,invoke-virtual 表示调用了 PrintStream 中的 printIn() 方法,参数传递的是 v1 寄存器中的值,就是之前存储的 “HelloXidian! ----16180120002”。
2.Testsmali.smali的编写
Android程序员用Java语言开发APP,编译工具会将Java源文件(.java)编译成Dalvik可执行文件(.dex)。Android系统中Dalvik Virtual Machine 会执行该文件。smali/baksmali则是Dalvik VM可执行文件的汇编器/反汇编器。反汇编Dalvik可执行文件(.dex)后,将会得到.smali后缀文件。smali代码拥有特定的语法。
相比于.dex文件,smali文件的语法更容易理解些。下面是.dex文件、.smali文件、.java文件三者之间的转化关系。
smali文件的语法相对复杂,所以我打算先编写java文件,通过转化来得到smali文件。
三、实验操作过程及实验结果
实验一
在SDK的tools文件夹下,新建文本文件,命名为HelloXidian.smali,输入代码:
.class public LHelloXidian;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, “HelloXidian! ----16180120002”
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
打开命令提示符窗口,进入到SDK下的tools文件夹
输入命令: java –jar smali-2.1.0.jar –o classes.dex HelloXidian.smali
当执行成功时,会在当前目录下生成classes.dex文件。
将classes.dex文件打包为HelloXidian.zip文件,放到当前目录下。
至此,smali文件编译完毕,接下来启动Android运行环境,我打开了Android Studio的模拟器。
在命令提示符窗口中执行以下命令(手机要ROOT):
adb push HelloXidian.zip /data/local
adb shell dalvikvm –cp /data/local/HelloXidian.zip HelloXidian
实验二
新建文本,改名为Test2.java,输入使用java语言编写的代码。
代码如下:
public class Test2
{
public static void main(String[] args)
{
int sum;
int a=7,b=5;
sum=(a+b)(a-b);
System.out.println("The result of (7+5)(7-5) is "+sum);
}
}
为了方便起见,将Test2.java与baksmali-2.1.0.jar和dx.jar放在同一路径下。
编译java代码为class文件,打开命令提示符,命令执行成功后会生成Test2.class文件。
执行命令:javac Test2.java
然后执行命令 java Test2 可以输出计算结果。
至此,我们已经得到计算结果,接下来就来通过java代码转换成Test2的smali代码。
把class文件转成dex文件,apk包里java代码最后生成的是class.dex文件,把class转化成dex文件就需要用到android SDK提供的一个工具dx,所以我已经提前将这个包一同放在了同一路径下。
执行命令:java -jar dx.jar --dex --output=Test2.dex Test2.class
命令分别执行成功,文件夹中依次出现Test2.class和Test2.dex文件。
下面需要把dex转化成smali文件,这时会使用到工具baksmali,同样已经提前放进文件夹里了。当以下命令执行成功后,会生成一个out目录,在out目录下的Test2.smali就是我们要的smali代码了。
命令:java -jar baksmali-2.1.0.jar Test2.dex
使用notepad++打开Test2.smali,得到smali代码。
我发现这里的.source “Test2.java”,验证了前文中所提到的“.source表示它对应的Java文件的文件名”而实验一中的smali文件由于是手工编写的,所以.source为空值。
使用命令 java Test2 输出结果
至此,第二部分的实验就完成了。不过我结合生成的代码和网上的一些资料以及第一个实验的启发,自己又写了一份Test3.smali。
接下来的操作和实验一一样,这里就不再赘述了。
经过验证,自己手写的smali代码执行得到的结果也是正确的。
因为这个实验不复杂,所以smali代码手写也是可以实现的,但是对于逻辑复杂的程序设计,使用java书写更为合适,所以掌握由java文件转化成smali文件的方法是很有必要的。
四、遇到的问题及解决方法
1.
遇到问题:
当输入命令: java –jar smali.jar –o classes.dex HelloXidian.smali时,首先需要注意smali.jar的版本号,我最初的是smali-2.2.5.jar,出现了报错。
解决方法:
这是由于版本的问题导致的,当我换成smali-2.1.0.jar再次执行此命令时,可以顺利执行。
2.
遇到问题:
出现报错 permission denied。
解决方法:
使用命令adb root即可解决。因为手机需要root,没有root获取不到权限删除某些目录的文件。
五、源代码
HelloXidian.smali
.class public LHelloXidian;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "HelloXidian! ----16180120002"
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
Test2.java
public class Test2
{
public static void main(String[] args)
{
int sum;
int a=7,b=5;
sum=(a+b)*(a-b);
System.out.println("The result of (7+5)*(7-5) is "+sum);
}
}
Test3.smali
.class public LTest3;
.super Ljava/lang/Object;
#主函数
.method public static main([Ljava/lang/String;)V
.registers 5
const/16 v0, 0x07
const/16 v1, 0x05
#两个参数相加,值存在v3
add-int v2,v0,v1
#两个参数相减,值存在v4
sub-int v3,v0,v1
#两个参数相乘,值存在v3
mul-int v2,v2,v3
#将v3的值输出
sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v4, v2}, Ljava/io/PrintStream;->println(I)V
return-void
.end method