本文参考:《Reverse Engineering for Beginners》Dennis Yurichev著
数组
简单的例子
创建一个长度是10的整型的数组,对其初始化
public class ArrayInit {
public static void main(String[] args) {
int a[] = new int[10];
for (int i = 0; i < 10; i++)
a[i] = i;
dump(a);
}
public static void dump(int a[]) {
for (int i = 0; i < a.length; i++)
System.out.println(a[i]);
}
}
编译
javac ArrayInit.java
反编译
javap -c -verbose ArrayInit.class
main函数部分
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: bipush 10
2: newarray int
4: astore_1
5: iconst_0
6: istore_2
7: iload_2
8: bipush 10
10: if_icmpge 23
13: aload_1
14: iload_2
15: iload_2
16: iastore
17: iinc 2, 1
20: goto 7
23: aload_1
24: invokestatic #2 // Method dump:([I)V
27: return
指令解释
0: bipush 10 //将10压入栈顶
2: newarray int //将10弹出操作数栈,作为长度,创建一个元素类型为int, 维度为1的数组,并将数组的引用压入操作数栈
4: astore_1 //将数组的引用从操作数栈中弹出,保存在索引为1的局部变量(即a)中
5: iconst_0 //将0压入栈
6: istore_2 //将0从操作数栈中弹出,保存在索引为2的局部变量(即i)中
7: iload_2 //将索引为2的局部变量(即i)压入操作数栈
8: bipush 10 //将10压入操作数栈
10: if_icmpge 23 //栈顶弹出两个值,并且比较两个数值,如果第的二个值大于或等于第一个,跳转到偏移位23
13: aload_1 //将索引为1的局部变量(即a)压入操作数栈
14: iload_2 //将索引为2的局部变量(即i)压入操作数栈
15: iload_2 //将索引为2的局部变量(即i)压入操作数栈
16: iastore //栈顶弹出两个值,将栈顶int型数值存入指定数组的指定索引位置,这里都是弹出的i究竟那个是要存的数哪个是索引位置呢,经过测试,第一个弹出来的是要存的数,第二个弹出的是索引位置
17: iinc 2, 1 //将索引为2的局部变量(即i)加1
20: goto 7 //跳转到偏移位7
23: aload_1 //将索引为1的局部变量(即a)压入操作数栈
24: invokestatic #2 // Method dump:([I)V //将数组的引用a从操作数栈弹出,调用dump方法并传参
27: return //返回
再看dump函数的反编译结果
public static void dump(int[]);
descriptor: ([I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: aload_0
4: arraylength
5: if_icmpge 23
8: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: iload_1
13: iaload
14: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
17: iinc 1, 1
20: goto 2
23: return
指令解释
0: iconst_0 //将0压入栈顶
1: istore_1 //将0从操作数栈中弹出,保存在索引为1的局部变量(即i)中
2: iload_1 //将索引为1的局部变量(即i)压入操作数栈
3: aload_0 //将索引为0的局部变量(即参数a)压入操作数栈
4: arraylength //将数组引用a从栈顶弹出,计算a数组的长度值并将长度值压入栈顶
5: if_icmpge 23 //栈顶弹出两个值,并且比较两个数值,如果第的二个值大于或等于第一个,跳转到偏移位23
8: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 获得System.out的引用
11: aload_0 //将索引为0的局部变量(即a)压入操作数栈
12: iload_1 //将索引为1的局部变量(即i)压入操作数栈
13: iaload //栈顶弹出两个值,将int型数组指定索引的值推送至栈顶,这里索引为第一个弹出值,数组引用为第二个弹出值
14: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 调用println方法,需要从栈弹两个参数传值
17: iinc 1, 1 //将索引为1的局部变量(即i)加1
20: goto 2 //跳转到偏移位2
23: return //返回
数组元素的求和
例子
public class ArraySum {
public static int f(int[] a) {
int sum = 0;
for (int i = 0; i < a.length; i++)
sum = sum + a[i];
return sum;
}
}
反编译
public static int f(int[]);
descriptor: ([I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_2
5: aload_0
6: arraylength
7: if_icmpge 22
10: iload_1
11: aload_0
12: iload_2
13: iaload
14: iadd
15: istore_1
16: iinc 2, 1
19: goto 4
22: iload_1
23: ireturn
部分指令解释
10: iload_1 //将索引为1的局部变量(即sum)压入操作数栈
11: aload_0 //将索引为0的局部变量(即a,数组的引用)压入操作数栈
12: iload_2 //将索引为2的局部变量(即i)压入操作数栈
13: iaload //栈顶弹出两个值,将int型数组指定索引的值推送至栈顶,这里索引为第一个弹出值,数组引用为第二个弹出值
14: iadd //将栈顶两int型数值相加并将结果压入栈顶,即a[i]+sum
main()方法的参数作为唯一参数例子
public class UseArgument {
public static void main(String[] args) {
System.out.print("Hi, ");
System.out.print(args[1]);
System.out.println(". How are you?");
}
}
反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hi,
5: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: iconst_1
13: aaload
14: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
17: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
20: ldc #5 // String . How are you?
22: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: return
部分指令解释
11: aload_0 //将索引为0的局部变量(即arg,数组的引用)压入操作数栈
12: iconst_1 //将1压入栈顶
13: aaload //栈顶弹出两个值,将引用型数组指定索引的值推送至栈顶,这里索引为第一个弹出值,数组引用为第二个弹出值
初始化字符串数组
class Month {
public static String[] months = { "January", "February", "March", "April", "May", "June", "July", "August",
"September", "October", "November", "December" };
public String get_month(int i) {
return months[i];
};
}
反编译
get_month()函数很简单
public java.lang.String get_month(int);
descriptor: (I)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: getstatic #2 // Field months:[Ljava/lang/String;
3: iload_1
4: aaload
5: areturn
指令解释
0: getstatic #2 // Field months:[Ljava/lang/String; //获得静态变量months的引用
3: iload_1 //将索引为1的局部变量(即参数i)压入操作数栈
4: aaload //栈顶弹出两个值,将引用型数组指定索引的值推送至栈顶,这里索引为第一个弹出值,数组引用为第二个弹出值
5: areturn //栈顶弹出一个值返回
再看month[]数值是如果初始化的
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: bipush 12
2: anewarray #3 // class java/lang/String
5: dup
6: iconst_0
7: ldc #4 // String January
9: aastore
10: dup
11: iconst_1
12: ldc #5 // String February
14: aastore
15: dup
16: iconst_2
17: ldc #6 // String March
19: aastore
20: dup
21: iconst_3
22: ldc #7 // String April
24: aastore
25: dup
26: iconst_4
27: ldc #8 // String May
29: aastore
30: dup
31: iconst_5
32: ldc #9 // String June
34: aastore
35: dup
36: bipush 6
38: ldc #10 // String July
40: aastore
41: dup
42: bipush 7
44: ldc #11 // String August
46: aastore
47: dup
48: bipush 8
50: ldc #12 // String September
52: aastore
53: dup
54: bipush 9
56: ldc #13 // String October
58: aastore
59: dup
60: bipush 10
62: ldc #14 // String November
64: aastore
65: dup
66: bipush 11
68: ldc #15 // String December
70: aastore
71: putstatic #2 // Field months:[Ljava/lang/String;
74: return
部分指令解释
0: bipush 12 //将12压入栈顶
2: anewarray #3 // class java/lang/String //栈顶弹出一个值,创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶 ,数组大小由弹出值决定,数组类型由#3决定
5: dup //复制栈顶数值并将复制值压入栈顶 ,这里复制的是数组的引用
6: iconst_0 //将0压入栈顶
7: ldc #4 // String January //将字符串压入栈顶
9: aastore //栈顶弹出两个值,将栈顶引用型数值存入指定数组的指定索引位置,这里都是弹出的i究竟那个是要存的数哪个是索引位置呢,经过测试,第一个弹出来的是要存的数,第二个弹出的是索引位置
10: dup //复制栈顶数值并将复制值压入栈顶 ,这里复制的是数组的引用
可变参数
可变参数实际上就是数组
public class VarParam {
public static void f(int... values) {
for (int i = 0; i < values.length; i++)
System.out.println(values[i]);
}
public static void main(String[] args) {
f(1, 2, 3, 4, 5);
}
}
反编译
f()函数
public static void f(int...);
descriptor: ([I)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=3, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: aload_0
4: arraylength
5: if_icmpge 23
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: iload_1
13: iaload
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
17: iinc 1, 1
20: goto 2
23: return
可以看到
3: aload_0 //将索引为0的局部变量(即参数int... values这个数组的引用)压入操作数栈
再看main()的反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=1
0: iconst_5
1: newarray int
3: dup
4: iconst_0
5: iconst_1
6: iastore
7: dup
8: iconst_1
9: iconst_2
10: iastore
11: dup
12: iconst_2
13: iconst_3
14: iastore
15: dup
16: iconst_3
17: iconst_4
18: iastore
19: dup
20: iconst_4
21: iconst_5
22: iastore
23: invokestatic #4 // Method f:([I)V
26: return
可以看到数组是在main()中用newarray指令构造的,填充完整个数组之后调用f()
随便提一句,数组对象并不是在main()中销毁的,在整个java中也没有被析构。因为JVM的垃圾收集齐不是自动的,当他感觉需要的时候。
再看一个 format()方法
方法定义
public PrintStream format(String format, Object... args)
它接收两个参数,一个是格式,另一个是对象数组
看一个例子
public class format {
public static void main(String[] args) {
int i = 123;
double d = 123.456;
System.out.format("int: %d double: %f.%n", i, d);
}
}
反编译
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=7, locals=4, args_size=1
0: bipush 123
2: istore_1
3: ldc2_w #2 // double 123.456d
6: dstore_2
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String int: %d double: %f.%n
12: iconst_2
13: anewarray #6 // class java/lang/Object
16: dup
17: iconst_0
18: iload_1
19: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: aastore
23: dup
24: iconst_1
25: dload_2
26: invokestatic #8 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
29: aastore
30: invokevirtual #9 // Method java/io/PrintStream.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
33: pop
34: return
这里的中文翻译估计是机器翻的,就简单解释一下指令意思
0: bipush 123 //将123压入栈顶
2: istore_1 //栈顶弹出存入本地变量数组1号元素
3: ldc2_w #2 // double 123.456d //将123.456d压入栈顶
6: dstore_2 //栈顶弹出存入本地变量数组2号元素
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; //获得System.out的引用压入栈顶
10: ldc #5 // String int: %d double: %f.%n //将字符串int: %d double: %f.%n压入栈顶
12: iconst_2 //将2压入栈顶
13: anewarray #6 // class java/lang/Object //栈顶弹出2,构造个数为2的对象数组,将数组对象的引用压入栈顶
16: dup //复制栈顶数值并将复制值压入栈顶 ,这里复制的是数组的引用
17: iconst_0 //将0压入栈顶
18: iload_1 //载入第1个参数即i,压入栈
19: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; //将栈顶的i弹出,调用Integer.valueOf,返回int值压入栈
22: aastore //将栈顶引用型数值存入指定数组的指定索引位置,这里弹出了三个值,分别是i值,0,数组的引用,将i存入数组0位置
23: dup //复制栈顶数值并将复制值压入栈顶 ,这里复制的是数组的引用
24: iconst_1 //将1压入栈顶
25: dload_2 //载入第2个参数即d,压入栈
26: invokestatic #8 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; //将栈顶的d弹出,调用Integer.valueOf,返回Double值压入栈
29: aastore //将栈顶引用型数值存入指定数组的指定索引位置,这里弹出了三个值,分别是d值,1,数组的引用,将d存入数组1位置
30: invokevirtual #9 // Method java/io/PrintStream.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; //调用format方法,这里弹出了三个值
数组的引用,字符串int: %d double: %f.%n,System.out的引用,调用后返回PrintStream类型的对象到栈顶
33: pop 弹出栈顶
34: return 返回
二维数组
例子
public class twoDArray {
public static void main(String[] args) {
int[][] a = new int[5][10];
a[1][2] = 3;
}
public static int get12(int[][] in) {
return in[1][2];
}
}
反编译
main方法
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: iconst_5
1: bipush 10
3: multianewarray #2, 2 // class "[[I"
7: astore_1
8: aload_1
9: iconst_1
10: aaload
11: iconst_2
12: iconst_3
13: iastore
14: return
指令解释
0: iconst_5 //将5压入栈顶
1: bipush 10 //将2压入栈顶
3: multianewarray #2, 2 // class "[[I" //创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶,#2的值为"[[I"指定类型,后面的2指定维数是二维,5和10已经压入栈顶,调用这个的时候5和10会先出栈,指定1维和2维的长度
7: astore_1 //将二维数组的引用,保存在索引为1的局部变量(即a)中
8: aload_1 //将索引为1的局部变量(即a)压入操作数栈
9: iconst_1 //将1压入栈顶
10: aaload //栈顶弹出两个值,将引用型数组指定索引的值推送至栈顶,这里索引为第一个弹出值1,数组引用为第二个弹出值a,结果是将a[1]这个数组引用送入栈顶
11: iconst_2 //将2压入栈顶
12: iconst_3 //将3压入栈顶
13: iastore //栈顶弹出两个值,将栈顶int型数值存入指定数组的指定索引位置,这里都是弹出的i究竟那个是要存的数哪个是索引位置呢,经过测试,第一个弹出来的是要存的数3,第二个弹出的是索引位置3,原始数组是a[1],即将3存入a[1]的第2位置,即将3存入a[1][2]
14: return
从上面看出访问二维的要先获取1维的引用放到栈顶再操作
get12方法
public static int get12(int[][]);
descriptor: ([[I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: iconst_1
2: aaload
3: iconst_2
4: iaload
5: ireturn
指令解释
0: aload_0 //将索引为0的局部变量(即参数in,是一个二维数组的引用)压入操作数栈
1: iconst_1 //将1压入栈顶
2: aaload //栈顶弹出两个值,将引用型数组指定索引的值推送至栈顶,这里索引为第一个弹出值1,数组引用为第二个弹出值in数组引用,结果是将a[1]这个数组引用送入栈顶
3: iconst_2 //将2压入栈顶
4: iaload //栈顶弹出两个值,将int型数组指定索引的值推送至栈顶,这里索引2为第一个弹出值,数组引用a[1]为第二个弹出值,即将a[1][2]的值送入栈顶
5: ireturn //返回栈顶的值
三维数组
public class threeDArray {
public static void main(String[] args) {
int[][][] a = new int[5][10][15];
a[1][2][3] = 4;
get_elem(a);
}
public static int get_elem(int[][][] a) {
return a[1][2][3];
}
}
反编译
main()方法
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: iconst_5
1: bipush 10
3: bipush 15
5: multianewarray #2, 3 // class "[[[I"
9: astore_1
10: aload_1
11: iconst_1
12: aaload
13: iconst_2
14: aaload
15: iconst_3
16: iconst_4
17: iastore
18: aload_1
19: invokestatic #3 // Method get_elem:([[[I)I
22: pop
23: return
它用了两个aaload去找一维和二维的引用
get_elem方法
public static int get_elem(int[][][]);
descriptor: ([[[I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: iconst_1
2: aaload
3: iconst_2
4: aaload
5: iconst_3
6: iaload
7: ireturn
get_elem方法也使用了两个aaload去找一维和二维的引用
在java中可能出现栈溢出吗?不可能,数组长度实际就代表有多少个对象,数组的边界是可控的,而发生越界访问的情况时,会抛出异常
转载于:https://blog.51cto.com/7317859/2105841