美文网首页
如何解码java.jar生成js类文件

如何解码java.jar生成js类文件

作者: kevinfuture | 来源:发表于2018-08-16 17:04 被阅读0次

    背景

        在一些场景下,我们需要通过识别java字节码的各项内容,生成对应其他语言或者方式的文件。在我们场景下,就是为了快速开发、能有良好的可读性的与易用性的架构代码,有时候,有些工具的提供也是必不可少的。

        有时候仍然要结合oracle官网文档来看,同时为了便于熟悉,仍然建议去编译jvm源码,其实不只是在熟悉node,对于java开发人员来说的话,对于jvm的声明周期也会很明朗,比如在class的加载机制中,根据c的文件名classloader就能很容易看出加载周期来!!!

    步骤

        一、获取要解析的java.jar

        二、解压java.jar到指定文件夹名称下,生成到对应文件夹目录的*.class文件

        三、完整结构化单个*.class文件的buffer

        四、从常量池以及其他二进制结构中取对应数据

        五、解析成指定内容

        六、选用nunjucks对结构化的内容编写模板文件

        七、执行代码并生成

        八、形成命令工具

    需要内容

    文件名、全限定类名、父类全限定类名、接口类型、符号引用、类信息、类注解、方法名、方法类型、方法注解、方法参数值、方法参数类型、方法参数名称、属性值、属性名、属性注解等等,需要分别从constantvalue、constant_pool、field_info、attribute_info、code、线性表、方法表等等内容获取!PS:暂时没有取处理innerClasses即内部类。

    生成方式

    模板渲染,为了省事当然最好的是用模板渲染一个文件并且生成!如果将所有的内容已经结构话了,剩下就是寻找一个合适的可以直接渲染,并且生成文件的组件了,最终选择了nunjucks。

    解析方式

    如果自己结构化二进制会非常耗时,幸亏有个巴西的哥们提供了一个插件java-class-tools!不过这个组件只是将java的二进制字节码结构化成了一个数据结构,里面的内容也全部都是buffer内容,仍然需要将结构化的二进制,解析成另一个数据结构,并把具体内容设置到该结构里。

    解析要点

    从常量池中获取数据;数据类型;数值精度。

    类似递归方式从常量池中解释对应字段的tag值,通过tag值执行不同的解析方式,然后再深度解析得到最终的数据。

    目前问题

    参数名称;8字节精度解析(所以没有生成构造函数中的默认常量属性值);

    解析详情:

    首先必须要了解,js类文件的定义需要的jvm字节码,也就是二进制字节码的组成。

    可以使用一些工具,比如winhex,结合oracle官网api看出组成形式

    ClassFile 接下来的,偏向于底层jvm对class的设计,偏向java知识。

    根据文档:一个class文件分别由magic、版本号、常量池、类名、方法名、属性值、接口名称等组成,也就是可以按照如上截图进行解析。

    使用反码工具解析一个test.class文件,可以看到对应的二进制码如下。

    Magic:又叫魔法值。常规开发中并没有特殊的意义,只是jvm对class文件的验证。每个class文件开头的四字节都是由它标记的“cafebabe”。

    Version:java中比如1.8.0_66,是区分大小版本的。也就是minor_version、major_version。其实是这样的,当一个java的版本如果发生了大的变化会修改major_version;如果仅是一些调优或者小的修改,并不影响整体当前版本的兼容性,则是minor_version变化。

    Constant_pool:class中绝大部分的信息,包括值、类型、注解等等,基本上都是从这里取出来的,如果这块明白了,其实也就不难理解关于入栈出栈的一些指令为什么有些限制了。

    Access_flags:访问标记。标记这个类的类型信息,比如public、final、abstract等。

    This_class:当前类索引值。因为是索引值,所以还需要通过索引值,从常量池中获取对应的全限定类名等。

    Super_class:父类索引值,一般如果没有继承,则是java.lang.Object,同上。

    Interfaces_count、interfaces[]:接口索引,同上。

    Fields_count、field_info:字段表结构,这里不仅是索引值了。包括索引值、名称索引、访问标记、属性值、描述索引等。访问标记不用多说,类比access_flags;我们可以根据name_index、descriptor_index获取到字段的名称与类型信息等。

    (类型信息:jvm底层对于基础数据类型、包装类、泛型、对象、array等表达方式均不同,详情可看光放doc的表格:

    可以看出,基础数据类型B:byte、C:char、D:double、F:float、I:int、J:long、S:short、Z:boolean。

    文档的4.3.2 Field Descriptors也有详细描述。比如L表示对象类型,也就是如果是一个对象类型,那么他的前面肯定跟着一个L。对于数组格式则以 ‘[’ 表示一维数组,以‘[[’ 表示二维数组,然后后边跟对应类型标记。比如double[] 即 [D。)

    Methods_cout、method_info、methods:同样这里就是方法类型,返回值等。入参s与回参s分别以 ‘()’ 分开,全限定类名也就是对象类型,则以 ‘;’ 分割表示 ,如果是void类型则是V表示。如官网example:

    Object mymethod(int i, double d, Thread t) =》 (IDLjava/lang/Thread;)Ljava/lang/Object;

    Attributes_count、Attribute_info:比如打日志过程中记录的报错信息的行号、文件名称、内部类、后来又支持的T泛型信息、异常、注解等,东西太多我也描述不全。

    PS:首先可以确认,以上信息是可以生成基本的pojo对象的。对于当前业务来说是够用的了。剩下的就是根据 当前形成的数据结构进行渲染到模板了。

    值得注意的是,除了以上内容,还需要注意常量池的结构与使用。针对不同的结构,jvm指定了不同的枚举常量,用于解析不同的内容:

    也就是代码中,会非常频繁的判断,同时因为一些解析方式的通用性,又要频繁的类似递归似得解析。场景如下:比如查找一个field,会获取到该类型的各种信息的索引值index与tag、然后如果优势一个对象类型,那么就又会再次执行当前的方式进行解释。

    Debugger

    Class二进制文件的数据结构如下:

    基本上大量信息是从constant_pool中获取的,看到这里我又想起一个点,可以看到constant_pool第一个也就是索引为0的时候值为null/undefined。根据《深入理解Java虚拟机》的描述:第0项空出来的“目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表的 ‘不引用任何一个常量池项目’ ” 的含义。就是空指向呗。

    待续,后续将会涉及到java字节码各项内容,包括异常处理、线性表、字节码指令的执行等等。

    内容已经实现,细节暂不阐述!!!

    相关文章

      网友评论

          本文标题:如何解码java.jar生成js类文件

          本文链接:https://www.haomeiwen.com/subject/miyobftx.html