Obsah
3. Struktura zásobníkového rámce (a zásobníku operandů) při zavolání metody
4. Informace o zásobníkovém rámci metody uložené v bajtkódu
5. Zdrojový kód testovací třídy Test3
6. Rozhraní com.sun.jdi.LocalVariable
7. Rozhraní com.sun.jdi.Type a všechna odvozená rozhraní
8. Výpis informací o lokálních proměnných v metodě printMethodLocalVariablesInfo()
9. Kompletní zdrojový kód demonstračního příkladu JDIMethodVariables
10. Spuštění demonstračního příkladu a ukázka informací vypsaných na standardní výstup
11. Repositář se zdrojovými kódy demonstračního příkladu JDIMethodVariables i podpůrných skriptů
1. Pohled pod kapotu JVM – čtení informací o parametrech i o lokálních proměnných metod s využitím rozhraní JDI
V předchozí části seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme si popsali, jakým způsobem je možné s využitím rozhraní JDI (Java Debugger Interface) získat základní informace o zásobníkových rámcích. Víme již, že pro každé vlákno se ve virtuálním stroji Javy vytváří v průběhu volání metod takzvané zásobníkové rámce (stack frames). Jednotlivé zásobníkové rámce jednoho vlákna jsou vzájemně propojeny a v každém zásobníkovém rámci se nachází jak parametry předané volané metodě, tak i oblast vyhrazená pro lokální proměnné této metody. Velikost této paměťové oblasti, na níž se můžeme s určitým zjednodušením dívat jako na pole s možností indexace jednotlivých prvků, je zjištěna již při překladu zdrojových kódů a virtuální stroj Javy automaticky provádí kontrolu, zda nedochází k překročení indexu, tj. ke čtení či zápisu mimo vyhrazenou oblast (jedná se ovšem o pole nehomogenní, protože jeho prvky mohou mít různý typ). Navíc je součástí zásobníkového rámce i takzvaný zásobník operandů (operand stack) používaný při vyhodnocování výrazů či při volání dalších metod – bližší informace o zásobníku operandů budou uvedeny v následující kapitole.
Logická struktura paměti spravované virtuálním strojem Javy, která nás nyní bude zajímat, je zobrazena pod tímto odstavcem:
+============================================+ | Zásobník (stack) | +============================================+ | | | +------------------------------------+ | | | Zásobníkový rámec #1 (stack frame) | | | +------------------------------------+ | | | | | | | +---------------------+ | | | | | Zásobník operandů | | | | | +---------------------+ | | | | | | | | +---------------------+ | | | | | Parametry metody | | | | | |.....................| | | | | | Lokální proměnné | | | | | +---------------------+ | | | | | | | +------------------------------------+ | | | | +------------------------------------+ | | | Zásobníkový rámec #2 (stack frame) | | | +------------------------------------+ | | | | | | | +---------------------+ | | | | | Zásobník operandů | | | | | +---------------------+ | | | | | | | | +---------------------+ | | | | | Parametry metody | | | | | |.....................| | | | | | Lokální proměnné | | | | | +---------------------+ | | | | | | | +------------------------------------+ | | | | +------------------------------------+ | | | Zásobníkový rámec #3 (stack frame) | | | +------------------------------------+ | | | | | | | +---------------------+ | | | | | Zásobník operandů | | | | | +---------------------+ | | | | | | | | +---------------------+ | | | | | Parametry metody | | | | | |.....................| | | | | | Lokální proměnné | | | | | +---------------------+ | | | | | | | +------------------------------------+ | | | | :::::::::::::::::::::::::::::::::::::::: | | :: Další zásobníkové rámce vytvářené :: | | :: v čase běhu aplikace při volání :: | | :: metod. :: | | :::::::::::::::::::::::::::::::::::::::: | +============================================+
2. Zásobník operandů
Většina instrukcí virtuálního stroje Javy pracuje s operandy uloženými na takzvaném zásobníku operandů (operand stack). Zásobník operandů (v tomto případě se již jedná o skutečný zásobník typu LIFO – Last In, First Out) je vytvářen v čase běhu aplikace pro každou zavolanou metodu, což mj. znamená, že je při spuštění metody vždy prázdný (zásobník operandů je podle specifikace součástí zásobníkového rámce, jeho konkrétní umístění však je libovolné). Již v čase překladu zdrojového kódu je pro každou metodu zjištěno, jak velká oblast paměti má být pro zásobník operandů vyhrazena a samozřejmě je prováděna kontrola, zda se v době běhu aplikace tato velikost nepřekročí (to by se nemělo u validního bajtkódu v žádném případě stát). Virtuální stroj Javy kontroluje typy operandů uložených na zásobník operandů a zajišťuje, že se nad těmito operandy budou provádět pouze typově bezpečné operace. V praxi to například znamená, že není možné na zásobník uložit dvě hodnoty typu float a následně provést instrukci iadd, protože tato instrukce vyžaduje, aby na zásobníku byly uloženy dvě hodnoty typu int (i když float i int mají shodnou bitovou šířku).
Podívejme se nyní na jednoduchou ukázku využití zásobníku operandů při vyhodnocování složitějšího aritmetického výrazu:
class Test { void test() { int a=10; int b=20; int c=30; int d=40; int z=a+b*(c-d); } }
Přeložený bajtkód metody Test.test() je následující:
void test(); Code: 0: bipush 10 2: istore_1 // naplnění proměnné a hodnotou 10 3: bipush 20 5: istore_2 // naplnění proměnné b hodnotou 20 6: bipush 30 8: istore_3 // naplnění proměnné c hodnotou 30 9: bipush 40 11: istore 4 // naplnění proměnné d hodnotou 40 13: iload_1 // vložení a na zásobník [a] 14: iload_2 // vložení b na zásobník [a b] 15: iload_3 // vložení c na zásobník [a b c] 16: iload 4 // vložení d na zásobník [a b c d] 18: isub // provedení operace x=c-d [a b c-d] 19: imul // provedení operace y=b*x [a b*(c-d)] 20: iadd // provedení operace a+y [a+b*(c-d)] 21: istore 5 // uložení výsledku do z [] 23: return }
3. Struktura zásobníkového rámce (a zásobníku operandů) při zavolání metody
Zásobník operandů je taktéž použit při volání metod pro předání parametrů těmto metodám a pro vyzvednutí jejich návratových hodnot. Záleží jen na konkrétní implementaci virtuálního stroje Javy, jakým způsobem dojde při volání metody ke konverzi mezi zásobníkem operandů volající metody a lokálními parametry metody volané, většinou však můžeme předpokládat, že se minimalizují přesuny dat. Ukažme si způsob použití zásobníku operandů jak pro provádění aritmetické operace součtu, tak i pro předání parametrů volající metodě a získání výsledku z volané metody. Mějme jednoduchou třídu Test s dvojicí metod add() a callAdd():
class Test { // provedení součtu dvou celých čísel static int add(int a, int b) { int c=a+b; return c; } // zavolá metodu pro provedení součtu dvou čísel // a uloží návratovou hodnotu do své lokální proměnné static void callAdd() { int result = add(1234,5678); } }
Přeložený bajtkód obou metod vypadá následovně. Všechny poznámky jsou do disasemblovaného bajtkódu samozřejmě dopsány ručně:
// v metodě add je zásobník operandů použit pouze pro provedení operace součtu static int add(int, int); Code: 0: iload_0 // uložení prvního parametru metody na zásobník operandů 1: iload_1 // uložení druhého parametru metody na zásobník operandů 2: iadd // provedení operace součtu s odstraněním obou operandů 3: istore_2 // vyzvednutí výsledku součtu a uložení do lokální proměnné 4: iload_2 // opětovné uložení obsahu lokální proměnné na zásobník 5: ireturn // při operaci ireturn se využije hodnota z vrcholu zásobníku
// v této metodě se zásobník operandů používá i pro komunikaci s metodou Test.add(int, int) static void callAdd(); Code: 0: sipush 1234 // uložení konstanty 1234 na zásobník operandů 3: sipush 5678 // uložení konstanty 5678 na zásobník operandů 6: invokestatic #2 // zavolání statické metody Test.add(int, int) 9: istore_0 // výsledná hodnota je umístěna na vrchol zásobníku operandů, // tak ji odtud vyzvedneme a uložíme do lokální proměnné 10: return // návrat z metody
Povšimněte si, že překladač javac neprovedl žádné optimalizace, i když se nám například může zdát dvojice po sobě jdoucích instrukcí istore2 a iload2 nadbytečná (ve skutečnosti je to nutné pro správnou funkci debuggeru).
V dalším testovacím příkladě si opět ukážeme způsob předávání parametrů do volaných metod:
class Test2 { static void method1(int a, int b, boolean vypln1, boolean vypln2, int c) { a = b; b = c; c = a; } static void method2(long a, long b, boolean vypln1, boolean vypln2, long c) { a = b; b = c; c = a; } static void method3(float a, float b, boolean vypln1, boolean vypln2, float c) { a = b; b = c; c = a; } static void method4(double a, double b, boolean vypln1, boolean vypln2, double c) { a = b; b = c; c = a; } static void method5(String a, String b, boolean vypln1, boolean vypln2, String c) { a = b; b = c; c = a; } }
Disassemlovaný bajtkód všech pěti testovacích metod s ručně dopsanými komentáři vypadá následovně:
static void method1(int, int, boolean, boolean, int); Code: Stack=1, Locals=5, Args_size=5 0: iload_1 // index je součástí operačního kódu instrukce 1: istore_0 // index je součástí operačního kódu instrukce 2: iload 4 // index je uveden v bajtu za operačním kódem 4: istore_1 // index je součástí operačního kódu instrukce 5: iload_0 // index je součástí operačního kódu instrukce 6: istore 4 // index je uveden v bajtu za operačním kódem 8: return // návrat do volající metody
static void method2(long, long, boolean, boolean, long); Code: Stack=2, Locals=8, Args_size=5 0: lload_2 // index je součástí operačního kódu instrukce 1: lstore_0 // index je součástí operačního kódu instrukce 2: lload 6 // index je uveden v bajtu za operačním kódem 4: lstore_2 // index je součástí operačního kódu instrukce 5: lload_0 // index je součástí operačního kódu instrukce 6: lstore 6 // index je uveden v bajtu za operačním kódem 8: return // návrat do volající metody
static void method3(float, float, boolean, boolean, float); Code: Stack=1, Locals=5, Args_size=5 0: fload_1 // index je součástí operačního kódu instrukce 1: fstore_0 // index je součástí operačního kódu instrukce 2: fload 4 // index je uveden v bajtu za operačním kódem 4: fstore_1 // index je součástí operačního kódu instrukce 5: fload_0 // index je součástí operačního kódu instrukce 6: fstore 4 // index je uveden v bajtu za operačním kódem 8: return // návrat do volající metody
static void method4(double, double, boolean, boolean, double); Code: Stack=2, Locals=8, Args_size=5 0: dload_2 // index je součástí operačního kódu instrukce 1: dstore_0 // index je součástí operačního kódu instrukce 2: dload 6 // index je uveden v bajtu za operačním kódem 4: dstore_2 // index je součástí operačního kódu instrukce 5: dload_0 // index je součástí operačního kódu instrukce 6: dstore 6 // index je uveden v bajtu za operačním kódem 8: return // návrat do volající metody
static void method5(java.lang.String, java.lang.String, boolean, boolean, java.lang.String); Code: Stack=1, Locals=5, Args_size=5 0: aload_1 // index je součástí operačního kódu instrukce 1: astore_0 // index je součástí operačního kódu instrukce 2: aload 4 // index je uveden v bajtu za operačním kódem 4: astore_1 // index je součástí operačního kódu instrukce 5: aload_0 // index je součástí operačního kódu instrukce 6: astore 4 // index je uveden v bajtu za operačním kódem 8: return // návrat do volající metody
4. Informace o zásobníkovém rámci metody uložené v bajtkódu
Při práci s parametry metod i s jejich lokálními proměnnými velmi brzy zjistíme, že při použití rozhraní JDI lze získat všechny potřebné informace o těchto objektech pouze v případě, že třídy byly přeloženy s přepínačem -g, tedy s vložením přídavných metainformací od generovaného bajtkódu. Ve skutečnosti totiž virtuální stroj Javy nepotřebuje při běhu znát přesná jména ani typy jednotlivých parametrů/lokálních proměnných. Typy jsou kontrolovány při každé operaci a jména není nutné znát vůbec – pouze pozice (indexy) parametrů a lokálních proměnných. Aby tedy náš demonstrační příklad pracoval korektně, musíme použít překlad s použitím již zmíněného přepínače -g. Podívejme se nyní na to, jakým způsobem se změní bajtkód při překladu testovací třídy Test3, jejíž zdrojový kód naleznete v následující kapitole.
Překlad třídy Test3 bez přepínače -g:
Compiled from "Test3.java" public class Test3 extends java.lang.Object SourceFile: "Test3.java" minor version: 0 major version: 50 Constant pool: === vynecháno === { public Test3(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public void run(int, char); Code: Stack=3, Locals=5, Args_size=3 0: bipush 10 2: istore_3 3: aconst_null 4: astore 4 6: aload_0 7: fconst_1 8: iload_1 9: i2f 10: fdiv 11: invokevirtual #2; //Method foo:(F)V 14: return LineNumberTable: line 4: 0 line 5: 3 line 6: 6 line 7: 14 public void foo(float); Code: Stack=6, Locals=3, Args_size=2 0: aload_0 1: astore_2 2: aload_0 3: fload_1 4: iconst_2 5: newarray float 7: dup 8: iconst_0 9: fload_1 10: fastore 11: dup 12: iconst_1 13: fload_1 14: fastore 15: ldc #3; //String Hello world! 17: invokevirtual #4; //Method bar:(F[FLjava/lang/String;)V 20: return LineNumberTable: line 10: 0 line 11: 2 line 12: 20 public void bar(float, float[], java.lang.String); Code: Stack=2, Locals=4, Args_size=4 0: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_3 4: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: goto 0 LineNumberTable: line 16: 0 StackMapTable: number_of_entries = 1 frame_type = 0 /* same */ public static void main(java.lang.String[]); Code: Stack=3, Locals=1, Args_size=1 0: new #7; //class Test3 3: dup 4: invokespecial #8; //Method "<init>":()V 7: bipush 42 9: bipush 97 11: invokevirtual #9; //Method run:(IC)V 14: return LineNumberTable: line 21: 0 line 22: 14 }
Překlad třídy Test3 s použitím přepínače -g:
Compiled from "Test3.java" public class Test3 extends java.lang.Object SourceFile: "Test3.java" minor version: 0 major version: 50 Constant pool: === vynecháno === { public Test3(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LTest3; public void run(int, char); Code: Stack=3, Locals=5, Args_size=3 0: bipush 10 2: istore_3 3: aconst_null 4: astore 4 6: aload_0 7: fconst_1 8: iload_1 9: i2f 10: fdiv 11: invokevirtual #2; //Method foo:(F)V 14: return LineNumberTable: line 4: 0 line 5: 3 line 6: 6 line 7: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this LTest3; 0 15 1 value I 0 15 2 znak C 3 12 3 int_value I 6 9 4 object_value Ljava/lang/Object; public void foo(float); Code: Stack=6, Locals=3, Args_size=2 0: aload_0 1: astore_2 2: aload_0 3: fload_1 4: iconst_2 5: newarray float 7: dup 8: iconst_0 9: fload_1 10: fastore 11: dup 12: iconst_1 13: fload_1 14: fastore 15: ldc #3; //String Hello world! 17: invokevirtual #4; //Method bar:(F[FLjava/lang/String;)V 20: return LineNumberTable: line 10: 0 line 11: 2 line 12: 20 LocalVariableTable: Start Length Slot Name Signature 0 21 0 this LTest3; 0 21 1 value F 2 19 2 object_value Ljava/lang/Object; public void bar(float, float[], java.lang.String); Code: Stack=2, Locals=4, Args_size=4 0: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_3 4: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: goto 0 LineNumberTable: line 16: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this LTest3; 0 10 1 value F 0 10 2 array [F 0 10 3 message Ljava/lang/String; StackMapTable: number_of_entries = 1 frame_type = 0 /* same */ public static void main(java.lang.String[]); Code: Stack=3, Locals=1, Args_size=1 0: new #7; //class Test3 3: dup 4: invokespecial #8; //Method "<init>":()V 7: bipush 42 9: bipush 97 11: invokevirtual #9; //Method run:(IC)V 14: return LineNumberTable: line 21: 0 line 22: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 args [Ljava/lang/String; }
Vidíme, že se ve druhém případě v bajtkódu objevila nová struktura s metainformacemi nazvaná LocalVariableTable. Právě tuto strukturu bude používat náš demonstrační příklad JDIMethodVariables popsaný v navazujících kapitolách.
5. Zdrojový kód testovací třídy Test3
Dnešní demonstrační příklad nazvaný JDIMethodVariables bude vypisovat parametry metod jednoduché testovací třídy Test3, v níž se postupně zavolají metody main()→run()→foo()→bar(). V poslední metodě je implementována nekonečná smyčka, která umožní snadné připojení k cílové JVM přes rozhraní JDI:
public class Test3 { public void run(int value, char znak) { int int_value = 10; Object object_value = null; foo(1.0f/value); } public void foo(float value) { Object object_value = this; bar(value, new float[] {value, value}, "Hello world!"); } public void bar(float value, float[] array, String message) { while (true) { System.out.println(message); } } public static void main(String[] args) { new Test3().run(42, 'a'); } }
6. Rozhraní com.sun.jdi.LocalVariable
Seznam všech viditelných lokálních proměnných i parametrů metod lze ze zásobníkového rámce získat s využitím metody:
com.sun.jdi.StackFrame.visibleVariables()
Návratovou hodnotou této metody je seznam (List) objektů, jejichž třídy implementují rozhraní com.sun.jdi.LocalVariable. Tyto objekty nesou všechny důležité informace o proměnných/parametrech metod, které dokáže rozhraní JDI z cílového virtuálního stroje získat. V následující tabulce je seznam metod, jež lze s objekty typu com.sun.jdi.LocalVariable zavolat:
# | Návratový typ | Metoda + parametry | Popis |
---|---|---|---|
1 | boolean | equals(Object obj) | zjištění, zda se jedná o shodnou proměnnou/parametr |
2 | String | genericSignature() | vrátí signaturu generického typu, pokud je ovšem k proměnné přiřazen |
3 | int | hashCode() | vrací hashovací hodnotu (většinou nezajímavé) |
4 | boolean | isArgument() | dokáže rozlišit mezi parametrem a skutečnou lokální proměnnou |
5 | boolean | isVisible(StackFrame frame) | zjistí, zda je proměnná dostupná v předaném zásobníkovém rámci |
6 | String | name() | vrátí čitelné jméno proměnné |
7 | String | signature() | vrátí signaturu proměnné či parametru |
8 | Type | type() | vrátí typ proměnné (com.sun.jdi.Type) |
9 | String | typeName() | vrátí textovou (čitelnou) reprezentaci typu proměnné/parametru |
Metoda signature() vrací údaje v následujícím formátu:
# | Signatura | Typ v Javě |
---|---|---|
1 | Z | boolean |
2 | B | byte |
3 | C | char |
4 | S | short |
5 | I | int |
6 | J | long |
7 | F | float |
8 | D | double |
9 | L | plně kvalifikované jméno třídy ukončené středníkem |
10 | [typ | typ[] |
Totéž platí i pro metodu genericSignature().
7. Rozhraní com.sun.jdi.Type a všechna odvozená rozhraní
Zastavme se na chvíli u metody com.sun.jdi.LocalVariable.type(). Tato metoda vrací objekt, jehož třída implementuje rozhraní com.sun.jdi.Type, ve skutečnosti se však vždy bude jednat o třídu implementující i nějaké odvozené rozhraní. Pro primitivní datové typy existuje celkem osm takových rozhraní:
# | Datový typ | Rozhraní |
---|---|---|
1 | boolean | com.sun.jdi.BooleanType |
2 | byte | com.sun.jdi.ByteType |
3 | char | com.sun.jdi.CharType |
4 | double | com.sun.jdi.DoubleType |
5 | float | com.sun.jdi.FloatType |
6 | int | com.sun.jdi.IntegerType |
7 | long | com.sun.jdi.LongType |
8 | short | com.sun.jdi.ShortType |
Pro reference či pro pole se bude jednat o tato rozhraní:
# | Datový typ | Rozhraní |
---|---|---|
1 | třída | com.sun.jdi.ClassType |
2 | rozhraní | com.sun.jdi.InterfaceType |
3 | pole | com.sun.jdi.ArrayType |
Rozhraní com.sun.jdi.ArrayType navíc předepisuje metodu componentType(), z níž lze získat typ prvků pole.
8. Výpis informací o lokálních proměnných v metodě printMethodLocalVariablesInfo()
Podívejme se nyní, jakým způsobem je výpis informací o lokálních proměnných a parametrech metod implementován v našem demonstračním příkladu JDIMethodVariables. Tento příklad je založen na zdrojovém kódu minule popsaného příkladu JDIStackTraceList (http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8fad31d36206/jdi/JDIStackTraceList.java). Došlo však k úpravě metody printThreadInfo() takovým způsobem, že se podrobnější výpis obsahu zásobníkových rámců provede pouze pro vlákno se jménem „main“:
/** * Vypis informaci o vlaknech existujicich ve sledovanem virtualnim stroji. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printThreadInfo(VirtualMachine virtualMachine) { System.out.println("Thread info:"); System.out.println(" UniqueID Thread name Status Suspended"); List<ThreadReference> threads = virtualMachine.allThreads(); // projit vsemi vlakny for (ThreadReference thread : threads) { String threadName = thread.name(); String threadStatus = getThreadStatus(thread); String threadSuspended = getThreadSuspended(thread); long uniqueID = thread.uniqueID(); System.out.format(" %8d %-20s %-12s %-5s\n", uniqueID, threadName, threadStatus, threadSuspended); // pro "hlavni" vlakno vypsat i informace o zasobnikovych ramcich if (MAIN_THREAD_NAME.equals(threadName)) { printDetailInfoAboutStackFrames(thread); } } System.out.println(); }
Nás bude dnes zajímat především výpis jmen, typů a hodnot parametrů metod i jejich lokálních proměnných. Tato funkcionalita je implementována v metodě pojmenované printMethodLocalVariablesInfo(), v níž se nejprve zjistí seznam všech viditelných proměnných/parametrů a posléze se pro každý z těchto objektů zjistí jeho jméno, jméno typu a taktéž hodnota (zde se s využitím ""+… provedl převod hodnoty na řetězec, což bude skvěle fungovat i v případě, kdy je hodnota/reference nastavena na null):
/** * Vypis informaci o lokalnich promennych. * * @param frame zasobnikovy ramec */ private static void printMethodLocalVariablesInfo(StackFrame frame) { List<LocalVariable> variables; try { // precist seznam vsech viditelnych lokalnich promennych variables = frame.visibleVariables(); System.out.format("%20sVariables %d\n", "", variables.size()); // vypis informaci o kazde lokalni promenne for (LocalVariable variable : variables) { String name = variable.name(); String type = variable.typeName(); // pomerne primitivni zpusob ziskani retezcove podoby // hodnoty promenne String value = "" + frame.getValue(variable); // vypsat vsechny zjistene informace o promenne System.out.format("%20s%s %s = %s\n", "", type, name, value); } } catch (AbsentInformationException e) { // neudelame nic, protoze udaje o lokalnich promennych // skutecne nemuseji byt k dispozici } }
9. Kompletní zdrojový kód demonstračního příkladu JDIMethodVariables
V předchozích kapitolách byly popsány ty nejdůležitější metody, které jsou součástí dnešního demonstračního příkladu nazvaného JDIMethodVariables. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto příkladu:
import java.io.IOException; import java.util.List; import java.util.Map; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.Bootstrap; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; import com.sun.jdi.VirtualMachine; import com.sun.jdi.VirtualMachineManager; import com.sun.jdi.connect.AttachingConnector; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.IllegalConnectorArgumentsException; /** * Pripojeni k bezicimu virtualnimu stroji Javy, * ktery byl spusten s parametry: * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida * * Po pripojeni se vypisou obsahy zasobnikovych ramcu vlakna "main" * vcetne jmen, typu a hodnot vsech lokalnich promennych * (pokud jsou tyto informace dostupne) * * @author Pavel Tisnovsky */ public class JDIMethodVariables { /** * Jmeno "hlavniho" vlakna. */ private static final String MAIN_THREAD_NAME = "main"; /** * Navratovy kod pouzity pri ukoncovani sledovane JVM. */ private static final int EXIT_VALUE = 0; /** * Jmeno konektoru, ktery pro pripojeni pouziva sockety. */ private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach"; public static void main(String[] args) { // ziskat (jedinou) instanci tridy VirtualMachineManager VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager(); // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors(); // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety AttachingConnector connector = getSocketAttachConnector(connectors); if (connector == null) { System.out.println("Socket connector is not available"); return; } debugVirtualMachineUsingSocket(connector); } /** * Ziskat konektor pouzivajici pro pripojeni sockety */ private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) { for (AttachingConnector connector : connectors) { if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) { return connector; } } return null; } /** * Pripojeni k bezicimu virtualnimu stroji pres socket. * @throws InterruptedException */ private static void debugVirtualMachineUsingSocket(AttachingConnector connector) { // nastaveni argumentu pouzivanych konektorem Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector); try { // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments); // spustit sledovany virtualni stroj po dobu jedne sekundy runVirtualMachineForOneSecond(virtualMachine); // vypis zakladnich informaci o pripojenem VM printVirtualMachineInfo(virtualMachine); // ukonceni behu vzdaleneho virtualniho stroje shutdownVirtualMachine(virtualMachine); } catch (IOException e) { e.printStackTrace(); } catch (IllegalConnectorArgumentsException e) { e.printStackTrace(); } } /** * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni. * * @param connector konektor pouzity pro pripojeni * @return mapa obsahujici parametry konektoru */ private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) { Map<String, Connector.Argument> arguments = connector.defaultArguments(); arguments.get("port").setValue("6502"); return arguments; } /** * Pripojeni debuggeru ke sledovanemu virtualnimu stroji. * * @param connector konektor vyuzivajici pro spojeni sockety * @param arguments mapa obsahujici parametry pripojeni * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen * * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru */ private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments) throws IOException, IllegalConnectorArgumentsException { System.out.println("Connecting to virtual machine"); VirtualMachine virtualMachine = connector.attach(arguments); System.out.println("Connected"); return virtualMachine; } /** * Spustit sledovany virtualni stroj po dobu jedne sekundy * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void runVirtualMachineForOneSecond(VirtualMachine virtualMachine) { virtualMachine.resume(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } virtualMachine.suspend(); } /** * Ukonceni prace beziciho sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void shutdownVirtualMachine(VirtualMachine virtualMachine) { System.out.println("Calling exit"); virtualMachine.exit(EXIT_VALUE); } /** * Vypis informaci ziskanych ze sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printVirtualMachineInfo(VirtualMachine virtualMachine) { System.out.println("Basic virtual machine info:"); printThreadInfo(virtualMachine); } /** * Vypis informaci o vlaknech existujicich ve sledovanem virtualnim stroji. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printThreadInfo(VirtualMachine virtualMachine) { System.out.println("Thread info:"); System.out.println(" UniqueID Thread name Status Suspended"); List<ThreadReference> threads = virtualMachine.allThreads(); // projit vsemi vlakny for (ThreadReference thread : threads) { String threadName = thread.name(); String threadStatus = getThreadStatus(thread); String threadSuspended = getThreadSuspended(thread); long uniqueID = thread.uniqueID(); System.out.format(" %8d %-20s %-12s %-5s\n", uniqueID, threadName, threadStatus, threadSuspended); // pro "hlavni" vlakno vypsat i informace o zasobnikovych ramcich if (MAIN_THREAD_NAME.equals(threadName)) { printDetailInfoAboutStackFrames(thread); } } System.out.println(); } /** * Prevod stavu vlakna na retezec. * * @param thread JDI objekt predstavujici vlakno * @return stav vlakna v retezcove podobe */ private static String getThreadStatus(ThreadReference thread) { switch (thread.status()) { case ThreadReference.THREAD_STATUS_NOT_STARTED: return "not started"; case ThreadReference.THREAD_STATUS_RUNNING: return "running"; case ThreadReference.THREAD_STATUS_SLEEPING: return "sleeping"; case ThreadReference.THREAD_STATUS_MONITOR: return "wait/monitor"; case ThreadReference.THREAD_STATUS_WAIT: return "Object.wait"; case ThreadReference.THREAD_STATUS_ZOMBIE: return "zombie"; case ThreadReference.THREAD_STATUS_UNKNOWN: return "*unkwnown*"; default: return "should not happen!"; } } /** * Informace (ve tvaru retezce) o tom, zda je vlakno pozastaveno ci nikoli. * * @param thread JDI objekt predstavujici vlakno * @return stav pozastaveni vlakna v retezcove podobe */ private static String getThreadSuspended(ThreadReference thread) { return thread.isSuspended() ? "yes" : "no"; } /** * Vypis zasobnikovych ramcu * * @param thread JDI objekt predstavujici vlakno */ private static void printDetailInfoAboutStackFrames(ThreadReference thread) { try { System.out.format("%16s-------------------------------------------------\n", ""); System.out.format("%16sStack frame count: %d\n", "", thread.frameCount()); // vypsat detailni informace o vsech zasobnikovych ramcich for (StackFrame frame : thread.frames()) { printStackFrameDetailInfo(frame); } System.out.format("%16s-------------------------------------------------\n", ""); } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } } /** * Vypis podrobnych informaci o vybranem zasobnikovem ramci * * @param frame zasobnikovy ramec */ private static void printStackFrameDetailInfo(StackFrame frame) { // informace o volane metode printLocationInfo(frame); // informace o lokalnich promennych volane metody printMethodLocalVariablesInfo(frame); } /** * Vypis informaci o volane metode * * @param frame zasobnikovy ramec */ private static void printLocationInfo(StackFrame frame) { Location location = frame.location(); // ziskat vsechny informace o pozici v pozastavenem vlaknu String className = getClassName(location); String methodName = getMethodName(location); String sourceName = getSourceName(location); String lineNumber = getLineNumber(location); // nyni mame vsechny informace, lze je tedy vypsat System.out.format("%16s%s.%s (%s:%s)\n", "", className, methodName, sourceName, lineNumber); } /** * Jmeno tridy, jejiz metoda byla zavolana. */ private static String getClassName(Location location) { return location.method().declaringType().name(); } /** * Jmeno volane metody. */ private static String getMethodName(Location location) { return location.method().name(); } /** * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci. */ private static String getSourceName(Location location) { try { return location.sourceName(); } catch (AbsentInformationException e) { return "unknown"; } } /** * Prevod cisla radku na retezec, pokud je to mozne. */ private static String getLineNumber(Location location) { int lineNumber = location.lineNumber(); // u nativnich metod nelze zjistit cisla radku return lineNumber >= 0 ? "" + lineNumber : "<native method>"; } /** * Vypis informaci o lokalnich promennych. * * @param frame zasobnikovy ramec */ private static void printMethodLocalVariablesInfo(StackFrame frame) { List<LocalVariable> variables; try { // precist seznam vsech viditelnych lokalnich promennych variables = frame.visibleVariables(); System.out.format("%20sVariables %d\n", "", variables.size()); // vypis informaci o kazde lokalni promenne for (LocalVariable variable : variables) { String name = variable.name(); String type = variable.typeName(); // pomerne primitivni zpusob ziskani retezcove podoby // hodnoty promenne String value = "" + frame.getValue(variable); // vypsat vsechny zjistene informace o promenne System.out.format("%20s%s %s = %s\n", "", type, name, value); } } catch (AbsentInformationException e) { // neudelame nic, protoze udaje o lokalnich promennych // skutecne nemuseji byt k dispozici } } }
10. Spuštění demonstračního příkladu a ukázka informací vypsaných na standardní výstup
Před vlastním spuštěním demonstračního příkladu JDIMethodVariables je nejprve nutné spustit samostatnou cílovou JVM i s testovanou aplikací Test3, jejíž zdrojový kód byl vypsán v páté kapitole. Tato aplikace je velmi jednoduchá, protože se po své inicializaci dostane do nekonečné smyčky, což nám samozřejmě vyhovuje, neboť bude jisté, že po cca jednosekundovém běhu cílové JVM se bude hlavní vlákno aplikace skutečně nacházet uvnitř této smyčky.
Překlad a spuštění této aplikace v cílovém virtuálním stroji Javy se provede následujícím způsobem (důležité je zde především číslo portu, k němuž se následně připojíme s využitím rozhraní JDI):
javac Test3.java java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test3
Nyní již zbývá provést překlad a spuštění našeho demonstračního příkladu v samostatné JVM:
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIMethodVariables.java java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIMethodVariables
Po spuštění by se měl na standardním výstupu objevit přibližně tento text obsahující jména metod a parametrů metod třídy Test3:
Connecting to virtual machine Connected Basic virtual machine info: Thread info: UniqueID Thread name Status Suspended 56 Attach Listener running yes 57 Signal Dispatcher running yes 58 Finalizer Object.wait yes 59 Reference Handler Object.wait yes 1 main running yes ------------------------------------------------- Stack frame count: 16 java.io.FileOutputStream.writeBytes (FileOutputStream.java:) java.io.FileOutputStream.write (FileOutputStream.java:260) java.io.BufferedOutputStream.flushBuffer (BufferedOutputStream.java:65) java.io.BufferedOutputStream.flush (BufferedOutputStream.java:123) java.io.PrintStream.write (PrintStream.java:432) sun.nio.cs.StreamEncoder.writeBytes (StreamEncoder.java:202) sun.nio.cs.StreamEncoder.implFlushBuffer (StreamEncoder.java:272) sun.nio.cs.StreamEncoder.flushBuffer (StreamEncoder.java:85) java.io.OutputStreamWriter.flushBuffer (OutputStreamWriter.java:168) java.io.PrintStream.write (PrintStream.java:477) java.io.PrintStream.print (PrintStream.java:619) java.io.PrintStream.println (PrintStream.java:756) Test3.bar (Test3.java:16) Variables 3 float value = 0.023809524 float[] array = instance of float[2] (id=65) java.lang.String message = "Hello world!" Test3.foo (Test3.java:11) Variables 2 float value = 0.023809524 java.lang.Object object_value = instance of Test3(id=68) Test3.run (Test3.java:6) Variables 4 int value = 42 char znak = a int int_value = 10 java.lang.Object object_value = null Test3.main (Test3.java:21) Variables 1 java.lang.String[] args = instance of java.lang.String[0] (id=69) ------------------------------------------------- Calling exit
11. Repositář se zdrojovými kódy demonstračního příkladu JDIMethodVariables i podpůrných skriptů
Zdrojové kódy demonstračního příkladu JDIMethodVariables, testovací třídy i skriptů použitých pro překlad a spuštění tohoto demonstračního příkladu byly uloženy (podobně jako tomu bylo i v předchozích částech tohoto seriálu) do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze všech zmíněných zdrojových souborů a skriptů můžete najít na adresách:
# | Zdrojový soubor/skript | Umístění v repositáři |
---|---|---|
1 | JDIMethodVariables.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/773bb88e02b6/jdi/JDIMethodVariables.java |
2 | compile_JDIMethodVariables.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/773bb88e02b6/jdi/compile_JDIMethodVariables.sh |
3 | Test3.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/773bb88e02b6/jdi/Test3.java |
4 | Test3.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/773bb88e02b6/jdi/Test3.sh |
12. Odkazy na Internetu
- Breakpoint (Wikipedia)
http://cs.wikipedia.org/wiki/Breakpoint - JVM Tool Interface Version 1.2 Documentation
http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html - JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#SetBreakpoint - JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#ClearBreakpoint - JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#Breakpoint - The JVM Tool Interface (JVM TI): How VM Agents Work
http://www.oracle.com/technetwork/articles/javase/jvm-ti-141370.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html