字节码与类的加载(笔记1)
2024-1-5 00:3:3 Author: 白帽子(查看原文) 阅读量:2 收藏

字节码是什么?

源代码通过编译器编译之后生成一个字节码文件,字节码是一种二进制的类文件,它的内容是JVM的指令,而不是C/C++经由编译器直接生成机器码。

什么是字节码指令(Byte code) ?

Java虚拟机由一个字节长度的,代表着某种特定操作含义的操作码(opcode) 以及跟随其后的0至多个代表此操作所需参数的操作数。

虚拟机中许多指令并不包含操作数 只有一个操作码。

操作码 + 操作数 = 字节码指令

解析Class文件的三种方式

方式一

JClassLib插件,在Idea中安装JClassLib插件即可。

方式二

使用JDK自带的Javap工具来进行解析字节码文件。

javap -v xxx.class

方式三

使用Binary Editor 插件,这个解析出来的字节码是最原生的状态。

Class文件解析

Class文件的本质

任何一个Class文件都对应着唯一的类或接口,但反过来说Class文件实际上它并不是以磁盘文件的形式存在。也就是说可能是从网络中传输的。

Class文件的格式

Class的结构不像XML等描述语言,由于它没有任何分割符号,所以在其中的数据项,无论是字节顺序还是数量,都是被严格规定的,那个字节代表什么含义,长度是多少,先后顺序如何,都不允许被改变。

字节码解析
1.魔数

CA FE BA BE 表示魔数,其实意思就是Java Class文件的一个标识,主要目的是为了让Java虚拟机识别,并且安全。让Java虚拟机觉得你这个Class文件是可以识别的。

使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意改动。

2.Class文件版本号

紧接着魔数后面的4个字节存储的是Class的版本号,同样也是4个字节,第五个字节和第二个字节表示编译的副版本号minor_version,而第7个和第八个字节就是编译的主版本号。

16进制的34转换为10进制就是52可以对应下图的表进行对应。

注意:

不同版本的Java编译器编译的Class文件对应的版本是不一样的,目前,高版本的java虚拟机可以执行由低版本编译器生成的Class文件,但是低版本的Java虚拟机不能执行高版本编译器生成的Class文件,否则会抛出异常: java.lang.UnsupportedClassVersionError

常量池
常量池计数器

常量池中存储着Class文件中的字段和方法。

在版本号之后,紧跟着的是常量池的数量以及若干个常量池表项。

常量池中常量的数量是不固定的,所以需要再常量池的入口需要放置一项u2类型的无符号数,代表常量池容量计数值,也就是常量池计数器。

常量池表中,用于存放编译时期生成的各种字面量以及符号引用,这部分内容将会在类加载之后进入方法区的的运行时常量池。

比如常量池中有十项,那么常量池计数器是不是就是10?那显然是不是的。

例如如果我们常量池计数器为1 那么常量池中是没有项的,他是有一个偏差的。

比如如下例子:

可以看到常量池计数器的值为22,但是22是十六进制的,它对应的十进制就是34。那么常量池计数器的值为34,常量池中就有33项

常量池表

常量池表中存储着两大类量,字面量和符号引用。

它包含class文件结构以及子结构引用的所有字符串常量,类或接口名,字段名,和其他常量。常量池中的每一项都具备想通的特征。

第一个字节作为类型标记,用于确定该项的格式,他这个字节称为tag byte。

例如OA,这里的十进制就是10,那么10代表什么呢?如下:

那么我们可以通过对照表得知,在10这个标识他表示CONSTANT_Methodref_info 类中方法的符号引用,所以我们就可以通过第一个表示来确定这一块的内容是什么。

字面量和符号引用

字面量比如: String str = "hello"   这里str就是一个字面量 存放在常量池表中。

符号引用:类和接口的全限定类名,字段的名称,方法的名称等等。比如com/nanchensec/ 这样就是全限定名

名称就是方法的名字和属性的名字。

描述符就是方法的参数列表中的数量 类型 以及顺序和返回值等。

常量池中主要存放字面量和符号引用

他表示的就是如下图:

常量池解析

这里从0A开始进行解析,0A的十进制是10,10表示方法的引用。他占用5个字节所以到了09。

09表示Fieldref_info也就是方法引用,他也是5个字节所以到了08。

08表示String_info 就是字符串类型字面量 占用3个字节 所以到了0A。

0A表示方法的引用,他占用5个字节所以到了07。

07表示类和接口的符号引用他占用3个字节,所以到了07。

07还是一样占用3个字节,所以来到了01。

01很特殊他表示字符串,第一个u1表示他的值为1,第二个u2表示字符串所占用的长度。

那么我们来看:这里01表示字符串 00 和 06表示字符串的长度,也就是说他的长度为6

所以这里的01 00 06 3C 69 6E 69 74 3E 表示的就是我们的字符串。这里的06表示的是我们字符串的长度,所以需要划分三个u1来表达。

