有没有一种聪明的方法来确定Java字节码指令的长度?

时间:2016-06-25 20:32:32

标签: java jvm jvm-bytecode

我正在为Java创建一个静态分析工具,并且有一些关于我正在分析的程序的信息,如果我可以从{{{}中的字节码中获取它,将会更容易获得。 1}}文件。

我不关心可能在类文件中instructions的每一个人。例如,我可能只需要查看是否有.class条指令。

问题在于,由于每条指令都有一个可变长度,似乎在一般情况下,我需要(在我的代码中)指定每个操作码的长度,然后才能确定(例如){{1说明开始和结束。

对于其他一些指令集(如x86),有类似&#34的规则;任何低于0x0F的操作码都是1字节,任何等于或大于0x0F的操作都是两个字节。"

Java字节码指令中是否有这样的方便模式?

3 个答案:

答案 0 :(得分:4)

如果您尝试将指令操作码映射到指令大小,您将得到以下令人沮丧的表:

0 - 15       1 bytes
16           2 bytes
17           3 bytes
18           2 bytes
19 - 20      3 bytes
21 - 25      2 bytes
26 - 53      1 bytes
54 - 58      2 bytes
59 - 131     1 bytes
132          3 bytes
133 - 152    1 bytes
153 - 168    3 bytes
169          2 bytes
170 - 171    special handling
172 - 177    1 bytes
178 - 184    3 bytes
185 - 186    5 bytes
187          3 bytes
188          2 bytes
189          3 bytes
190 - 191    1 bytes
192 - 193    3 bytes
194 - 195    1 bytes
196          special handling
197          4 bytes
198 - 199    3 bytes
200 - 201    5 bytes

换句话说,没有在指令的数值和其位模式中编码的大小信息,但是还有另一个属性,您可以考虑某种模式:在~200个定义的指令中,大约有150个指令一个字节的大小,只留下约50条指令,需要任何处理。即便是这一小组指令也可以进一步细分为逻辑组,大多数占用三个字节,第二大指占用两个字节。

因此,执行指令的方法代码可能如下所示:

static void readByteCode(ByteBuffer bb) {
    while(bb.hasRemaining()) {
        switch(bb.get()&0xff) {
            case BIPUSH: // one byte embedded constant
            case LDC:    // one byte embedded constant pool index
            // follow-up: one byte embedded local variable index
            case ILOAD:  case LLOAD:  case FLOAD:  case DLOAD:  case ALOAD:
            case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case RET:
            case NEWARRAY: // one byte embedded array type
                bb.get();
                break;

            case IINC: // one byte local variable index, another one for the constant
            case SIPUSH: // two bytes embedded constant
            case LDC_W: case LDC2_W: // two bytes embedded constant pool index
            // follow-up: two bytes embedded branch offset
            case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE:
            case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE:
            case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE:
            case GOTO: case JSR: case IFNULL: case IFNONNULL:
            // follow-up: two bytes embedded constant pool index to member or type
            case GETSTATIC: case PUTSTATIC: case GETFIELD: case PUTFIELD:
            case INVOKEVIRTUAL: case INVOKESPECIAL: case INVOKESTATIC: case NEW:
            case ANEWARRAY: case CHECKCAST: case INSTANCEOF:
                bb.getShort();
                break;

            case MULTIANEWARRAY:// two bytes pool index, one byte dimension
                bb.getShort();
                bb.get();
                break;

            // follow-up: two bytes embedded constant pool index to member, two reserved
            case INVOKEINTERFACE: case INVOKEDYNAMIC:
                bb.getShort();
                bb.getShort();
                break;

            case GOTO_W: case JSR_W:// four bytes embedded branch offset
                bb.getInt();
                break;

            case LOOKUPSWITCH:
                // special handling left as an exercise for the reader...
                break;
            case TABLESWITCH:
                // special handling left as an exercise for the reader...
                break;
            case WIDE:
                int widened=bb.get()&0xff;
                bb.getShort(); // local variable index
                if(widened==IINC) {
                    bb.getShort(); // constant offset value
                }
                break;
            default: // one of the ~150 instructions taking one byte
        }
    }
}

我故意将一些指令分开,具有相同数量的后续字节,但具有不同的含义。毕竟,你想在某些地方插入一些实际的逻辑,我想。

请注意,省略了对两个switch字节码指令的处理,它们需要填充,其实现需要了解缓冲区内的代码对齐,这是调用者的控制。这取决于您的具体应用。请参阅lookupswitchtableswitch的文档。

当然,将所有单字节指令视为default意味着代码不会捕获未知或无效的指令。如果你想要安全,你必须插入案例...

答案 1 :(得分:1)

The JVM spec对指令集非常清楚:

  

操作数的数量和大小由操作码确定。

您可以尝试利用现有的字节码库,例如Apache Commons BCEL,并使用有关其中定义的操作码的元数据为您的应用构建单独的数据结构。例如,it contains a GETFIELD class以及表示JVM指令的getLength()方法。

答案 2 :(得分:0)

字节码设计中没有这样的功能。根据其含义,操作码只是grouped。 JVM实现我已经看到使用表查找字节码长度,特殊处理widetableswitchlookupswitch字节码。

Such table非常小:只有202个字节码。

注意,长度不仅取决于操作码本身,还取决于字节码的位置tableswitchlookupswitch由于对齐要求而导致的可变长度填充。