javaclass文件可视化工具

class文件可视化工具

Author:guanjt(解析class文件),Liujr(UI化)

1.目录结构

|—JVM

​ |—ch03

​ |—classpath

​ |—classfile

​ |—cmd

​ |—main

文件夹含义:

cmd —— 捕获控制台输入

main —— 主函数入口

classfile —— *.class文件搜寻

classpath —— *.class文件解析

2.class文件分析

2.1 测试java文件

public class ClassFileTest{

    public static final boolean FLAG = true;

    public static final byte BYTE = 123;

    public static final short SHORT = 12345;

    public static final char x = 'X';

    public static final int INT = 123456789;

    public static final long LONG = 12345678901L;

    public static final float PI = 3.14f;

    public static final double E = 2.71828;

    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }

}

2.2 解析文件

​ Go与java的数据类型的对比

Go语言类型Java语言类型说明
int8byte8比特有符号整数
uint8N/A8比特无符号整数
int16short16比特有符号整数
uint16char16比特无符号整数
int32 (别名rune)int32比特有符号整数
uint32N//A32比特无符号整数
int64long64比特有符号整数
uint64N/A64比特无符号整数
float32float32比特IEEE-754浮点数
float64double64比特IEEE-754浮点数
2.2.1 class文件解析效果(javap工具)

[外链图片转存失败(img-WLVrcZwW-1563279799426)(C:\Users\gjt\AppData\Roaming\Typora\typora-user-images\1563279717741.png)]

2.2.2 读取数据(class_reader)

​ 解析class文件可以当作字节流处理读取。定义的文件位Class_reader.go。主要是提供几个字节流读取方法(针对class文件的类型来设计的)供我们使用。

func readUint8()    // 读取u1类型的数据
func readUint16()   // 读取u2类型的数据。
func readUint32()   // 读取u4类型数据。
func readUint64()   // 读取uint64位的数据(java虚拟机规范中没有定义u8)类型数据
func readUint16s()  // 读取uint16表,表的大小由开头的uint16数据类型。
func readBytes()    // 读取指定数量的字节
2.2.3 整体结构(class_file)

定义结构体

type ClassFile struct {
	magic uint32               
	minorVersion uint16
	majorVersion  uint16
	constantPool ConstantPool
	accessFlags uint16
	thisClass uint16
	superClass uint16
	interfaceClass []uint16
	fields    [] *MemberInfo
	methods   [] *MemberInfo
	attributes   [] AttributeInfo
}

magic:魔数,很多文件格式都会规定满足该格式的文件必须以某几个固定字节开头,就叫做魔数。如果解析的class文件不符合格式要求就会抛出异常。(开头四个字节)如图所示:

在这里插入图片描述

0xCAFEBABE 这个就代表了可以识别的class文件

实现代码:

// 读取魔数,如果不是class文件特定的开头,就抛出异常。
func (self *ClassFile)  readAndCheckMagic(reader *ClassReader){
   magic := reader.readUint32()

   if magic != 0xCAFEBABE {
      panic("Java.lang.ClassFormatError:magic")
   }
}

minorVersion,majorVersion 版本号:主版本号和次版本号。主版本号是M次版本号是m,则组合起来就是M.m,特定的JAVA虚拟机只能支持特定范围内的版本号的class文件(比如JDK8就是52.0)具体如图所示
在这里插入图片描述

00 为次版本号,0x34 为主版本号

然后紧接着就是常量池:

[外链图片转存失败(img-CbnoI1HC-1563279799431)(C:\Users\gjt\AppData\Roaming\Typora\typora-user-images\1563278433346.png)]

​ 0038 代表了该java文件中所包含的容量大小。0038 => 55 代表有55个常量存在。后面开始就是第一个常量。后面两个字节代表了类型。然后根据类型我们去查找后面几个字节是代表的位置。基本类型和实现代码如下。

