java逆向数组_Java逆向基础之数组

本文通过实例分析了Java数组的创建、初始化、元素求和以及多维数组的反编译过程,详细解读了字节码指令,包括newarray、aaload、iastore等,帮助读者深入理解Java数组的内部工作原理。
摘要由CSDN通过智能技术生成

本文参考:《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 

a[i] = i;

dump(a);

}

public static void dump(int a[]) {

for (int i = 0; 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 

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 

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中可能出现栈溢出吗?不可能,数组长度实际就代表有多少个对象,数组的边界是可控的,而发生越界访问的情况时,会抛出异常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值