-
java字节码文件的格式_Java字节码(.class文件)格式详解(一)
2021-02-26 08:42:19小介:去年在读《深入解析JVM》的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分。现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事。当时也只是为了学习。这份资料...小介:去年在读《深入解析JVM》的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分。现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事。当时也只是为了学习。这份资料主要参考《深入解析JVM》和《Java虚拟机规范》貌似是1.2版本的,整理出来的。里面包含了一些自己的理解和用实际代码的测试。有兴趣的童鞋可以研究研究。嘿嘿。要有错误也希望能为小弟指点出来,感激不尽。:)1.总体格式 Class File format
type descriptor remark
u4 magic 0xCAFEBABE
u2 minor_version
u2 major_version
u2 constant_pool_count
cp_info constant_pool[cosntant_pool_count – 1] index 0 is invalid
u2 access_flags
u2 this_class
u2 super_class
u2 interfaces_count
u2 interfaces[interfaces_count]
u2 fields_count
field_info fields[fields_count]
u2 methods_count
method_info methods[methods_count]
u2 attributes_count
attribute_info attributes[attributes_count]
2.格式详解
2.1magic
magic被称为“魔数”,用来标识.class文件的开头。所有合法的.class字节码都应该是该数开头,占4个字节。
2.2major_version.minor_version
major_version.minor_version合在一起形成当前.class文件的版本号,该版本号一般由编译器产生,并且由sun定义。如59.0。它们一起占4个字节。
2.3constant_pool
在Java字节码中,有一个常量池,用来存放不同类型的常量。由于Java设计的目的之一就是字节码需要经网络传输的,因而字节码需要比较紧凑,以减少网络传输的流量和时间。常量池的存在则可以让一些相同类型的值通过索引的方式从常量池中找到,而不是在不同地方有不同拷贝,缩减了字节码的大小。
每个常量池中的项是通过cp_info的类型来表示的,它的格式如下: cp_info format
type descriptor remark
u1 tag
u1 info[]
这里tag用来表示当前常量池不同类型的项。info中存放常量池项中存放的数据。
tag中表示的数据类型:
CONSTANT_Class_info(7)、
CONSTANT_Integer_info(3)、
CONSTANT_Long_info(5)、
CONSTANT_Float_info(4)、
CONSTANT_Double_info(6)、
CONSTANT_String_info(8)、
CONSTANT_Fieldref_info(9)、
CONSTANT_Methodref_info(10)、
CONSTANT_InterfaceMethodref_info(11)、
CONSTANT_NameAndType_info(12)、
CONSTANT_Utf8_info(1)、
注:在Java字节码中,所有boolean、byte、char、short类型都是用int类型存放,因而在常量池中没有和它们对应的项。
2.3.1CONSTANT_Class_info
用于记录类或接口名(used to represent a class or an interface) CONSTANT_Class_info format
type descriptor remark
u1 tag CONSTANT_Class (7)
u2 name_index constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。
注:在Java字节码中,类和接口名不同于源码中的名字,详见附件A.
2.3.2CONSTANT_Integer_info
用于记录int类型的常量值(represent 4-byte numeric (int) constants:) CONSTANT_Integer_info
type descriptor remark
u1 tag CONSTANT_Integer (3)
u4 bytes 整型常量值
2.3.3CONSTANT_Long_info
用于记录long类型的常量值(represent 8-byte numeric (long) constants:) CONSTANT_Long_info
type descriptor remark
u1 tag CONSTANT_Long (5)
u4 high_bytes 长整型的高四位值
u4 low_bytes 长整型的低四位值
2.3.4CONSTANT_Float_info
用于记录float类型的常量值(represent 4-byte numeric (float) constants:) CONSTANT_Float_info
type descriptor remark
u1 tag CONSTANT_Float(4)
u4 bytes 单精度浮点型常量值
几个特殊值:0x7f800000 => Float.POSITIVE_INFINITY、0xff800000 => Float.NEGATIVE_INFINITY、
0x7f800001 to 0x7fffffff => Float.NaN、0xff800001 to 0xffffffff => Float.NaN
2.3.5CONSTANT_Double_info
用于记录double类型的常量值(represent 8-byte numeric (double) constants:) CONSTANT_Double_info
type descriptor remark
u1 tag CONSTANT_Double(6)
u4 high_bytes 双精度浮点的高四位值
u4 low_bytes 双精度浮点的低四位值
几个特殊值:0x7ff0000000000000L => Double.POSITIVE_INFINITY、
0xfff0000000000000L => Double.NEGATIVE_INFINITY
0x7ff0000000000001L to 0x7fffffffffffffffL => Double.NaN 、
0xfff0000000000001L to 0xffffffffffffffffL => Double.NaN
2.3.6CONSTANT_String_info
用于记录常量字符串的值(represent constant objects of the typeString:) CONSTANT_String_info
type descriptor remark
u1 tag CONSTANT_String(8)
u2 string_index constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。
2.3.7CONSTANT_Fieldref_info
用于记录字段信息(包括类或接口中定义的字段以及代码中使用到的字段)。 CONSTANT_Fieldref_info
type descriptor remark
u1 tag CONSTANT_Fieldref(9)
u2 class_index constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。
u2 name_and_type_index constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。
2.3.8CONSTANT_Methodref_info
用于记录方法信息(包括类中定义的方法以及代码中使用到的方法)。 CONSTANT_Methodref_info
type descriptor remark
u1 tag CONSTANT_Methodref(10)
u2 class_index constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。
u2 name_and_type_index constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。
2.3.9CONSTANT_InterfaceMethodref_info
用于记录接口中的方法信息(包括接口中定义的方法以及代码中使用到的方法)。 CONSTANT_InterfaceMethodref_info
type descriptor remark
u1 tag CONSTANT_InterfaceMethodref(11)
u2 class_index constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。
u2 name_and_type_index constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。
2.3.10CONSTANT_NameAndType_info
记录方法或字段的名称(name)和描述符(descriptor)(represent a field or method, without indicating which class or interface type it belongs to:)。 CONSTANT_NameAndType_info
type descriptor remark
u1 tag CONSTANT_NameAndType (12)
u2 name_index constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。
u2 descriptor_index constant_pool中的索引,CONSTANT_utf8_info类型。指定字段或方法的描述符(见附录C)
2.3.11CONSTANT_Utf8_info
记录字符串的值(represent constant string values. String content is encoded in modified UTF-8.)
modifie
d UTF-8 refer to : CONSTANT_Utf8_info
type descriptor remark
u1 tag CONSTANT_Utf8 (1)
u2 length bytes所代表
的字符串的长度
u1 bytes[length] 字符串的byte数据,可以通过DataInputStream中的readUtf()方法(实例方法或静态方法读取该二进制的字符串的值。)
2.4access_flags
指定类或接口的访问权限。 类或接口的访问权限
Flag Name Value Remarks
ACC_PUBLIC 0x0001 pubilc,包外可访问。
ACC_FINAL 0x0010 final,不能有子类。
ACC_SUPER 0x0020 用于兼容早期编译器,新编译器都设置该标记,以在使用 invokespecial指令时对子类方法做特定处理。
ACC_INTERFACE 0x0200 接口,同时需要设置:ACC_ABSTRACT。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_ENUM
ACC_ABSTRACT 0x0400 抽象类,无法实例化。不可和ACC_FINAL同时设置。
ACC_SYNTHETIC 0x1000 synthetic,由编译器产生,不存在于源代码中。
ACC_ANNOTATION 0x2000 注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT
ACC_ENUM 0x4000 枚举类型
2.5this_class
this_class是指向constant pool的索引值,该值必须是CONSTANT_Class_info类型,指定当前字节码定义的类或接口。
2.6super_class
super_class是指向constant pool的索引值,该值必须是CONSTANT_Class_info类型,指定当前字节码定义的类或接口的直接父类。只有Object类才没有直接父类,此时该索引值为0。并且父类不能是final类型。接口的父类都是Object类。
2.7interfaces
interfaces数组记录所有当前类或接口直接实现的接口。interfaces数组中的每项值都是一个指向constant pool的索引值,这些值必须是CONSTANT_Class_info类型。数组中接口的顺序和源代码中接口定义的顺序相同。
2.8fields
fields数组记录了类或接口中的所有字段,包括实例字段和静态字段,但不包含父类或父接口中定义的字段。fields数组中每项都是field_info类型值,它描述了字段的详细信息,如名称、描述符、字段中的attribute等。 field_info
type descriptor remark
u2 access_flags 记录字段的访问权限。见2.8.1
u2 name_index constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段的名称。
u2 descriptor_index constant_pool中的索引,CONSTANT_Utf8_info类型,指定字段的描述符(见附录C)。
u2 attributes_count attributes包含的项目数。
attribute_info attributes[attributes_count] 字段中包含的Attribute集合。见2.8.2-2.8.7
注:fields中的项目和CONSTANT_Fieldref_info中的项目部分信息是相同的,他们主要的区别是CONSTANT_Fieldref_info中的项目不仅包含了类或接口中定义的字段,还包括在字节码中使用到的字段信息。不过这里很奇怪,为什么field_info结构中不把name_index和descriptor_index合并成fieldref_index,这样的class文件不是更加紧凑吗??不知道这是sun因为某些原因故意这样设计还是这是他们的失误??
2.8.1字段访问权限 字段的访问权限
Flag Name Value Remarks
ACC_PUBLIC 0x0001 pubilc,包外可访问。
ACC_PRIVATE 0x0002 private,只可在类内访问。
ACC_PROTECTED 0x0004 protected,类内和子类中可访问。
ACC_STATIC 0x0008 static,静态。
ACC_FINAL 0x0010 final,常量。
ACC_VOILATIE 0x0040 volatile,直接读写内存,不可被缓存。不可和ACC_FINAL一起使用。
ACC_TRANSIENT 0x0080 transient,在序列化中被忽略的字段。
ACC_SYNTHETIC 0x1000 synthetic,由编译器产生,不存在于源代码中。
ACC_ENUM 0x4000 enum,枚举类型字段
注:接口中的字段必须同时设置:ACC_PUBLIC、ACC_STATIC、ACC_FINAL
2.8.2ConstantValue Attribute(JVM识别) ConstantValue Attribute
type descriptor remark
u2 attribute_name_index constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“ConstantValue”)。
u4 attribute_length 该Attribute内容的字节长度(固定值:2)
u2 constant_value_index constant_pool中的索引,
CONSTANT_Integer_info(int,boolean,char、short、byte)、
CONSTANT_Float_info(float)、
Constant_Double_info(double)、
CONSTANT_Long_info(long)
CONSTANT_String_info(String)类型
每个常量字段(final,静态常量或实例常量)都包含有且仅有一个ConstantValue Attribute。ConstantValue Attribute结构用于存储一个字段的常量值。
对一个静态常量字段,该常量值会在类或接口被初始化之前,由JVM负责赋给他们,即它在任何静态字段之前被赋值。
对一个非静态常量字段,该值会被虚拟机忽略,它的赋值由生成的实例初始化函数()实现。如类:
classA {
public static final int fa= 10;
public final int fa2= 30;
private static int sa= 20;
static{
sa= 30;
}
}
生成的字节码如下:
// Compiled from Test.java (version 1.6 : 50.0, super bit)
class org.levin.insidejvm.miscs.staticinit.A {
public static final int fa = 10;
public final int fa2 = 30;
private static int sa;
static {};
0 bipush 20
2 putstatic org.levin.insidejvm.miscs.staticinit.A.sa : int [16]
5 bipush 30
7 putstatic org.levin.insidejvm.miscs.staticinit.A.sa : int [16]
10 return
public A();
0 aload_0 [this]
1 invokespecial java.lang.Object() [21]
4 aload_0 [this]
5 bipush 30
7 putfield org.levin.insidejvm.miscs.staticinit.A.fa2 : int [23]
10 return
2.8.3Synthetic Attribute
参考2.11.1
2.8.4Signature Attribute
参考2.11.2
2.8.5Deprecated Attribute
参考2.11.3
2.8.6RuntimeVisibleAnnotations Attribute
参考2.11.4
2.8.7RuntimeInvisibleAnnotations Attribute
参考2.11.5
于2010-12-19
-
.class文件格式--java字节码文件的格式
2011-05-14 23:07:25Java 虚拟机识别的 class 文件格式包含 Java 虚拟机指令 (或者 bytecodes )和一个符号表以及其他的辅助信息。本文将使用 VC++ 语言解析 Java Class 文件符号表,逆向生成 Java 源代码结构。如图 1 : [img]...1 . 目的
Java 虚拟机识别的 class 文件格式包含 Java 虚拟机指令 (或者 bytecodes )和一个符号表以及其他的辅助信息。本文将使用 VC++ 语言解析 Java Class 文件符号表,逆向生成 Java 源代码结构。如图 1 :
[img]http://dl.iteye.com/upload/attachment/481423/2dfcd790-9811-30b4-b1d6-626a8687a303.jpg[/img]
之所以使用 VC++ 而不使用 Java 的主要是因为 VC++ 界面开发简单;运行速度快,不需要虚拟机;需要用指针建立复杂的数据结构。
2 . 实现
实现该工具的过程如下:
1. 解析 Class 文件,从 Class 文件中读取数据并保存到称为 ClassFile 结构体中;
2. 解析 ClassFile 结构体,生成源代码字符串;
3. 将字符串显示到视图中。
2.1 解析 Class 文件
为实现第 1 步,首先需要了解 Class 文件格式规范,参考《 Java 虚拟机规范》第四章 class 文件格式,总结 class 文件的数据结构如图 2 。
2.1.1 Class 文件格式
Class 文件格式 ClassFile 结构体的 C 语言描述如下:struct ClassFile
{
u4 magic; // 识别 Class 文件格式,具体值为 0xCAFEBABE ,
u2 minor_version; // Class 文件格式副版本号,
u2 major_version; // Class 文件格式主版本号,
u2 constant_pool_count; // 常数表项个数,
cp_info **constant_pool;// 常数表,又称变长符号表,
u2 access_flags; //Class 的声明中使用的修饰符掩码,
u2 this_class; // 常数表索引,索引内保存类名或接口名,
u2 super_class; // 常数表索引,索引内保存父类名,
u2 interfaces_count; // 超接口个数,
u2 *interfaces; // 常数表索引,各超接口名称,
u2 fields_count; // 类的域个数,
field_info **fields; // 域数据,包括属性名称索引,
// 域修饰符掩码等,
u2 methods_count; // 方法个数,
method_info **methods;// 方法数据,包括方法名称索引,方法修饰符掩码等,
u2 attributes_count; // 类附加属性个数,
attribute_info **attributes; // 类附加属性数据,包括源文件名等。
};
其中 u2 为 unsigned short , u4 为 unsigned long :
typedef unsigned char u1;
typedef unsigned short u2;
typedef unsigned long u4;
cp_info **constant_pool 是常量表的指针数组,指针数组个数为 constant_pool_count ,结构体 cp_info 为struct cp_info
{
u1 tag; // 常数表数据类型
u1 *info; // 常数表数据
};
常数表数据类型 Tag 定义如下:
每种类型对应一个结构体保存该类型数据,例如 CONSTANT_Class 的 info 指针指向的数据类型应为 CONSTANT_Class_info#define CONSTANT_Class 7
#define CONSTANT_Fieldref 9
#define CONSTANT_Methodref 10
#define CONSTANT_InterfaceMethodref 11
#define CONSTANT_String 8
#define CONSTANT_Integer 3
#define CONSTANT_Float 4
#define CONSTANT_Long 5
#define CONSTANT_Double 6
#define CONSTANT_NameAndType 12
#define CONSTANT_Utf8 1struct CONSTANT_Class_info
{
u1 tag;
u2 name_index;
};
[img]http://dl.iteye.com/upload/attachment/481425/78611425-855d-3142-9cc0-0c99746ac646.jpg[/img]
CONSTANT_Utf8 的 info 指针指向的数据类型应为 CONSTANT_Utf8_infostruct CONSTANT_Utf8_info
{
u1 tag;
u2 length;
u1 *bytes;
};
Tag 和 info 的详细说明参考《 Java 虚拟机规范》第四章 4.4 节。
access_flags 为类修饰符掩码,域与方法都有各自的修饰符掩码。
例如类的修饰符为 public abstract 则 access_flags 的值为 ACC_PUBLIC | ACC_ABSTRACT=0x0401 。#define ACC_PUBLIC 0x0001
#define ACC_PRIVATE 0x0002
#define ACC_PROTECTED 0x0004
#define ACC_STATIC 0x0008
#define ACC_FINAL 0x0010
#define ACC_SYNCHRONIZED 0x0020
#define ACC_SUPER 0x0020
#define ACC_VOLATILE 0x0040
#define ACC_TRANSIENT 0x0080
#define ACC_NATIVE 0x0100
#define ACC_INTERFACE 0x0200
#define ACC_ABSTRACT 0x0400
#define ACC_STRICT 0x0800
this_class 的值是常数表的索引,索引的 info 内保存类或接口名。例如类名为 com.sum.java.swing.SwingUtitlities2 在 info 保存为 com/sum/java/swing/SwingUtitlities2
super_class 的值是常数表的索引,索引的 info 内保存超类名,在 info 内保存形式和类名相同。
interfaces 是数组,数组个数为 interfaces_count ,数组内的元素为常数表的索引,索引的 info 内保存超接口名,在 info 内保存形式和类名相同。
field_info **fields 是类域数据的指针数组,指针数组个数为 fields_count ,结构体 field_info 定义如下:struct field_info
{
u2 access_flags; // 域修饰符掩码
u2 name_index; // 域名在常数表内的索引
u2 descriptor_index; // 域的描述符,其值是常数表内的索引
u2 attributes_count; // 域的属性个数
attribute_info **attributes; // 域的属性数据,即域的值
};
例如一个域定义如下:
private final static byte UNSET=127;
则该域的修饰符掩码值为: ACC_PRIVATE | ACC_STATIC | ACC_FINAL=0x001A
常数表内 name_index 索引内保存数据为 UNSET ,常数表内 descriptor_index 索引内保存的数据为 B ( B 表示 byte, 其他类型参考《 Java 虚拟机规范》第四章 4.3.2 节)。 attributes_count 的值为 1 ,其中 attributes 是指针数组。指针数组个数为 attributes_count ,在此为 1 , attribute_info 结构体如下:struct attribute_info
{
u2 attribute_name_index; // 常数表内索引
u4 attribute_length; // 属性长度
u1 *info; // 根据属性类型不同而值不同
};
attribute_info 可以转换 (cast) 为多种类型 ConstantValue_attribute , Exceptions_attribute , LineNumberTable_attribute , LocalVariableTable_attribute , Code_attribute 等。
因为域的属性只有一种: ConstantValue_attribute ,因此此结构体转换为
struct ConstantValue_attribute
{
u2 attribute_name_index; // 常数表内索引
u4 attribute_length; // 属性长度值,永远为 2
u2 constantvalue_index; // 常数表内索引,保存域的值
// 在此例中,常数表内保存的值为 127
};
method_info **methods 是方法数据的指针数组,指针数组个数为 methods_count ,结构体 method_info 定义如下:struct method_info
{
u2 access_flags; // 方法修饰符掩码
u2 name_index; // 方法名在常数表内的索引
u2 descriptor_index; // 方法描述符,其值是常数表内的索引
u2 attributes_count; // 方法的属性个数
attribute_info **attributes; // 方法的属性数据,
// 保存方法实现的 Bytecode 和异常处理
};
例如一个方法定义如下:
public static boolean canAccessSystemClipboard(){
...
}
则 access_flags 的值为 ACC_PUBLIC | ACC_STATIC =0x0009 ,常数表内 name_index 索引内保存数据为 canAccessSystemClipboard ,常数表内 descriptor_index 索引内保存数据为 ()Z ; ( 括号表示方法参数, Z 表示返回值为布尔型,详细说明参照《 Java 虚拟机规范》第四章 4.3.2 节 ) 。 attribute_info **attributes 是方法的属性指针数组,个数为 attributes_count ,数组内保存的是常数表索引, info 为 Code_attribute 或 Exceptions_attribute 。
本文不解析方法内容,因此忽略 Code_attribute 和 Exceptions_attribute 的内容。
ClassFile 结构体中的 attribute_info **attributes 是附加属性数组指针,个数为 attributes_count ,本文只识别 SourceFile 属性。struct SourceFile_attribute
{
u2 attribute_name_index; // 常数表内索引
u4 attribute_length; // 属性长度值,永远为 2
u2 sourcefile_index; // 常数表内索引, info 保存源文件名
};
例如 com.sum.java.swing.SwingUtitlities2 类的源文件名为 SwingUtitlities2.java 。
以上是本文需要解析的 Class 文件格式。
2.1.2 读取数据
定义 CJavaClass 类完成解析 Class 文件,生成 Java 源程序字符串。使用 VC++ 的 MFC 类 CFile 从 Class 文件读取数据。 例如:用 16 进制编辑器打开 Class 文件,如图 3 ,前 4 个 byte 分别是 CA FE BA BE ,使用 CFile::Read(tmp,sizeof(u4)) 读取后, tmp 的值为 0xBEBAFECA ,所以需要位转换。定义以下方法从文件读取定长数据:void readu1(u1 *buff);
void readu2(u2 *buff);
void readu4(u4 *buff);
定义如下方法读取变长数据。
void readun(void *buff,u4 len) ;
读取的 u2 和 u4 的数据需要位转换:
U1 [0]
U1 [1]
U1 [1]
U1 [0]
U2 :
U1 [0]
U1 [1]
U1 [3]
U4 :
U1 [2]
U1 [3]
U1 [2]
U1 [0]
U1 [1]
调用 void readu4(u4 *buff); 后 buff 的值为 0x CAFEBABE ,该值为 ClassFile 的 magic ,识别该文件是 Java Class 文件。
[img]http://dl.iteye.com/upload/attachment/481427/e56502a3-abbd-328e-b622-291bbbada96b.jpg[/img]
magic 的后面是 Class 格式的版本号,图 3 的版本为 0x00000030=0.48 。版本后面是常数表的元素个数,图 3 的常数表的元素个数为 0xD2=210 个。常数表的元素个数之后如 ClassFile 结构体定义的常数表,类信息,接口信息,域信息,方法信息和附加属性等。
2.2 生成 Java 源文件
解析 Class 文件后,生产 ClassFile 结构体。遍历该结构体数据,则可根据 Java 语言规范生成 Java 源文件。例如根据 ClassFile 的 access_flags 值获得 Java 类的修饰符,其中 access 是 CArray<CString,CString&> ,保存类所有的修饰符 :if((flag & ACC_PUBLIC )==ACC_PUBLIC)
{
access.Add(CString("public"));
}
if((flag & ACC_PRIVATE)==ACC_PRIVATE)
{
access.Add(CString("private"));
}
if((flag & ACC_PROTECTED)==ACC_PROTECTED)
{
access.Add(CString("protected"));
}
…
2.3 显示视图
将获得的 Java 源代码显示在 MFC 的 CScrollView 视图非常简单,可以添加一些关键字颜色,例如注释显示为草绿色等,如图 4 。
[img]http://dl.iteye.com/upload/attachment/481429/167d0170-62f8-39db-bd69-5b641496adf0.jpg[/img]
3 .总结
本文根据《 Java 虚拟机规范》开发了解析 Java Class 文件格式,并生成 Java 源代码结构的工具。其优点是:
1. 脱离 Java 虚拟机或 Java 开发环境;
2. 可查阅没有 Java 源代码的 Class 文件的内容;
3. 为一些复杂的 Java Jar 包生成相同类名的替代类,方便开发调试。例如,用返回固定字符串的 java 源文件更换需要网络链接的相同 java 类,有助于本地运行与调试。
缺点是:
1. 由于没有反编译 Bytecode ,工具生成的部分 Java 源文件需要手动添加一些 Java 属性值;
2. Java 源文件内的所需要使用的 Java 方法内容需要程序员手动实现。 -
.class文件格式(java字节码文件的格式)
2010-09-27 17:15:00一个.class文件对应一个类(Class) 包含虚拟机指令一个.class文件对应一个类(Class)
包含字节码(虚拟机指令)
1 . 目的
Java 虚拟机识别的 class 文件格式包含 Java 虚拟机指令 (或者 bytecodes )和一个符号表以及其他的辅助信息。本文将使用 VC++ 语言解析 Java Class 文件符号表,逆向生成 Java 源代码结构。如图 1 :
图1
之所以使用 VC++ 而不使用 Java 的主要是因为 VC++ 界面开发简单;运行速度快,不需要虚拟机;需要用指针建立复杂的数据结构。
2 . 实现
实现该工具的过程如下:
1. 解析 Class 文件,从 Class 文件中读取数据并保存到称为 ClassFile 结构体中;
2. 解析 ClassFile 结构体,生成源代码字符串;
3. 将字符串显示到视图中。
2.1 解析 Class 文件
为实现第 1 步,首先需要了解 Class 文件格式规范,参考《 Java 虚拟机规范》第四章 class 文件格式,总结 class 文件的数据结构如图 2 。
2.1.1 Class 文件格式
Class 文件格式 ClassFile 结构体的 C 语言描述如下:
struct ClassFile
{
u4 magic; // 识别 Class 文件格式,具体值为 0xCAFEBABE ,
u2 minor_version; // Class 文件格式副版本号,
u2 major_version; // Class 文件格式主版本号,
u2 constant_pool_count; // 常数表项个数,
cp_info **constant_pool;// 常数表,又称变长符号表,
u2 access_flags; //Class 的声明中使用的修饰符掩码,
u2 this_class; // 常数表索引,索引内保存类名或接口名,
u2 super_class; // 常数表索引,索引内保存父类名,
u2 interfaces_count; // 超接口个数,
u2 *interfaces; // 常数表索引,各超接口名称,
u2 fields_count; // 类的域个数,
field_info **fields; // 域数据,包括属性名称索引,
// 域修饰符掩码等,
u2 methods_count; // 方法个数,
method_info **methods;// 方法数据,包括方法名称索引,方法修饰符掩码等,
u2 attributes_count; // 类附加属性个数,
attribute_info **attributes; // 类附加属性数据,包括源文件名等。
};
其中 u2 为 unsigned short , u4 为 unsigned long :
typedef unsigned char u1;
typedef unsigned short u2;
typedef unsigned long u4;
cp_info **constant_pool 是常量表的指针数组,指针数组个数为 constant_pool_count ,结构体 cp_info 为
struct cp_info
{
u1 tag; // 常数表数据类型
u1 *info; // 常数表数据
};
常数表数据类型 Tag 定义如下:
#define CONSTANT_Class 7
#define CONSTANT_Fieldref 9
#define CONSTANT_Methodref 10
#define CONSTANT_InterfaceMethodref 11
#define CONSTANT_String 8
#define CONSTANT_Integer 3
#define CONSTANT_Float 4
#define CONSTANT_Long 5
#define CONSTANT_Double 6
#define CONSTANT_NameAndType 12
#define CONSTANT_Utf8 1
每种类型对应一个结构体保存该类型数据,例如 CONSTANT_Class 的 info 指针指向的数据类型应为 CONSTANT_Class_info
struct CONSTANT_Class_info
{
u1 tag;
u2 name_index;
};
图 2
CONSTANT_Utf8 的 info 指针指向的数据类型应为 CONSTANT_Utf8_info
struct CONSTANT_Utf8_info
{
u1 tag;
u2 length;
u1 *bytes;
};
Tag 和 info 的详细说明参考《 Java 虚拟机规范》第四章 4.4 节。
access_flags 为类修饰符掩码,域与方法都有各自的修饰符掩码。
#define ACC_PUBLIC 0x0001
#define ACC_PRIVATE 0x0002
#define ACC_PROTECTED 0x0004
#define ACC_STATIC 0x0008
#define ACC_FINAL 0x0010
#define ACC_SYNCHRONIZED 0x0020
#define ACC_SUPER 0x0020
#define ACC_VOLATILE 0x0040
#define ACC_TRANSIENT 0x0080
#define ACC_NATIVE 0x0100
#define ACC_INTERFACE 0x0200
#define ACC_ABSTRACT 0x0400
#define ACC_STRICT 0x0800
例如类的修饰符为 public abstract 则 access_flags 的值为 ACC_PUBLIC | ACC_ABSTRACT=0x0401 。
this_class 的值是常数表的索引,索引的 info 内保存类或接口名。例如类名为 com.sum.java.swing.SwingUtitlities2 在 info 保存为 com/sum/java/swing/SwingUtitlities2
super_class 的值是常数表的索引,索引的 info 内保存超类名,在 info 内保存形式和类名相同。
interfaces 是数组,数组个数为 interfaces_count ,数组内的元素为常数表的索引,索引的 info 内保存超接口名,在 info 内保存形式和类名相同。
field_info **fields 是类域数据的指针数组,指针数组个数为 fields_count ,结构体 field_info 定义如下:
struct field_info
{
u2 access_flags; // 域修饰符掩码
u2 name_index; // 域名在常数表内的索引
u2 descriptor_index; // 域的描述符,其值是常数表内的索引
u2 attributes_count; // 域的属性个数
attribute_info **attributes; // 域的属性数据,即域的值
};
例如一个域定义如下:
private final static byte UNSET=127;
则该域的修饰符掩码值为: ACC_PRIVATE | ACC_STATIC | ACC_FINAL=0x001A
常数表内 name_index 索引内保存数据为 UNSET ,常数表内 descriptor_index 索引内保存的数据为 B ( B 表示 byte, 其他类型参考《 Java 虚拟机规范》第四章 4.3.2 节)。 attributes_count 的值为 1 ,其中 attributes 是指针数组。指针数组个数为 attributes_count ,在此为 1 , attribute_info 结构体如下:
struct attribute_info
{
u2 attribute_name_index; // 常数表内索引
u4 attribute_length; // 属性长度
u1 *info; // 根据属性类型不同而值不同
};
attribute_info 可以转换 (cast) 为多种类型 ConstantValue_attribute , Exceptions_attribute , LineNumberTable_attribute , LocalVariableTable_attribute , Code_attribute 等。
因为域的属性只有一种: ConstantValue_attribute ,因此此结构体转换为
struct ConstantValue_attribute
{
u2 attribute_name_index; // 常数表内索引
u4 attribute_length; // 属性长度值,永远为 2
u2 constantvalue_index; // 常数表内索引,保存域的值
// 在此例中,常数表内保存的值为 127
};
method_info **methods 是方法数据的指针数组,指针数组个数为 methods_count ,结构体 method_info 定义如下:
struct method_info
{
u2 access_flags; // 方法修饰符掩码
u2 name_index; // 方法名在常数表内的索引
u2 descriptor_index; // 方法描述符,其值是常数表内的索引
u2 attributes_count; // 方法的属性个数
attribute_info **attributes; // 方法的属性数据,
// 保存方法实现的 Bytecode 和异常处理
};
例如一个方法定义如下:
public static boolean canAccessSystemClipboard(){
...
}
则 access_flags 的值为 ACC_PUBLIC | ACC_STATIC =0x0009 ,常数表内 name_index 索引内保存数据为 canAccessSystemClipboard ,常数表内 descriptor_index 索引内保存数据为 ()Z ; ( 括号表示方法参数, Z 表示返回值为布尔型,详细说明参照《 Java 虚拟机规范》第四章 4.3.2 节 ) 。 attribute_info **attributes 是方法的属性指针数组,个数为 attributes_count ,数组内保存的是常数表索引, info 为 Code_attribute 或 Exceptions_attribute 。
本文不解析方法内容,因此忽略 Code_attribute 和 Exceptions_attribute 的内容。
ClassFile 结构体中的 attribute_info **attributes 是附加属性数组指针,个数为 attributes_count ,本文只识别 SourceFile 属性。
struct SourceFile_attribute
{
u2 attribute_name_index; // 常数表内索引
u4 attribute_length; // 属性长度值,永远为 2
u2 sourcefile_index; // 常数表内索引, info 保存源文件名
};
例如 com.sum.java.swing.SwingUtitlities2 类的源文件名为 SwingUtitlities2.java 。
以上是本文需要解析的 Class 文件格式。
2.1.2 读取数据
定义 CJavaClass 类完成解析 Class 文件,生成 Java 源程序字符串。使用 VC++ 的 MFC 类 CFile 从 Class 文件读取数据。 例如:用 16 进制编辑器打开 Class 文件,如图 3 ,前 4 个 byte 分别是 CA FE BA BE ,使用 CFile::Read(tmp,sizeof(u4)) 读取后, tmp 的值为 0xBEBAFECA ,所以需要位转换。定义以下方法从文件读取定长数据:
void readu1(u1 *buff);
void readu2(u2 *buff);
void readu4(u4 *buff);
定义如下方法读取变长数据。
void readun(void *buff,u4 len) ;
读取的 u2 和 u4 的数据需要位转换:
U1 [0]
U1 [1]
U1 [1]
U1 [0]
U2 :
U1 [0]
U1 [1]
U1 [3]
U4 :
U1 [2]
U1 [3]
U1 [2]
U1 [0]
U1 [1]
调用 void readu4(u4 *buff); 后 buff 的值为 0x CAFEBABE ,该值为 ClassFile 的 magic ,识别该文件是 Java Class 文件。
图 3
magic 的后面是 Class 格式的版本号,图 3 的版本为 0x00000030=0.48 。版本后面是常数表的元素个数,图 3 的常数表的元素个数为 0xD2=210 个。常数表的元素个数之后如 ClassFile 结构体定义的常数表,类信息,接口信息,域信息,方法信息和附加属性等。
2.2 生成 Java 源文件
解析 Class 文件后,生产 ClassFile 结构体。遍历该结构体数据,则可根据 Java 语言规范生成 Java 源文件。例如根据 ClassFile 的 access_flags 值获得 Java 类的修饰符,其中 access 是 CArray<CString,CString&> ,保存类所有的修饰符 :
if((flag & ACC_PUBLIC )==ACC_PUBLIC)
{
access.Add(CString("public"));
}
if((flag & ACC_PRIVATE)==ACC_PRIVATE)
{
access.Add(CString("private"));
}
if((flag & ACC_PROTECTED)==ACC_PROTECTED)
{
access.Add(CString("protected"));
}
…
2.3 显示视图
将获得的 Java 源代码显示在 MFC 的 CScrollView 视图非常简单,可以添加一些关键字颜色,例如注释显示为草绿色等,如图 4 。
图 4
3 .总结
本文根据《 Java 虚拟机规范》开发了解析 Java Class 文件格式,并生成 Java 源代码结构的工具。其优点是:
1. 脱离 Java 虚拟机或 Java 开发环境;
2. 可查阅没有 Java 源代码的 Class 文件的内容;
3. 为一些复杂的 Java Jar 包生成相同类名的替代类,方便开发调试。例如,用返回固定字符串的 java 源文件更换需要网络链接的相同 java 类,有助于本地运行与调试。
缺点是:
1. 由于没有反编译 Bytecode ,工具生成的部分 Java 源文件需要手动添加一些 Java 属性值;
2. Java 源文件内的所需要使用的 Java 方法内容需要程序员手动实现。
-
java 字节码文件_java字节码文件
2021-02-12 12:32:141.简述java语言具有一处编译,到处运行,该功能实现原理是基于class文件实现的,java格式文件经过编译后...所以下面研究字节码文件格式。2.字节码文件格式package com.xiayu.demo;public class ClassFile {privat...1.简述
java语言具有一处编译,到处运行,该功能实现原理是基于class文件实现的,java格式文件经过编译后生成class文件,jvm加载class文件进行运行,不管什么格式的文件只要能编译成符合规范的class文件,那么就可以在jvm上运行。所以下面研究字节码文件格式。
2.字节码文件格式
package com.xiayu.demo;
public class ClassFile {
private int field;
public void method(){
String a = "test";
String b = new String("b");
System.out.println("method()");
}
}
对应的字节码16进制显示
字节码文件是由魔数,版本号,常量池,访问标志,类索引,父类索引,接口索引,字段表集合,方法,属性组成。
魔数: 4个字节,对应的是16进制的cafe babe,其是表明class文件的标志,class文件也可以通过class后缀来表示,但后缀可以修改,魔数在文件内容上表明该文件是class文件
版本号:上图0000 0034,0000代表次版本号,0034代表主版本号(52),52对应1.8.
常量池:常量池主要包含字面量和符号引用,字面量如文本字符串,final声明的变量等,符号引用主要是类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。如上图字节码文件中:0x0021(33)代表常量池中常量的数量,实际上只有32个常量。
常量池中的信息
访问标志 Access_flag
声明class文件是类还是接口,访问控制权限是public abstract 还是protected等等。
类索引、父类索引和接口索引等
方法
构造方法
method()方法
上述仅仅为字节码的简要描述,更多可以参考官方文档或者使用java自带javap工具进行查看字节码文件内容.
3.字节码指令
字节码指令主要包含加载存储指令,运算指令、类型转换指令、对象创建与访问指令、操作数栈管理指令、控制转移指令、方法调用和返回指令、异常处理指令和同步指令等。
4.泛型的字节码
举例
public T sum(T a,T b){
System.out.println(a);
System.out.println(b);
return b;
}
对应的字节码,通过字节码文件可以看到泛型在字节码文件中具体的类型是Object类型。
-
java的字节码文件是什么后缀_【Java虚拟机1】Java字节码文件格式入门
2021-03-12 22:54:31第一次学习看字节码文件,这个对工作没什么用,但是会提升内功。首先介绍两个IDEA插件以及使用:BinEd:以16进制格式查看class文件使用方法:右键class文件,点击Open as binaryJClassLib:以一种更为方便的方式查看... -
JAVA字节码文件格式
2019-01-15 00:24:41字节码文件(.class)的格式固定如下 因为觉得这几张表后面的学习可能会用到,所以在https://www.cnblogs.com/paddix/p/5282004.html盗窃了这几张图,留作备份。 目录 字节码文件(.class)的格式固定如下 因为觉得... -
java 字节码 格式_面试官:解释一下Java字节码文件中的JVM指令
2021-03-06 22:08:57即 Java 源码只需要编译成字节码文件,之后就可以在不同的操作系统(Windows、Mac、Linux)运行,准确讲是运行在操作系统上的 JVM 中。我们都知道通过命令 javac 来编译 Java 源代码,但是编译的具体流程步骤你有没有... -
hex文件格式解析_Java字节码文件的解析
2020-11-29 07:41:42class字节码文件的解析1.1. 背景最近由于工作的需要,对Class文件做了一定的了解。然而枯燥无味的课程总是让人犯困,在学习的过程中,总有一种虚无缥缈的感受,看起来好像已经会了。但又好像什么也没懂。故想到不如... -
java字节码文件对象_Java字节码(.class文件)格式详解(一)
2021-03-11 10:32:11小介:去年在读《深入解析JVM》的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分。现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事。当时也只是为了学习。这份资料... -
Java字节码文件
2020-08-10 16:57:47上一篇我们分析了字节码文件加载到虚拟机的过程,在继续深入class文件类解析前先了解下class文件格式。...一,Java字节码文件 1. 类文件结构 ClassFile { u4 magic; //魔数 0xCAFEBABE u2 minor_version; //副版本号 -
java字节码文件的特点_java字节码(class)文件深度解析
2021-02-28 15:16:171.Class文件基础(1)文件格式Class文件的结构不像XML等描述语言那样松散自由。由于它没有任何分隔符号,所以,以上数据项无论是顺序还是数量都是被严格限定的。哪个字节代表什么含义,长度是多少,先后顺序如何,都不... -
java 字节码 格式_Java字节码(.class文件)格式详解(一)
2021-03-06 22:09:10小介:去年在读《深入解析JVM》的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分。现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事。当时也只是为了学习。这份资料... -
《Java平台体系》——第二章 JVM——Java字节码类文件格式
2013-01-10 16:19:16在学习该小节内容之前建议大家下载工具JBE,它是一个Java字节码编辑器,能够浏览和编辑Java字节码。...无论使用的Java编译器具体如何实现,其编译之后的Java字节码类文件(.class)应该在任何JVM上运行。 -
java字节码class_Java字节码(.class文件)格式详解(一)
2021-02-12 17:03:59小介:去年在读《深入解析JVM》的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分。现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事。当时也只是为了学习。这份资料... -
深入浅出Java字节码.class文件格式
2020-12-31 23:37:21“同一份输入,不同的输出”,我们只需要生成一份字节码文件,然后同一份.class字节码文件在不同的操作系统中,由不同的虚拟机生成对应机器码。虚拟机和字节码是Java的两个最底层的原理。 最简单的编译运行流程,... -
探秘Java字节码文件
2019-11-05 21:21:16实现语言无关性的基础仍然为虚拟机和字节码存储格式,Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。 Class文件的结构 Class文件是一组以8位字节为基础单位的二... -
值对于无符号的字节太大或太小_Java 字节码文件结构
2020-12-18 21:13:32在通过jvm中的编译器将源码编译为机器码进行执行1 字节码文件格式《Java 虚拟机规范》规定了 Java 虚拟机结构、Class 类文件结构、字节码指令等内容。字节码文件结构是一组以 8 位字节为基础的二进制流,各数据项目... -
深入Java虚拟机之字节码文件格式
2013-10-07 17:02:47这两天在研究JavaAgent,动力是想弄明白play...从网上找到的相关资料上来看,如果想理解清楚其原理,必须要了解字节码文件的格式和指令,也就是我们的.java文件编译后生成的.class二进制文件。听起来挺难的,毕竟我们 -
java 字节码 常量池_Java 字节码常量池
2021-03-16 19:09:14Java 字节码常量池上篇文章简单介绍了 java Class 字节码文件的基本格式. 本文我们直接通过阅读字节码文件来进一步理解字节码中的常量池结构首先我们新建一个最简单的 Java 文件publicclassTest{... -
Java字节码文件指令操作码助记符含义
2018-01-08 17:17:31Java二进制指令代码解析 小注:去年在看《深入解析JVM》书的时候做的一些记录,同时参考了《Java虚拟机规范》。只是对指令的一些列举,加入了一些自己的理解。...在字节码文件中,指令代码只是其中的一部 -
python字节码解析_Java8虚拟机规范——Class字节码文件格式&样例解析
2020-12-26 18:10:34接下来介绍class文件的格式,希望能让大家对class字节码文件的认识更深入。Class文件是一组以8位字节为基础单位的2进制流,各个数据项按顺序紧凑的排列在文件中,Class文件采用类似C语言的结构体的伪结构表示,在这种... -
java 字节码 putfield asm_关于java字节码框架ASM的学习
2021-03-13 19:44:22一、什么是ASMASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义... -
java字节码常量池_java字节码常量池
2021-02-28 11:01:36引言上篇文章简单介绍了java Class 字节码文件的基本格式。本文我们直接通过阅读字节码文件来进一步理解字节码中的常量池结构首先我们新建一个最简单的Java文件12345public class{public static void main(String[] ... -
java 字节码 putfield asm_java字节码(class)文件深度解析
2021-03-22 14:13:161.Class文件基础(1)文件格式Class文件的结构不像XML等描述语言那样松散自由。由于它没有任何分隔符号,所以,以上数据项无论是顺序还是数量都是被严格限定的。哪个字节代表什么含义,长度是多少,先后顺序如何,都不... -
java字节代码解析_Java字节码(.class文件)的代码解析
2021-03-10 08:31:12Java二进制指令代码以以下格式紧凑排列(opcode占一个字节):opcode operand*除了...通过对上面Java指令集的分析可以知道,Java指令集中很大一部分没有操作数,因而对这部分指令,只需要读取一个字节的操作码,将... -
从一个基本的类的反编译说起让你读懂Java字节码文件
2018-09-04 17:12:18从一个基本的类的反编译...可以方便的查阅Java的字节码。 例如下面的例子: public class Coo{ private int tryBlock; private int catchBlock; private int finallyBlock; private int methodExit; ... -
java字节码反编译_打造一个简单的Java字节码反编译器
2021-03-04 06:45:41简介本文示范了一种反编译Java字节码的方法,首先通过解析class文件,然后将解析的结果转成java代码。但是本文并没有覆盖所有的class文件的特性和指令,只针对部分规范进行解析。所有的代码代码都是示范性的,追求...