2013-02-11 79 views
11

我最近在开发一个库时,在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长时间的翻页后,我偶然发现this document中的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是否运行它们?我为什么收到VerifyError?弃用?他们是否有优势于他们记录的同行?

任何有识之士将不胜感激。

回答

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在解析操作数后才能做到这一点。

4

我不知道你已经列出的操作码的,但他们三个人— 断点的,impdep1impdep2 —被保留在Section 6.2 of the Java Virtual Machine Specification记录操作码。它说,在部分:

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

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

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

3

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

相关问题