最近,我在开发一个对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操作码:
操作推项从常量池Forms ldc_quick = 203 (0xcb) Stack .,项目 Description索引是一个无符号字节,必须是当前类常量池的有效索引(§3.6)。索引处的常量池项必须已被解析,并且必须是一个字宽。从常量池中获取项并将其推送到操作数堆栈上。 Notes本指令的操作码最初是最不发达国家。不修改最不发达国家指令的操作数。
好的。看起来很有趣,所以我决定试一试。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?反对?他们是否比有文件记录的同行更有优势?
任何洞察力都将不胜感激。
发布于 2013-02-12 00:22:45
Java虚拟机规范的第一版描述了Sun早期Java虚拟机实现之一使用的一种技术,以加快字节码的解释。在此方案中,当解析常量池项时,将引用常量池项的操作码替换为"_quick“操作码。当虚拟机遇到_quick指令时,它知道常量池条目已经被解析,因此可以更快地执行指令。 Java虚拟机的核心指令集由200个单字节操作码组成.这200个操作码是您将在类文件中看到的唯一操作码。使用"_quick“技术的虚拟机实现在内部使用另外25个单字节操作码,即"_quick”操作码。 例如,当使用_quick技术的虚拟机解析最不发达国家指令(操作码值0x12)所引用的常量池条目时,它将字节码流中的最不发达国家操作码字节替换为ldc_quick指令(操作码值0xcb)。这项技术是在Sun早期虚拟机中将符号引用替换为直接引用的过程的一部分。 对于某些指令,除了用_quick操作码覆盖普通操作码之外,使用_quick技术的虚拟机用表示直接引用的数据覆盖指令的操作数。例如,除了用invokevirtual_quick替换invokevirtual之外,虚拟机还将方法表偏移量和参数数放入每个InvokeVirtual指令后面的两个操作数字节中。将方法表偏移量放在invokevirtual_quick操作码后面的字节码流中,可以节省虚拟机在解析常量池条目中查找偏移量所需的时间。
Java虚拟机内部第8章
基本上,您不能只是将操作码放在类文件中。只有JVM在解析操作数之后才能这样做。
发布于 2013-02-11 23:10:26
我不知道您列出的所有操作码,但是其中的三个操作码--断点、impdep1和in 2--都是在Java虚拟机规范第6.2节中记录的保留操作码。它的部分内容是:
两个保留的操作码,号码254 (0xfe)和255 (0xff),分别具有助记符impdep1和impdep2。这些指令的目的是提供“后门”或陷阱的具体实现功能的实现,分别在软件和硬件。第三个保留操作码,编号202 (0xca),具有助记符断点,用于调试器实现断点。 尽管已经保留了这些操作码,但它们只能在Java虚拟机实现中使用。它们不能出现在有效的类文件中。。。。
我怀疑(从它们的名称)其他操作码是JIT机制的一部分,也不能出现在有效的类文件中。
发布于 2013-02-11 23:39:47
这些操作码是保留的,不能出现在有效的类文件中,因此出现了VerifyError。但是,JVM在内部使用它们。因此,某些字节码的内存表示在VM修改后可能包含这些操作码。然而,这纯粹是一个实现细节。
https://stackoverflow.com/questions/14822557
复制相似问题