Есть ли умный способ определить длину инструкций байт-кода Java?

Я создаю инструмент статического анализа для Java, и есть некоторая информация о программах, которые я анализирую, которую будет легче получить, если я смогу получить ее из байт-кода в .class файлах.

Меня не волнуют все до единой инструкции, которые могут быть в файле класса. Например, мне может понадобиться только посмотреть, есть ли какие-либо getfield инструкции.

Проблема в том, что, поскольку каждая инструкция имеет переменную длину, кажется, что в общем случае мне нужно (в моем коде) указать длину каждого отдельного кода операции, прежде чем я смогу определить, где (например) getfield инструкции начинаются и заканчиваются.

Для некоторых других наборов инструкций (например, x86) существуют такие правила, как "любой код операции ниже 0x0F равен 1 байт, все, что равно или больше 0x0F, составляет два байта».

Есть ли такой удобный шаблон в инструкциях байт-кода Java?


person LMF    schedule 25.06.2016    source источник
comment
Даже если шаблона нет (и есть несколько кодов операций варадика), вы хотите написать инструмент статического анализа и не хотите, в худшем случае, создавать таблицу сопоставления из ‹‹ 256 кодов операций. Удачи.   -  person msw    schedule 25.06.2016


Ответы (3)


Если вы попытаетесь сопоставить коды операций инструкций с размерами инструкций, вы получите следующую обескураживающую таблицу:

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< /a> и tableswitch.

Конечно, обработка всех однобайтовых инструкций как default подразумевает, что код не будет перехватывать неизвестные или недопустимые инструкции. Если вы хотите безопасности, вам придется вставить чехлы…

person Holger    schedule 27.06.2016

Спецификация JVM достаточно ясно о наборе инструкций:

Количество и размер операндов определяются кодом операции.

Вы можете попытаться использовать существующую библиотеку байт-кода, такую ​​как Apache Commons BCEL, и использовать метаданные о кодах операций, определенных там, для создания отдельной структуры данных для вашего приложения. Например, он содержит GETFIELD класс< /a> вместе с методом getLength(), который представляет инструкцию JVM.

person ck1    schedule 25.06.2016

В дизайне байт-кода такой функции нет. Коды операций просто сгруппированы по своему значению. Реализации JVM, которые я видел, используют поиск по таблице длины байт-кода со специальной обработкой wide, tableswitch и lookupswitch байт-коды.

Такая таблица довольно маленькая: всего 202 байткода.

Обратите внимание, что длина зависит не только от самого кода операции, но и от позиции байт-кода: tableswitch и lookupswitch имеют отступы переменной длины из-за требований к выравниванию.

person apangin    schedule 26.06.2016