Незаконни кодове за операции в 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)

Стек ... ..., елемент

Описание Индексът е неподписан байт, който трябва да бъде валиден индекс в постоянния пул на текущия клас (§3.6). Елементът на постоянния пул в индекса трябва вече да е разрешен и трябва да е широк една дума. Елементът се извлича от постоянния пул и се избутва в стека на операндите.

Бележки Операционният код на тази инструкция първоначално беше 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 изпълняват ли ги? Защо получавам VerifyErrors? Отписване? И имат ли предимство пред документираните си аналози?

Всяко прозрение ще бъде високо оценено.


person Xyene    schedule 11.02.2013    source източник


Отговори (3)


Първото издание на спецификацията на виртуалната машина на Java описва техника, използвана от една от ранните реализации на виртуалната машина на Java на Sun за ускоряване на интерпретацията на байт кодове. В тази схема кодовете за операции, които се отнасят до записи в постоянен пул, се заменят с код на операция _quick, когато записът в постоянния пул бъде разрешен. Когато виртуалната машина срещне инструкция _quick, тя знае, че записът в постоянния пул вече е разрешен и следователно може да изпълни инструкцията по-бързо.

Основният набор от инструкции на виртуалната машина на Java се състои от 200 еднобайтови кода за операции. Тези 200 кода за операции са единствените кодове за операции, които някога ще видите във файловете на класа. Реализациите на виртуална машина, които използват техниката _quick, използват вътрешно други 25 еднобайтови кода за операции, кодовете за операция _quick.

Например, когато виртуална машина, която използва техниката _quick, разреши постоянен запис в пул, посочен от ldc инструкция (стойност на кода на операцията 0x12), тя замества байта на кода на операцията на ldc в потока на байт кода с инструкция ldc_quick (стойност на кода на операцията 0xcb). Тази техника е част от процеса на замяна на символна препратка с директна препратка в ранната виртуална машина на Sun.

За някои инструкции, в допълнение към презаписването на нормалния код на операцията с код на операцията _quick, виртуална машина, която използва техниката _quick, презаписва операндите на инструкцията с данни, които представляват директната препратка. Например, в допълнение към замяната на invokevirtual opcode с invokevirtual_quick, виртуалната машина също така поставя отместването на таблицата на метода и броя на аргументите в двата операндни байта, които следват всяка invokevirtual инструкция. Поставянето на отместването на таблицата на метода в потока на байт кода след кода на операцията invokevirtual_quick спестява на виртуалната машина времето, необходимо за търсене на отместването в разрешения запис на постоянен пул.

Глава 8 от Вътре във виртуалната машина на Java

По принцип не можете просто да поставите кода на операцията във файла на класа. Само JVM може да направи това, след като разреши операндите.

person jdb    schedule 12.02.2013

Не знам за всички кодове за операции, които сте посочили, но три от тях—breakpoint, impdep1 и impdep2—са запазени кодове за операции документиран в Раздел 6.2 на Java Virtual Спецификация на машината. Отчасти се казва:

Два от запазените кодове за операции, номера 254 (0xfe) и 255 (0xff), имат съответно мнемоники impdep1 и impdep2. Тези инструкции са предназначени да осигурят задни врати или капани за специфична за изпълнението функционалност, внедрена съответно в софтуера и хардуера. Третият запазен код за операция, номер 202 (0xca), има мнемоничната точка на прекъсване и е предназначен да се използва от програмите за отстраняване на грешки за прилагане на точки на прекъсване.

Въпреки че тези кодове на операции са запазени, те могат да се използват само в реализация на виртуална машина на Java. Те не могат да се показват във валидни файлове на класове. . . .

Подозирам (от имената им), че останалите други кодове на операции са част от JIT механизма и също не могат да се появят във валиден клас файл.

person Ted Hopp    schedule 11.02.2013

Тези кодове на операции са запазени и не могат да се появят във валиден клас файл, оттук и VerifyError. JVM обаче ги използва вътрешно. Следователно представянето в паметта на някакъв байт код може да съдържа тези кодове на операции след модификация от VM. Това обаче е чисто детайл от изпълнението.

person Antimony    schedule 11.02.2013