Obsah
1. Pohled pod kapotu JVM – detekce vstupu do metod s využitím rozhraní JDI
2. Registrace události vyvolané při vstupu do libovolné metody
3. Výpis základních informací o volané metodě
4. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection1
5. Filtrace tříd, pro jejichž metody se vyvolá událost MethodEntryEvent
6. Zjištění podrobnějších informací o volané metodě
7. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection2
8. Zjištění modifikátorů, parametrů a návratových typů volaných metod
9. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection3
10. Zdrojový kód testovací třídy Test5
11. Repositář se zdrojovými kódy všech tří demonstračních příkladů
1. Pohled pod kapotu JVM – detekce vstupu do 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 řekli základní informace o tom, jakým způsobem se v rozhraní JDI (Java Debugger Interface) registrují a následně zpracovávají takzvané „události“ (events), které jsou automaticky generovány ve chvíli, kdy dojde ke změně stavu sledovaného (monitorovaného) virtuálního stroje Javy. Připomeňme si, že každá událost se na straně debuggeru projeví vytvořením objektu typu com.sun.jdi.event.Event, který je ihned po svém vytvoření vložen do takzvané fronty událostí (event queue). Debugger může (resp. přesněji řečeno dokonce musí) události postupně z této fronty číst a nějakým způsobem na tyto události reagovat. Postupné čtení událostí je většinou implementováno formou programové smyčky (event loop), na jejímž začátku se přečte nová událost z fronty událostí a posléze se provede rozeskok na základě typu přečtené události.
Registrovat a zpracovávat lze následující typy událostí:
- Start sledovaného virtuálního stroje
- Ukončení práce sledovaného virtuálního stroje
- Vytvoření a spuštění nového vlákna
- Ukončení činnosti vlákna
- Vstup vlákna do stavu čekání na jiné vlákno (monitor)
- Vstup na breakpoint v nějakém vlákně běžícím ve sledované JVM
- Čtení či zápis do sledovaného atributu (watchpoint)
- Provedení jednoho kroku při krokování programu ve sledované JVM
- Vznik výjimky
- Vstup do metody
- Výskok z metody
Debugger většinou musí umět zareagovat minimálně na událost typu VMDeathEvent, která nastane ve chvíli, kdy je činnost sledovaného virtuálního stroje ukončována. Zpracování události tohoto typu jsme si ukázali minule v demonstračním příkladu JDIEventRequestManager.java. Dnes si řekneme, jak může debugger jednoduše sledovat volání jednotlivých metod v monitorovaném virtuálním stroji Javy; posléze si také ukážeme možnosti filtrace událostí na základě jména třídy, popř. jednoduchého regulárního výrazu popisujícího větší množinu jmen.
2. Registrace události vyvolané při vstupu do libovolné metody
Žádost o registraci události se provádí s využitím tříd implementujících rozhraní com.sun.jdi.request.EventRequest, které je předkem dalších rozhraní vypsaných v následující tabulce:
# | Rozhraní | Popis |
---|---|---|
1 | com.sun.jdi.request.BreakpointRequest | Žádost o generování událostí typu com.sun.jdi.event.BreakpointEvent |
2 | com.sun.jdi.request.ClassPrepareRequest | Žádost o generování událostí typu com.sun.jdi.event.ClassPrepareEvent |
3 | com.sun.jdi.request.ClassUnloadRequest | Žádost o generování událostí typu com.sun.jdi.event.ClassUnloadEvent |
4 | com.sun.jdi.request.ExceptionRequest | Žádost o generování událostí typu com.sun.jdi.event.ExceptionEvent |
5 | com.sun.jdi.request.MethodEntryRequest | Žádost o generování událostí typu com.sun.jdi.event.MethodEntryEvent |
6 | com.sun.jdi.request.MethodExitRequest | Žádost o generování událostí typu com.sun.jdi.event.MethodExitEvent |
7 | com.sun.jdi.request.MonitorContendedEnteredRequest | Žádost o generování událostí typu com.sun.jdi.event.MonitorContendedEnteredEvent |
8 | com.sun.jdi.request.MonitorContendedEnterRequest | Žádost o generování událostí typu com.sun.jdi.event.MonitorContendedEnterEvent |
9 | com.sun.jdi.request.MonitorWaitedRequest | Žádost o generování událostí typu com.sun.jdi.event.MonitorWaitedEvent |
10 | com.sun.jdi.request.MonitorWaitRequest | Žádost o generování událostí typu com.sun.jdi.event.MonitorWaitEvent |
11 | com.sun.jdi.request.StepRequest | Žádost o generování událostí typu com.sun.jdi.event.StepEvent |
12 | com.sun.jdi.request.ThreadDeathRequest | Žádost o generování událostí typu com.sun.jdi.event.ThreadDeathEvent |
13 | com.sun.jdi.request.ThreadStartRequest | Žádost o generování událostí typu com.sun.jdi.event.ThreadStartEvent |
14 | com.sun.jdi.request.VMDeathRequest | Žádost o generování událostí typu com.sun.jdi.event.VMDeathEvent |
15 | com.sun.jdi.request.WatchpointRequest | Žádost o generování událostí typu com.sun.jdi.event.WatchpointEvent |
16 | com.sun.jdi.request.AccessWatchpointRequest | Žádost o generování událostí typu com.sun.jdi.AccessWatchpointEvent |
17 | com.sun.jdi.request.ModificationWatchpointRequest | Žádost o generování událostí typu com.sun.jdi.event.ModificationWatchpointEvent |
V dnešních demonstračních příkladech využijeme pouze rozhraní VMDeathRequest a MethodEntryRequest. Registrace příslušných typů událostí se provádí v uživatelské metodě acquireAndUseEventRequestManager(), která nejprve zaregistruje oba typy událostí a posléze zavolá smyčku událostí implementovanou v samostatné metodě:
/** * Ukazka pouziti EventRequestManageru. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) { EventRequestManager eventRequestManager = virtualMachine.eventRequestManager(); VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager); MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest); }
V metodě registerMethodEntryRequest() se vyžádá generování událostí typu MethodEntryEvent a posléze se generování těchto typů událostí ještě musí explicitně povolit:
/** * Registrace udalosti typu MethodEntry */ private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) { MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest(); // po registraci udalosti je jeste nutne tento typ udalosti povolit methodEntryRequest.enable(); return methodEntryRequest; }
3. Výpis základních informací o volané metodě
Vlastní implementace smyčky událostí je, stejně jako v demonstračním příkladu popsaném minule, rozdělená do dvou uživatelských metod. V metodě nazvané příznačně eventLoop() se nejprve získá objekt typu EventQueue a následně se v programové smyčce opakovaně volá uživatelská metoda processEvents() vracející pravdivostní hodnotu true v případě, že se má pokračovat ve čtení a zpracování dalších událostí a hodnotu false ve chvíli, kdy přišla událost typu VMDeathEvent a kdy má tedy debugger ukončit svoji činnost. Metodu eventLoop() si dnes popisovat nebudeme, protože ji známe již z minula, ovšem v metodě processEvents() došlo k rozšíření „rozeskoku“ o další typ události:
/** * Precteni a zpracovani udalosti * * @param eventQueue fronta udalosti * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent * * @return true pokud se ma pokracovat ve zpracovavani udalosti * false pokud se ma sledovana VM ukoncit */ private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) { EventSet eventSet; try { // precist udalosti z fronty eventSet = eventQueue.remove(); int events = eventSet.size(); System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":")); // projit vsemi udalostmi for (Event event : eventSet) { if (event instanceof VMStartEvent) { System.out.println(" VMStartEvent"); } else if (event instanceof VMDeathEvent) { System.out.println(" VMDeathEvent"); // zakazat dalsi generovani udalosti // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) vmDeathRequest.disable(); // posleze se zavola shutdownVirtualMachine() return false; } else if (event instanceof MethodEntryEvent) { printMethodEntryInfo((MethodEntryEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; }
Při přečtení události typu MethodEntryEvent se zavolá uživatelská metoda printMethodEntryInfo(), která ve své první verzi pouze vytiskne jméno zavolané metody:
/** * Zavolano pri vyskytu udalosti typu MethodEntry */ private static void printMethodEntryInfo(MethodEntryEvent event) { String methodName = event.method().name(); // nyni mame vsechny informace, lze je tedy vypsat System.out.format(" entry into method: %s\n", methodName); }
4. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection1
Metody popsané v předchozích dvou kapitolách jsou součástí dnešního demonstračního příkladu nazvaného JDIMethodEntryDetection1. Tento příklad po svém spuštění vytiskne jména všech volaných metod, a to nezávisle na tom, ve kterém vláknu jsou tyto metody volány či ke které třídě metody náleží:
import java.io.IOException; import java.util.List; import java.util.Map; import com.sun.jdi.Bootstrap; 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; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.MethodEntryEvent; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.EventRequestManager; import com.sun.jdi.request.MethodEntryRequest; import com.sun.jdi.request.VMDeathRequest; /** * Pripojeni k bezicimu virtualnimu stroji Javy, * ktery byl spusten s parametry: * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida * * * @author Pavel Tisnovsky */ public class JDIMethodEntryDetection1 { /** * 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"; /** * 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; } // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni 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 Javy runVirtualMachine(virtualMachine); // ukazka pouziti EventRequestManageru acquireAndUseEventRequestManager(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 * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void runVirtualMachine(VirtualMachine virtualMachine) { virtualMachine.resume(); } /** * 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); } /** * Ukazka pouziti EventRequestManageru. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) { EventRequestManager eventRequestManager = virtualMachine.eventRequestManager(); VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager); MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest); } /** * Registrace udalosti typu VMDeathEvent */ private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) { VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest(); // po registraci udalosti je jeste nutne tento typ udalosti povolit vmDeathRequest.enable(); return vmDeathRequest; } /** * Registrace udalosti typu MethodEntry */ private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) { MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest(); // po registraci udalosti je jeste nutne tento typ udalosti povolit methodEntryRequest.enable(); return methodEntryRequest; } /** * Klasicka smycka pro postupne zpracovani udalosti. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent * @param methodEntryRequest objekt pro rizeni udalosti typu MethodEntry */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, MethodEntryRequest methodEntryRequest) { EventQueue eventQueue = virtualMachine.eventQueue(); // precist a zpracovat udalosti while (processEvents(eventQueue, vmDeathRequest)) { // jojo tady skutecne nic neni :) } } /** * Precteni a zpracovani udalosti * * @param eventQueue fronta udalosti * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent * * @return true pokud se ma pokracovat ve zpracovavani udalosti * false pokud se ma sledovana VM ukoncit */ private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) { EventSet eventSet; try { // precist udalosti z fronty eventSet = eventQueue.remove(); int events = eventSet.size(); System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":")); // projit vsemi udalostmi for (Event event : eventSet) { if (event instanceof VMStartEvent) { System.out.println(" VMStartEvent"); } else if (event instanceof VMDeathEvent) { System.out.println(" VMDeathEvent"); // zakazat dalsi generovani udalosti // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) vmDeathRequest.disable(); // posleze se zavola shutdownVirtualMachine() return false; } else if (event instanceof MethodEntryEvent) { printMethodEntryInfo((MethodEntryEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } /** * Zavolano pri vyskytu udalosti typu MethodEntry */ private static void printMethodEntryInfo(MethodEntryEvent event) { String methodName = event.method().name(); // nyni mame vsechny informace, lze je tedy vypsat System.out.format(" entry into method: %s\n", methodName); } }
Příklad výstupu generovaného tímto demonstračním příkladem (výstup je zkrácen):
Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: getClassSignature ... ... ... Got 1 request: entry into method: hook Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: run Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: toArray Got 1 request: entry into method: size Got 1 request: entry into method: size Got 1 request: entry into method: iterator Got 1 request: entry into method: keySet Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: iterator Got 1 request: entry into method: newKeyIterator Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: access$100 Got 1 request: entry into method: hasNext Got 1 request: entry into method: access$100 Got 1 request: entry into method: getClass Got 1 request: entry into method: reverse Got 1 request: entry into method: size Got 1 request: entry into method: iterator Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: <init> Got 1 request: entry into method: hasNext Got 1 request: entry into method: size Got 1 request: entry into method: hasNext Got 1 request: entry into method: size Got 2 requests: VMDeathEvent Calling exit
5. Filtrace tříd, pro jejichž metody se vyvolá událost MethodEntryEvent
Pokud jste si vyzkoušeli přeložit a spustit debugger z předchozí kapitoly, zajisté jste si všimli, že počet událostí generovaných při vstupu do metod, je značný, a to i pro jednoduché aplikace typu „Hello world!“. Většinou však nepotřebujeme detekovat vstup do všech metod, ale jen do metod náležejících nějaké třídě nebo několika vybraným třídám. Samozřejmě je možné pro každou událost typu MethodEntryEvent zjistit jméno třídy příslušné volané metody, to je však poměrně náročné z hlediska zpracování – filtrace se totiž provádí v poměrně pozdním zpracování události (po vytvoření události a jejím vložení do fronty událostí).
Namísto toho je možné pro události typu MethodEntryEvent definovat filtr, který povolí vytvoření událostí pouze pro metody tříd, jejichž jména odpovídají zadanému regulárnímu výrazu. Pro tento účel lze využít com.sun.jdi.request.MethodEntryRequest.addClassFilter(java.lang.String) popř. com.sun.jdi.request.MethodEntryRequest.addClassFilter(com.sun.jdi.ReferenceType) (zde se již regulární výraz samozřejmě nepoužívá). Kromě „pozitivního“ filtru, je možné použít i filtr „negativní“, který se zadává s využitím com.sun.jdi.request.MethodEntryRequest.addClassExclusionFilter(java.lang.String). Upravme nyní uživatelskou metodu registerMethodEntryRequest() takovým způsobem, aby se zjišťovaly vstupy do metod třídy „Test5“:
/** * Registrace udalosti typu MethodEntry */ private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) { MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest(); // pridame jeste filtr methodEntryRequest.addClassFilter("Test5"); // po registraci udalosti je jeste nutne tento typ udalosti povolit methodEntryRequest.enable(); return methodEntryRequest; }
Po přidání filtru se výstup debuggeru výrazným způsobem zkrátí:
Connecting to virtual machine Connected Got 1 request: VMStartEvent Got 1 request: entry into method: main Got 1 request: entry into method: <init> Got 1 request: entry into method: run Got 1 request: entry into method: foo Got 1 request: entry into method: bar Got 1 request: entry into method: baz Got 2 requests: VMDeathEvent Calling exit
6. Zjištění podrobnějších informací o volané metodě
Uživatelskou metodu printMethodEntryInfo() je samozřejmě možné různými způsoby rozšiřovat. Kromě jména metody (což je zcela základní informace) lze zjistit i jméno příslušné třídy a dokonce i pozici volané metody v rámci zdrojového souboru příslušné třídy. První úprava metody printMethodEntryInfo() využívá objekt typu Location, s nímž jsme se již v tomto seriálu setkali:
/** * Zavolano pri vyskytu udalosti typu MethodEntry */ private static void printMethodEntryInfo(MethodEntryEvent event) { Location location = event.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(" entry into method: %s.%s (%s:%s)\n", className, methodName, sourceName, lineNumber); }
Pro získání jména třídy, ve které je volaná metoda deklarována, se používá volání com.sun.jdi.Method.declaringType() (přesněji řečeno com.sun.jdi.TypeComponent.declaringType()) vracející objekt typu ReferenceType:
/** * Jmeno tridy, jejiz metoda byla zavolana. */ private static String getClassName(Location location) { return location.method().declaringType().name(); }
Jako ukázku toho, že jméno volané metody je možné získat více způsoby, je zde ukázáno použití Location.method() namísto MethodEntryEvent.method():
/** * Jmeno volane metody. */ private static String getMethodName(Location location) { return location.method().name(); }
Zjištění jména zdrojového kódu, v němž je volaná metoda a samozřejmě i příslušná třída deklarována, je velmi jednoduché, protože pro tento účel lze použít volání com.sun.jdi.Location.sourceName(). V případě potřeby je možné zjistit i cestu k tomuto souboru s využitím com.sun.jdi.Location.sourcePath() (tuto informaci jsme v rozhraní JVM TI zjišťovali jen velmi složitým způsobem):
/** * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci. */ private static String getSourceName(Location location) { try { return location.sourceName(); } catch (AbsentInformationException e) { return "unknown"; } }
Informaci o čísle řádku v rámci zdrojového souboru (pokud je ovšem tato informace vůbec dostupná) získáme s využitím com.sun.jdi.Location.lineNumber(), která vrátí kladné číslo v případě, že je číslo řádku známé a zápornou či nulovou hodnotu ve chvíli, kdy číslo řádku není možné z nějakého důvodu zjistit (typicky se jedná o nativní metody).
/** * 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>"; }
7. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection2
Vylepšení popsaná v páté a šesté kapitole jsou součástí dnešního druhého demonstračního příkladu nazvaného JDIMethodEntryDetection2, jehož zdrojový kód je zobrazen pod tímto odstavcem:
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.Location; 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; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.MethodEntryEvent; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.EventRequestManager; import com.sun.jdi.request.MethodEntryRequest; import com.sun.jdi.request.VMDeathRequest; /** * Pripojeni k bezicimu virtualnimu stroji Javy, * ktery byl spusten s parametry: * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida * * * @author Pavel Tisnovsky */ public class JDIMethodEntryDetection2 { /** * 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"; /** * 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; } // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni 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 Javy runVirtualMachine(virtualMachine); // ukazka pouziti EventRequestManageru acquireAndUseEventRequestManager(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 * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void runVirtualMachine(VirtualMachine virtualMachine) { virtualMachine.resume(); } /** * 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); } /** * Ukazka pouziti EventRequestManageru. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) { EventRequestManager eventRequestManager = virtualMachine.eventRequestManager(); VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager); MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest); } /** * Registrace udalosti typu VMDeathEvent */ private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) { VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest(); // po registraci udalosti je jeste nutne tento typ udalosti povolit vmDeathRequest.enable(); return vmDeathRequest; } /** * Registrace udalosti typu MethodEntry */ private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) { MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest(); // pridame filtr na jmeno tridy methodEntryRequest.addClassFilter("Test5"); // po registraci udalosti je jeste nutne tento typ udalosti povolit methodEntryRequest.enable(); return methodEntryRequest; } /** * Klasicka smycka pro postupne zpracovani udalosti. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent * @param methodEntryRequest objekt pro rizeni udalosti typu MethodEntry */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, MethodEntryRequest methodEntryRequest) { EventQueue eventQueue = virtualMachine.eventQueue(); // precist a zpracovat udalosti while (processEvents(eventQueue, vmDeathRequest)) { // jojo tady skutecne nic neni :) } } /** * Precteni a zpracovani udalosti * * @param eventQueue fronta udalosti * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent * * @return true pokud se ma pokracovat ve zpracovavani udalosti * false pokud se ma sledovana VM ukoncit */ private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) { EventSet eventSet; try { // precist udalosti z fronty eventSet = eventQueue.remove(); int events = eventSet.size(); System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":")); // projit vsemi udalostmi for (Event event : eventSet) { if (event instanceof VMStartEvent) { System.out.println(" VMStartEvent"); } else if (event instanceof VMDeathEvent) { System.out.println(" VMDeathEvent"); // zakazat dalsi generovani udalosti // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) vmDeathRequest.disable(); // posleze se zavola shutdownVirtualMachine() return false; } else if (event instanceof MethodEntryEvent) { printMethodEntryInfo((MethodEntryEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } /** * Zavolano pri vyskytu udalosti typu MethodEntry */ private static void printMethodEntryInfo(MethodEntryEvent event) { Location location = event.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(" entry into method: %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>"; } }
Po spuštění tohoto příkladu již získáme zajímavější výstup:
Connecting to virtual machine Connected Got 1 request: VMStartEvent Got 1 request: entry into method: Test5.<init> (Test5.java:1) Got 1 request: entry into method: Test5.run (Test5.java:4) Got 1 request: entry into method: Test5.foo (Test5.java:10) Got 1 request: entry into method: Test5.bar (Test5.java:15) Got 1 request: entry into method: Test5.baz (Test5.java:19) Got 2 requests: VMDeathEvent Calling exit
8. Zjištění modifikátorů, parametrů a návratových typů volaných metod
Pro zajímavost ještě debugger rozšíříme o výpis prakticky všech informací, které lze zjistit pro volané metody. Již jsme si ukázali, jak se zjistí a zobrazí jméno třídy, jméno metody, jméno zdrojového souboru s třídou a popř. i číslo řádku, na němž metodu nalezneme. Tyto informace lze doplnit o plnou hlavičku metody, včetně jejích modifikátorů, návratového typu i typů argumentů metody. Uživatelská metoda printMethodEntryInfo() je tedy náležitým způsobem rozšířena:
/** * Zavolano pri vyskytu udalosti typu MethodEntry * * @param event * udalost typu MethodEntry */ private static void printMethodEntryInfo(MethodEntryEvent event) { Method method = event.method(); Location location = event.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); // typ navratove hodnoty reprezentovany jako retezec String type = method.returnTypeName(); // priznaky pristupovych prav String accessibilityStr = getAccessibility(method); String staticStr = method.isStatic() ? "static " : ""; String finalStr = method.isFinal() ? "final " : ""; String nativeStr = method.isNative() ? "native " : ""; String synchronizedStr = method.isSynchronized() ? "synchronized " : ""; // nyni mame vsechny informace, lze je tedy vypsat System.out.format(" entry into method: %s%s%s%s%s%s %s.%s", accessibilityStr, staticStr, finalStr, nativeStr, synchronizedStr, type, className, methodName); printMethodArguments(method); // po vypisu argumentu se jeste vypise jmeno zdrojoveho // souboru a cislo radku System.out.format(" (%s:%s)\n", sourceName, lineNumber); }
U volaných metod je možné zjistit a vypsat další 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ů, popř. 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 pro spuštění metody 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{} (tuto část kódu již vlastně známe z předchozích částí tohoto seriálu):
/** * 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 // pokud je to samozrejme mozne 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, // alespon 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.print(");"); }
Poslední důležitou informací o volané metodě jsou její přístupová práva, která lze zjistit velmi snadno:
/** * Pristupova prava k atributu ci k metode (my zde tuto metodu * vyuzijeme pouze pro ziskani pristupovych prav k volanym metodam) */ private static String getAccessibility(Accessible methodOrField) { if (methodOrField.isPublic()) { return "public "; } if (methodOrField.isProtected()) { return "protected "; } if (methodOrField.isPrivate()) { return "private "; } return ""; }
9. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection3
Konečně se dostáváme ke zdrojovému kódu dnešního posledního demonstračního příkladu nazvaného JDIMethodEntryDetection3. Tento příklad – debugger – dokáže vypsat všechny dostupné informace o volaných metodách náležejících do třídy Test5. Kromě jmen metod se vypíšou i jejich modifikátory, návratový typ a argumenty (včetně jmen argumentů, pokud je ovšem tato informace dostupná):
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.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.Method; 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; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.MethodEntryEvent; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.EventRequestManager; import com.sun.jdi.request.MethodEntryRequest; import com.sun.jdi.request.VMDeathRequest; /** * Pripojeni k bezicimu virtualnimu stroji Javy, * ktery byl spusten s parametry: * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida * * Detekce vstupu do metod deklarovanych ve tride Test5. * U kazde metody se vypise jeji typ, jmeno i typy parametru, popr. * i jmena parametru, pokud je ovsem lze zjistit. * * @author Pavel Tisnovsky */ public class JDIMethodEntryDetection3 { /** * 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"; /** * 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; } // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni 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 Javy runVirtualMachine(virtualMachine); // ukazka pouziti EventRequestManageru acquireAndUseEventRequestManager(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 * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void runVirtualMachine(VirtualMachine virtualMachine) { virtualMachine.resume(); } /** * 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); } /** * Ukazka pouziti EventRequestManageru. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) { EventRequestManager eventRequestManager = virtualMachine.eventRequestManager(); // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager); MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest); } /** * Registrace udalosti typu VMDeathEvent */ private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) { VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest(); // po registraci udalosti je jeste nutne tento typ udalosti povolit vmDeathRequest.enable(); return vmDeathRequest; } /** * Registrace udalosti typu MethodEntry */ private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) { MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest(); // pridame filtr na jmeno tridy methodEntryRequest.addClassFilter("Test5"); // po registraci udalosti je jeste nutne tento typ udalosti povolit methodEntryRequest.enable(); return methodEntryRequest; } /** * Klasicka smycka pro postupne zpracovani udalosti. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param vmDeathRequest * objekt pro rizeni udalosti typu VMDeathEvent * @param methodEntryRequest * objekt pro rizeni udalosti typu MethodEntry */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, MethodEntryRequest methodEntryRequest) { EventQueue eventQueue = virtualMachine.eventQueue(); // precist a zpracovat udalosti while (processEvents(eventQueue, vmDeathRequest)) { // jojo tady skutecne nic neni :) } } /** * Precteni a zpracovani udalosti * * @param eventQueue * fronta udalosti * @param vmDeathRequest * objekt pro rizeni udalosti typu VMDeathEvent * * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud * se ma sledovana VM ukoncit */ private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) { EventSet eventSet; try { // precist udalosti z fronty eventSet = eventQueue.remove(); int events = eventSet.size(); System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":")); // projit vsemi udalostmi for (Event event : eventSet) { if (event instanceof VMStartEvent) { System.out.println(" VMStartEvent"); } else if (event instanceof VMDeathEvent) { System.out.println(" VMDeathEvent"); // zakazat dalsi generovani udalosti // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) vmDeathRequest.disable(); // posleze se zavola shutdownVirtualMachine() return false; } else if (event instanceof MethodEntryEvent) { printMethodEntryInfo((MethodEntryEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } /** * Zavolano pri vyskytu udalosti typu MethodEntry * * @param event * udalost typu MethodEntry */ private static void printMethodEntryInfo(MethodEntryEvent event) { Method method = event.method(); Location location = event.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); // typ navratove hodnoty reprezentovany jako retezec String type = method.returnTypeName(); // priznaky pristupovych prav String accessibilityStr = getAccessibility(method); String staticStr = method.isStatic() ? "static " : ""; String finalStr = method.isFinal() ? "final " : ""; String nativeStr = method.isNative() ? "native " : ""; String synchronizedStr = method.isSynchronized() ? "synchronized " : ""; // nyni mame vsechny informace, lze je tedy vypsat System.out.format(" entry into method: %s%s%s%s%s%s %s.%s", accessibilityStr, staticStr, finalStr, nativeStr, synchronizedStr, type, className, methodName); printMethodArguments(method); // po vypisu argumentu se jeste vypise jmeno zdrojoveho // souboru a cislo radku System.out.format(" (%s:%s)\n", 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 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 // pokud je to samozrejme mozne 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, // alespon 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.print(");"); } /** * Pristupova prava k atributu ci k metode (my zde tuto metodu * vyuzijeme pouze pro ziskani pristupovych prav k volanym metodam) */ private static String getAccessibility(Accessible methodOrField) { if (methodOrField.isPublic()) { return "public "; } if (methodOrField.isProtected()) { return "protected "; } if (methodOrField.isPrivate()) { return "private "; } return ""; } }
Po spuštění tohoto demonstračního debuggeru získáme podrobnou informaci o všech volaných metodách třídy Test5 (tato třída musela být přeložena s volbou -g):
Connecting to virtual machine Connected Got 1 request: VMStartEvent Got 1 request: entry into method: public static void Test5.main(java.lang.String[] args); (Test5.java:23) Got 1 request: entry into method: public void Test5.<init>(); (Test5.java:1) Got 1 request: entry into method: public void Test5.run(int value, char znak); (Test5.java:4) Got 1 request: entry into method: public void Test5.foo(float value); (Test5.java:10) Got 1 request: entry into method: public void Test5.bar(float value, float[] array, java.lang.String message); (Test5.java:15) Got 1 request: entry into method: public void Test5.baz(); (Test5.java:19) Got 2 requests: VMDeathEvent Calling exit
10. Zdrojový kód testovací třídy Test5
public class Test5 { 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) { baz(); } public void baz() { System.out.println("baz"); } public static void main(String[] args) { System.out.println("Press any key"); try { System.in.read(); } catch (java.io.IOException e) { } new Test5().run(42, 'a'); } }
11. Repositář se zdrojovými kódy všech tří demonstračních příkladů
Zdrojové kódy všech třech demonstračních příkladů popsaných v předchozích kapitolách byly uloženy (podobně jako tomu bylo i v předešlý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 zmíněných zdrojových souborů můžete najít na adresách:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | JDIMethodEntryDetection1.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8c1c55bcd7d5/jdi/JDIMethodEntryDetection1.java |
2 | JDIMethodEntryDetection2.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8c1c55bcd7d5/jdi/JDIMethodEntryDetection2.java |
3 | JDIMethodEntryDetection3.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8c1c55bcd7d5/jdi/JDIMethodEntryDetection3.java |
4 | Test5.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8c1c55bcd7d5/jdi/Test5.java |
12. 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