【问题标题】:Illegal Opcodes in the JVMJVM 中的非法操作码
【发布时间】:2013-01-27 04:04:09
【问题描述】:

我最近在开发一个对 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 会运行它们吗?为什么我会收到VerifyErrors?弃用?与记录在案的同行相比,他们有优势吗?

任何见解将不胜感激。

【问题讨论】:

    标签: java jvm bytecode opcode


    【解决方案1】:

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

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

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

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

    Chapter 8 of Inside the Java Virtual Machine

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        我不知道你列出的所有操作码,但其中三个——breakpointimpdep1impdep2——是Section 6.2 of the Java Virtual Machine Specification 中记录的保留操作码。它部分表示:

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-07
          • 2013-12-08
          • 2013-08-20
          • 1970-01-01
          • 2013-01-10
          相关资源
          最近更新 更多