2013-02-11 12 views
11

Recentemente mi sono imbattuto nello sviluppo di una libreria che esegue operazioni su bytecode JVM su alcuni opcode su cui non esiste documentazione (che ho trovato), ma che sono riconosciuti dall'implementazione di riferimento JVM. Ho trovato un elenco di questi, e sono:Codici opzionali non validi nella 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; 

Essi sembrano essere rimontaggi per le loro altre implementazioni, ma hanno diversi codici operativi. Dopo un lungo periodo di navigazione pagina dopo pagina attraverso Google, ho trovato una menzione degli opcode LDC*_QUICK in this document.

Citazione da esso sul LDC_QUICK codice operativo:

operazione Voce spinta dal pool di costanti

Forme ldc_quick = 203 (0xcb)

Stack ... ..., articolo

Descrizione L'indice è un byte senza segno che deve essere un indice valido nel pool costante della classe corrente (§3.6). La costante articolo di pool all'indice deve essere già stata risolta e deve essere una sola parola . L'elemento viene recuperato dal pool costante e spostato su stack di operandi.

Note L'opcode di questa istruzione era originariamente ldc. L'operando dell'istruzione ldc non viene modificato.

OK. Sembrava interessante, quindi ho deciso di provarlo. LDC_QUICK sembra avere lo stesso formato di LDC, quindi ho proceduto a modificare un codice operativo LDC in uno LDC_QUICK. Ciò ha comportato un fallimento, anche se la JVM ovviamente lo ha riconosciuto. Dopo aver tentato di eseguire il file modificato, la JVM si è arrestata in modo anomalo con il seguente output:

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) 

L'errore sopra riportato genera messaggi misti. Ovviamente, la verifica del file di classe non è riuscita: java.lang.VerifyError: Bad instruction: cc. Allo stesso tempo, la JVM ha riconosciuto l'opcode: @9: fast_bgetfield. Inoltre, sembra pensare che sia un'istruzione diversa, perché fast_bgetfield non implica una costante spinta ...

Penso che sia giusto dire che sono abbastanza confuso. Quali sono questi codici opzionali illegali? Le JVM le gestiscono? Perché sto ricevendo VerifyError s? Deprecation? E hanno un vantaggio rispetto alle loro controparti documentate?

Qualsiasi intuizione sarebbe molto apprezzata.

risposta

13

La prima edizione della specifica della macchina virtuale Java descriveva una tecnica utilizzata da una delle prime implementazioni Sun della Java virtual machine per accelerare l'interpretazione dei bytecode. In questo schema, gli opcode che fanno riferimento a voci di pool costanti vengono sostituiti da un opcode "_quick" quando viene risolta la voce del pool costante. Quando la macchina virtuale incontra un'istruzione _quick, sa che la voce del pool costante è già risolta e può quindi eseguire l'istruzione più velocemente.

Il set di istruzioni nucleo della macchina virtuale Java è costituito da 200 codici opzionali a byte singolo. Questi 200 opcode sono gli unici opcode che potrai mai vedere nei file di classe. Le implementazioni di macchine virtuali che utilizzano la tecnica "_quick" utilizzano internamente altri 25 opcode a byte singolo, gli opcode "_quick". Ad esempio, quando una macchina virtuale che utilizza la tecnica _quick risolve una voce di pool costante a cui fa riferimento un'istruzione ldc (valore opcode 0x12), sostituisce il byte opcode ldc nel flusso bytecode con un'istruzione ldc_quick (valore opcode 0xcb). Questa tecnica fa parte del processo di sostituzione di un riferimento simbolico con un riferimento diretto nella prima macchina virtuale di Sun.

Per alcune istruzioni, oltre a sovrascrivere il normale codice operativo con un _opzio opaco, una macchina virtuale che utilizza la tecnica _quick sovrascrive gli operandi dell'istruzione con i dati che rappresentano il riferimento diretto. Ad esempio, oltre a sostituire un opcode invokevirtual con un invokevirtual_quick, la macchina virtuale inserisce anche l'offset della tabella del metodo e il numero di argomenti nei due byte dell'operando che seguono ogni istruzione invokevirtual. Posizionando l'offset della tabella del metodo nel flusso bytecode seguendo l'opcode invokevirtual_quick, la macchina virtuale viene risparmiata il tempo necessario per cercare l'offset nella voce del pool costante risolto.

Chapter 8 of Inside the Java Virtual Machine

In sostanza non si può semplicemente mettere il codice operativo nel file di classe. Solo la JVM può farlo dopo aver risolto gli operandi.

4

Non so su tutti i codici operativi che hai elencato, ma tre di loro — breakpoint, impdep1, e impdep2 — sono riservati codici operativi documentati in Section 6.2 of the Java Virtual Machine Specification.Si dice, in parte:

Due dei codici operativi riservati, i numeri 254 (0xFE) e 255 (0xFF), hanno le mnemonici impdep1 e impdep2, rispettivamente. Queste istruzioni hanno lo scopo di fornire "backdoor" o trappole alle funzionalità specifiche dell'implementazione implementate rispettivamente in software e hardware. Il terzo opcode riservato, numero 202 (0xca), ha il punto di riferimento mnemonico e deve essere utilizzato dai debugger per implementare i punti di interruzione.

Sebbene questi codici opzionali siano stati riservati, possono essere utilizzati solo all'interno di un'implementazione di una macchina virtuale Java. Non possono apparire in file di classe validi. . . .

Sospetto (dai loro nomi) che il resto degli altri opcode faccia parte del meccanismo JIT e che non possa apparire in un file di classe valido.

3

Questi opcode sono riservati e non possono essere visualizzati in un file di classe valido, da cui il VerifyError. Tuttavia, la JVM li usa internamente. Pertanto, la rappresentazione in memoria di qualche bytecode potrebbe contenere questi codici opzionali dopo la modifica da parte della VM. Tuttavia, questo è puramente un dettaglio di implementazione.

Problemi correlati