0

我正在为 Java 创建一个静态分析工具,如果我可以从文件中的字节码中获取它,我正在分析的程序有一些信息将更容易获得.class

我不关心类文件中可能包含的每一个指令。例如,我可能只需要查看是否有任何getfield说明。

问题在于,由于每条指令的长度都是可变的,因此在一般情况下,我需要(在我的代码中)指定每个操作码的长度,然后才能确定(例如)getfield指令的开始和结束位置。

对于其他一些指令集(如x86),有诸如“任何低于 0x0F 的操作码为 1 个字节,任何等于或大于 0x0F 的操作码为两个字节”之类的规则。

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

4

3 回答 3

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字节码指令的处理被忽略了,它们需要填充,其实现需要了解缓冲区内的代码对齐情况,这由调用者控制。所以这取决于你的具体应用。lookupswitch请参阅和的文档tableswitch

当然,处理所有单字节指令default意味着代码不会捕获未知或无效指令。如果你想要安全,你必须插入箱子……</p>

于 2016-06-27T16:29:12.593 回答
1

JVM 规范对指令集相当清楚:

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

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

于 2016-06-25T20:52:54.603 回答
0

字节码设计中没有这样的功能。操作码只是按其含义分组。我见过的 JVM 实现使用表查找来获取字节码长度,并对wide,tableswitch​​ 和lookupswitch字节码进行特殊处理。

这样的表非常小:只有 202 个字节码。

请注意,长度不仅取决于操作码本身,还取决于字节码的位置tableswitch:并且lookupswitch由于对齐要求而具有可变长度的填充。

于 2016-06-26T03:43:48.563 回答