手写 Class 字节码解析技术(一)

     上章节写到关于 java热部署功能的技术点,简单的阐述了关于类加载的问题,既然了解到了class这个知识点了,那就不能不刨根问底的对class解析一番。看看能不能完成一个类似于ASM、Javassist之类的java字节码操纵功能。

  • Class文件是一组以8位字节为基础单位的二进制流,当遇到需要8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。各项按照严格顺序连续存放的,它们之间没有任何填充或对齐作为各项间的分隔符号。每个结构体有结构u1 u2 u3 u4规定结构体的长度,分别代表1个字节、2个字节、4个字节、8个字节的无符号数。

       结构说明:

符号中文名结构作用规则
magic魔数U4所有的由Java编译器编译而成的class文件的前4个字节都是 “0xCAFEBABE” ,JVM用来判断是否是可加载的.class文件 
minor_version次版本号u2JVM 
major_version主版本号u2JVM加载class文件的时候,判断是否可加载,如果JDK.Mj_Version<Class.Mj_Version,则认为加载不了。需要重新编译JDK1.0->45;1.7->51
constant_pool_count常量池中常量数量u2记录了constatn_pool中constant_pool_info的数量index从1开始;index=0:某些指向常量池的索引值的数据在特定的情况下表达“不引用任何一个常量池项”
constatn_pool常量池数据区constant_pool_info结构包含Class文件结构及其子结构中引用的所有 字符串常量、类、接口、字段名和其它常量(字面量和符号引用)tag bytes:第一个字节,用于识别哪种类型的常量。index=constant_pool_count - 1
access_flags访问标志u2表示某个类或者接口的访问权限及基础属性 
this_class类索引u2this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口 
super_class父类索引u2super_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的直接父类 
interfaces_count接口计数器u2当前类或接口的直接父类接口数 
interfaces接口信息数据区(接口表)u2interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值, 它的长度为 interfaces_count 
fields_count字段计数区u2fields_count的值表示当前Class文件 fields[] 数组的成员个数 
fields字段信息数据区(字段表)field_info结构fields[]数组中的每个成员都必须是一个fields_info结构 的数据项,用于表示当前类或接口某个字段的完整描述,但不包括从父类或父接口继承的部分 
methods_count方法计数器u2methods_count的值表示当前Class 文件 methods[]数组的成员个数 
methods方法信息数据区(方法表)method_info 结构methods[] 数组中的每个成员都必须是一个 method_info 结构 的数据项,用于表示当前类或接口中某个方法的完整描述 
attributions_count属性计数器u2attributes_count的值表示当前 Class 文件attributes表的成员个数 
attributions属性信息数据区(属性表)attribute_info结构attributes 表的每个项的值必须是attribute_info结构在Java 7 规范里,Class文件结构中的attributes表的项包括下列定义的属性InnerClasses 、 EnclosingMethod 、 Synthetic 、Signature、SourceFile,SourceDebugExtension 、Deprecated、RuntimeVisibleAnnotations 、RuntimeInvisibleAnnotations以及BootstrapMethods属性

     

     定义一个简单的javaMainHello类

public class MainHello {

    private static  final   String main_type="1";

    private  String name;

    private  int age;

    public void hello(){
        System.out.println("我是新的 MainHello");
    }

    public String  myQQ(){
        return "123456";
    }

}

以UltraEdit打开calss编译文件,如下。

次版本号是:0000
主版本号是:0034,十进制是52,表示采用的是jdk1.8

  • 以class 文件流以下标0开始,从0到8(u4)的位置定义为magic。从8到12(u2)的位置定义为 JDK次版本号。以此类推。只要推动指针位按照class结构体的长度去读取数据,就可以从class流文件中,读取出完整的java 代码信息。

  于是我们定义   ClazzAnalysis 类用于解析class字节流

public class ClazzAnalysis {

    private  int fetchLen = 0; //当前读取结构体的长度

    private  int start_pointer = 0; //文件指针下标

    private  String hexString = ""; // 十六进制数据总串

    private Class_info info = new Class_info(); // 存放所有数据的 class_info 表



