Obsah
1. Pohled pod kapotu JVM – práce s breakpointy s využitím rozhraní JDI
2. Registrace události vyvolané při dosažení breakpointu
3. Zjištění přesné lokace, na níž má být breakpoint umístěn
4. Upravená smyčka událostí (event loop)
5. Výpis základních informací při dosažení breakpointu
6. Zdrojový kód prvního demonstračního příkladu JDIBreakpointDemo1
7. Zdrojový kód testovací třídy Test8
8. Výstup generovaný demonstračním příkladem JDIBreakpointDemo1
9. Přečtení podrobnějších informací o vláknu při vstupu na breakpoint
10. Hodnoty parametrů metod i lokálních proměnných
11. Zdrojový kód druhého demonstračního příkladu JDIBreakpointDemo2
12. Výstup generovaný demonstračním příkladem JDIBreakpointDemo2
13. Repositář se zdrojovými kódy demonstračních příkladů
1. Pohled pod kapotu JVM – práce s breakpointy 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 vysvětlíme práci s breakpointy s využitím rozhraní JDI (Java Debugger Interface). Breakpointy, které jsou nedílnou součástí prakticky všech univerzálních debuggerů, určují místo v programovém kódu, po jehož dosažení většinou dojde k pozastavení vlákna, které na breakpoint vstoupilo, a následně k získání dalších informací souvisejících s breakpointem – například je možné si nechat zobrazit seznam lokálních proměnných apod. Díky rozhraní JDI je práce s breakpointy poměrně jednoduchá, což si ostatně ukážeme i na dvojici demonstračních příkladů. Jejich jedinou funkcí bude zobrazení informací o dosažení breakpointu a v případě druhého demonstračního příkladu se navíc zobrazí i informace o jménech a hodnotách všech viditelných lokálních proměnných, včetně argumentů metody, v níž došlo k zastavení na breakpointu.
Pro přehled si nejprve uveďme některé třídy, rozhraní a metody z JDI, s nimiž se setkáme v následujících kapitolách:
# | Balíček | Třída/rozhraní | Metoda | Popis |
---|---|---|---|---|
1 | com.sun.jdi.request | EventRequestManager | createBreakpointRequest() | registrace breakpointu na zadané lokaci |
2 | com.sun.jdi.request | EventRequest | setSuspendPolicy() | určuje, která vlákna se mají zastavit, když dojde k zaregistrované události (zde vstupu na breakpoint) |
3 | com.sun.jdi | Location | lineNumber() | vrátí číslo řádku pro danou lokaci |
4 | com.sun.jdi | ReferenceType | allLineLocations() | vrátí seznam všech lokací pro danou třídu |
5 | com.sun.jdi | VirtualMachine | classesByName() | vrátí všechny třídy s odpovídajícím jménem |
6 | com.sun.jdi.event | LocatableEvent | thread() | vrátí thread, v němž došlo k události |
7 | com.sun.jdi | ThreadReference | frame() | získá n-tý zásobníkový rámec vlákna |
8 | com.sun.jdi | StackFrame | visibleVariables() | vrátí seznam všech viditelných proměnných |
2. Registrace události vyvolané při dosažení breakpointu
První věcí, kterou je nutné při práci s breakpointy s využitím rozhraní JDI udělat, je zaregistrovat a taktéž povolit vytváření událostí typu BreakpointEvent. Tyto události jsou do fronty událostí (event queue) vloženy ve chvíli, kdy nějaké vlákno skutečně breakpointu dosáhne. Událost tohoto typu se registruje opět přes objekt typu EventRequestManager s využitím dalšího objektu, který je typu BreakpointRequest. Kromě události BreakpointEvent zaregistrujeme i událost typu VMDeathRequest, protože náš demonstrační debugger musí korektně ukončit svoji činnost ve chvíli, kdy je monitorovaný (sledovaný) virtuální stroj Javy zastaven. Jak se registrace obou zmíněných typů událostí provádí, zjistíme při pohledu na zdrojový kód uživatelské metody acquireAndUseEventRequestManager(), který je vypsán pod tímto odstavcem:
/** * 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); // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint BreakpointRequest breakpointRequest = registerBreakpointEvent(virtualMachine, eventRequestManager); //System.out.println("Breakpoint set: " + breakpointRequest.toString()); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest); }
O vlastní registraci a povolení generování událostí typu BreakpointEvent se stará další uživatelská metoda nazvaná registerBreakpointEvent(). U registrovaného breakpointu musíme znát přesnou lokaci v kódu, kam má být breakpoint vložen. Tato lokace je představována objektem typu Location, jenž je získán v metodě computeBreakpointLocation(), jejíž popis bude uveden v navazující kapitole. Zajímavá je taktéž specifikace chování sledované JVM ve chvíli dosažení breakpointu – my zde vyžadujeme pozastavení všech vláken sledované aplikace, což je zajištěno zavolání metody breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL):
/** * Registrace udalosti typu BreakpointRequest. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu BreakpointRequest */ private static BreakpointRequest registerBreakpointEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) { Location breakpointLocation; try { // potrebujeme zjistit lokaci, kde se ma breakpoint nastavit breakpointLocation = computeBreakpointLocation(virtualMachine, TEST_CLASS_NAME, BREAKPOINT_LINE_NUMBER); // ne vzdy se muze podarit zjistit lokaci if (breakpointLocation == null) { System.err.println("Can not find location to set breakpoint!"); return null; } // registrace breakpointu BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(breakpointLocation); // pri vstupu na breakpoint se zastavi vsechna vlakna // ve sledovanem virtualnim stroji Javy breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); breakpointRequest.enable(); return breakpointRequest; } catch (AbsentInformationException e) { e.printStackTrace(); } return null; }
3. Zjištění přesné lokace, na níž má být breakpoint umístěn
Pokud se breakpoint nastavuje v nějakém skutečném debuggeru, ať již se jedná o standardní jdb ovládaný z příkazové řádky, nebo některý debugger zabudovaný do integrovaného vývojového prostředí (Eclipse, IDEA, Netbeans), může uživatel specifikovat, na kterém řádku se má breakpoint nacházet. Musí se tedy zadat dvojice Jméno_třídy:číslo_řádku; přičemž u vizuálních debuggerů, které jsou součástí IDE, se jméno třídy odvodí od pozice breakpointu v rámci zdrojového souboru (ten může obsahovat více tříd – neveřejných tříd, anonymních tříd, vnitřních tříd atd.). Získání přesné lokace v rozhraní JDI je poněkud složitější, protože číslo řádku je nutné vyhledat v seznamu obsahujícím objekty typu Location. Těmito objekty je nutné projít a vyhledat objekt, jehož číslo řádku se shoduje s číslem řádku zadaným uživatelem:
/** * Zjisteni lokace, kde se ma breakpoint nastavit. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param className * jmeno tridy, v jejimz ramci se pracuje s breakpointem * @param breakpointLineNumber * cislo radku, na nemz ma byt breakpoint nastaven * @return lokace, kde se ma breakpoint nastavit nebo null * @throws AbsentInformationException */ private static Location computeBreakpointLocation(VirtualMachine virtualMachine, String className, int breakpointLineNumber) throws AbsentInformationException { ReferenceType class1 = getClassInMonitoredVM(virtualMachine, className); // precist vsechny lokace ve zjistene tride List<Location> locations = class1.allLineLocations(); // projit vsemi lokacemi // pro kazdou lokaci se pokusime zjistit cislo radku // a porovnat ji se zadanym cislem radku for (Location location : locations) { // pokud dojde ke shode cisel radku, // lze na teto lokaci nastavit breakpoint if (location.lineNumber() == breakpointLineNumber) { return location; } } // nepovedlo se najit lokaci pro dane cislo radku return null; }
V předchozí metodě se nejprve získala reference třídy nějakého jména (v našem případě je jméno nastaveno na „Test8“). Získání této třídy je relativně jednoduché, o čemž se můžete přesvědčit z následujícího úryvku kódu. Nejdříve se přes VirtualMachine.classesByName() získá seznam všech tříd, jejichž jméno odpovídá řetězci „Test6“. Vzhledem k tomu, že tato třída existuje pouze jedna, můžeme z vráceného seznamu následně jednoduše přečíst první prvek a vrátit ho volající metodě:
/** * Precteni tridy zadaneho jmena v monitorovanem virtualnim stroji. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param className * jmeno tridy, v jejimz ramci se pracuje s breakpointem * @return reference tridy v monitorovanem virtualnim stroji */ private static ReferenceType getClassInMonitoredVM(VirtualMachine virtualMachine, String className) { // mela by se vratit jedina trida List<ReferenceType> allClasses = virtualMachine.classesByName(className); System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size()); // vratila se pravdepodobne jedina trida, takze ji precteme ReferenceType class1 = allClasses.get(0); return class1; }
4. Upravená smyčka událostí (event loop)
Implementace smyčky událostí je zcela stejná, jako smyčka použitá v demonstračních příkladech popsaných minule a předminule:
/** * 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 */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) { EventQueue eventQueue = virtualMachine.eventQueue(); // precist a zpracovat udalosti while (processEvents(eventQueue, vmDeathRequest)) { // jojo tady skutecne nic neni :) } }
Změnil se však kód další uživatelské metody nazvané processEvents(), protože v této metodě je nutné reagovat na výskyt dvou typů událostí. První událost typu VMDeathEvent je vygenerována ve chvíli ukončování práce sledovaného virtuálního stroje Javy a událost typu BreakpointEvent vznikne ve chvíli, kdy nějaké vlákno vstoupí na nastavený breakpoint. Povšimněte si toho, že se všechna vlákna sledovaného virtuálního stroje opět spustí příkazem eventSet.resume():
/** * 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 BreakpointEvent) { // nejake vlakno se zastavilo na breakpointu printInfoAboutBreakpoint((BreakpointEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; }
5. Výpis základních informací při dosažení breakpointu
Pokud je ve smyčce událostí popsané v předchozí kapitole přečtena z fronty událostí (event queue) událost typu BreakpointEvent, je zavolána uživatelská metoda nazvaná printInfoAboutBreakpoint(), jejímž úkolem je informovat uživatele formou zprávy vypsané na standardní výstup. První verze této metody je velmi jednoduchá, protože se zde pouze vypíše, na jakém řádku bylo dosaženo breakpointu (breakpointů lze totiž samozřejmě zaregistrovat větší množství). Dopředu si řekněme, že značně vylepšená varianta uživatelské metody printInfoAboutBreakpoint() bude popsána v deváté a taktéž v desáté kapitole:
/** * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu. * * @param event udalost s informacemi o dosazenem breakpointu */ private static void printInfoAboutBreakpoint(BreakpointEvent event) { // precist cislo radku, na kterem doslo k zastaveni vlakna int lineNumber = event.location().lineNumber(); System.out.println("Breakpoint at line " + lineNumber + ": "); } }
6. Zdrojový kód prvního demonstračního příkladu JDIBreakpointDemo1
Celý dnešní první demonstrační příklad nazvaný JDIBreakpointDemo1 pracuje tak, že se nejdříve zaregistrují oba výše zmíněné typy zpracovávaných událostí (včetně nastavení breakpointu) a posléze se spustí smyčka událostí (event loop). Jakmile nějaké vlákno dosáhne breakpointu, vypíše se na standardní výstup informace o řádku (ve zdrojovém kódu), kde se breakpoint nacházel a posléze se vlákno (resp. i všechna ostatní vlákna) ve sledované JVM opět spustí:
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.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; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; 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 * * Po pripojeni se nastavi breakpoint a pri jeho dosazeni se vypisou zakladni * informace o sledovane aplikaci. * * @author Pavel Tisnovsky */ public class JDIBreakpointDemo1 { /** * 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"; /** * Jmeno testovane tridy. */ private static final String TEST_CLASS_NAME = "Test8"; /** * Cislo radku, na kterem bude nastaveny breakpoint. */ private static int BREAKPOINT_LINE_NUMBER = 6; /** * Vstupni metoda tohoto demonstracniho 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 * * @param connectors * seznam vsech dostupnych konektoru pro pripojeni * @return 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. * * @param connector * konektor pouzivajici pro pripojeni sockety * @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); // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint BreakpointRequest breakpointRequest = registerBreakpointEvent(virtualMachine, eventRequestManager); //System.out.println("Breakpoint set: " + breakpointRequest.toString()); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest); } /** * Registrace udalosti typu VMDeathEvent. * * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu VMDeathRequest */ 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 BreakpointRequest. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu BreakpointRequest */ private static BreakpointRequest registerBreakpointEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) { Location breakpointLocation; try { // potrebujeme zjistit lokaci, kde se ma breakpoint nastavit breakpointLocation = computeBreakpointLocation(virtualMachine, TEST_CLASS_NAME, BREAKPOINT_LINE_NUMBER); // ne vzdy se muze podarit zjistit lokaci if (breakpointLocation == null) { System.err.println("Can not find location to set breakpoint!"); return null; } // registrace breakpointu BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(breakpointLocation); // pri vstupu na breakpoint se zastavi vsechna vlakna // ve sledovanem virtualnim stroji Javy breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); breakpointRequest.enable(); return breakpointRequest; } catch (AbsentInformationException e) { e.printStackTrace(); } return null; } /** * Zjisteni lokace, kde se ma breakpoint nastavit. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param className * jmeno tridy, v jejimz ramci se pracuje s breakpointem * @param breakpointLineNumber * cislo radku, na nemz ma byt breakpoint nastaven * @return lokace, kde se ma breakpoint nastavit nebo null * @throws AbsentInformationException */ private static Location computeBreakpointLocation(VirtualMachine virtualMachine, String className, int breakpointLineNumber) throws AbsentInformationException { ReferenceType class1 = getClassInMonitoredVM(virtualMachine, className); // precist vsechny lokace ve zjistene tride List<Location> locations = class1.allLineLocations(); // projit vsemi lokacemi // pro kazdou lokaci se pokusime zjistit cislo radku // a porovnat ji se zadanym cislem radku for (Location location : locations) { // pokud dojde ke shode cisel radku, // lze na teto lokaci nastavit breakpoint if (location.lineNumber() == breakpointLineNumber) { return location; } } // nepovedlo se najit lokaci pro dane cislo radku return null; } /** * Precteni tridy zadaneho jmena v monitorovanem virtualnim stroji. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param className * jmeno tridy, v jejimz ramci se pracuje s breakpointem * @return reference tridy v monitorovanem virtualnim stroji */ private static ReferenceType getClassInMonitoredVM(VirtualMachine virtualMachine, String className) { // mela by se vratit jedina trida List<ReferenceType> allClasses = virtualMachine.classesByName(className); System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size()); // vratila se pravdepodobne jedina trida, takze ji precteme ReferenceType class1 = allClasses.get(0); return class1; } /** * 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 */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) { 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 BreakpointEvent) { // nejake vlakno se zastavilo na breakpointu printInfoAboutBreakpoint((BreakpointEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } /** * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu. * * @param event udalost s informacemi o dosazenem breakpointu */ private static void printInfoAboutBreakpoint(BreakpointEvent event) { // precist cislo radku, na kterem doslo k zastaveni vlakna int lineNumber = event.location().lineNumber(); System.out.println("Breakpoint at line " + lineNumber + ": "); } }
7. Zdrojový kód testovací třídy Test8
Oba dva dnešní demonstrační příklady budeme testovat proti sledovanému virtuálnímu stroji Javy, v němž bude spuštěna velmi jednoduchá aplikace představovaná třídou Test8. V této třídě se mj. nachází i metoda run() s programovou smyčkou. Pokud se podíváte na hodnotu konstant TEST_CLASS_NAME a BREAKPOINT_LINE_NUMBER uvedených ve zdrojovém kódu prvního demonstračního příkladu, snadno zjistíte, že breakpoint bude nastaven na příkaz y+=x; tvořícího tělo zmíněné programové smyčky:
public class Test8 { public void run(int x) { int y = 0; for (int i = 1; i < 10; i++) { y += x; } } public static void main(String[] args) { System.out.println("Press any key"); try { System.in.read(); } catch (java.io.IOException e) { // . // } new Test8().run(10); } }
8. Výstup generovaný demonstračním příkladem JDIBreakpointDemo1
Podívejme se nyní na to, jak vypadá výstup demonstračního příkladu JDIBreakpointDemo1 ve chvíli, kdy je tento jednoduchý debugger spuštěn oproti výše popsané třídě Test8 v monitorovaném virtuálním stroji Javy:
Connecting to virtual machine Connected Number of classes with name 'Test8': 1 Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 1 request: Breakpoint at line 6: Got 2 requests: VMDeathEvent Calling exit
Vidíme, že na registrovaný breakpoint se přistoupilo celkem devětkrát, což velmi dobře koresponduje s programovou smyčkou implementovanou v testovací metodě Test8.run():
for (int i = 1; i < 10; i++) { y += x; }
9. Přečtení podrobnějších informací o vláknu při vstupu na breakpoint
V předchozí kapitole byly vypsány zprávy vytvořené dnešním prvním demonstračním příkladem. Zajisté uznáte, že informace o tom, že nějaké vlákno vstoupilo na breakpoint, mohou sice být velmi zajímavé (například při trasování), ovšem většinou od debuggerů vyžadujeme mnohem větší množství informací. Zejména se jedná o informace vztažené k vláknu, které na breakpoint vstoupilo: hodnoty lokálních proměnných a parametrů aktuální metody, popř. i obsah všech zásobníkových rámců. Tyto informace je samozřejmě možné s využitím rozhraní JDI relativně snadné získat, musí se však dodržet jedna podmínka – testované Javovské třídy musí být přeloženy společně s ladicími informacemi, tj. při překladu je nutné použít přepínač -g. Pokud by ladicí informace nebyly součástí bajtkódu, tedy souborů s koncovkou .class, nebylo by možné namapovat hodnoty lokálních proměnných a argumentů na jejich jména. V další kapitole bude ukázáno, jakým způsobem lze získat již zmíněné informace o argumentech a lokálních proměnných metody, v níž byl breakpoint zaregistrován.
10. Hodnoty parametrů metod i lokálních proměnných
Ve vylepšené variantě uživatelské metody nazvané printInfoAboutBreakpoint() se nejprve vypíše číslo řádku, na němž nějaké vlákno vstoupilo na breakpoint (tuto část kódu již známe). Následně se s využitím metody Event.thread() získá reference vlákna a z něho posléze i objekt typu StackFrame představující jeden zásobníkový rámec. Povšimněte si, že lze získat libovolný zásobníkový rámec, protože jednotlivé rámce jsou zpětně číslovány on nuly (nejvyšší rámec) směrem ke kladným hodnotám. Důležité je, že zásobníkový rámec je možné získat pouze pro zastavená vlákna, což je však v našem případě splněno. Následně se z objektu typu StackFrame načte seznam všech viditelných lokálních proměnných a argumentů (nejedná se tedy o všechny lokální proměnné, nemusí se například vrátit lokální počitadla smyček). U každé proměnné se přečte a následně i vypíše její jméno a hodnota převedená na řetězec:
/** * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu. * * @param event udalost s informacemi o dosazenem breakpointu */ private static void printInfoAboutBreakpoint(BreakpointEvent event) { // precist cislo radku, na kterem doslo k zastaveni vlakna int lineNumber = event.location().lineNumber(); System.out.println("Breakpoint at line " + lineNumber + ": "); // ziskat referenci na vlakno ThreadReference threadRef = event.thread(); try { // ziskat posledni zasobnikovy ramec vlakna StackFrame stackFrame = threadRef.frame(0); // a projit vsemi viditelnymi promennymi List<LocalVariable> visibleVars = stackFrame.visibleVariables(); for (LocalVariable visibleVar : visibleVars) { Value val = stackFrame.getValue(visibleVar); System.out.println(" Local variable: " + visibleVar.name() + " = " + val); } } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } catch (AbsentInformationException e) { e.printStackTrace(); } }
11. Zdrojový kód druhého demonstračního příkladu JDIBreakpointDemo2
Upravená uživatelská metoda printInfoAboutBreakpoint() se stala součástí dnešního druhého demonstračního příkladu pojmenovaného JDIBreakpointDemo2. Celý zdrojový kód tohoto příkladu je vypsán 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.IncompatibleThreadStateException; import com.sun.jdi.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.ReferenceType; import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; import com.sun.jdi.Value; 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.BreakpointEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; 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 * * Po pripojeni se nastavi breakpoint a pri jeho dosazeni se vypisou zakladni * informace o sledovane aplikaci. * * @author Pavel Tisnovsky */ public class JDIBreakpointDemo2 { /** * 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"; /** * Jmeno testovane tridy. */ private static final String TEST_CLASS_NAME = "Test8"; /** * Cislo radku, na kterem bude nastaveny breakpoint. */ private static int BREAKPOINT_LINE_NUMBER = 6; /** * Vstupni metoda tohoto demonstracniho 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 * * @param connectors * seznam vsech dostupnych konektoru pro pripojeni * @return 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. * * @param connector * konektor pouzivajici pro pripojeni sockety * @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); // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint BreakpointRequest breakpointRequest = registerBreakpointEvent(virtualMachine, eventRequestManager); //System.out.println("Breakpoint set: " + breakpointRequest.toString()); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest); } /** * Registrace udalosti typu VMDeathEvent. * * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu VMDeathRequest */ 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 BreakpointRequest. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu BreakpointRequest */ private static BreakpointRequest registerBreakpointEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) { Location breakpointLocation; try { // potrebujeme zjistit lokaci, kde se ma breakpoint nastavit breakpointLocation = computeBreakpointLocation(virtualMachine, TEST_CLASS_NAME, BREAKPOINT_LINE_NUMBER); // ne vzdy se muze podarit zjistit lokaci if (breakpointLocation == null) { System.err.println("Can not find location to set breakpoint!"); return null; } // registrace breakpointu BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(breakpointLocation); // pri vstupu na breakpoint se zastavi vsechna vlakna // ve sledovanem virtualnim stroji Javy breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); breakpointRequest.enable(); return breakpointRequest; } catch (AbsentInformationException e) { e.printStackTrace(); } return null; } /** * Zjisteni lokace, kde se ma breakpoint nastavit. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param className * jmeno tridy, v jejimz ramci se pracuje s breakpointem * @param breakpointLineNumber * cislo radku, na nemz ma byt breakpoint nastaven * @return lokace, kde se ma breakpoint nastavit nebo null * @throws AbsentInformationException */ private static Location computeBreakpointLocation(VirtualMachine virtualMachine, String className, int breakpointLineNumber) throws AbsentInformationException { ReferenceType class1 = getClassInMonitoredVM(virtualMachine, className); // precist vsechny lokace ve zjistene tride List<Location> locations = class1.allLineLocations(); // projit vsemi lokacemi // pro kazdou lokaci se pokusime zjistit cislo radku // a porovnat ji se zadanym cislem radku for (Location location : locations) { // pokud dojde ke shode cisel radku, // lze na teto lokaci nastavit breakpoint if (location.lineNumber() == breakpointLineNumber) { return location; } } // nepovedlo se najit lokaci pro dane cislo radku return null; } /** * Precteni tridy zadaneho jmena v monitorovanem virtualnim stroji. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param className * jmeno tridy, v jejimz ramci se pracuje s breakpointem * @return reference tridy v monitorovanem virtualnim stroji */ private static ReferenceType getClassInMonitoredVM(VirtualMachine virtualMachine, String className) { // mela by se vratit jedina trida List<ReferenceType> allClasses = virtualMachine.classesByName(className); System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size()); // vratila se pravdepodobne jedina trida, takze ji precteme ReferenceType class1 = allClasses.get(0); return class1; } /** * 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 */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) { 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 BreakpointEvent) { // nejake vlakno se zastavilo na breakpointu printInfoAboutBreakpoint((BreakpointEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } /** * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu. * * @param event udalost s informacemi o dosazenem breakpointu */ private static void printInfoAboutBreakpoint(BreakpointEvent event) { // precist cislo radku, na kterem doslo k zastaveni vlakna int lineNumber = event.location().lineNumber(); System.out.println("Breakpoint at line " + lineNumber + ": "); // ziskat referenci na vlakno ThreadReference threadRef = event.thread(); try { // ziskat posledni zasobnikovy ramec vlakna StackFrame stackFrame = threadRef.frame(0); // a projit vsemi viditelnymi promennymi List<LocalVariable> visibleVars = stackFrame.visibleVariables(); for (LocalVariable visibleVar : visibleVars) { Value val = stackFrame.getValue(visibleVar); System.out.println(" Local variable: " + visibleVar.name() + " = " + val); } } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } catch (AbsentInformationException e) { e.printStackTrace(); } } }
12. Výstup generovaný demonstračním příkladem JDIBreakpointDemo2
Při spuštění druhého demonstračního příkladu JDIBreakpointDemo2 oproti virtuálnímu stroji Javy s běžící testovací aplikací Test8 dostaneme už mnohem podrobnější informaci o vláknu, které vstoupilo na breakpoint. Vypíše se totiž nejenom číslo řádku, ale i hodnoty argumentů a lokálních proměnných funkce run(), takže můžeme snadno sledovat hodnotu počitadla programové smyčky (i) i postupnou změnu hodnoty lokální proměnné y:
Connecting to virtual machine Connected Number of classes with name 'Test8': 1 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: Breakpoint at line 6: Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 2 requests: VMDeathEvent Calling exit
13. Repositář se zdrojovými kódy demonstračních příkladů
Zdrojové kódy obou dnes popsaných demonstračních příkladů JDIBreakpointDemo1 a JDIBreakpointDemo2 jsou společně s dalšími pomocnými soubory uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze těchto zdrojových kódů:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | JDIBreakpointDemo1.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/JDIBreakpointDemo1.java |
2 | JDIBreakpointDemo2.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/JDIBreakpointDemo2.java |
3 | compile_JDIBreakpointDemo1.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/compile_JDIBreakpointDemo1.sh |
4 | compile_JDIBreakpointDemo2.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/compile_JDIBreakpointDemo2.sh |
5 | Test8.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/Test8.java |
6 | Test8.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/Test8.sh |
14. 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