字节码与类的加载(笔记1)

admin 2024年1月5日09:02:55评论21 views字数 4055阅读13分31秒阅读模式

字节码是什么?

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

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

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

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

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

字节码与类的加载(笔记1)

解析Class文件的三种方式

方式一

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

字节码与类的加载(笔记1)

方式二

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

javap -v xxx.class

字节码与类的加载(笔记1)

方式三

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

字节码与类的加载(笔记1)

Class文件解析

Class文件的本质

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

Class文件的格式

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

字节码解析
1.魔数

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

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

字节码与类的加载(笔记1)

2.Class文件版本号

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

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

字节码与类的加载(笔记1)

字节码与类的加载(笔记1)

注意:

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

常量池
常量池计数器

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

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

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

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

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

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

比如如下例子:

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

字节码与类的加载(笔记1)

常量池表

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

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

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

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

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

字面量和符号引用

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

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

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

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

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

他表示的就是如下图:

字节码与类的加载(笔记1)

常量池解析

这里从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表示字符串所占用的长度。

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

后面也是一样的字符串。

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

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

字节码与类的加载(笔记1)

常量类型和结构表

常量池表数据解析

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

字节码与类的加载(笔记1)

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

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

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

字节码与类的加载(笔记1)

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

09 00 15 00 16 解析:

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

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

字节码与类的加载(笔记1)

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

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

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

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

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

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

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

字节码与类的加载(笔记1)

小总结:

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

访问标识

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

如下图:

字节码与类的加载(笔记1)

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

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

字节码与类的加载(笔记1)

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

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

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

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

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

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

字节码与类的加载(笔记1)

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

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

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

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结构,用于表示类或借口所声明的所有类字段或实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

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

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

如图:

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

字节码与类的加载(笔记1)

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

字节码与类的加载(笔记1)

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

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

后续就是属性的情况了。

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

字节码与类的加载(笔记1)

方法表

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

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

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

字节码与类的加载(笔记1)

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

原文始发于微信公众号(白帽子):字节码与类的加载(笔记1)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月5日09:02:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   字节码与类的加载(笔记1)http://cn-sec.com/archives/2366316.html

发表评论

匿名网友 填写信息