MainActivity$1.smali
、R$anim.smali
这类文件名中带美元符号的 smali 文件,这些文件就是内部类 smali 文件apktool d app-release.apk
MainActivity$1.smali
为例,执行如下命令查看其内容MainActivity$1.smali
中存放的是原文件名;.resource
指令中指定的是 MainActivity.java;.implements
指令告诉我们,这个类实现了 View 类的 OnClickListener 接口,“Landroid/view/View$OnClickListener;”这一长串指令的内容是 Java 的类或方法的完整签名.class
与 .super
指令会指明 smali 文件所保存的类的完整签名,和类的父类的完整签名.field
指令声明,后面跟着字段的访问权限和完整的签名;“# virtual methods”表示接下来存放的是类的虚方法#
标识注释的开始,井号后的部分都是注释.field
指令声明.annotation
指令开始,以 .end annotation
指令结束(声明一个注解)。.annotation
指令后跟着注解的类型和完整的签名,若类中有多个注解,则会有多个指令对.method
指令开始,以 .end method
指令结束,.method
指令后跟着方法的访问权限和完整的签名.locals
:声明当前方法中使用的寄存器数目。对 DEX 中的每个方法,都可通过静态分析知道其使用了多少个寄存器。这样做的好处是,虚拟机执行 DEX 中类的方法时可提前为方法准备栈空间.param
:指定方法中的参数名,以便程序的调试。经过 Proguard(混淆工具)处理的 DEX 可能不含该信息.prologue
:表示下面的部分是 DEX 指令.line
:保存 DEX 中的方法在 Java 源文件中的行号信息,以便调试。经过 Proguard 处理的 DEX 可能不含该信息// 形式一
Iterator<对象> <对象名> = <方法返回一个对象列表>;
for (<对象> <对象名>: <对象列表>) {
[处理单个对象的代码体];
}
// 形式二
Iterator<对象> <迭代器> = <方法返回一个迭代器>;
while (<迭代器>.hasNext()) {
<对象> <对象名> = <迭代器>.next();
[处理单个对象的代码体];
}
第一种形式的迭代是在 for 关键字中将对象名和对象列表用冒号分隔,然后在循环体中直接访问单个对象。这种形式的代码简练、可读性好,编程中使用颇多
第二种形式是手动获取一个迭代器,然后在一个循环中调用迭代器中的 hasNext() 检测其是否为空,最后在循环体中调用其 next() 遍历迭代器
现在,反编译实例程序 Circulate(实例代码:GitHub)。打开反编译工程 smali/com/droider/circulate 目录下的 MainActivity.smali 文件,找到 iterator() 方法,代码如下:
.method private iterator()V
.locals 4
const-string v0, "activity"
.line 42
invoke-virtual {p0, v0}, Lcom/droider/circulate/MainActivity;->getSystemService(Ljava/lang/String;)Ljava/lang/Object; # 获取 ActivityManager
move-result-object v0
check-cast v0, Landroid/app/ActivityManager;
.line 44
invoke-virtual {v0}, Landroid/app/ActivityManager;->getRunningAppProcesses()Ljava/util/List;
move-result-object v0 # 正在运行的进程列表
.line 45
new-instance v1, Ljava/lang/StringBuilder; # 新建一个 StringBuilder 对象
# 调用 StringBuilder 的构造方法
invoke-direct {v1}, Ljava/lang/StringBuilder;->()V
.line 46
# 获取进程列表的迭代器
invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator;
move-result-object v0
:goto_0 # 迭代循环开始
invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z # 开始迭代
move-result v2
# 若迭代器为空就跳走
if-eqz v2, :cond_0
invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
# 循环获取每一项
move-result-object v2
check-cast v2, Landroid/app/ActivityManager$RunningAppProcessInfo;
.line 47
# 新建一个临时的 StringBuilder 对象
new-instance v3, Ljava/lang/StringBuilder;
invoke-direct {v3}, Ljava/lang/StringBuilder;->()V
# 获取进程名
iget-object v2, v2, Landroid/app/ActivityManager$RunningAppProcessInfo;->processName:Ljava/lang/String;
invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# 换行符
const-string v2, "\n"
# 组合进程名和换行符
invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v2
# 将组合后的字符串添加到 StringBuilder 末尾
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# 跳到循环开始处
goto :goto_0
.line 50
:cond_0
# 将 StringBuilder 转为字符串
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v0
const/4 v1, 0x0
.line 49
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
.line 51
# 用 Toast 弹出 StringBuilder 的内容
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
# 方法返回
return-void
.end method
上面这段代码的功能是获取正在运行的进程列表,然后用 Toast 弹出所有进程名。获取正在运行的进程列表用的是 ActivityManager 类的 getRunningAppProcesses(),该方法返回一个 List
对象。
上面的代码中,调用了 List 的 iterator() 获取进程列表的迭代器,然后从 goto_0 处开始进入迭代循环。在循环中,首先调用迭代器的 hasNext() 检测迭代器是否为空。若迭代器为空,就跳转到 cond_0 处,调用 Toas 弹出所有的进程信息;若迭代器不为空,则说明迭代器中的内容还没取完,要调用迭代器的 next() 获取单个 RunningAppProcessInfo 对象,然后新建一个临时的 StringBuilder,将进程名与换行符组合后添加到循环开始前创建的 StringBuilder 中,最后用 goto 语句跳转到循环开始处
可看出,这段代码与前面列出的 while 循环声明很相似,其实,第一种迭代器循环展开后就是第二种循环的实现,尽管它们的 Java 代码不同,但生成的反汇编代码很相似
迭代器循环的特点:
迭代器循环会调用迭代器的 hasNext() 检测循环条件是否满足
迭代器循环会调用迭代器的 next() 获取单个对象
迭代器循环会使用 goto 指令控制代码的流程
for 形式的迭代器循环展开后即 while 形式的迭代器循环
接下来是传统的 for 循环。来看 MainActivity.smali 文件中的 forCirculate(),代码:
.method private forCirculate()V
.locals 7
.line 56
invoke-virtual {p0}, Lcom/droider/circulate/MainActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v0
# 获取 PackageManager
invoke-virtual {v0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;
move-result-object v0
const/16 v1, 0x2000
.line 58
# 获取已安装程序列表
invoke-virtual {v0, v1}, Landroid/content/pm/PackageManager;->getInstalledApplications(I)Ljava/util/List;
move-result-object v0
.line 60
# 获取列表中 ApplicationInfo 对象个数
invoke-interface {v0}, Ljava/util/List;->size()I
move-result v1
.line 61
# 新建 StringBuilder 对象
new-instance v2, Ljava/lang/StringBuilder;
# 调用 StringBuilder 的构造方法
invoke-direct {v2}, Ljava/lang/StringBuilder;->()V
const/4 v3, 0x0
# v4 = 0
const/4 v4, 0x0
# 循环开始
:goto_0
# v4 = 0,v1 = ApplicationInfo 对象个数
# 若 v4 大于等于 v1 就跳转,即 v1 < 0 时跳转到 cond_0 处
if-ge v4, v1, :cond_0
.line 63
# 获取单个 ApplicationInfo 对象
invoke-interface {v0, v4}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v5
check-cast v5, Landroid/content/pm/ApplicationInfo;
.line 64
# 新建临时的 StringBuilder 对象
new-instance v6, Ljava/lang/StringBuilder;
invoke-direct {v6}, Ljava/lang/StringBuilder;->()V
# 获取包名
iget-object v5, v5, Landroid/content/pm/ApplicationInfo;->packageName:Ljava/lang/String;
# 将包名添加到临时的 StringBuilder 中
invoke-virtual {v6, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# 换行符
const-string v5, "\n"
# 组合包名和换行符
invoke-virtual {v6, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# 转换为字符串
invoke-virtual {v6}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v5
# 添加到循环外的 StringBuilder 中
invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
# 下一个索引
add-int/lit8 v4, v4, 0x1
# 跳转到循环开始处
goto :goto_0
.line 67
:cond_0
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v0
.line 66
# 构造 Toast
invoke-static {p0, v0, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
.line 68
# 显示已安装程序列表
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
# 方法返回
return-void
.end method
上面这段代码的功能是获取已安装的程序,用 Toast 弹出所有的软件包名。获取已安装程序用的是 PackageManager 类的 getInstalledApplications(),首先创建一个 StringBuilder 对象存放所有的字符串信息,接着初始化 v4 寄存器为 0(作为获取列表项的索引)。for 循环的起始处是 goto_0 标号。循环条件的代码为 if-ge v4, v1, :cond_0
,其中 v4 为索引值,v1 为列表中 ApplicationInfo 的个数,cond_0 标号处为循环结束后的代码,若索引没有完成(没找到最后一项),代码会顺序执行(循环体中代码);若索引完成(找到最后一项),会跳转到 cond_0 处执行 Toast 以显示所有字符串信息。if-ge
下的第一行代码调用 List 的 get() 获取列表中单个 ApplicationInfo 对象,然后将包名和换行符组合后添加到之前声明的 StringBuilder 中,最后将 v4 索引值加 1 后调用 goto :goto_0
语句跳转到循环开始处
for 循环代码的特点:
进入循环前,要初始化循环计数器变量,且其值要在循环体中更改
循环条件判断可以是由条件跳转指令组合成的合法语句
在循环中用 goto 指令控制代码的流程
while、do while 循环的结构差异不大,二者的代码和前面的迭代器循环的代码非常相似,只是循环条件判断的位置不同
.method private packedSwitch(I)Ljava/lang/String;
.locals 1
# 若 p1 = 0 就跳转到 cond_3 处
# p1 为传进来的参数
if-eqz p1, :cond_3
# v0 = 1
const/4 v0, 0x1
# 若 p1 = 1 就跳转到 cond_2 处
if-eq p1, v0, :cond_2
# v0 = 2
const/4 v0, 0x2
# 若 p1 = 2 就跳转到 cond_1 处
if-eq p1, v0, :cond_1
# v0 = 3
const/4 v0, 0x3
# 若 p1 = 3 就跳转到 cond_0 处
if-eq p1, v0, :cond_0
# 若能执行到这儿,说明到了 default
# 定义一个字符串
const-string p1, "She is a person"
# 跳转到 goto_0 处
goto :goto_0
# 当 p1 = 3 时定义的字符串
:cond_0
const-string p1, "She is a obasan"
# 跳转到 goto_0 处
goto :goto_0
# 当 p1 = 2 时定义的字符串
:cond_1
const-string p1, "She is a women"
# 跳转到 goto_0 处
goto :goto_0
# 当 p1 = 1 时定义的字符串
:cond_2
const-string p1, "She is a girl"
# 跳转到 goto_0 处
goto :goto_0
# 当 p1 = 0 时定义的字符串
:cond_3
const-string p1, "She is a baby"
# 所有 case 的出口
:goto_0
# 返回字符串 p1
return-object p1
.end method
.method private tryCatch(ILjava/lang/String;)V
.locals 5
const/4 v0, 0x0
.line 21
# 第一个 try 语句块开始
:try_start_0
# 将第二个参数转换为 int 类型
invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result p2
# 第一个 try 语句块结束
:try_end_0
# catch_1
.catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} :catch_1
# 若出现异常,这里不会执行,会跳转到 catch_1 标号处
.line 23
# 第二个 try 语句块开始
:try_start_1
# v1 = 第一个参数除以第二个参数(转换为 int 后)
div-int v1, p1, p2
# v2 = 上面的商乘以第二个参数(即人数)
mul-int v2, v1, p2
# v2 = 第一个参数(即鸡腿数)减去上面的积,现在 v2 为余数
sub-int v2, p1, v2
# Unicode 字符串“共有 %d 只鸡腿,%d 个人平分,每人可分 %d 只,还剩 %d 只”
const-string v3, "\u5171\u6709 %d \u53ea\u9e21\u817f\uff0c%d \u4e2a\u4eba\u5e73\u5206\uff0c\u6bcf\u4eba\u53ef\u5206 %d \u53ea\uff0c\u8fd8\u5269 %d \u53ea"
const/4 v4, 0x4
new-array v4, v4, [Ljava/lang/Object;
.line 26
invoke-static {p1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object p1
aput-object p1, v4, v0
const/4 p1, 0x1
invoke-static {p2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object p2
aput-object p2, v4, p1
const/4 p1, 0x2
invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object p2
aput-object p2, v4, p1
const/4 p1, 0x3
invoke-static {v2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object p2
aput-object p2, v4, p1
.line 25
# 格式化字符串
invoke-static {v3, v4}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
move-result-object p1
.line 27
invoke-static {p0, p1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object p1
.line 29
# 用 Toast 显示格式化字符串
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
# 第二个 try 语句块结束
:try_end_1
# catch_0
.catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} :catch_0
# catch_1
.catch Ljava/lang/NumberFormatException; {:try_start_1 .. :try_end_1} :catch_1
# 跳转到 goto_0 标号处,即方法返回处
goto :goto_0
:catch_0
# 第三个 try 语句块开始
:try_start_2
# 字符串“人数不能为 0”
const-string p1, "\u4eba\u6570\u4e0d\u80fd\u4e3a 0"
.line 32
invoke-static {p0, p1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object p1
.line 34
# 用 Toast 显示产生异常的原因
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
# 第三个 try 语句块结束
:try_end_2
.catch Ljava/lang/NumberFormatException; {:try_start_2 .. :try_end_2} :catch_1
# 跳转到 goto_0 处,即方法返回处
goto :goto_0
:catch_1
# 字符串“无效的数值字符串”
const-string p1, "\u65e0\u6548\u7684\u6570\u503c\u5b57\u7b26\u4e32"
.line 38
invoke-static {p0, p1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object p1
.line 40
# 用 Toast 显示异常产生的原因
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
:goto_0
# 方法返回
return-void
.end method
.catch
指令指定所处理的异常类型和 catch 的标号,格式:.catch <异常类型> { .. }
.catch
指令定义了 catch_0 和 catch_1 两个 catch。在 catch_0 标号的下面有一个标号为 try_start_2 的 try 语句块,其实这个语句块是虚构的。假设有如下代码:private void a() {
try {
...
try {
...
}
catch (XXX) {
...
}
}
catch (YYY) {
...
}
}
struct DexCode {
u2 registersSize; // 使用的寄存器个数
u2 insSize; // 参数个数
u2 outsSize; // 调用其他方法时使用的寄存器个数
u2 triesSize; // try/catch 语句的个数
u4 debugInfoOff; // 指向调试信息的偏移量
u4 insnsSize; // 指令集的个数,以 2 字节为单位
u2 insns[1]; // 指令集
// 2 字节空间用于结构对齐
//try_item[triesSize]; // DexTry 结构
// try/catch 语句中 handler 的个数
// catch_handler_item[handlersSize]; // DexCatchHandler 结构
};
struct DexTry {
u4 startAddr; // 起始地址
u2 insnCount; // 指令数量
u2 handleOff; // handler 的偏移量
};
(0x503ac + 1 * 2) ~ (0x503ac + 5 * 2) = 0x503ae ~ 0x503b6
private void tryCatch(int drumsticks, String people) {
try {
int i = Integer.parseInt(people);
try {
int m = drumsticks / i;
int n = drumsticks - m * i;
String str = String.format("共有 %d 只鸡腿,%d 个人平分," +
"每人可分 %d 只,还剩 %d 只", drumsticks, i, m, n);
Toast.makeText(this,
str,
Toast.LENGTH_SHORT).show();
}
catch (ArithmeticException e) {
Toast.makeText(this,
"人数不能为 0",
Toast.LENGTH_SHORT).show();
}
}
catch (NumberFormatException e) {
Toast.makeText(this,
"无效的数值字符串",
Toast.LENGTH_SHORT).show();
}
}