`
itoracja
  • 浏览: 141740 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论
阅读更多


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(&#8220;\t&#8221; + constByte);

17.   sipush指令,以两个字节的常量作为操作数。
short constShort = codes.readShort();
builder.append(&#8220;\t&#8221; + constShort);

以上还有一些没有完成的代码,包括字段(方法)的签名和描述符没有解析,有一些解析的格式还需要调整等。不管怎么样,总体的结构就是这样了,其它的都是细节问题,这里不讨论了。
参见bcel项目的org.apache.bcel.classfile.Utility类.2010年10月6日
0
1
分享到:
评论

相关推荐

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

    Java字节码是Java程序在运行时被JVM(Java虚拟机)解释执行的一种中间语言。每个Java类都由一个`.class`文件表示,其中包含了编译后的字节码指令。`.class`文件的结构非常严谨,它不仅包含了类的信息,如类名、方法...

    class运行器v6(可以运行java字节码文件的工具 含代码)

    标题中的“class运行器v6”是一个用于执行Java字节码文件的应用程序,它允许用户在没有完整Java环境的情况下运行单个.class文件。这个工具可能是由开发者为了方便测试或教学目的而创建的,特别是对于那些不熟悉或者...

    java反编译工具用于.class文件编译成.java文件

    Java反编译工具是开发者和逆向工程人员在处理Java字节码时的重要工具,它能够将已编译的.class文件转换回可读性强的.java源代码文件。这对于理解类库的功能、学习代码实现或者在丢失源代码的情况下进行调试都极其...

    java字节码文件查看工具,查看class文件

    Java字节码文件查看工具,如JD-GUI,是开发者们深入理解Java应用程序内部机制的重要辅助工具。这类工具能够帮助我们查看并分析.class文件,这些文件是Java源代码经过编译后的二进制形式,包含了运行时所需的所有指令...

    .class文件反编译工具。 .class文件转java文件

    在Java编程世界中,`.class`文件是Java源代码编译后的产物,它包含了Java程序的字节码。然而,有时我们需要查看这些字节码如何对应到原始的Java源代码,这就需要用到类文件反编译工具。本文将围绕".class文件反编译...

    Android-Java代码解析class文件

    1. **Java字节码结构**: - 类文件以魔数`CAFEBABE`开头,用于识别文件格式。 - 接着是版本信息,包括字节码版本号和常量池计数器。 - 常量池是类文件的核心部分,包含字符串、类名、方法名、字段名等各种常量。 ...

    java反编译(将class文件反向编译成java源文件)

    5. ** Krakatau**:这是一款相对新颖的反编译工具,专注于Java字节码的解析和重构。Krakatau对于研究字节码和理解底层工作原理非常有用。 反编译的过程通常包含以下几个步骤: 1. **字节码解析**:反编译器首先...

    java反编译工具,能将.class文件转换成.java文件

    jd-gui的工作原理是通过解析.class文件的结构,然后根据字节码指令推断出对应的源代码。这个过程并不总是完美无缺的,因为编译器可以优化代码,使得反编译后的源代码可能难以阅读。但是,对于大多数情况,jd-gui能...

    java反编译工具 对.class文件很有效

    反编译工具首先解析.class文件中的字节码指令,然后根据指令重建源代码结构,如类、方法、变量等。由于字节码并不直接对应源代码,反编译器需要模拟Java虚拟机(JVM)的执行流程来推测源代码。 5. **反编译的限制*...

    修改class字节码需要的工具.zip

    1. **Luyten 0.5.4**: 这是一个跨平台的Java字节码查看器和编辑器,可以帮助开发者查看并修改`.class`文件。Luyten提供了图形化的界面,使得字节码的分析和修改变得更加直观。你可以通过加载`.class`文件,查看其...

    JavaDecompiler打开class文件

    `JavaDecompiler`这个标题暗示我们将探讨如何使用Java反编译器来打开和解析.class文件。 Java类文件是Java虚拟机(JVM)执行的二进制格式,包含了程序的指令、常量池、字段和方法定义等信息。然而,这些信息对于...

    Test.class文件

    - **java**:这是一个命令,用于启动Java虚拟机(JVM),运行编译好的Java字节码文件。 #### 部分内容解析: - **路径说明**:文件路径“E:/java/Test.java”表明了源文件的具体位置。 - **命令操作**:通过“开始-...

    字节码实战包含class,字节码.zip

    当我们使用javac命令编译Java源代码时,Java编译器会生成.class文件,每个文件包含一个或多个类或接口的字节码。字节码文件结构包括魔术数字、版本信息、常量池、访问标志、类索引、父类索引、接口索引集合、字段表...

    python实现的简单的class解析以及简单执行class.zip

    这是一项有趣的技术,可以帮助我们理解Java字节码的工作原理,同时也可以在Python环境中运行Java代码。 首先,Java的Class文件是编译后的Java源代码的二进制表示,它包含了类的结构、方法定义、变量定义等信息。...

    class文件编译器.zip

    5. **Java字节码指令**: `.class`文件中的字节码由一系列的16位数字组成,每个数字代表一个特定的指令。例如,`aload_0`用于将局部变量表的第一个引用加载到操作数栈,`invokevirtual`用于调用实例方法等。 6. **...

    从一个class文件深入理解Java字节码结构

    【Java字节码结构解析】 Java程序在执行时,首先需要通过Java编译器将源代码(.java文件)编译成二进制的字节码文件(.class文件),这些字节码由Java虚拟机(JVM)解析并执行。深入理解字节码结构有助于我们了解...

    JAVA.class文件的反编译插件

    在Java编程语言中,`.class`文件是Java字节码的载体,它是源代码经过编译后的产物。这些文件包含了程序的结构信息,但通常人类无法直接阅读,因为它们是机器可理解的形式。为了理解`.class`文件的内容,开发者有时会...

    java源码查看器(查看.class文件源码)

    当开发人员编写完`.java`文件并执行`javac`命令进行编译时,这些源代码会被转化为字节码,存储在`.class`文件中。字节码是一种平台无关的中间语言,使得Java具有“一次编写,到处运行”的特性。 反编译是将`.class`...

    借助jclasslib与javassist修改java class字节码

    `jclasslib`则是一款图形化的Java字节码浏览器,主要用于解析和展示Java字节码文件(`.class`文件)的内容。它可以直观地显示字节码指令和常量池中的信息,并且提供了一定程度上的编辑功能,使得用户可以直接在图形...

Global site tag (gtag.js) - Google Analytics