2013-02-11 37 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修改後包含這些操作碼。但是,這純粹是一個實現細節。