本文环境
java version "1.7.0_75"
dx version 1.11
baksmali 2.1.0

iterator

(for 和 while-do 两种形式)

java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.*;
class HeyIterator
{
static void iter_for()
{
ArrayList al = new ArrayList();
al.add("C");
for( Iterator itr = al.iterator(); itr.hasNext(); ){
System.out.print(itr.next());
}
Iterator itr2 = al.iterator();
while(itr2.hasNext()) {
System.out.print(itr2.next());
}
}
}

smali 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.class LHeyIterator;
.super Ljava/lang/Object;
.source "HeyIterator.java"
# direct methods
.method constructor <init>()V
.registers 1
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method static iter_for()V
.registers 4
.prologue
.line 7
new-instance v0, Ljava/util/ArrayList;
invoke-direct {v0}, Ljava/util/ArrayList;-><init>()V
.line 8
const-string v1, "C"
invoke-virtual {v0, v1}, Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z
.line 10 //得到迭代器
invoke-virtual {v0}, Ljava/util/ArrayList;->iterator()Ljava/util/Iterator;
move-result-object v1
:goto_e //判断是否有下一个元素
invoke-interface {v1}, Ljava/util/Iterator;->hasNext()Z
move-result v2
if-eqz v2, :cond_1e
.line 11
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-interface {v1}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v3
invoke-virtual {v2, v3}, Ljava/io/PrintStream;->print(Ljava/lang/Object;)V
goto :goto_e //跳至循环头部
//--------------分割线---------------------
.line 14
:cond_1e //得到迭代器
invoke-virtual {v0}, Ljava/util/ArrayList;->iterator()Ljava/util/Iterator;
move-result-object v0
.line 15
:goto_22 //判断是否有下一个元素
invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z
move-result v1
if-eqz v1, :cond_32
.line 16
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v2
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(Ljava/lang/Object;)V
goto :goto_22 //跳至循环头部
.line 18
:cond_32
return-void
.end method

两种方式完全一样啊


switch

java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;
class HeySwitch
{
static void func()
{
int a = 10;
//连续的case
switch(a){
case 0:
System.out.println(0);
case 1:
System.out.println(1);
case 2:
System.out.println(2);
case 3:
System.out.println(3);
}
//不连续的case
switch(a){
case 0:
System.out.println(0);
case 32:
System.out.println(32);
case 64:
System.out.println(64);
case 128:
System.out.println(128);
}
}
}

smali 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
.class LHeySwitch;
.super Ljava/lang/Object;
.source "HeySwitch.java"
# direct methods
.method constructor <init>()V
.registers 1
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method static func()V
.registers 4
.prologue
const/4 v3, 0x0
.line 7
const/16 v0, 0xa
.line 8 //第一个switch的packed矩阵,指令格式 packed-switch vAA, +BBBBBBBB
packed-switch v0, :pswitch_data_3e
.line 19
:goto_6 //第二个switch的sparse矩阵,指令格式 sparse-switch vAA, +BBBBBBBB
sparse-switch v0, :sswitch_data_4a
.line 30
:goto_9 //函数结束
return-void
//------------分割线-----------
.line 10
:pswitch_a
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v1, v3}, Ljava/io/PrintStream;->println(I)V
.line 12
:pswitch_f
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/4 v2, 0x1
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V
.line 14
:pswitch_15
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/4 v2, 0x2
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V
.line 16
:pswitch_1b
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/4 v2, 0x3
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V
goto :goto_6
//------------分割线-----------
.line 21
:sswitch_22
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v0, v3}, Ljava/io/PrintStream;->println(I)V
.line 23
:sswitch_27
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/16 v1, 0x20
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(I)V
.line 25
:sswitch_2e
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/16 v1, 0x40
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(I)V
.line 27
:sswitch_35
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/16 v1, 0x80
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(I)V
goto :goto_9
//------------分割线-----------
.line 8
nop
:pswitch_data_3e //第一个矩阵
.packed-switch 0x0 //初始值为0,依次递增
:pswitch_a
:pswitch_f
:pswitch_15
:pswitch_1b
.end packed-switch
.line 19
:sswitch_data_4a //第二个矩阵
.sparse-switch //没有规定初始值,而是每个值对应相应的代码
0x0 -> :sswitch_22
0x20 -> :sswitch_27
0x40 -> :sswitch_2e
0x80 -> :sswitch_35
.end sparse-switch
.end method

switch 指令格式

1
2
packed-switch vAA, +BBBBBBBB
sparse-switch vAA, +BBBBBBBB

AA 表示要判断的值
BBBB 表示相应 switch-payload 的偏移

两种 switch-payload 的格式

(1) packed-switch-payload format

