.class文件的解析

287 阅读12分钟

.class文件结构表如下图:


魔数

CAFE BABE -> 固定值如果不是这个虚拟机会拒绝执行


次版本号

00 00 -> 0x00 -> 10进制结果就是0


主版本号

00 34 -> 0x34 -> 10进制结果就是52
查表得到结果 java主版本就是1.8


常量池大小

00 13 -> 0x13 -> 10进制结果就是19 但是需要减去1

得到常量池大小18个

可通过命令javap -verbose Demo.class查看得到结果

下面是常量池结果

Constant pool:
    #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
    #2 = Fieldref           #3.#16         // com/neo/asmtest/Bytecode/Demo.age:I
    #3 = Class              #17            // com/neo/asmtest/Bytecode/Demo
    #4 = Class              #18            // java/lang/Object
    #5 = Utf8               age
    #6 = Utf8               I
    #7 = Utf8               <init>
    #8 = Utf8               ()V
    #9 = Utf8               Code
   #10 = Utf8               LineNumberTable
   #11 = Utf8               getAge
   #12 = Utf8               ()I
   #13 = Utf8               SourceFile
   #14 = Utf8               Demo.java
   #15 = NameAndType        #7:#8          // "<init>":()V
   #16 = NameAndType        #5:#6          // age:I
   #17 = Utf8               com/neo/asmtest/Bytecode/Demo
   #18 = Utf8               java/lang/Object

下面开始分析具体的常量需要用到如下常量表

具体结构如下图
描述符对照表如下图


常量池

常量1

0A 就是0x0A 10进制就是10 查表得到类型是CONSTANT_Methodref_info

它的具体结构是:

CONSTANT_Methodref_info{
	u1 tag;
	u2 class_index;
	u2 name_and_type_index;
}

所以这个常量的所有字节码就是

0A 00 04 00 0F

通过结构可知:

tag = 0x0A = 10

class_index = 0x04 = 4 -> 指向第4常量 -> 指向第18常量 -> java/lang/Object

name_and_type_index = 0x0F = 15 -> 指向第15常量 -> <init> ()V

此常量表示:

java/lang/Object."<init>":()V


常量2

09 就是0x09 10进制就是9 查表得到类型CONSTANT_Fieldref_info

它的具体结构是:

CONSTANT_Fieldref_info{
    u1 tag;
    u2 class_index;
    u2 name_and_type_index
}

所以这个常量的所有字节码就是

09 00 03 00 10

通过结构可知:

tag = 0x09 = 9

class_index = 0x03 = 3 -> 指向第3常量 -> 指向第17常量 -> com/neo/asmtest/Bytecode/Demo

name_and_type_index = 0x10 = 16 -> 指向第16常量 -> age:I

此常量表示:

com/neo/asmtest/Bytecode/Demo.age:I


常量3

07 就是0x07 10进制就是7 查表得到类型CONSTANT_Class_info

它的具体结构是:

CONSTANT_Class_info{
    u1 tag;
    u2 name_and_type_index
}

所以这个常量的所有字节码就是

07 00 11

通过结构可知:

tag = 0x07 = 7

name_and_type_index = 0x11 = 17 -> 指向第17常量 -> com/neo/asmtest/Bytecode/Demo

此常量表示:

com/neo/asmtest/Bytecode/Demo


常量4

07 就是0x07 10进制就是7 查表得到类型CONSTANT_Class_info

它的具体结构是:

CONSTANT_Class_info{
    u1 tag;
    u2 name_and_type_index
}

所以这个常量的所有字节码就是

07 00 12

通过结构可知:

tag = 0x07 = 7

name_and_type_index = 0x12 = 18 ->指向第18常量 -> java/lang/Object


常量5

01 就是0x01 10进制就是1 查表得到类型CONSTANT_utf8_info

它的具体结构是:

CONSTANT_utf8_info{
    u1 tag;
    u2 length;
    length bytes[];
}

所以这个常量的所有字节码就是

01 00 03 61 67 65

通过结构可知:

tag = 0x01 = 1

length = 0x03 = 3 有3个utf8字节

byets = 0x61 0x67 0x65 -> age


常量6

01 00 01 49

通过结构可知:

tag = 0x01 = 1

length = 0x01 = 1 有1个utf8字节

byets = 0x49 -> I


常量7

01 00 06 3C 69 6E 69 74 3E

通过结构可知:

tag = 0x01 = 1

length = 0x06 = 6 有6个utf8字节

byets = 0x3C 0x69 0x6E 0x69 0x74 0x3E -> <init>


常量8

01 00 03 28 29 56

通过结构可知:

tag = 0x01 = 1

length = 0x03 = 3 有3个utf8字节

byets = 0x3C 0x28 0x29 0x56 -> ()V


常量9

01 00 04 43 6F 64 65

通过结构可知:

tag = 0x01 = 1

length = 0x04 = 4 有4个utf8字节

byets = 0x43 0x6F 0x64 0x65 -> Code


常量10

01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65

通过结构可知:

tag = 0x01 = 1

length = 0x0F = 15 有15个utf8字节