后面也是一样的字符串。

01 00 03 28 29 56 表示这个字符串

后面依旧对照表进行解析。

常量类型和结构表

常量池表数据解析

经过我们上面的分析和使用jclasslib可视化工具查看可以看到是一一对应的,都是我们解析的。

那我们从0A 00 06 00 14开始分析吧。

06表示06这个索引项,06的索引项是Class_info 06索引项又需要去找27索引项,所以拿到字符串字面量为:java/lang/Object

14的十进制的20 20表示20的索引项,20是NameAndType_info。

其实最后指向还是字符串面量。

09 00 15 00 16 解析:

这里的09表示的就是第9项,00 15 表示一个u2 00 16 表示一个u2 ,这里的u2就是他所对应的意思。

可以参考类似这样的表: 这里看到这里的tag的值代表的就是几项,我们上面的09表示第九项,也就是CONSTANT_Fieldref_info

那么我们第一个u2 也就是00 15 表示只想声明字段的类或者接受描述符。

第二个u2表示的就是指向字段描述符CONSTANT_NameAndType_info的索引项。

那么00 15这个u2,15的十进制就是21,所以我们需要找到21项。

那么21项表示的是CONSTANT_Class_info这个项,对照如上表他的值为7。也可以使用jclassLib来查看。

我们可以看到CONSTANT_Class_info这个项,他又指向了十进制的28项,而28就是我们的字符串字面量,他的值为:java/lang/System

那么继续解析00 16 ,16的十进制是22,所以我们需要找到22的索引项。

22的索引项又找到了29 和 30索引项,而29 和 30索引项是2个字符串字面量,分别为:out/Ljava/io/PrintStream;

小总结:

总的来说就是一个引用一个最后都会指向字面量。

访问标识

在常量池之后,紧跟着的就是访问标识,说白了就相当于权限修饰符,该标识使用两个字节表示,用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口,是否定义为public类型还是抽象类型,如果是类的话是否被声明为final等等。

如下图:

类的访问权限通常为ACC_开头的常量。

可以看到这里的00 21 2个字节表示的就是标识,但是21似乎在我们的表中是不存在的,21这个项是我们通过 加法进行合成的一项,ACC_PUBLIC + ACC_SUPER  ACC_SUPER是一个默认的标识。所以加起来就是21。

类索引和父类索引以及接口索引集合

在访问标识之后会指定类的类别 父类类别和实现的接口。注意是在标识符后面。

下图u2表示2个字节,this_class表示当类的Class。

紧接着后面两个字节表示的就是他父类的class,也就是super_class。

再接着两个字节表示的就是接口计数器。

最后一个标识接口表示接口索引集合。

我们来看下第一个this_class,可以看到在标识符后面 00 03 表示的就是this_class。

那么这个03 表示的就是第三项。

第三项表示的就是我们本类。

Super_class:可以看到他表示的是 00 07 那么07表示的就是07项。07项就是Object。

字段表集合

fields

1.表示接口或类中声明的变量,字段(field)包括类变量以及实例变量但是不包括方法内部,代码块内部声明的局部变量。

2.字段叫什么,字段被定义什么数据类型 这些都是无法固定的,只能引用常量池中的常量来描述。

3.它只指向常量池索引集合,他描述了每个字段的完整信息,比如字段的标识符,访问修饰符,(public provate 或protected) 是类变量还是实例变量(static修饰符) 是否是常量final修饰符。

Fields_count(字段计数器)

Fields_count的值表示当前class文件felds表的成员个数 使用两个字节来表示。

Fields表每个成员都是一个Fields_info结构,用于表示类或借口所声明的所有类字段或实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

如下图表示字段计数器: 可以看到有一个字段。

如下图就是字段表的结构: 可以看到前四个都占用2个字节。

如图:

接着00 02 表示第二项,02表示num。

00 08表示的就是描述符,也就是

紧接着就是属性计数器和属性集合,因为涉及到集合了,所以需要提供属性计数器。

我们可以看到属性机器的值是09。

后续就是属性的情况了。

我们可以在jclassLib也可以查看到属性。

方法表

method表中的每个成员都必须是一个method_info结构,用于表示当前类或借口某个方法的完整描述。

method_info结构可以表示类和接口定义的所有方法,包括实例方法,类方法,实例初始化方法和类接口初始化方法。

方法表的结构实际跟字段表的结构是一样的。

如果遇到哪里写的不对 可以联系我 Get__Post


文章来源: http://mp.weixin.qq.com/s?__biz=MzAwMDQwNTE5MA==&mid=2650247261&idx=1&sn=694f79c828fe05cbe6ab2a08eda63464&chksm=83b5ba3cbfaf7878bb400554a5dde30553a65f6040ebd1aa2a102d47ba68e49a94f40d52a5f0&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh