Незаконные Opcodes в JVM
Недавно я столкнулся с разработкой библиотеки, которая выполняет операции с байт-кодом JVM с некоторыми кодами операций, на которых нет документации (которая была найдена мной), но которая распознается с помощью эталонной реализации JVM. Я нашел их список, и они:
BREAKPOINT = 202;
LDC_QUICK = 203;
LDC_W_QUICK = 204;
LDC2_W_QUICK = 205;
GETFIELD_QUICK = 206;
PUTFIELD_QUICK = 207;
GETFIELD2_QUICK = 208;
PUTFIELD2_QUICK = 209;
GETSTATIC_QUICK = 210;
PUTSTATIC_QUICK = 211;
GETSTATIC2_QUICK = 212;
PUTSTATIC2_QUICK = 213;
INVOKEVIRTUAL_QUICK = 214;
INVOKENONVIRTUAL_QUICK = 215;
INVOKESUPER_QUICK = 216;
INVOKESTATIC_QUICK = 217;
INVOKEINTERFACE_QUICK = 218;
INVOKEVIRTUALOBJECT_QUICK = 219;
NEW_QUICK = 221;
ANEWARRAY_QUICK = 222;
MULTIANEWARRAY_QUICK = 223;
CHECKCAST_QUICK = 224;
INSTANCEOF_QUICK = 225;
INVOKEVIRTUAL_QUICK_W = 226;
GETFIELD_QUICK_W = 227;
PUTFIELD_QUICK_W = 228;
IMPDEP1 = 254;
IMPDEP2 = 255;
Они, похоже, являются заменами для их других реализаций, но имеют разные коды операций. После долгого периода траления страницы после страницы через Google я натолкнулся на упоминание опций LDC*_QUICK
в этом документе.
Цитата из него в коде LDC_QUICK
:
Операция Нажмите элемент из пула констант
Формыldc_quick = 203 (0xcb)
Стек......, item
Описание Индекс - это неподписанный байт, который должен быть допустимым индексом в пуле констант текущего класса (п. 3.3). Постоянная элемент пула по индексу должен быть уже разрешен и должен быть одним слово широкое. Элемент извлекается из постоянного пула и нажимается на стек операнда.
Примечания Код операции этой команды был первоначально ldc. Операнд команды ldc не изменяется.
Хорошо. Казалось интересным, и поэтому я решил попробовать. LDC_QUICK
похоже, имеет тот же формат, что и LDC
, поэтому я приступил к изменению кода операции LDC
на LDC_QUICK
. Это привело к провалу, хотя JVM, очевидно, это узнал. После попытки запустить измененный файл JVM разбился со следующим выходом:
Exception in thread "main" java.lang.VerifyError: Bad instruction: cc
Exception Details:
Location:
Test.main([Ljava/lang/String;)V @9: fast_bgetfield
Reason:
Error exists in the bytecode
Bytecode:
0000000: bb00 0559 b700 064c 2bcc 07b6 0008 572b
0000010: b200 09b6 000a 5710 0ab8 000b 08b8 000c
0000020: 8860 aa00 0000 0032 0000 0001 0000 0003
0000030: 0000 001a 0000 0022 0000 002a b200 0d12
0000040: 0eb6 000f b200 0d12 10b6 000f b200 0d12
0000050: 11b6 000f bb00 1259 2bb6 0013 b700 14b8
0000060: 0015 a700 104d 2cb6 0016 b200 0d12 17b6
0000070: 000f b1
Exception Handler Table:
bci [84, 98] => handler: 101
Stackmap Table:
append_frame(@60,Object[#41])
same_frame(@68)
same_frame(@76)
same_frame(@84)
same_locals_1_stack_item_frame(@101,Object[#42])
same_frame(@114)
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Вышеупомянутая ошибка дает смешанные сообщения. Очевидно, что проверка файла класса не удалась: java.lang.VerifyError: Bad instruction: cc
. В то же время JVM распознал код операции: @9: fast_bgetfield
. Кроме того, кажется, что это другая инструкция, потому что fast_bgetfield
не подразумевает постоянного нажатия...
Я считаю, что справедливо сказать, что я очень смущен. Что это за незаконные коды операций? Работает ли JVM? Почему я получаю VerifyError
s? Устаревшие? И есть ли у них преимущество перед своими документально оформленными коллегами?
Любое понимание будет принята с благодарностью.
Ответы
Ответ 1
В первом выпуске спецификации виртуальной машины Java описана техника, используемая одной из ранних реализаций Sun виртуальной машины Java для ускорения интерпретации байткодов. В этой схеме коды операций, которые ссылаются на константные записи пула, заменяются кодом операции "_quick" , когда разрешена запись константного пула. Когда виртуальная машина встречает команду _quick, она знает, что запись константного пула уже разрешена и поэтому может быстрее выполнять инструкцию.
Набор основных команд виртуальной машины Java состоит из 200 однобайтовых кодов операций. Эти 200 опкодов являются единственными кодами операций, которые вы когда-либо видели в файлах классов. Реализации виртуальной машины, использующие метод "_quick" , используют еще 25 однобайтовых кодов операций внутри, коды "_quick" .
Например, когда виртуальная машина, использующая метод _quick, разрешает постоянную запись пула, на которую ссылается инструкция ldc (значение кода операции 0x12), она заменяет байты opcode ldc в потоке байт-кода командой ldc_quick (значение кода операции 0xcb). Этот метод является частью процесса замены символьной ссылки прямой ссылкой в ранней виртуальной машине Sun.
Для некоторых инструкций, помимо перезаписывания нормального кода операции с помощью кода операции _quick, виртуальная машина, использующая метод _quick, перезаписывает операнды инструкции данными, которые представляют собой прямую ссылку. Например, помимо замены invokevirtual opcode с invokevirtual_quick, виртуальная машина также помещает смещение таблицы методов и количество аргументов в два байта операнда, которые следуют за каждой invokevirtual инструкцией. Размещение смещения таблицы методов в потоке байт-кода после кода операции invokevirtual_quick сохраняет виртуальную машину за время, необходимое для поиска смещения в разрешенной записи пула констант.
Глава 8 виртуальной машины Java
В принципе, вы не можете просто поместить код операции в файл класса. Только JVM может это сделать после того, как он разрешит операнды.
Ответ 2
Я не знаю обо всех кодах операций, которые вы указали, но три из них: breakpoint, impdep1 и impdep2 — - зарезервированные коды операций, задокументированные в Section 6.2 спецификации виртуальной машины Java. В нем говорится, в частности:
Два зарезервированных кода операций, числа 254 (0xfe) и 255 (0xff), имеют мнемонику impdep1 и impdep2 соответственно. Эти инструкции предназначены для обеспечения "задних дверей" или ловушек для реализации конкретных функций, реализованных в программном и аппаратном обеспечении, соответственно. Третий зарезервированный код операции, номер 202 (0xca), имеет мнемоническую точку останова и предназначен для использования отладчиками для реализации контрольных точек.
Хотя эти коды операций зарезервированы, они могут использоваться только в реализации виртуальной машины Java. Они не могут отображаться в действительных файлах классов.,.
Я подозреваю (от их имен), что остальные другие коды операций являются частью механизма JIT и также не могут отображаться в допустимом файле класса.
Ответ 3
Эти коды операций зарезервированы и не могут отображаться в допустимом файле класса, следовательно, VerifyError. Однако JVM использует их внутренне. Следовательно, представление в памяти некоторого байт-кода может содержать эти коды операций после изменения VM. Однако это просто детализация реализации.