byets = 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 -> LineNumberTable


常量11

01 00 06 67 65 74 41 67 65

通过结构可知:

tag = 0x01 = 1

length = 0x06 = 6 有6个utf8字节

byets = 67 65 74 41 67 65 -> getAge


常量12

01 00 03 28 29 49

通过结构可知:

tag = 0x01 = 1

length = 0x03 = 3 有3个utf8字节

byets = 28 29 49 -> ()I


常量13

01 00 0A 53 6F 75 72 63 65 46 69 6C 65

通过结构可知:

tag = 0x01 = 1

length = 0x0A = 10 有10个utf8字节

byets = 53 6F 75 72 63 65 46 69 6C 65 -> SourceFile


常量14

01 00 09 44 65 6D 6F 2E 6A 61 76 61

通过结构可知:

tag = 0x01 = 1

length = 0x09 = 9 有9个utf8字节

byets = 44 65 6D 6F 2E 6A 61 76 61 -> Demo.java

常量15

0C 就是0x0C 10进制就是12 查表得到类型CONSTANT_NameAndType_info

它的具体结构是:

CONSTANT_NameAndType_info{
    u1 tag;
    u2 name_index;//指向该方法或字段名称常量项的索引
    u2 type_index;//指向其字段或方法描述符的常量索引
}

所以这个常量的所有字节码就是

0C 00 07 00 08

通过结构可知:

tag = 0x0C = 12

name_index = 0x07 = 7 -> 指向常量7 -> <init>

type_index = 0x08 = 8 -> 指向常量8 -> ()V -> 基本数据类型void

()->方法的参数列表 V 返回值Void

此变量表示没有形参且返回值为void的构造函数 ->

没有定义构造函数的时候,Java会自动帮我们生成一个无参构造函数


常量16

0C 00 05 00 06

通过结构可知 : tag = 0x0C = 12

name_index = 0x05 = 5 -> 指向常量5 -> age

type_index = 0x08 = 6 -> 指向常量6 -> I -> 基本数据类型int

此变量表示 int 类型的 age 参数


常量17

01 00 1D 63 6F 6D 2F 6E 65 6F 2F 61 73 6D 74 65 73 74 2F 42 79 74 65 63 6F 64 65 2F 44 65 6D 6F

通过结构可知

tag = 0x01 = 1

length = 0x1D = 29 有29个utf8字节

byets = 63 6F 6D 2F 6E 65 6F 2F 61 73 6D 74 65 73 74 2F 42 79 74 65 63 6F 64 65 2F 44 65 6D 6F -> com/neo/asmtest/Bytecode/Demo


常量18

01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74

通过结构可知

tag = 0x01 = 1

length = 0x10 = 16 有16个utf8字节

byets = 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 -> java/lang/Object


常量池分析结束.~~~


访问标识

00 21 -> 标示表示类或者接口的访问信息

见下表:

00 21 -> 0x01 + 0x20 -> ACC_SUPER + ACC_PUBLIC

可通过取操作,判断这个标识符是否被某标示符修饰。

例如:

0x21的二进制100001 -> ACC_PUBLIC的二进制1 -> 100001&1 = 1 ->是包含ACC_PUBLIC访问表示符


类索引

00 03 -> 指向该类的CONSTANT_Class常量 -> 0x0003 -> 3 -> 常量3 结果是com/neo/asmtest/Bytecode/Demo 类索引指向了该类的全限定名


父类索引

00 04 -> 指向该类的CONSTANT_Class常量 -> 0x0004 -> 4 -> 常量4 结果是java/lang/Object Demo类没有继承任何类,所以其默认的父类是Object类


接口计数器

00 00 -> 表示该类实现了几个接口,即implements了几个接口。由于Demo没有实现接口,其值为0x00 00,转换为十进制也为0。


接口索引集合

接口索引集合是一个集合,包含了所有实现的接口的索引,每个接口索引占用2个字节,指向常量中的接口。

由于Demo.java没有实现任何接口,所以不存在这部分的值。


字段个数

00 01 -> 0x0001 -> 之后有1个字段 -> 字段主要用来描述类或者接口中声明的变量(包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量)


字段表

结构如下:

field_info{
	u2 access_flags;//访问标志	
	u2 name_index;//字段名索引	
	u2 descriptor_index;//描述符索引	
	u2 attributes_count;//属性计数器	
	attribute_info attributes;//属性集合	
}

00 02 00 05 00 06 00 00

access_flags = 0x0002 -> 2 -> 0010 -> ACC_PRIVATE -> private

name_index = 0x00 05 -> 5 -> 指向常量5 -> age

descriptor_index = 0x00 06 -> 6 -> 指向常量6 -> I ->表示int类型

attributes_count = 0x00 00 -> 0 -> attribute_info不存在

结果:

private int a


方法计数器

00 02 -> 0x00 02 -> 2个方法

无参构造函数与getAge()


方法表

方法表结构如下:

