Obsah
1. Pohled pod kapotu JVM – získání informací o třídách ve sledované JVM s využitím rozhraní JDI
2. Získání seznamu všech tříd ve sledovaném virtuálním stroji
3. Výpis všech základních informací o třídě
4. Výpis informací o všech atributech třídy
5. Výpis informací o všech metodách třídy
6. Získání jmen a typů argumentů metod
7. Kompletní zdrojový kód demonstračního příkladu JDIAllClassesList
8. Spuštění demonstračního příkladu JDIAllClassesList
9. Repositář se zdrojovými kódy demonstračního příkladu JDIAllClassesList i podpůrných skriptů
1. Pohled pod kapotu JVM – získání informací o třídách ve sledované JVM s využitím rozhraní JDI
V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si na praktickém demonstračním příkladu ukážeme, jakým způsobem je možné s využitím rozhraní JDI (Java Debugger Interface) získat všechny důležité informace o třídách načtených do sledovaného (monitorovaného) virtuálního stroje Javy. Přes JDI je možné přečíst například všechny metody i atributy vybrané třídy, informace o parametrech metod atd. Tyto údaje jsou velmi důležité, protože je využijeme při programování skutečného (i když velmi jednoduchého) debuggeru.
Dnes popsané či použité metody rozhraní JDI:
# | Třída/rozhraní | Návratový typ | Metoda |
---|---|---|---|
1 | com.sun.jdi.VirtualMachine | void | resume() |
2 | com.sun.jdi.VirtualMachine | void | suspend() |
3 | com.sun.jdi.VirtualMachine | List<ReferenceType> | allClasses() |
4 | com.sun.jdi.ReferenceType | String | name() |
5 | com.sun.jdi.ReferenceType | List<Field> | allFields() |
6 | com.sun.jdi.ReferenceType | List<Method> | allMethods() |
7 | com.sun.jdi.Type | String | signature() |
8 | com.sun.jdi.ReferenceType | String | genericSignature() |
9 | com.sun.jdi.ReferenceType | String | sourceName() |
10 | com.sun.jdi.TypeComponent | boolean | isStatic() |
11 | com.sun.jdi.TypeComponent | boolean | isFinal() |
12 | com.sun.jdi.Field | boolean | isTransient() |
13 | com.sun.jdi.Field | boolean | isVolatile() |
14 | com.sun.jdi.Method | boolean | isNative() |
15 | com.sun.jdi.Method | boolean | isSynchronized() |
16 | com.sun.jdi.Accessible | boolean | isPublic() |
17 | com.sun.jdi.Accessible | boolean | isProtected() |
18 | com.sun.jdi.Accessible | boolean | isPrivate() |
19 | com.sun.jdi.Method | List<LocalVariable> | arguments() |
20 | com.sun.jdi.Method | List<String> | argumentTypeNames() |
21 | com.sun.jdi.LocalVariable | String | name() |
22 | com.sun.jdi.LocalVariable | String | typeName() |
2. Získání seznamu všech tříd ve sledovaném virtuálním stroji
Zdrojové kódy dnešního demonstračního příkladu nazvaného JDIAllClassesList jsou z velké části založeny na zdrojových kódech příkladu JDIMethodVariables [1], který byl popsán v předchozí části tohoto seriálu. Tento program se nejprve pokusí o připojení ke sledovanému virtuálnímu stroji Javy s využitím konektoru používajícího socket na portu 6502 (číslo portu je použito při spouštění sledované JVM). Následně je sledovaná JVM spuštěna po dobu přibližně jedné sekundy a po jejím opětovném zastavení je již téměř jisté, že do virtuálního stroje byly načteny všechny třídy, které posléze budeme moci zkoumat přes rozhraní JDI.
V metodě printFilteredClassList(), která je součástí demonstračního příkladu, se nejprve s využitím metody VirtualMachine.allClasses() získá seznam všech tříd, které byly načteny do sledovaného virtuálního stroje Javy, a posléze se na základě jména každé třídy provede výběr pouze těch tříd, v jejichž názvu se vyskytují řetězce „String“ a „Test“. Tyto řetězce jsou uloženy v poli CLASS_NAME_FILTERS, takže je snadné je změnit, popř. upravit demonstrační příklad takovým způsobem, aby se tyto parametry načítaly například z příkazové řádky. Důležité je, že metoda VirtualMachine.allClasses() vrací seznam objektů typu ReferenceType, nikoli objektů typu Class. To je ovšem pochopitelné, protože přes JDI sice dokážeme získat všechny potřebné informace, ovšem například třída String může být ve sledované JVM zcela odlišná od třídy String použité v debuggeru/demonstračním příkladu. Obě aplikace například mohou běžet na jiných typech virtuálního stroje a dokonce i na jiném počítači:
/** * Vypis vyfiltrovanych trid nactenych do sledovaneho virtualniho stroje. * Filtr se nastavuje pres {@link JDIAllClassesList#CLASS_NAME_FILTERS} * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printFilteredClassList(VirtualMachine virtualMachine) { List<ReferenceType> allClasses = virtualMachine.allClasses(); // projit vsemi tridami, ktere se nachazeji ve sledovanem virtualnim stroji for (ReferenceType klass : allClasses) { String className = klass.name(); // zjistit, zda se ma tato trida vyfiltrovat ci nikoli boolean filterClass = true; // jednoduchy filtr for (String filter : CLASS_NAME_FILTERS) { if (className.contains(filter)) { filterClass = false; break; } } // cast jmena tridy nebyla v CLASS_NAME_FILTERS nalezena if (filterClass) { continue; } printClassInfo(klass); } }
3. Výpis všech základních informací o třídě
V případě, že jméno třídy obsahuje řetězec „String“ nebo „Test“, je v debuggeru zavolána metoda printClassInfo() sloužící pro výpis téměř všech dostupných informací o nalezené třídě. Zdrojový kód této metody je velmi jednoduchý:
/** * Vypis vsech relevantnich informaci o vybrane tride. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printClassInfo(ReferenceType klass) { printBasicClassInfo(klass); printClassFields(klass); printClassMethods(klass); printSeparator(); }
Zajímavější je až zdrojový kód metody nazvané printBasicClassInfo(), v níž se zjišťují čtyři důležité údaje o prozkoumávané třídě. V první řadě se jedná o jméno třídy v podobě, v jaké je jméno použito (například jako identifikátor) v samotném programovacím jazyku Java. Dále se pak zjišťuje signatura třídy, jejíž formát jsme si popsali ve druhé části tohoto seriálu věnované popisu bajtkódu JVM. Kromě signatury třídy se přečte i její takzvaná obecná signatura, z níž je možné mj. získat seznam implementovaných rozhraní. O přečtení jména zdrojového souboru s definicí třídy se stará samostatná metoda nazvaná getClassSourceName(), která je popsána v dalším odstavci:
/** * Zakladni informace o tride. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printBasicClassInfo(ReferenceType klass) { String className = klass.name(); String classSignature = klass.signature(); String genericSignature = klass.genericSignature(); String sourceName = getClassSourceName(klass); System.out.println("Class " + className); System.out.println(" Signature: " + classSignature); System.out.println(" Generic signature: " + (genericSignature == null ? "not present" : genericSignature)); System.out.println(" Source: " + sourceName); }
Poslední obecnou informací, která se zjišťuje pro zkoumanou třídu, je jméno zdrojového souboru s definicí třídy. Toto jméno nemusí být v některých případech dostupné, například tehdy, pokud se jedná o pole libovolného typu, tj. například o třídu String[] apod. V takovém případě se namísto skutečného jména zdrojového souboru vrátí řetězec „unknown“. V rozhraní JDI nelze přímo zjistit, zda je jméno zdrojového souboru dostupné, jinak, než zachycením výjimky:
/** * Zjisteni jmena zdrojoveho souboru. * * @param klass trida ve sledovanem virtualnim stroji Javy * @return jmeno zdrojoveho souboru */ private static String getClassSourceName(ReferenceType klass) { String sourceName; try { sourceName = klass.sourceName(); } catch (AbsentInformationException e) { sourceName = "unknown"; } return sourceName; }
4. Výpis informací o všech atributech třídy
Další důležitou a zajímavou metodou použitou v dnešním demonstračním příkladu je metoda s názvem printClassFields(), která slouží k výpisu informací o všech atributech zkoumané třídy. Atributy jsou získány s využitím metody com.sun.jdi.ReferenceType.allFields() vracející seznam objektů typu com.sun.jdi.Field. Tyto objekty mají společné rozhraní com.sun.jdi.TypeComponent a com.sun.jdi.Accessible s objekty typu com.sun.jdi.Method, čehož využijeme později. Jaké informace však lze o atributech třídy získat? Jedná se samozřejmě o jméno atributu, dále pak o typ atributu (popř. v našem případě o jméno typu), a v neposlední řadě taktéž o prakticky všechny modifikátory atributu – static, final, transient, volatile – i o přístupová práva k atributům – public, protected, private a (package protected):
/** * Vypis informaci o vsech atributech tridy. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printClassFields(ReferenceType klass) { System.out.println(" Fields:"); List<Field> fields = klass.allFields(); // projit vsemi atributy for (Field field : fields) { String name = field.name(); String type = field.typeName(); String accessibilityStr = getAccessibility(field); String staticStr = field.isStatic() ? "static " : ""; String finalStr = field.isFinal() ? "final " : ""; String transientStr = field.isTransient() ? "transient " : ""; String volatileStr = field.isVolatile() ? "volatile " : ""; System.out.format(" %s%s%s%s%s%s %s; \n", accessibilityStr, staticStr, finalStr, transientStr, volatileStr, type, name); } }
Mimochodem – je zajímavé, jak modifikátory a přístupová práva atributů získaná přes rozhraní JDI korespondují s informacemi o atributech uložených přímo v bajtkódu dané třídy, což bylo téma podrobněji popsané ve čtvrté části tohoto seriálu v kapitolách 3 a 4:
# | Název masky | Hodnota | Význam, pokud je maska nastavena |
---|---|---|---|
1 | ACC_PUBLIC | 0×0001 | veřejná datová položka dostupná vně třídy i vně balíčku |
2 | ACC_PRIVATE | 0×0002 | privátní datová položka dostupná pouze uvnitř třídy |
3 | ACC_PROTECTED | 0×0004 | chráněná datová položka dostupná uvnitř třídy, z případných odvozených tříd i v rámci celého balíčku |
4 | ACC_STATIC | 0×0008 | statická datová položka, může se kombinovat s předchozí trojicí příznaků |
5 | ACC_FINAL | 0×0010 | finální datová položka |
6 | ACC_VOLATILE | 0×0040 | příznak volatile – hodnota se vždy přečte či zapíše do paměti (nezůstane uložena pouze v registrech) |
7 | ACC_TRANSIENT | 0×0080 | příznak transient – nebude se zpracovávat při (de)serializaci objektu |
8 | ACC_SYNTHETIC | 0×1000 | syntetická datová položka, která nemá svůj protějšek ve zdrojovém kódu (je generována překladačem) |
9 | ACC_ENUM | 0×4000 | jedná se o prvek výčtového typu |
5. Výpis informací o všech metodách třídy
Podobným způsobem, jakým jsme získali informace o všech atributech sledované třídy, je možné přečíst i informace o jejích metodách. S využitím rozhraní JDI lze přečíst jméno metody, její návratový typ (popř. jméno návratového typu, což je pro nás jednodušší a především dostačující), modifikátory metody (static, final, native, synchronized) a samozřejmě i modifikátory přístupu k metodě, tj. public, private, protected a nepřímo i package protected (implicitní modifikátor). Samostatně je implementován výpis všech parametrů metody, což je téma podrobněji popsané až v šesté kapitole:
/** * Vypis informaci o vsech metodach tridy. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printClassMethods(ReferenceType klass) { System.out.println(" Methods:"); List<Method> methods = klass.allMethods(); // projit vsemi metodami for (Method method : methods) { String name = method.name(); String type = method.returnTypeName(); String accessibilityStr = getAccessibility(method); String staticStr = method.isStatic() ? "static " : ""; String finalStr = method.isFinal() ? "final " : ""; String nativeStr = method.isNative() ? "native " : ""; String synchronizedStr = method.isSynchronized() ? "synchronized " : ""; System.out.format(" %s%s%s%s%s%s %s", accessibilityStr, staticStr, finalStr, nativeStr, synchronizedStr, type, name); printMethodArguments(method); } }
V metodách printClassFields() i printClassMethods() implementovaných v dnešním demonstračním příkladu bylo mj. nutné zjistit i modifikátory přístupu k metodám a k atributům zkoumané třídy. Vzhledem k tomu, že metody zkoumané třídy jsou v debuggeru reprezentovány objekty typu com.sun.jdi.Method a atributy jsou reprezentovány objekty typu com.sun.jdi.Field, které mají společné rozhraní com.sun.jdi.Accessible, je implementace metody getAccessibility() (vracející textovou podobu modifikátorů) velmi jednoduchá a hlavně společná jak pro atributy, tak i pro metody sledované třídy:
/** * Pristupova prava k atributu ci k metode. */ private static String getAccessibility(Accessible methodOrField) { if (methodOrField.isPublic()) { return "public "; } if (methodOrField.isProtected()) { return "protected "; } if (methodOrField.isPrivate()) { return "private "; } return ""; }
6. Získání jmen a typů argumentů metod
U metod zkoumané třídy je nutné zjistit a vypsat ještě jednu důležitou informaci. Jedná se o seznam argumentů metody. V závislosti na tom, zda byl bajtkód zkoumané třídy přeložen s přepínačem -g či bez tohoto modifikátoru, lze o argumentech zjistit buď pouze informaci o typech jednotlivých argumentů, nebo i jména argumentů – v tomto případě je však nutné provést překlad s přepínačem -g, jinak se tato informace do bajtkódu neuloží, protože ji virtuální stroj Javy ve skutečnosti nepotřebuje. S využitím rozhraní JDI nelze snadno zjistit, které informace o argumentech metod jsou k dispozici, proto je kód metody nazvané printMethodArguments() rozdělen na dvě části. V první části umístěné v bloku try {} se zjišťují jak jména, tak i typy argumentů, v části umístěné v bloku catch {} pak pouze typy argumentů metody, neboť tyto informace má JVM k dispozici vždy. Výjimka, která rozhodne o použití prvního či druhého bloku, je vyhozena z metody Method.arguments() umístěné jako první příkaz v bloku try{}:
/** * Vypis argumentu metody. * * @param method metoda ve sledovanem virtualnim stroji */ private static void printMethodArguments(Method method) { System.out.print("("); boolean first = true; try { // idealni je zjistit jmena i typy argumentu List<LocalVariable> arguments = method.arguments(); // vypsat typy a jmena vsech argumentu metody for (LocalVariable argument : arguments) { if (first) { first = false; } else { System.out.print(", "); } System.out.print(argument.typeName() + " " + argument.name()); } } catch (AbsentInformationException e) { // pokud jmena argumentu nelze zjistit, jejich typy jsou vzdy k dispozici List<String> arguments = method.argumentTypeNames(); // vypsat typy vsech argumentu metody for (String argument : arguments) { if (first) { first = false; } else { System.out.print(", "); } System.out.print(argument); } } System.out.println(");"); }
7. Kompletní zdrojový kód demonstračního příkladu JDIAllClassesList
V předchozích kapitolách byly popsány ty nejdůležitější a nejzajímavější metody, které jsou součástí dnešního demonstračního příkladu nazvaného JDIAllClassesList. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto příkladu; jeho originální verzi pak lze najít v Mercurial repositáři popsaném v deváté kapitole:
import java.io.IOException; import java.util.List; import java.util.Map; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.Accessible; import com.sun.jdi.Bootstrap; import com.sun.jdi.Field; import com.sun.jdi.LocalVariable; import com.sun.jdi.Method; import com.sun.jdi.ReferenceType; 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 vsechny tridy nactene do sledovaneho virtualniho stroje, * pricemz jmena trid (ktere maji byt vypsany) jsou vyfiltrovany na zaklade retezcu * ulozenych v konstante {@link JDIAllClassesList#CLASS_NAME_FILTERS}. * * @author Pavel Tisnovsky */ public class JDIAllClassesList { /** * 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"; /** * Filtr, pomoci nehoz jsou vybrany pouze tridy urciteho jmena. */ private static final String[] CLASS_NAME_FILTERS = { "String", "Test" }; /** * Vstupni metoda debuggeru. */ 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; } } // nenasli jsme zadny vhodny konektor 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 vsech vybranych trid nactenych ve sledovanem virtualnim stroji printFilteredClassList(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 vyfiltrovanych trid nactenych do sledovaneho virtualniho stroje. * Filtr se nastavuje pres {@link JDIAllClassesList#CLASS_NAME_FILTERS} * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printFilteredClassList(VirtualMachine virtualMachine) { List<ReferenceType> allClasses = virtualMachine.allClasses(); // projit vsemi tridami, ktere se nachazi ve sledovanem virtualnim stroji for (ReferenceType klass : allClasses) { String className = klass.name(); // zjistit, zda se ma tato trida vyfiltrovat ci nikoli boolean filterClass = true; // jednoduchy filtr for (String filter : CLASS_NAME_FILTERS) { if (className.contains(filter)) { filterClass = false; break; } } // cast jmena tridy nebyla v CLASS_NAME_FILTERS nalezena if (filterClass) { continue; } printClassInfo(klass); } } /** * Vypis vsech relevantnich informaci o vybrane tride. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printClassInfo(ReferenceType klass) { printBasicClassInfo(klass); printClassFields(klass); printClassMethods(klass); printSeparator(); } /** * <hr> */ private static void printSeparator() { System.out.println("-------------------------------------------------------"); } /** * Zakladni informace o tride. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printBasicClassInfo(ReferenceType klass) { String className = klass.name(); String classSignature = klass.signature(); String genericSignature = klass.genericSignature(); String sourceName = getClassSourceName(klass); System.out.println("Class " + className); System.out.println(" Signature: " + classSignature); System.out.println(" Generic signature: " + (genericSignature == null ? "not present" : genericSignature)); System.out.println(" Source: " + sourceName); } /** * Zjisteni jmena zdrojoveho souboru. * * @param klass trida ve sledovanem virtualnim stroji Javy * @return jmeno zdrojoveho souboru */ private static String getClassSourceName(ReferenceType klass) { String sourceName; try { sourceName = klass.sourceName(); } catch (AbsentInformationException e) { sourceName = "unknown"; } return sourceName; } /** * Vypis informaci o vsech atributech tridy. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printClassFields(ReferenceType klass) { System.out.println(" Fields:"); List<Field> fields = klass.allFields(); // projit vsemi atributy for (Field field : fields) { String name = field.name(); String type = field.typeName(); String accessibilityStr = getAccessibility(field); String staticStr = field.isStatic() ? "static " : ""; String finalStr = field.isFinal() ? "final " : ""; String transientStr = field.isTransient() ? "transient " : ""; String volatileStr = field.isVolatile() ? "volatile " : ""; System.out.format(" %s%s%s%s%s%s %s; \n", accessibilityStr, staticStr, finalStr, transientStr, volatileStr, type, name); } } /** * Vypis informaci o vsech metodach tridy. * * @param klass trida ve sledovanem virtualnim stroji Javy */ private static void printClassMethods(ReferenceType klass) { System.out.println(" Methods:"); List<Method> methods = klass.allMethods(); // projit vsemi metodami for (Method method : methods) { String name = method.name(); String type = method.returnTypeName(); String accessibilityStr = getAccessibility(method); String staticStr = method.isStatic() ? "static " : ""; String finalStr = method.isFinal() ? "final " : ""; String nativeStr = method.isNative() ? "native " : ""; String synchronizedStr = method.isSynchronized() ? "synchronized " : ""; System.out.format(" %s%s%s%s%s%s %s", accessibilityStr, staticStr, finalStr, nativeStr, synchronizedStr, type, name); printMethodArguments(method); } } /** * Vypis argumentu metody. * * @param method metoda ve sledovanem virtualnim stroji */ private static void printMethodArguments(Method method) { System.out.print("("); boolean first = true; try { // idealni je zjistit jmena i typy argumentu List<LocalVariable> arguments = method.arguments(); // vypsat typy a jmena vsech argumentu metody for (LocalVariable argument : arguments) { if (first) { first = false; } else { System.out.print(", "); } System.out.print(argument.typeName() + " " + argument.name()); } } catch (AbsentInformationException e) { // pokud jmena argumentu nelze zjistit, jejich typy jsou vzdy k dispozici List<String> arguments = method.argumentTypeNames(); // vypsat typy vsech argumentu metody for (String argument : arguments) { if (first) { first = false; } else { System.out.print(", "); } System.out.print(argument); } } System.out.println(");"); } /** * Pristupova prava k atributu ci k metode. */ private static String getAccessibility(Accessible methodOrField) { if (methodOrField.isPublic()) { return "public "; } if (methodOrField.isProtected()) { return "protected "; } if (methodOrField.isPrivate()) { return "private "; } return ""; } }
8. Spuštění demonstračního příkladu JDIAllClassesList
Dnešní demonstrační příklad si vyzkoušíme spustit proti virtuálnímu stroji, v němž poběží testovací aplikace Test3, kterou jsme použili již minule. 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:. JDIAllClassesList.java java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIAllClassesList
Po spuštění by se měl na standardním výstupu objevit přibližně následující text obsahující metody a atributy vybraných tříd. Povšimněte si, že kvůli překladu třídy Test3 bez použití přepínače -g nezískáme jména argumentů metod, ale pouze jejich typy:
Connecting to virtual machine Connected Class Test3 Signature: LTest3; Generic signature: not present Source: Test3.java Fields: Methods: public void <init>(); public void run(int, char); public void foo(float); public void bar(float, float[], java.lang.String); public static void main(java.lang.String[]); public void <init>(); private static native void registerNatives(); public final native java.lang.Class getClass(); public native int hashCode(); public boolean equals(java.lang.Object); protected native java.lang.Object clone(); public java.lang.String toString(); public final native void notify(); public final native void notifyAll(); public final native void wait(long); public final void wait(long, int); public final void wait(); protected void finalize(); static void <clinit>(); ------------------------------------------------------- Class java.lang.String Signature: Ljava/lang/String; Generic signature: Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/lang/CharSequence; Source: String.java Fields: private final char[] value; private final int offset; private final int count; private int hash; private static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; public static final java.util.Comparator CASE_INSENSITIVE_ORDER; Methods: public void <init>(); public void <init>(java.lang.String); public void <init>(char[]); public void <init>(char[], int, int); public void <init>(int[], int, int); public void <init>(byte[], int, int, int); public void <init>(byte[], int); private static void checkBounds(byte[], int, int); public void <init>(byte[], int, int, java.lang.String); public void <init>(byte[], int, int, java.nio.charset.Charset); public void <init>(byte[], java.lang.String); public void <init>(byte[], java.nio.charset.Charset); public void <init>(byte[], int, int); public void <init>(byte[]); public void <init>(java.lang.StringBuffer); public void <init>(java.lang.StringBuilder); void <init>(int, int, char[]); public int length(); public boolean isEmpty(); public char charAt(int); public int codePointAt(int); public int codePointBefore(int); public int codePointCount(int, int); public int offsetByCodePoints(int, int); void getChars(char[], int); public void getChars(int, int, char[], int); public void getBytes(int, int, byte[], int); public byte[] getBytes(java.lang.String); public byte[] getBytes(java.nio.charset.Charset); public byte[] getBytes(); public boolean equals(java.lang.Object); public boolean contentEquals(java.lang.StringBuffer); public boolean contentEquals(java.lang.CharSequence); public boolean equalsIgnoreCase(java.lang.String); public int compareTo(java.lang.String); public int compareToIgnoreCase(java.lang.String); public boolean regionMatches(int, java.lang.String, int, int); public boolean regionMatches(boolean, int, java.lang.String, int, int); public boolean startsWith(java.lang.String, int); public boolean startsWith(java.lang.String); public boolean endsWith(java.lang.String); public int hashCode(); public int indexOf(int); public int indexOf(int, int); public int lastIndexOf(int); public int lastIndexOf(int, int); public int indexOf(java.lang.String); public int indexOf(java.lang.String, int); static int indexOf(char[], int, int, char[], int, int, int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String, int); static int lastIndexOf(char[], int, int, char[], int, int, int); public java.lang.String substring(int); public java.lang.String substring(int, int); public java.lang.CharSequence subSequence(int, int); public java.lang.String concat(java.lang.String); public java.lang.String replace(char, char); public boolean matches(java.lang.String); public boolean contains(java.lang.CharSequence); public java.lang.String replaceFirst(java.lang.String, java.lang.String); public java.lang.String replaceAll(java.lang.String, java.lang.String); public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence); public java.lang.String[] split(java.lang.String, int); public java.lang.String[] split(java.lang.String); public java.lang.String toLowerCase(java.util.Locale); public java.lang.String toLowerCase(); public java.lang.String toUpperCase(java.util.Locale); public java.lang.String toUpperCase(); public java.lang.String trim(); public java.lang.String toString(); public char[] toCharArray(); public static java.lang.String format(java.lang.String, java.lang.Object[]); public static java.lang.String format(java.util.Locale, java.lang.String, java.lang.Object[]); public static java.lang.String valueOf(java.lang.Object); public static java.lang.String valueOf(char[]); public static java.lang.String valueOf(char[], int, int); public static java.lang.String copyValueOf(char[], int, int); public static java.lang.String copyValueOf(char[]); public static java.lang.String valueOf(boolean); public static java.lang.String valueOf(char); public static java.lang.String valueOf(int); public static java.lang.String valueOf(long); public static java.lang.String valueOf(float); public static java.lang.String valueOf(double); public native java.lang.String intern(); public int compareTo(java.lang.Object); static void <clinit>(); public void <init>(); private static native void registerNatives(); public final native java.lang.Class getClass(); public native int hashCode(); public boolean equals(java.lang.Object); protected native java.lang.Object clone(); public java.lang.String toString(); public final native void notify(); public final native void notifyAll(); public final native void wait(long); public final void wait(long, int); public final void wait(); protected void finalize(); static void <clinit>(); public int compareTo(java.lang.Object); public int length(); public char charAt(int); public java.lang.CharSequence subSequence(int, int); public java.lang.String toString(); -------------------------------------------------------
V případě, že se testovací třída přeloží i s ladicími informacemi
javac -g Test3.java java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test3
získáme po spuštění demonstračního příkladu
java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIAllClassesList
výstup, v němž budou zobrazena i jména argumentů metod testovací třídy:
Connecting to virtual machine Connected Class Test3 Signature: LTest3; Generic signature: not present Source: Test3.java Fields: Methods: public void <init>(); public void run(int value, char znak); public void foo(float value); public void bar(float value, float[] array, java.lang.String message); public static void main(java.lang.String[] args); public void <init>(); private static native void registerNatives(); public final native java.lang.Class getClass(); public native int hashCode(); public boolean equals(java.lang.Object); protected native java.lang.Object clone(); public java.lang.String toString(); public final native void notify(); public final native void notifyAll(); public final native void wait(long); public final void wait(long, int); public final void wait(); protected void finalize(); static void <clinit>(); ------------------------------------------------------- Class java.lang.String Signature: Ljava/lang/String; Generic signature: Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/lang/CharSequence; Source: String.java Fields: private final char[] value; private final int offset; private final int count; private int hash; private static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; public static final java.util.Comparator CASE_INSENSITIVE_ORDER; Methods: public void <init>(); public void <init>(java.lang.String); public void <init>(char[]); public void <init>(char[], int, int); public void <init>(int[], int, int); public void <init>(byte[], int, int, int); public void <init>(byte[], int); private static void checkBounds(byte[], int, int); public void <init>(byte[], int, int, java.lang.String); public void <init>(byte[], int, int, java.nio.charset.Charset); public void <init>(byte[], java.lang.String); public void <init>(byte[], java.nio.charset.Charset); public void <init>(byte[], int, int); public void <init>(byte[]); public void <init>(java.lang.StringBuffer); public void <init>(java.lang.StringBuilder); void <init>(int, int, char[]); public int length(); public boolean isEmpty(); public char charAt(int); public int codePointAt(int); public int codePointBefore(int); public int codePointCount(int, int); public int offsetByCodePoints(int, int); void getChars(char[], int); public void getChars(int, int, char[], int); public void getBytes(int, int, byte[], int); public byte[] getBytes(java.lang.String); public byte[] getBytes(java.nio.charset.Charset); public byte[] getBytes(); public boolean equals(java.lang.Object); public boolean contentEquals(java.lang.StringBuffer); public boolean contentEquals(java.lang.CharSequence); public boolean equalsIgnoreCase(java.lang.String); public int compareTo(java.lang.String); public int compareToIgnoreCase(java.lang.String); public boolean regionMatches(int, java.lang.String, int, int); public boolean regionMatches(boolean, int, java.lang.String, int, int); public boolean startsWith(java.lang.String, int); public boolean startsWith(java.lang.String); public boolean endsWith(java.lang.String); public int hashCode(); public int indexOf(int); public int indexOf(int, int); public int lastIndexOf(int); public int lastIndexOf(int, int); public int indexOf(java.lang.String); public int indexOf(java.lang.String, int); static int indexOf(char[], int, int, char[], int, int, int); public int lastIndexOf(java.lang.String); public int lastIndexOf(java.lang.String, int); static int lastIndexOf(char[], int, int, char[], int, int, int); public java.lang.String substring(int); public java.lang.String substring(int, int); public java.lang.CharSequence subSequence(int, int); public java.lang.String concat(java.lang.String); public java.lang.String replace(char, char); public boolean matches(java.lang.String); public boolean contains(java.lang.CharSequence); public java.lang.String replaceFirst(java.lang.String, java.lang.String); public java.lang.String replaceAll(java.lang.String, java.lang.String); public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence); public java.lang.String[] split(java.lang.String, int); public java.lang.String[] split(java.lang.String); public java.lang.String toLowerCase(java.util.Locale); public java.lang.String toLowerCase(); public java.lang.String toUpperCase(java.util.Locale); public java.lang.String toUpperCase(); public java.lang.String trim(); public java.lang.String toString(); public char[] toCharArray(); public static java.lang.String format(java.lang.String, java.lang.Object[]); public static java.lang.String format(java.util.Locale, java.lang.String, java.lang.Object[]); public static java.lang.String valueOf(java.lang.Object); public static java.lang.String valueOf(char[]); public static java.lang.String valueOf(char[], int, int); public static java.lang.String copyValueOf(char[], int, int); public static java.lang.String copyValueOf(char[]); public static java.lang.String valueOf(boolean); public static java.lang.String valueOf(char); public static java.lang.String valueOf(int); public static java.lang.String valueOf(long); public static java.lang.String valueOf(float); public static java.lang.String valueOf(double); public native java.lang.String intern(); public int compareTo(java.lang.Object); static void <clinit>(); public void <init>(); private static native void registerNatives(); public final native java.lang.Class getClass(); public native int hashCode(); public boolean equals(java.lang.Object); protected native java.lang.Object clone(); public java.lang.String toString(); public final native void notify(); public final native void notifyAll(); public final native void wait(long); public final void wait(long, int); public final void wait(); protected void finalize(); static void <clinit>(); public int compareTo(java.lang.Object); public int length(); public char charAt(int); public java.lang.CharSequence subSequence(int, int); public java.lang.String toString(); -------------------------------------------------------
9. Repositář se zdrojovými kódy demonstračního příkladu JDIAllClassesList i podpůrných skriptů
Zdrojové kódy dnešního demonstračního příkladu JDIAllClassesList, testovací třídy Test3 i skriptů použitých pro spuštění tohoto demonstračního příkladu byly uloženy (podobně jako tomu bylo i v mnoha předchozích částech tohoto seriálu) do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Navíc byly do repositáře dodány i výsledky běhu tohoto příkladu oproti třídě Test3 přeložené bez přidání ladicích informací i s ladicími informacemi (jedná se o soubory JDIAllClassesList_debug.txt a JDIAllClassesList_nodebug.txt). 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í souboru v repositáři |
---|---|---|
1 | JDIAllClassesList.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/22bec18f2089/jdi/JDIAllClassesList.java |
2 | JDIAllClassesList_debug.txt | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/22bec18f2089/jdi/JDIAllClassesList_debug.txt |
3 | JDIAllClassesList_nodebug.txt | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/22bec18f2089/jdi/JDIAllClassesList_nodebug.txt |
4 | Test3.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/773bb88e02b6/jdi/Test3.java |
5 | Test3.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/773bb88e02b6/jdi/Test3.sh |
10. Odkazy na Internetu
- Class com.sun.jdi.Bootstrap
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/package-tree.html - Interface com.sun.jdi.VirtualMachine
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/VirtualMachine.html - Interface com.sun.jdi.Field
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/Field.html - Interface com.sun.jdi.ReferenceType
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/ReferenceType.html - Interface com.sun.jdi.TypeComponent
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/TypeComponent.html - Interface com.sun.jdi.Accessible
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/Accessible.html - 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