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

2014-11-24 02:38:39 · 作者: · 浏览: 0

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, if_icmp, ifnull, ifnonnull, if_acmp)。无条件跳转指令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类型结构,这些指令