Java字节码(.class文件)的代码解析

Java二进制指令代码以以下格式紧凑排列(opcode占一个字节):

opcode operand*

除了tableswitch和lookupswitch两条指令中间存在填充字节以外,其他指令都没有填充字节,即使在两条指令之间也没有。因而在读取指令的时候,要根据指令的定义读取。 

通过对上面Java指令集的分析可以知道,Java指令集中很大一部分没有操作数,因而对这部分指令,只需要读取一个字节的操作码,将操作码映射成助记符即可。 

而对其他带操作数的指令,则需要根据不同类型分析(由于apache中的bcel(Binary Code Engineering Library)对字节码的支持,操作码和助记符的映射可以用com.sun.org.apache.bcel.internal.Constats中提供的映射表数组来完成)。

1.       处理两条特殊的指令tableswitch和lookupswitch指令。

对这两条指令,首先都要去掉填充字符以使defaultbyte1索引号是字对齐的。

    private static void make4ByteAlignment(ByteSequence codes) {

       int usedBytes = codes.getIndex() % 4;

       int paddingBytes = (usedBytes == 0) ? 0 : 4 - usedBytes;

       for(int i = 0;i < paddingBytes;i++) {

           codes.readByte();

       }

}

对tableswitch指令,读取defaultoffset值,最小项的值,最大项的值以及在最小项和最大项之间每一项的offset值。并且将读取到的offset值和当前指令的基地址相加:

           int defaultOffset1 = baseOffset + codes.readInt();

           builder.append("\tdefault = #" + defaultOffset1);

           int low = codes.readInt();

           int high = codes.readInt();

           int npair1 = high - low + 1;

           builder.append(", npairs = " + npair1 + "\n");

           for(int i = low;i <= high;i++) {

              int match = i;

              offset = baseOffset + codes.readInt();

              builder.append(String.format("\tcase %d : #%d\n", match, offset));

        }

 

对lookupswitch指令,读取defaultoffset值,键值对数值(npairs),以及npairs对的键值对,将得到的offset值和当前指令的基地址相加:

           int defaultOffset2 = baseOffset + codes.readInt();

           builder.append("\tdefault = #" + defaultOffset2);

           int npairs2 = codes.readInt();

           builder.append(", npairs = " + npairs2 + "\n");

           for(int i = 0;i < npairs2;i++) {

              int match = codes.readInt();

              offset = baseOffset + codes.readInt();

              builder.append(String.format("\tcase %d : #%d\n", match, offset));

        }

 

2.       所有条件跳转指令都有两个字节的偏移量操作数(if<cond>, if_icmp<cond>, ifnull, ifnonnull, if_acmp<cond>)。无条件跳转指令goto和子例程跳转指令jsr也都是两个字节的偏移量作为操作数。

offset = baseOffset + codes.readShort();

builder.append(String.format("\t\t#%d\n", offset));

 

3.       对宽偏移量的跳转指令goto_w和子例程跳转指令jsr_w的操作数是四个字节的偏移量。

offset = baseOffset + codes.readInt();

builder.append(String.format("\t\t#%d\n", offset));

 

4.       wide指令,则继续读取下一条指令,并将wide参数设置为true

byteCodeToString(codes, pool, verbose, true);

 

5.       还有一些指令值以一个字节的局部变量索引号作为操作数的,如果有wide修饰,则用两个字节作为操作数,代表局部变量索引号。这样的指令有:aload, iload, fload, lload, dload, astore, istore, fstore, lstore, dstore, ret

