JVM中的非法操作码

时间:2013-02-11 23:02:33

标签: java jvm bytecode opcode

我最近遇到了开发一个库,该库对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;

它们似乎是其他实现的替代品,但却有不同的操作码。经过长时间的网页浏览后,我在this document中提到了LDC*_QUICK操作码。

LDC_QUICK操作码上引用它:

  

操作从常量池推送项目

     

表单   ldc_quick = 203(0xcb)

     

堆叠 ......,项目

     

描述索引是一个无符号字节,必须是当前类的常量池的有效索引(第3.6节)。常数   索引处的池项必须已经解析且必须为1   字宽。该项目从常量池中获取并推送到   操作数堆栈。

     

注释此指令的操作码最初是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个?弃用?他们是否比他们记录的同行更具优势?

非常感谢任何见解。

3 个答案:

答案 0 :(得分:13)

  

Java虚拟机规范的第一版描述了Sun早期Java虚拟机实现之一使用的一种技术,以加速字节码的解释。在此方案中,当解析常量池条目时,引用常量池条目的操作码将被“_quick”操作码替换。当虚拟机遇到_quick指令时,它知道常量池条目已经解析,因此可以更快地执行指令。

     

Java虚拟机的核心指令集由200个单字节操作码组成。这200个操作码是您在类文件中看到的唯一操作码。使用“_quick”技术的虚拟机实现在内部使用另外25个单字节操作码,即“_quick”操作码。

     

例如,当使用_quick技术的虚拟机解析由ldc指令引用的常量池条目(操作码值0x12)时,它会使用ldc_quick指令替换字节码流中的ldc操作码字节(操作码值0xcb) )。此技术是在Sun的早期虚拟机中使用直接引用替换符号引用的过程的一部分。

     

对于某些指令,除了使用_quick操作码覆盖普通操作码之外,使用_quick技术的虚拟机会使用表示直接引用的数据覆盖指令的操作数。例如,除了用invokevirtual_quick替换invokevirtual操作码之外,虚拟机还将方法表偏移量和参数数量放入每个invokevirtual指令后面的两个操作数字节中。将方法表偏移量放在invokevirtual_quick操作码之后的字节码流中可以为虚拟机保存在已解析的常量池条目中查找偏移量所需的时间。

Chapter 8 of Inside the Java Virtual Machine

基本上你不能把操作码放在类文件中。只有JVM在解析操作数后才能这样做。

答案 1 :(得分:4)

我不知道您列出的所有操作码,但其中三个 - 断点 impdep1 impdep2 - 是Section 6.2 of the Java Virtual Machine Specification中记录的保留操作码。它部分地说:

  

两个保留的操作码(编号254(0xfe)和255(0xff))分别具有助记符 impdep1 impdep2 。这些指令旨在分别为在软件和硬件中实现的特定于实现的功能提供“后门”或陷阱。第三个保留操作码,编号202(0xca),具有助记符断点,旨在供调试器用于实现断点。

     

虽然这些操作码已被保留,但它们只能在Java虚拟机实现中使用。它们不能出现在有效的类文件中。 。 。

我怀疑(从他们的名字)其他操作码是JIT机制的一部分,也不能出现在有效的类文件中。

答案 2 :(得分:3)

这些操作码是保留的,不能出现在有效的类文件中,因此也是VerifyError。但是,JVM在内部使用它们。因此,在VM修改之后,某些字节码的内存表示可能包含这些操作码。但是,这纯粹是一个实现细节。