Name Format Description
ident ushort = 0x0100 固定值
size ushort 入口的个数
first_key int 第一个switch的值(最小的)
targets int[] 每个case相对于switch指令的偏移,而非此矩阵

(2) sparse-switch-payload format

Name Format Description
ident ushort = 0x0200 固定值
size ushort 入口的个数
keys int[] case的值,从低到高
targets int[] 每个case相对于switch指令的偏移,而非此矩阵

验证

官方文档可得
packed-switch 的 opcode 为 2bsparse-switch 的 opcode 为 2c .
在16进制编辑器中搜索 2b,可得两条 switch 的位置(0x146, 0x14c),从 smali 代码中也能看出他们是相邻的。(当然这一步可用ida直接搜索packed-swtch更方便)

1
2
00000140 .. .. .. .. .. .. 2b 00 3b 00 00 00 2c 00 44 00 |......+.;...,.D.|
00000150 00 00

第一条翻译为:packed-switch v0,146h+2*3bh,得矩阵的偏移为 0x1bc.

1
2
3
000001b0 .. .. .. .. .. .. .. .. .. .. .. .. 00 01 04 00 |..n ....(.......|
000001c0 00 00 00 00 07 00 00 00 0c 00 00 00 12 00 00 00 |................|
000001d0 18 00 00 00

ident: 0x0100
size: 0x04
first_key: 0x0
targets: 0x07, 0x0c, 0x12, 0x18
如:0x146+2*0x07=0x154 即为 case 0 对应的代码地址。

第二条翻译为:sparse-switch v0,14ch+2*44h,得矩阵的偏移为 0x1d4.

1
2
3
000001d0 .. .. .. .. 00 02 04 00 00 00 00 00 20 00 00 00 |............ ...|
000001e0 40 00 00 00 80 00 00 00 1c 00 00 00 21 00 00 00 |@...........!...|
000001f0 28 00 00 00 2f 00 00 00

ident: 0x0200
size: 0x04
keys: 0x00, 0x02, 0x04, 0x08
targets: 0x1c, 0x21, 0x28, 0x2f
如:0x14c+2*0x1c=0x184 即为 case 0 对应的代码地址。


try

java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.*;
class HeyTry
{
public static void func()
{
try{
int a = 10/0;
}
catch(ArithmeticException e){ //共有三个这样的异常
System.out.println("zero error.");
}
try{
int a = 0;
int b = 10/a;
}
catch(ArithmeticException e){
System.out.println("zero error.");
}
catch(Exception e){
System.out.println(e);
}
try{
int a = 0;
try{
int b = 10/a;
}
catch(ArithmeticException e){
System.out.println("zero error.");
}
int[] ar = {0,0};
int c = ar[2];
}
catch(Exception e){
System.out.println(e);
}
}
}

smali 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
.class LHeyTry;
.super Ljava/lang/Object;
.source "HeyTry.java"
# direct methods
.method constructor <init>()V
.registers 1
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static func()V
.registers 2
.prologue
.line 8
const/16 v0, 0xa
:try_start_2 //try开始
div-int/lit8 v0, v0, 0x0
:try_end_4 //try结束,紧跟着的是catch
.catch Ljava/lang/ArithmeticException; {:try_start_2 .. :try_end_4} :catch_18
.line 15
:goto_4
const/4 v0, 0x0 //原本是try内部的常量定义被挪到了外面
.line 16
const/16 v1, 0xa
:try_start_7
div-int v0, v1, v0
:try_end_9
.catch Ljava/lang/ArithmeticException; {:try_start_7 .. :try_end_9} :catch_21
.catch Ljava/lang/Exception; {:try_start_7 .. :try_end_9} :catch_2a
.line 26
:goto_9
const/4 v0, 0x0
.line 28
const/16 v1, 0xa
:try_start_c
div-int v0, v1, v0
:try_end_e //原本是内外层关系的两个catch被放在了一起
.catch Ljava/lang/ArithmeticException; {:try_start_c .. :try_end_e} :catch_31
.catch Ljava/lang/Exception; {:try_start_c .. :try_end_e} :catch_3a
.line 34
:goto_e
const/4 v0, 0x2
:try_start_f
new-array v0, v0, [I
fill-array-data v0, :array_42 //fill-array-data-payload
.line 35
const/4 v1, 0x2
aget v0, v0, v1
:try_end_17
.catch Ljava/lang/Exception; {:try_start_f .. :try_end_17} :catch_3a
.line 40
:goto_17
return-void
.line 10
:catch_18 //同样的异常代码没有被优化
move-exception v0
.line 11
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "zero error."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
goto :goto_4
.line 18
:catch_21
move-exception v0
.line 19
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "zero error."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
goto :goto_9
.line 21
:catch_2a
move-exception v0
.line 22
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
goto :goto_9
.line 30
:catch_31
move-exception v0
.line 31
:try_start_32
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "zero error."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
:try_end_39
.catch Ljava/lang/Exception; {:try_start_32 .. :try_end_39} :catch_3a
goto :goto_e
.line 37
:catch_3a
move-exception v0
.line 38
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
goto :goto_17
.line 34
nop
:array_42 //fill-array-data-payload
.array-data 4
0x0
0x0
.end array-data
.end method

格式

try 代码块以两个标号为界,:try_start_数字:try_end_数字,(不晓得这一段的数字为什么没有规律)。
紧接着是 catch 的格式:
.catch 异常类 {try起始标号 .. try结束标号} catch代码标号
java 并没有对重复的 catch 代码进行优化,但是变量的定义被挪到了 try 之外。
嵌套的 try-catch 被分成了单个的 try-catch .

try 在 dex 中的存在

关于try-catch是如何在dex文件中存在的参考解析 dex 文件结构 - 索引区和数据区(三) - ClassDefs .

下面是手动查找:

前期准备:
classDefsSize: 0x1
classDefsOff: 0x128
directMethodsSize: 0x2
func函数的codeOff: 0x160
DexCode.triesSize: 0x05
try-catch 地址: 0x204,数据如下:

1
2
3
4
00000200 .. .. .. .. 02 00 00 00 02 00 01 00 07 00 00 00 |................|
00000210 02 00 04 00 0c 00 00 00 02 00 09 00 0f 00 00 00 |................|
00000220 08 00 0e 00 32 00 00 00 07 00 0e 00 04 01 02 18 |....2...........|
00000230 02 02 21 03 2a 02 02 31 03 3a 01 03 3a

首先是0x5个DexTry,

startAddr insnCount handlerOff 对应的异常和handler(由下表所得)
0x02 0x02 0x01 0号handler: 抓到 ArithmeticException
0x07 0x02 0x04 1号handler: 抓到 ArithmeticException和Exception
0x0c 0x02 0x09 2号handler: 抓到 ArithmeticException和Exception
0x0f 0x08 0x0e 3号handler: 抓到 Exception
0x32 0x07 0x0e 3号handler: 抓到 Exception

[]这里的handleroff是以handler_list为起始的偏移

catch_handler列表个数: 0x04
下面是0x04个catch_handler:

序号 size typeId addr catch_all_addr
0x0 0x01 0x02(java.lang.ArithmeticException) 0x18 -
0x1 0x02 0x02(java.lang.ArithmeticException) 0x21 -
- - 0x03(java.lang.Exception) 0x2a -
0x2 0x02 0x02(java.lang.ArithmeticException) 0x31 -
- - 0x03(java.lang.Exception) 0x3a -
0x03 0x01 0x03(java.lang.Exception) 0x3a -

下面是使用 dexdump 的数据:
可用来验证结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Processing 'HeyTry.dex'...
Opened 'HeyTry.dex', DEX version '035'
Class #0 -
Class descriptor : 'LHeyTry;'
Access flags : 0x0000 ()
Superclass : 'Ljava/lang/Object;'
Interfaces -
Static fields -
Instance fields -
Direct methods -
#0 : (in LHeyTry;)
name : '<init>'
type : '()V'
access : 0x10000 (CONSTRUCTOR)
code -
registers : 1
ins : 1
outs : 1
insns size : 4 16-bit code units
catches : (none)
positions :
0x0000 line=3
locals :
0x0000 - 0x0004 reg=0 this LHeyTry;
#1 : (in LHeyTry;)
name : 'func'
type : '()V'
access : 0x0009 (PUBLIC STATIC)
code -
registers : 2
ins : 0
outs : 2
insns size : 74 16-bit code units //共74条指令
catches : 5 //五个catch
0x0002 - 0x0004 //startAddr + insnCount
Ljava/lang/ArithmeticException; -> 0x0018
0x0007 - 0x0009
Ljava/lang/ArithmeticException; -> 0x0021
Ljava/lang/Exception; -> 0x002a
0x000c - 0x000e
Ljava/lang/ArithmeticException; -> 0x0031
Ljava/lang/Exception; -> 0x003a
0x000f - 0x0017
Ljava/lang/Exception; -> 0x003a
0x0032 - 0x0039
Ljava/lang/Exception; -> 0x003a
positions :
0x0000 line=8
0x0004 line=15
0x0005 line=16
0x0009 line=26
0x000a line=28
0x000e line=34
0x0014 line=35
0x0017 line=40
0x0018 line=10
0x0019 line=11
0x0021 line=18
0x0022 line=19
0x002a line=21
0x002b line=22
0x0031 line=30
0x0032 line=31
0x003a line=37
0x003b line=38
0x0041 line=34
locals :
Virtual methods -
source_file_idx : 1 (HeyTry.java)