编写一个 smali 文件 & 简单算术表达式的计算
一、smali 文件的编写
(Ⅰ)smali 文件的编写
编写 “HelloXidian.smali” 文件,代码如下所示:
.class public LHelloXidian;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 2
.prologue
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v0, "Hello Xidian"
invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
代码编写过程说明:
首先,声明了 smali 文件的头,每个 smali 文件都会包含它们。.class 表示类名,这里定义了一个 public 的类,类名是 HelloXidian。.super 表示它的 父类,这里是 java.lang.Object。
紧接着,以一个 .method 开始,一个 .end method 结尾,表示它是一个方法,而 public static main 表示它是一个 公有 的 静态 方法,方法名是 main。之后紧跟的 ([LJava/lang/String;)V 表示它需要传递一个 Java.lang.String 类型的数组,并且返回值是 void。
方法内部的代码,.registers 表示 寄存器 的声明,这里声明了 2 个寄存器,供后面使用,.prologue 表示一个开场,之后跟随的才是业务逻辑的代码。
逐行分析:
-
new-instance v0, Ljava/lang/StringBuilder; 表示构造了一个 java.lang.StringBuilder 类型对象的新实例,并将对象引用赋值给 v0 寄存器。
-
invoke-direct {v0}, Ljava/lang/StringBuilder;-> < init >(),其中 MethodName 为 < init >,表明调用了一个 Ljava/lang/StringBuilder 的构造方法,不包含参数,并且返回值为 void。
-
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; 表示创建了一个 java.io.PrintStream 对象,并存入 v1 寄存器中。
-
const-string v0, “Hello Xidian” 表示声明了一个字符串 “Hello Xidian”,并存入 v0 寄存器中。
-
invoke-virtual {v1,v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V 表示调用了 java.io.PrintStream 中的 println() 方法,参数传递的是 v0 寄存器中的值,就是之前存储的 “Hello Xidian”。
-
最后,return-void 表示函数返回 void。
(Ⅱ)具体操作过程
- 编写并保存 HelloXidian.smali 文件:
2. 将 smali.jar 与 HelloXidian.smali 放到 Android SDK 的 tools 文件夹中,打开命令提示符窗口,进入到 SDK 下的 tools 文件夹,输入命令: java –jar smali.jar –o classes.dex HelloXidian.smali
,在当前目录下生成 classes.dex 文件:
3. 将 classes.dex 文件打包为 HelloXidian.zip 文件,放到当前目录下。
4. 启动 Android 运行环境
在命令提示符窗口中执行以下命令:
adb push HelloXidian.zip /data/local
adb shell dalvikvm –cp /data/local/HelloXidian.zip HelloXidian
命令执行后在窗口下输出 “Hello Xidian” 字符串:
对应的 Java 代码如下所示:
public class HelloXidian
{
public static void main(String[] args)
{
System.out.println("Hello Xidian");
}
}
(Ⅲ)运行结果
启动 Android 运行环境,在命令提示符窗口中执行命令,命令执行后在窗口下输出了 “Hello Xidian” 字符串。
二、编写 smali 文件,计算(7+5)*(7-5)并输出结果
(Ⅰ)smali 文件的编写
编写 “calculate.smali” 文件,代码如下所示:
.class public Lcalculate;
.super Ljava/lang/Object;
.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 main([Ljava/lang/String;)V
.registers 6
.prologue
.line 6
const/16 v0, 0x18
.line 7
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v2, "(7 + 5)*(7 - 5) = %d\n"
const/4 v3, 0x1
new-array v3, v3, [Ljava/lang/Object;
const/4 v4, 0x0
invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v0
aput-object v0, v3, v4
invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
.line 9
return-void
.end method
代码编写过程说明:
首先,声明了 smali 文件的头,每个 smali 文件都会包含它们。.class 表示类名,这里定义了一个 public 的类,类名是 calculate。.super 表示它的父类,这里是 java.lang.Object。
紧接着,以一个 .method 开始,一个 .end method 结尾,表示它是一个方法,public constructor 表示它是一个 公有的构造方法,这里是 Java 类 默认的构造方法,如果我们不声明构造方法,编译器会为我们创建一个无参的构造方法。
而下面的 public static main 表示它是一个公有的静态方法,方法名是 main。([LJava/lang/String;)V 表示它需要传递一个 Java.lang.String 类型的数组,并且返回值是 void。
方法内部的代码,.registers 表示寄存器的声明,这里声明了 6 个寄存器,供后面使用,.prologue 表示一个开场,之后跟随的才是业务逻辑的代码。
逐行分析:
- const/16 v0, 0x18 声明了一个常量,将一个 16 位的常量 0x18 赋值给 v0 寄存器。
- sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; 表示创建了一个java.io.PrintStream 对象,并存入 v1 寄存器中。
- const-string v2, “(7 + 5)*(7 - 5) = %d\n” 声明了一个字符串常量,将该字符串 “(7 + 5)*(7 - 5) = %d\n” 赋值给 v2 寄存器。
- const/4 v3, 0x1 声明了一个常量,将一个 4 位的常量 0x1 赋值给 v3 寄存器。
- new-array v3, v3, [Ljava/lang/Object; 构造类型为 java.lang.Object,大小为 v3 寄存器大小,即为 1 的数组,并将数组的引用赋给 v3 寄存器。
- const/4 v4, 0x0 声明了一个常量,将一个 4 位的常量 0x0 赋值给 v4 寄存器。
- invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer; 表示调用了一个java.lang.Integer中的静态方法 valueOf,形参保存在寄存器 v0 中,类型为整型 int,返回值类型为 java.lang.Integer。
- move-result-object v0 表示将上一个 invoke 类型指令操作的对象结果赋给 v0 寄存器。
- aput-object v0, v3, v4 将 v0 的对象引用作为元素存入对象引用数组,数组的引用位于 v3,元素的索引位于 v4,即为 0。此时,结果值 “0x18” 已被存入 v3 指向的内存单元中。
- new-instance v0, Ljava/lang/StringBuilder; 表示构造了一个 java.lang.StringBuilder 类型对象的新实例,并将对象引用赋值给 v0 寄存器。
- invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 表示调用了 v1 中 java.io.PrintStream 的 printf() 方法,参数传递的是 v2、v3 寄存器中的值,就是之前存储的 “(7 + 5)*(7 - 5) = %d\n” 和 “0x18” 。
- 最后,return-void 表示函数返回 void。
(Ⅱ)具体操作过程
- 编写并保存 calculate.smali 文件:
2. 将 smali.jar 与 calculate.smali 放到 Android SDK 的 tools 文件夹中,打开命令提示符窗口,进入到 SDK 下的 tools 文件夹,输入命令: java –jar smali.jar –o classes.dex calculate.smali
,在当前目录下生成 classes.dex 文件。
3. 将 classes.dex 文件打包为 calculate.zip 文件,放到当前目录下。
4. 启动 Android 运行环境:
在命令提示符窗口中执行以下命令:
adb push calculate.zip /data/local
adb shell dalvikvm –cp /data/local/calculate.zip calculate
命令执行后在窗口下输出了(7+5)*(7-5)= 24 的结果:
对应的 Java 代码为:
public class calculate
{
public static void main(String[] args)
{
int i;
i = (7 + 5)*(7 - 5);
System.out.printf("(7 + 5)*(7 - 5) = %d\n",i);
}
}
(Ⅲ)运行结果
启动 Android 运行环境,在命令提示符窗口中执行命令,命令执行后在窗口下输出了(7+5)*(7-5)= 24 的结果。
三、一些问题及解决方法
(1)问题:在进行测试时,未启动安卓虚拟设备,导致命令提示符提示出错:
解决方法:启动安卓虚拟设备后,再次执行命令,即可成功进行操作。
(2)问题:在执行adb push HelloXidian.zip /data/local
时,提示信息 “read-only file system”。
解决方法:利用 adb remount
命令,将设备改为 可读可写,即可成功执行接下来的 adb 操作。