method_info{
	u2 access_flags; //方法访问标志
	u2 name_index; //方法名称索引
	u2 descriptor_index; //方法描述符索引
	u2 attributes_count; //属性计数器	
	struct attribute_info{
		u2 attribute_name_index; //属性名的索引
		u4 attribute_length; //属性的长度
		u2 max_stack;//操作数栈深度的最大值
		u2 max_locals;//局部变量表所需的存续空间
		u4 code_length;//字节码指令的长度
		u1 code; //code_length个code,存储字节码指令
		u2 exception_table_length;//异常表长度
		exception_info exception_table;//exception_length个exception_info,组成异常表
		u2 attributes_count;//属性集合计数器
		attribute_info attributes;//attributes_count个attribute_info,组成属性表
	}
}

方法1

00 01 00 07 00 08 00 01 00

access_flags = 0x00 01 -> 1 -> public

name_index = 0x00 07 -> 7 -> 指向常量7 -> <init>

descriptor_index = 0x00 08 -> 8 ->指向常量8 -> ()V

attributes_count = 0x00 01 -> 1 -> 记录着该方法有几个属性 -> 1个属性

00 09 00 00 00 1D 00 01 00 01 00 00 00 05

attribute_name_index = 0x00 09 -> 9 -> 指向常量9 -> Code

attribute_length = 0000001D -> 29 -> 29个字节

max_stack = 0x00 01 -> 1

max_locals = 0x00 01 -> 1

code_length = 00000005 -> 5个字节

2A B7 00 01 B1

code -> 0x2A B7 00 01 B1 ->

不能转换成十进制取看类,需要把十六进制转换成指令集

->结果如下:

2A -> aload_0      //从局部变量0中装载引用类型值入栈
B7 -> invokespecia //编译时方法绑定调用方法
00 -> nop          //空操作
01 -> aconst_null  //null值入栈
B1 -> return       //void函数返回。

00 00 00 01

exception_table_length = 0x00 -> 0

这里存放的是处理异常的信息。 每个exception_table表项由start_pc,end_pc,handler_pc,catch_type组成。start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理;handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常,这个可以用来实现finally的功能

attributes_count = 0x00 01 -> 1 -> 表示有一个附加属性

attribute_info 结构如下:

attribute_info{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_tab_table_length
}
line_tab_table_length{
    u2 start_pc;
    u2 line_number;
}

00 0A 00 00 00 06 00 01 00 00 00 03

attribute_name_index = 0x00 0A -> 10 -> 指向常量10 -> LineNumberTable

attribute_length = 0x00 00 00 06-> 6个字节 -> 后面有6个字节的属性

line_tab_table_length = 0x00 01 -> 1代表LineNumberTable有一项值

start_pc = 0x00 00 -> 0,代表字节码行号

line_number = 0x 00 03 -> 3 -> 代表java源码的行号为第3行


方法2

00 01 00 0B 00 0C 00 01

access_flags = 0x00 01 -> 1 -> public

name_index = 0x00 0B -> 11 -> 指向常量11 -> getAge

descriptor_index = 0x00 0C -> 12 ->指向常量12 -> ()I

attributes_count = 0x00 01 -> 1 -> 记录着该方法有几个属性 -> 1个属性

00 09 00 00 00 1D 00 01 00 01 00 00 00 05

attribute_name_index = 0x00 09 -> 9 -> 指向常量9 -> Code

attribute_length = 0x0000001D -> 29 -> 29个字节

max_stack = 0x00 01 -> 1

max_locals = 0x00 01 -> 1

code_length = 0x00000005 -> 5个字节

2A B4 00 02 AC

code -> 0x2A B4 00 02 AC ->

不能转换成十进制取看类,需要把十六进制转换成指令集 ->结果如下:

2A -> aload_0 //从局部变量0中装载引用类型值入栈
B4 -> getfield // 获取对象字段的值
00 -> nop // 空操作
02 -> iconst_m1 //-1(int)值入栈
AC -> ireturn //返回int类型值

00 00 00 01

exception_table_length = 0x00 -> 0

attributes_count = 0x00 01 -> 1 -> 表示有一个附加属性

00 0A 00 00 00 06 00 01 00 00 00 08

attribute_name_index = 0x00 0A -> 10 -> 指向常量10 -> LineNumberTable

attribute_length = 0x00 00 00 06-> 6个字节 -> 后面有6个字节的属性

line_tab_table_length = 0x00 01 -> 1代表LineNumberTable有一项值

start_pc = 0x00 00 -> 0,代表字节码行号

line_number = 0x 00 08 -> 8 -> 代表java源码的行号为第8行


附加属性-属性个数

00 01 -> 1 -> 表示后面有1个附加属性值

附加属性-属性结构

00 0D 00 00 00 02 00 0E 结构如下

SourceFile_attribute {
     u2 attribute_name_index;
     u4 attribute_length;
     u2 sourcefile_index;
}

attribute_name_index = 0x00 0D = 13 -> 指向常量13 -> SourceFile

attribute_length = 0x00 00 00 02 = 2 -> 后面有2字节内容

sourcefile_index = 0x00 0E = 14 -> 指向常量14 -> Demo.java

代表着个class字节码文件的源码名为Demo.java


.class文件分析完毕