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

LMF*_*LMF 1 java jvm jvm-bytecode

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

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

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

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

Java字节码指令中有类似这样方便的模式吗?

Hol*_*ger 5

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

\n\n
0 - 15       1 bytes\n16           2 bytes\n17           3 bytes\n18           2 bytes\n19 - 20      3 bytes\n21 - 25      2 bytes\n26 - 53      1 bytes\n54 - 58      2 bytes\n59 - 131     1 bytes\n132          3 bytes\n133 - 152    1 bytes\n153 - 168    3 bytes\n169          2 bytes\n170 - 171    special handling\n172 - 177    1 bytes\n178 - 184    3 bytes\n185 - 186    5 bytes\n187          3 bytes\n188          2 bytes\n189          3 bytes\n190 - 191    1 bytes\n192 - 193    3 bytes\n194 - 195    1 bytes\n196          special handling\n197          4 bytes\n198 - 199    3 bytes\n200 - 201    5 bytes\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n
static void readByteCode(ByteBuffer bb) {\n    while(bb.hasRemaining()) {\n        switch(bb.get()&0xff) {\n            case BIPUSH: // one byte embedded constant\n            case LDC:    // one byte embedded constant pool index\n            // follow-up: one byte embedded local variable index\n            case ILOAD:  case LLOAD:  case FLOAD:  case DLOAD:  case ALOAD:\n            case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case RET:\n            case NEWARRAY: // one byte embedded array type\n                bb.get();\n                break;\n\n            case IINC: // one byte local variable index, another one for the constant\n            case SIPUSH: // two bytes embedded constant\n            case LDC_W: case LDC2_W: // two bytes embedded constant pool index\n            // follow-up: two bytes embedded branch offset\n            case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE:\n            case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE:\n            case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE:\n            case GOTO: case JSR: case IFNULL: case IFNONNULL:\n            // follow-up: two bytes embedded constant pool index to member or type\n            case GETSTATIC: case PUTSTATIC: case GETFIELD: case PUTFIELD:\n            case INVOKEVIRTUAL: case INVOKESPECIAL: case INVOKESTATIC: case NEW:\n            case ANEWARRAY: case CHECKCAST: case INSTANCEOF:\n                bb.getShort();\n                break;\n\n            case MULTIANEWARRAY:// two bytes pool index, one byte dimension\n                bb.getShort();\n                bb.get();\n                break;\n\n            // follow-up: two bytes embedded constant pool index to member, two reserved\n            case INVOKEINTERFACE: case INVOKEDYNAMIC:\n                bb.getShort();\n                bb.getShort();\n                break;\n\n            case GOTO_W: case JSR_W:// four bytes embedded branch offset\n                bb.getInt();\n                break;\n\n            case LOOKUPSWITCH:\n                // special handling left as an exercise for the reader...\n                break;\n            case TABLESWITCH:\n                // special handling left as an exercise for the reader...\n                break;\n            case WIDE:\n                int widened=bb.get()&0xff;\n                bb.getShort(); // local variable index\n                if(widened==IINC) {\n                    bb.getShort(); // constant offset value\n                }\n                break;\n            default: // one of the ~150 instructions taking one byte\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

请注意,两个字节码指令的处理switch被省略,它们需要填充,其实现需要了解缓冲区内的代码对齐情况,而缓冲区由调用者控制。因此,\xe2\x80\x99 取决于您的具体应用。lookupswitch请参阅和的文档tableswitch

\n\n

当然,处理所有单字节指令意味着default代码不会捕获未知或无效指令。如果你想安全,你\xe2\x80\x99必须插入case\xe2\x80\xa6

\n