     public String fetchClazzTructure(int len) {

        fetchLen = len;  //当前结构体长度

        //从上一个指针位 读取 len 个长度的流数据

        String cutStr = hexString.substring(start_pointer, start_pointer + len);

        //读取完数据后,指针移到下一个结构指针位

        start_pointer = start_pointer + fetchLen;

        return cutStr;

    }

}

         fetchClazzTructure函数为数据读取的代码,代码不多,只要将完整的class文件流按照结构长度,以此去读取就行,难就你难在,这calss的结构真心多,每个结构体中间又引用了其他的结构体,你得把每一个结构体的关系都得理清,不然只要其中一个指针位不对,后面的数据就全不对,笔者捣鼓这结构体代码时,也是弄得想哭啊。

         magic、minor_version、major_version读取方式并不复杂。fetchClazzTructure 数据读取完之后,调用DataHandler 接口用于数据的转换和保存。

 //魔数
        fetchClazzTructure(8, info, new DataHandler() {
            @Override
            public Object handle(String cutStr, Class_info info) {
                info.setMagic(cutStr);
                return cutStr;
            }
        });


        //JDK 次版本号
        fetchClazzTructure(4, info, new DataHandler() {
            @Override
            public Object handle(String cutStr, Class_info info) {
                String i = Integer.parseInt(cutStr, 16) + "";
                info.setMinor_version(i);
                return i;
            }
        });


        //JDK 次版本号
        fetchClazzTructure(4, info, new DataHandler() {
            @Override
            public Object handle(String cutStr, Class_info info) {
                String i = Integer.parseInt(cutStr, 16) + "";
                info.setMajor_version(i);
                return i;
            }
        });

      数据往下走,我们就到了calss的常量结构了。

二、class文件常量池

  1.  所有 符号(变量、方法、类) 都是通过cp_info结构来表示。
  2. 同一文件的所有符号(变量、方法、类) 的相同值都会指向同一地址
  3. 对所有的基本类型(int、float、long、double) 都是 字面类型+bytes表示
  4. 对所有引用类型(String,class),都会用单独的 Constant_utf8_info 构造,然后在通过 Constant_String_info, constant_Class_info 指针只过去
  5. 每个个类而言,其class文件中至少要有两个CONSTANT_Class_info常量池项,用来表示自己的类信息和其父类(Object)信息

     常量池结构 

 

      constant_pool_count 定义了此class 常量池的个数,constatn_pool定义常量池数据内容。

      读取方式为,从constatn_pool 指针位开始,读取1bit的长度,为tag ,此tag描述了常量的类型。info[]为数据流。在根据tag的类型,再去根据不同类型的结构体去解析info[]流中的内容。

      以CONSTANT_Utf8_info为例,constant_pool_count 读取完,指向constatn_pool,读取1bit的长度,tag值为CONSTANT_Utf8_info。指针向前读取2bit数据,意为当前字符串所占用X的长度,于是指针向前读取X个单位,读取的内容就是当前CONSTANT_Utf8_info的常量字符串的值。

      读取的方式也不复杂,复杂的地方在于结构体的复杂性,14种常量类型的读取指针位置一个都不能乱,只要其中一个错位了,后续的指针位就完全错位,导致数据读取错误。

 

于是我们按照常量结构体类型,定义对应的java类型。并定义一个数据读取转换的接口,由每一种数据类型实现onTransform函数,完成不同类型的数据解析。

    于是我们定义ConstantFactory  用于根据tag 分发数据类型,完成数据的解析。


public  class ConstantFactory  {

    public Constant_X_info transform(int tag, String tagByte, ClazzAnalysis clazzAnalysis) {

        Constant_X_info tagConst = null;

        if (tag == 1) {
            tagConst = new Constant_Utf8_info();
        }else  if(tag==3){
            tagConst = new Constant_Integer_info();
        }else  if(tag==4){
            tagConst = new Constant_Float_info();
        }else  if(tag==5){
            tagConst = new Constant_Long_info();
        }else  if(tag==6){
            tagConst = new Constant_Double_info();
        }else  if(tag==7){
            tagConst = new Constant_Class_info();
        }else  if(tag==8){
            tagConst = new Constant_String_info();
        }else  if(tag==9){
            tagConst = new Constant_Fieldref_info();
        }else  if(tag==10){
            tagConst = new Constant_Methodref_info();
        }else  if(tag==11){
            tagConst = new Constant_InterfaceMethodref_info();
        }else  if(tag==12){
            tagConst = new Constant_NameAndType_info();
        }else  if(tag==15){
            tagConst = new Constant_MethodHandle_info();
        }else  if(tag==16){
            tagConst = new Constant_MethodType_info();
        }else  if(tag==18){
            tagConst = new Constant_InvokeDynamic_info();
        }


        if (tagConst != null) tagConst.onTransform(tag, tagByte, clazzAnalysis);

        return tagConst;
    }

在于是,我们有了一下代码

  //常量池数量
        fetchClazzTructure(4, info, new DataHandler() {
            @Override
            public Object handle(String cutStr, Class_info info) {
                int cp_count = FileUtil.hex2Integer(cutStr);
                info.setCp_count(cp_count);
                return cp_count;
            }
        });

        // 5.常量池
        Map<Integer, Constant_X_info> constant_pool_Map = new HashMap<Integer, Constant_X_info>();

        ConstantFactory constantFactory = new ConstantFactory();

        for (int i = 0; i < info.getCp_count() - 1; i++) {

        

            String constTag = fetchClazzTructure(2);
            int tag = FileUtil.hex2Integer(constTag);

            Constant_X_info constantXInfo = constantFactory.transform(tag, constTag, this);

            constant_pool_Map.put(i, constantXInfo);
        }

