.class文件格式(java字节码文件的格式)

一个.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 方法内容需要程序员手动实现。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值