switch (tag) {
    case TagInfo.CONSTANT_UTF8:
        constantMemberInfo.setConstantName("CONSTANT_Utf8_info");
        constantMemberInfo.setConstantSize(3);
        constantMemberInfo.setConstantType("u2;u1;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_INTEGER:
        constantMemberInfo.setConstantName("CONSTANT_Integer_info");
        constantMemberInfo.setConstantSize(4);
        constantMemberInfo.setConstantType("u4;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_FLOAT:
        constantMemberInfo.setConstantName("CONSTANT_Float_info");
        constantMemberInfo.setConstantSize(4);
        constantMemberInfo.setConstantType("u4;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_LONG:
        constantMemberInfo.setConstantName("CONSTANT_Long_info");
        constantMemberInfo.setConstantSize(8);
        constantMemberInfo.setConstantType("u8;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_DOUBLE:
        constantMemberInfo.setConstantName("CONSTANT_Double_info");
        constantMemberInfo.setConstantSize(8);
        constantMemberInfo.setConstantType("u8;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_CLASS:
        constantMemberInfo.setConstantName("CONSTANT_Class_info");
        constantMemberInfo.setConstantSize(2);
        constantMemberInfo.setConstantType("u2;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_STRING:
        constantMemberInfo.setConstantName("CONSTANT_String_info");
        constantMemberInfo.setConstantSize(2);
        constantMemberInfo.setConstantType("u2;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_FIELD_REF:
        constantMemberInfo.setConstantName("CONSTANT_Fieldref_info");
        constantMemberInfo.setConstantSize(4);
        constantMemberInfo.setConstantType("u2;u2;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_METHOD_REF:
        constantMemberInfo.setConstantName("CONSTANT_Methodref_info");
        constantMemberInfo.setConstantSize(4);
        constantMemberInfo.setConstantType("u2;u2;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_INTERFACE_METHOD_REF:
        constantMemberInfo.setConstantName("CONSTANT_InterfaceMethodref_info");
        constantMemberInfo.setConstantSize(4);
        constantMemberInfo.setConstantType("u2;u2;");
        return constantMemberInfo;
    case TagInfo.CONSTANT_NAME_AND_TYPE:
        constantMemberInfo.setConstantName("CONSTANT_NameAndType_info");
        constantMemberInfo.setConstantSize(4);
        constantMemberInfo.setConstantType("u2;u2;");
        return constantMemberInfo;
}

目前我只写了以上这些,支持到JDK8。

TagInfo对应如下:

/**
 * UTF-8编码的Unicode字符串
 */
public final static int CONSTANT_UTF8 = 1;
/**
 * int类型的字面值
 */
public final static int CONSTANT_INTEGER = 3;
/**
 * float类型的字面值
 */
public final static int CONSTANT_FLOAT = 4;
/**
 * long类型的字面值
 */
public final static int CONSTANT_LONG = 5;
/**
 *     double类型的字面值
 */
public final static int CONSTANT_DOUBLE = 6;
/**
 * 类类型常量
 */
public final static int CONSTANT_CLASS = 7;
/**
 * 字符串常量
 */
public final static int CONSTANT_STRING = 8;
/**
 * 对一个字段的符号引用
 */
public final static int CONSTANT_FIELD_REF = 9;
/**
 * 对一个类中方法的符号引用
 */
public final static int CONSTANT_METHOD_REF = 10;
/**
 *     对一个接口中方法的符号引用
 */
public final static int CONSTANT_INTERFACE_METHOD_REF = 11;
/**
 * 对一个字段或方法的部分符号引用
 */
public final static int CONSTANT_NAME_AND_TYPE = 12;

​ 如图:第一个两个字节是0A = 10,然后我们对应找到taginfo 就是CONSTANT_METHOD_REF,也就是对于一个类中方法的引用符号。然后我们就去switch里面找,对应了哪几个数据类型。

[外链图片转存失败(img-RSVIqCnm-1563279799432)(C:\Users\gjt\AppData\Roaming\Typora\typora-user-images\1563278795900.png)]

​ 结果是对应了2个u2类型的数据。也就是2个字节。

​ 然后再取后面两个字节对应的数据,然后展示出来即可。以此类推,循环55次,把常量池读取完。然后分析后面的东西。

读取效果如图:

[外链图片转存失败(img-KRxGPC4g-1563279799433)(C:\Users\gjt\AppData\Roaming\Typora\typora-user-images\1563279254426.png)]

每一个都是经过解析。最后可视化展示到ui界面上来,界面是用fx做的,是女朋友帮我写的(因为我不会fx哈哈哈)。

AccessFlag,读取完成之后,后面两个字节就是accflag主要是为了标识类的类型,是公有还是私有之类的。0021 = 0020 | 0001

标志名 public类型(目前界面上的计算还未完成)

标志名标志值标志含义针对的对像
ACC_PUBLIC0x0001public类型所有类型
ACC_FINAL0x0010final类型
ACC_SUPER0x0020使用新的invokespecial语义类和接口
ACC_INTERFACE0x0200接口类型接口
ACC_ABSTRACT0x0400抽象类型类和接口
ACC_SYNTHETIC0x1000该类不由用户代码生成所有类型
ACC_ANNOTATION0x2000注解类型注解
ACC_ENUM0x4000枚举类型枚举

[外链图片转存失败(img-p8BHIAJ8-1563279799434)(C:\Users\gjt\AppData\Roaming\Typora\typora-user-images\1563279214635.png)]

依次类推依次向后读取,目前软件可视化工作已经完成到了,计算inferface_count阶段,预计下周能够完成剩余字段的解析。

(工具还没完成,暂时不传哈哈哈哈哈)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值