编程语言
首页 > 编程语言> > 有没有一种聪明的方法来确定Java字节码指令的长度?

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

作者:互联网

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

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

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

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

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

解决方法:

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

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
        }
    }
}

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

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

当然,默认情况下处理所有单字节指令意味着代码不会捕获未知或无效的指令.如果你想要安全,你将不得不插入案件……

标签:java,jvm,jvm-bytecode
来源: https://codeday.me/bug/20191003/1847298.html