当前位置: 首页 > 知识库问答 >
问题:

JVM中的非法操作码

宦博超
2023-03-14

我最近在开发一个对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;

它们似乎是其他实现的替代品,但有不同的操作码。在谷歌上一页又一页地搜索了很长一段时间后,我在这篇文档中遇到了一个关于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:错误指令:cc。同时,JVM识别了操作码:@9:fast_bgetfield。另外,它似乎认为它是一个不同的指令,因为fast_bgetfield并不意味着常量推送...

我觉得可以说我很困惑。这些非法操作码是什么?我们运行它们吗?为什么我收到VerifyErrors?反对?他们是否比记录在案的同行有优势?

任何见解都将不胜感激。

共有3个答案

仇阳州
2023-03-14

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

邓禄
2023-03-14

我不知道您列出的所有操作码,但其中三个断点、impdep1和impdep2是Java虚拟机规范第6.2节中记录的保留操作码。它在一定程度上说:

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

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

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

卓星波
2023-03-14

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

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

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

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

Java虚拟机内部的第8章

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

 类似资料:
  • Runtime data Area 什么是操作数栈? 与局部变量表一样,均以字长为单位的数组。不过局部变量表用的是索引,操作数栈是弹栈/压栈来访问。操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区。 存储的数据与局部变量表一致含int、long、float、double、reference、returnType,操作数栈中byte、short、char压栈前(bipush)会被转为

  • 问题内容: JVM运行时数据区为每个正在执行的方法提供单独的堆栈。它包含操作数堆栈和局部变量。每次加载变量时,都需要先到操作数堆栈,然后再到局部变量。为什么不直接操作局部变量表,并进行一些看似重复的工作? 问题答案: 具有直接操作数的指令集必须对每个指令中的操作数进行编码。相反,对于使用操作数堆栈的指令集,操作数是隐式的。 当查看小的琐碎运算(例如将常量加载到变量中)时,隐式参数的优势并不明显。本

  • 我有这样的代码 我试图与kategorikode_kategori,但出现错误,请帮助我

  • 如果能帮助我重写一些使用反射的Java代码,以便在Java10上删除编译器中的警告,我将不胜感激: 这就是所讨论的Java方法: 这是GitHub上的问题代码:https://github.com/librepdf/openpdf/blob/master/openpdf/src/main/java/com/lowagie/text/pdf/mappedrandomaccessfile.java#l

  • 在尝试构建时,。我按照其他人的建议添加了,但仍然得到相同的错误。 有什么建议吗? pom.xml

  • 我做了一个简单的科学测验java应用程序,有4个框架:登录、菜单、测试和结果。 前三个帧工作正常,但当我单击显示结果时,它显示错误。这是我在Test JFrame中的代码。如果您愿意,我也可以发送其他帧的编码 这是我的错误 测试JFrame编码