if(wide) {

    index = codes.readUnsignedShort();

else {

    index = codes.readUnsignedByte();

}

builder.append(String.format("\t\t%%%d\n", index));

6.       iinc指令,以一个字节的局部变量索引号和一个自己的常量作为参数;如果以wide修饰,则该指令的局部变量索引号和常量都占两个字节。

    if(wide) {

       index = codes.readUnsignedShort();

       constValue = codes.readShort();

    } else {

       index = codes.readUnsignedByte();

       constValue = codes.readByte();

    }

builder.append(String.format("\t\t%d %d\n", index, constValue));

 

7.       对象操作指令,它们的操作数都是常量池中的索引,长度为两个字节。指向CONSTANT_Class_info类型的结构,这些指令有new, checkcast, instanceof, anewarray

index = codes.readUnsignedShort();

builder.append("\t\t" + pool.getClassInfo(index).toInstructionString(verbose) + "\n");

 

8.       所有字段操作指令,它们的操作数都是常量池中的索引,长度为两个字节。指向CONSTANT_Fieldref_info类型结构,这些指令有getfield, putfield, getstatic, putstatic

index = codes.readUnsignedShort();

builder.append("\t\t" + pool.getFieldRefInfo(index).toInstructionString(verbose) + "\n");

 

9.       非接口方法调用指令,也都是以两个字节的索引号作为操作数,指向常量池中的CONSTANT_Methodref_info类型结构,这些指令有invokespecial, invokevirtual, invokestatic

index = codes.readUnsignedShort();

builder.append("\t\t" + pool.getMethodRefInfo(index).toInstructionString(verbose) + "\n");

 

10.   接口方法调用指令invokeinterface,它有四个字节的操作数,前两个字节为常量池的索引号,指向CONSTANT_InterfaceMethodref_info类型,第三个字节为count,表示参数的字节数,最后一个字节为0值。

index = codes.readUnsignedShort();

int nargs = codes.readUnsignedByte(); //Historical, redundant

builder.append("\t\t" + pool.getInterfaceMethodRefInfo(index).toInstructionString(verbose));

builder.append(" : " + nargs + "\n");

codes.readUnsignedByte(); //reserved should be zero

 

11.   基本类型的数组创建指令newarray,它的操作数为一个字节的类型标识。

String type = Constants.TYPE_NAMES[codes.readByte()];

builder.append(String.format("\t\t(%s)\n", type));

 

12.   多维数组的创建指令multianewarray,它有三个字节的操作数,前两个字节为索引号,指向CONSTANT_Class_info类型,表示数组的类型,最后一个字节指定数组的维度。

index = codes.readUnsignedShort();

int dimensions = codes.readUnsignedByte();

builder.append(String.format("\t\t%s (%d)\n", pool.getClassInfo(index).getName(), dimensions));

 

13.   常量入栈指令ldc,以一个字节的索引号作为参数,指向CONSTANT_Integer_info、CONSTANT_Float_info、CONSTANT_String_info、CONSTANT_Class_info类型,表示要入栈的常量值(int类型值、float类型值、String引用类型值或对象引用类型值)。

index = codes.readUnsignedByte();

builder.append("\t\t" + pool.getPoolItem(index).toInstructionString(verbose) + "\n");

 

14.   宽索引的常量入栈指令ldc_w,以两个字节的索引号作为参数,指向CONSTANT_Integer_info、CONSTANT_Float_info、CONSTANT_String_info、CONSTANT_Class_info类型,表示要入栈的常量值(int类型值、float类型值、String引用类型值或对象引用类型值)。

index = codes.readUnsignedShort();

builder.append("\t\t" + pool.getPoolItem(index).toInstructionString(verbose) + "\n");

 

15.   宽索引的常量入栈指令ldc2_w,以两个字节的索引号作为参数,指向CONSTANT_Long_info、CONSTANT_Double_info类型,表示要入栈的常量值(long类型值、double类型值)。

index = codes.readUnsignedShort();

builder.append("\t\t" + pool.getPoolItem(index).toInstructionString(verbose) + "\n");

 

16.   bipush指令,以一个字节的常量作为操作数。

byte constByte = codes.readByte();

builder.append(“\t” + constByte);

 

17.   sipush指令,以两个字节的常量作为操作数。

short constShort = codes.readShort();

builder.append(“\t” + constShort);

 

以上还有一些没有完成的代码,包括字段(方法)的签名和描述符没有解析,有一些解析的格式还需要调整等。不管怎么样,总体的结构就是这样了,其它的都是细节问题,这里不讨论了。 

参见bcel项目的org.apache.bcel.classfile.Utility类.

时间: 2025-01-29 14:32:03

Java字节码(.class文件)的代码解析的相关文章

通过java字节码分析学习对象初始化顺序_java

复制代码 代码如下: mockery.checking(new Expectations() {            {               one(new Object()).toString();               will(returnValue(""));           }       }); 下面写一个写一个简单的类演示这个例子 复制代码 代码如下: public class Test {     int i = 1;    {        int

Java字节码(.class文件)格式详解(一)

小介:去年在读<深入解析JVM>的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分.现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事.当时也只是为了学习.这份资料主要参考<深入解析JVM>和<Java虚拟机规范>貌似是1.2版本的,整理出来的.里面包含了一些自己的理解和用实际代码的测试.有兴趣的童鞋可以研究研究.嘿嘿.要有错误也希望能为小弟指点出来,感激不尽.:) 1.总体格式 Class File format type

Java字节码(.class文件)格式详解(三)

2.11 在ClassFile.method_info.field_info中同时存在的Attribute 2.11.1     Synthetic Attribute Synthetic Attribute用于指示当前类.接口.方法或字段由编译器生成,而不在源代码中存在(不包含类初始函数和实例初始函数).相同的功能还有一种方式就是在类.接口.方法或字段的访问权限中设置ACC_SYNTHETIC标记.   Synthetic Attribute由JDK1.1中引入,以支持内嵌类和接口(neste

Java字节码深入解析

一:Java字节代码的组织形式 类文件{ OxCAFEBABE,小版本号,大版本号,常量池大小,常量池数组,访问控制标记,当前类信息,父类信息,实现的接口个数,实现的接口信息数组,域个数,域信息数组,方法个数,方法信息数组,属性个数,属性信息数组 } 二:查看方法 --- javap命令 例子:有一个Java类Demo.java public class Demo {      private String str1;      private String str2;      private

关于java字节码框架ASM的学习

一.什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为.Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称.方法.属性以及 Java 字节码(指令).ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 使用ASM框架需要导入asm的jar包,下载链接:

使用ASM操作Java字节码,实现AOP原理

本文通过一个的例子来实现:使用ASM动态生成Java字节码文件(.class) 或者 加载字节码后动态修改字节码,添加我们需要执行的代码,来模拟实现Spring AOP. 年底了,也没心情抠字了,把写demo包含的几个类代码直接贴出来吧,代码拷贝下来后可以直接使用,不会有什么其他错误. 使用 asm-5.0.3.jar demo工程的package为com.shanhy.demo.asm.hello 5个Java文件: AopClassAdapter.java 用来处理哪些方法需要进行修改 Ao

掌握Java字节码(转)

Java是一门设计为运行于虚拟机之上的编程语言,因此它需要一次编译,处处运行(当然也是一次编写,处处测试).因此,安装到你系统上的JVM是原生的程序,而运行在它之上的代码是平台无关的.Java字节码就是你写的源代码的中间表现形式,也就是你的代码编译后的产物.你的class文件就是字节码. 简单点说,字节码就是JVM使用的代码集,它在运行时可能会被JIT编译器编译成本地代码. 你玩过汇编语言或者机器代码吗?字节码就是类似的东西,不过业界中许多人也很少会用及它,因为基本没这个必要.然而它对于理解程序

通过Java字节码发现有趣的内幕之String篇(上)(转)

原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知识进行介绍,如果想了解更多的Java字节码或对其感兴趣的朋友可以先阅读字节码基础:JVM字节码初探. String字面量可以通过'=='判断两个字符串是否相同,是因为大家都知道'=='是用来判断两个对象的值引用地址是否一致,两个值一样的字符串字面量定义是否指向同一个值内存地址呢?答案是肯定的. 1

Java字节码

原文出处:https://www.ibm.com/developerworks/library/it-haggar_bytecode/index.html#opcode 作者:Peter Haggar 发表时间:2001 / 07 / 01 理解字节码可以使你变成一个更好的程序员 关于字节码的信息,以及这里提供的字节码,都是基于Java 2 SDK标准版v1.2.1 javac编译器的.其他编译器生成的字节码可能略有不同. 为什么要理解字节码? 字节码就是 Java 代码的中间表示,就像是汇编代

什么时候我们需要修改java字节码

问题描述 spring,hibernate都有用到cglib, javassist来修改java字节码但是我不是很清楚为什么要这样做?以及他们是怎么做到的是在运行时还是编译时修改的?他们怎么确保修改后的语义是正确的呢 解决方案 javassist没有用过,说说cglib在AOP中,Java原生自带的模式需要依赖于接口,但是一些实现里面,是通过继承来实现的,压根就没有接口,或者只有很底层的接口.这种场景下,就没办法通过接口的这种动态代理方式来实现动作拦截.所以只好依赖于cglib.当然,最优雅的还