        info.setConstan_poolMap(constant_pool_Map);

       测试代码如下,为了方便阅读,就只打印了Constant_Utf8_info 其中的内容。通过javac -verbose  MainHello.class  命令查看该class的字节码信息。对照检查常量池数据是否吻合。 

Microsoft Windows [版本 6.1.7600]
版权所有 (c) 2009 Microsoft Corporation。保留所有权利。

D:\it.work\classLoaderTest>javap -verbose MainHello.class
Classfile /D:/it.work/classLoaderTest/MainHello.class
  Last modified 2019-7-24; size 720 bytes
  MD5 checksum e3ec731813868c40f58b2dee477ddec4
  Compiled from "MainHello.java"
public class cn.app.wuzhi.MainHello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#27         // java/lang/Object."<init>":()V
   #2 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/Print
Stream;
   #3 = String             #30            // 我是新的 MainHello
   #4 = Methodref          #31.#32        // java/io/PrintStream.println:(Ljava/
lang/String;)V
   #5 = String             #33            // 123456
   #6 = Class              #34            // cn/app/wuzhi/MainHello
   #7 = Class              #35            // java/lang/Object
   #8 = Utf8               main_type
   #9 = Utf8               Ljava/lang/String;
  #10 = Utf8               ConstantValue
  #11 = String             #36            // 1
  #12 = Utf8               name
  #13 = Utf8               age
  #14 = Utf8               I
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcn/app/wuzhi/MainHello;
  #22 = Utf8               hello
  #23 = Utf8               myQQ
  #24 = Utf8               ()Ljava/lang/String;
  #25 = Utf8               SourceFile
  #26 = Utf8               MainHello.java
  #27 = NameAndType        #15:#16        // "<init>":()V
  #28 = Class              #37            // java/lang/System
  #29 = NameAndType        #38:#39        // out:Ljava/io/PrintStream;
  #30 = Utf8               我是新的 MainHello
  #31 = Class              #40            // java/io/PrintStream
  #32 = NameAndType        #41:#42        // println:(Ljava/lang/String;)V
  #33 = Utf8               123456
  #34 = Utf8               cn/app/wuzhi/MainHello
  #35 = Utf8               java/lang/Object
  #36 = Utf8               1
  #37 = Utf8               java/lang/System
  #38 = Utf8               out
  #39 = Utf8               Ljava/io/PrintStream;
  #40 = Utf8               java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (Ljava/lang/String;)V
{
  public cn.app.wuzhi.MainHello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/app/wuzhi/MainHello;

  public void hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljav
a/io/PrintStream;
         3: ldc           #3                  // String 我是新的 MainHello
         5: invokevirtual #4                  // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 14: 0
        line 15: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcn/app/wuzhi/MainHello;

  public java.lang.String myQQ();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #5                  // String 123456
         2: areturn
      LineNumberTable:
        line 21: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       3     0  this   Lcn/app/wuzhi/MainHello;
}
SourceFile: "MainHello.java"

   从Constant pool结构中,可以看到下标8-10,12-26,30,33-42,为Constant_Utf8_info类型,与打印结果吻合,完美。

   章节一 完,这里就先写到常量结构,下章节详述其他结构体。

     文章参考 

          https://blog.csdn.net/sinat_38259539/article/details/78248454  

          https://my.oschina.net/u/2246410/blog/1800670

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
Promise是一种异步编程的解决方案,可以避免回调地狱的问题。下面是使用class手写一个Promise的示例代码: ``` class MyPromise { constructor(executor) { this.status = 'pending'; // Promise的状态 this.value = undefined; // Promise的结果 this.reason = undefined; // Promise的错误信息 const resolve = (value) => { if (this.status === 'pending') { this.status = 'fulfilled'; this.value = value; } }; const reject = (reason) => { if (this.status === 'pending') { this.status = 'rejected'; this.reason = reason; } }; try { executor(resolve, reject); } catch (error) { reject(error); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value; onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason }; const promise2 = new MyPromise((resolve, reject) => { if (this.status === 'fulfilled') { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); } else if (this.status === 'rejected') { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); } else { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); }); } }); return promise2; } catch(onRejected) { return this.then(null, onRejected); } } function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { reject(new TypeError('Chaining cycle detected for promise')); } let called = false; if (x !== null && (typeof x === 'object' || typeof x === 'function')) { try { const then = x.then; if (typeof then === 'function') { then.call(x, (y) => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, (r) => { if (called) return; called = true; reject(r); }); } else { resolve(x); } } catch (error) { if (called) return; called = true; reject(error); } } else { resolve(x); } } ``` 以上是一个简单的Promise实现,其中包含了Promise的基本功能,如状态管理、then方法、catch方法等。需要注意的是,Promise的实现并不是一件简单的事情,需要考虑到各种边界情况,才能保证其正确性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值