【问题标题】:Is there a clever way to determine the length of Java bytecode instructions?有没有一种聪明的方法来确定 Java 字节码指令的长度?
【发布时间】:2016-06-25 20:32:32
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 即使没有模式(并且有一些可变操作码),您也想编写一个静态分析工具,并且不想在最坏的情况下制作

标签: java jvm jvm-bytecode


【解决方案1】:

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

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 意味着代码不会捕获未知或无效指令。如果你想要安全,你必须插入箱子……

【讨论】:

    【解决方案2】:

    The JVM spec 对指令集相当清楚:

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

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

    【讨论】:

      【解决方案3】:

      字节码设计中没有这样的功能。就其含义而言,操作码只是grouped。我见过的 JVM 实现使用表查找来获取字节码长度,并对 widetableswitchlookupswitch 字节码进行特殊处理。

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

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

      【讨论】:

        猜你喜欢
        • 2011-08-18
        • 2022-01-23
        • 2020-08-28
        • 2015-04-07
        • 2021-03-02
        • 1970-01-01
        • 2013-11-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多