$2.3、我就是反编译器,走进字节码世界

按照我们上篇生成的字节码文件解析与组成部分,现在我们要挨个读取里面的内容,真正走进字节码文件的世界

模数:四个字节(1个字节占8位,其中16进制刚好4位代表,因此2个16进制就是代表1个字节)CAFE BABE,Java之父定义的固定值,没有啥好说的。

版本号:00 00 00 34,代表的版本号就是52,其中次版本号没有,JDK1.8对应的主版本号就是52,JDK1.7就是51,依次递减

常量池信息(u2个数+常量表n)00 18代表的个数是 24-1=23个,因此后续的常量表只要算对应的23个常量表即可

常量表(u1代表的都是tag信息,具体tag所代表的常量类型则是由规范定义):

  1. 0A代表的十进制是10,后续有2个常量信息,分别的长度是u2,00 04,00 14而且根据规范所代表的都指向常量索引,分别是4号和20号索引,具体表示的内容为什么方法的类描述信息、名称和类型的信息,我们从idea验证中可以得到:显然我们翻译过来应该就是object类的构造方法。
  2. 09 00 03 00 15-->代表tag等于9对应的描述,分别指向3号和21号索引,代表的意思什么字段的类或者接口信息、字段的描述符信息;其实就是字段所属的类或者接口,同时字段本身的名称和类型,我们查看下对应的2号常量信息见名思意,字段索引,所属的类是jp.zhang.bytecode.MyTest1,对应的名称是a,对应的类型是Int类型。
  3. 07 00 16-->代表tag是等于7,后面只有一个u2信息代表指向全限定名的常量索引,所指向的常量号为22,代表的就是类的全限定名(类路径)
  4. 07 00 17-->跟以上类似,只是指向的常量池号为23
  5. 01 00 01 61 -->读取tag为1的常量信息,一个u2代表的是utf-8字符串的长度,后续的u1代表的长度对应的字符串,16进制61代表的字符串就是a
  6. 01 00 01 49-->跟上述类似,49代表的就是I
  7. 01 00 06 3C 69 6E 69 74 3E-->因为代表的长度是6位,因此后续读取6位,那么对应的字符串信息就是<init>方法,这个方法代表的就是类的空参构造方法
  8. 后续的常量池信息以此类推了,根据tag的值和相对应的读取对应的字节数长度即可。

类的访问困控制权限(u2长度):00 21-->它是由对应的权限并集产生,因此是由

跟我们IDEA验证的对比(jdk1.0.2之后acc_super都必须为真)

类名(u2):00 03,指向3号常量,按照我们上面常量的解析,对应的3号为MyTest1

父类名(u2):00 04 按照上述的解析,对应的父类是Object

接口个数(u2):00 00,没有实现的接口

接口名(n,与接口个数有关):因为上述接口个数为0,因此该项也不存在。

字段个数(u2)00 01,代表的类中字段只有一个

字段表(n)根据上述字段的个数,依次解析对应的字段信息,其中字段表的组成为

00 02 00 05 00 06 00 00这就是字段表的具体信息,0002对应的访问权限为private,对应的字段名称是5号索引a,对应的描述信息是6号索引I,也就是Int类型,后续为0即没有别的属性数量,从这些信息中,其实就已经得知对应在代码中的表现形式了:private int a (完全是通过对字节码文件的解析的出来的)

方法个数(u2)00 03代表的是该类中存在三个方法,其实就是getA(),setA(int a)和空参构造init方法

方法表:跟字段表类似,它的方法表的组成结构为

00 01 00 07 00 08 00 01:
     方法方法对应的权限0001是public,方法对应的名称0007对应的是7号索引init(空参构造方法),0008对应的描述信息是指向8号索引()V,方法是空参的构造方法。

   接下来就是00 01代表属性的数量1,接下来的u2为00 09索引针对的是code,此时的意思为:该方法属性对应的attribute_info是code,因此按照code属性结构进行解析,在类、字段、方法中都有attribute_info,针对不同的attribute_name_info指向不同的具体属性,因此在不同的地方,对不同的属性解析不同(jvm目前自带21个属性,也可以用户自定义)

接下来的u4是00 00 00 38代表属性的长度是56,意为该属性信息占位56个字节

00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1

最大操作数栈stack是2;最大局部变量长度为1(默认的空参构造方法不是变量吗?这是因为在普通方法中,默认有个隐式变量:this),code的长度为10(是指往后10位代表了方法的具体内容),通过16进制转换对应的助记符:2A B7 00 01 2A 04 B5 00 02 B1wei

你问我怎么知道是这样的对应关系?。。。这个只能找jvm规范文档:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.aload_n

继续:00 00异常个数0,所以也就没有异常表; 00 02 属性个数2个,下面又是找attribute_info

对应的属性00 0A 索引10,对应的是LineNumberTable(字节码行号与源码行号对应关系),好了进入LineNumberTable的属性结构吧

00 00 00 0A 属性长度10,00 02 行号表个数,后面的line_number_table[2]代表分成2组,每一组u2进行配对,就00 00: 00 07; 00 04 :00 09(0对7,4对9)

00 0B 00 00 00 0C继续第二个属性,继续查看attribute_info信息:u2的0B对应的index是LocalVariableTable(局部变量表),代表后续进入局部变量表的结果中,长度为0c代表12个,意为后续属性值的长度为12,

00 01 00 00 00 0A 00 0C 00 0D 00 00

00 01代表局部变量表的长度,即里面有几个局部变量,当前只有1个

00 代表:start_pc=0,length=0A=10,name_idex=0C对应的常量池#12 = Utf8 this,description_index=0D对应常量池

  #13 = Utf8               Ljp/zhang/bytecode/MyTest1;类对象本身

最后的index=00即为0,这就是整个init方法
 

我们根据jclasslib插件进行验证

后续的方法依次如此。。。

最后的结构:附加属性的个数

00 01 00 12 00 00 00 02 00 13代表有一个附加属性,00 12代表的index是sourceFile,即为源文件属性,找到源文件属性的结构

长度为02就后面2个字节是属性值内容,13对应的常量池是19 = Utf8               MyTest1.java,即说明这个class文件对应的源码文件是MyTest1.java

至此,我们将简单的java字节码文件全部“反编译”完成,是不是很有成就感,但是,源码中的代码还是很简单的,没有牵扯到异常final常量等信息,所以:路漫漫其修远兮。。。

发布了12 篇原创文章 · 获赞 1 · 访问量 373

猜你喜欢

转载自blog.csdn.net/weixin_39800596/article/details/103644519
今日推荐