Obsah
2. Rozhraní deklarovaná v balíčku com.sun.jdi.event
3. Obecné události vztažené k celému sledovanému virtuálnímu stroji
4. Události, které vznikají v konkrétních vláknech a na známém místě v aplikaci
5. Rozhraní deklarovaná v baličku com.sun.jdi.request
6. Získání objektu typu com.sun.jdi.request.EventRequestManager
7. Registrace nového typu události a povolení generování událostí
8. Smyčka událostí implementovaná s využitím objektu typu com.sun.jdi.event.EventQueue
9. Zpracování jednotlivých typů událostí a reakce na ukončení práce sledované JVM
10. Kompletní zdrojový kód demonstračního příkladu JDIEventRequestManager
11. Repositář se zdrojovými kódy demonstračního příkladu JDIEventRequestManager i podpůrných skriptů
1. Pohled pod kapotu JVM – využití systémů událostí při sledování činnosti JVM s využitím rozhraní JDI
Dnešní část seriálu o programovacím jazyku Java i o virtuálním stroji Javy je věnována velmi důležité části rozhraní JDI (Java Debugger Interface), bez níž by nebylo možné efektivně sledovat všechny činnosti prováděné ve sledované (cílové) JVM. Jedná se o systém událostí (events), přičemž termínem „událost“ je zde myšlena nějaká změna stavu sledovaného virtuálního stroje Javy, která se na straně debuggeru projeví vznikem objektu typu com.sun.jdi.event.Event. Debugger může tyto události postupně číst z takzvané fronty událostí (event queue) 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. Debugger většinou musí reagovat minimálně na událost typu VMDeathEvent, která nastane ve chvíli, kdy je činnost sledovaného virtuálního stroje ukončována.
Jaké události je vlastně možné zpracovávat? Jedná se například o následující činnosti probíhající ve sledovaném virtuálním stroji:
- 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
V následujících kapitolách si na demonstračním příkladu ukážeme, jakým způsobem je možné sestavit velmi jednoduchý debugger, který nejprve povolí příjem některých typů událostí a posléze tyto události bude zpracovávat ve smyčce událostí (event loop).
2. Rozhraní deklarovaná v balíčku com.sun.jdi.event
Události, které může debugger využívající rozhraní JDI zpracovávat, i některé další pomocné objektové typy, jsou součástí balíčku com.sun.jdi.event. Tento balíček ve skutečnosti neobsahuje žádné veřejné třídy, ale pouze sadu rozhraní, která jsou interně implementována způsobem, který může být závislý na verzi JDI. V první tabulce je vypsáno pět základních rozhraní, které můžeme v balíčku com.sun.jdi.event nalézt a taktéž využít:
| # | Rozhraní | Popis |
|---|---|---|
| 1 | com.sun.jdi.event.EventQueue | Představuje frontu událostí, z níž debugger postupně může události vybírat a dále s nimi pracovat |
| 2 | com.sun.jdi.event.EventSet | Události nejsou z fronty vybírány jednotlivě, ale po skupinách sdružených v objektech implementujících rozhraní EventSet |
| 3 | com.sun.jdi.event.EventIterator | Jednotlivými událostmi uloženými v EventSet lze procházet přes tento iterátor získaný metodou EventSet.eventIterator() |
| 4 | com.sun.jdi.event.Event | Obecná libovolná událost (předek dalších rozhraní popsaných dále) |
| 5 | com.sun.jdi.event.LocatableEvent | Obecná událost vztažená ke konkrétnímu vláknu a pozici v tomto vláknu |
Fronta událostí, tj. objekt typu com.sun.jdi.event.EventQueue se získá velmi snadno ve chvíli, kdy debugger vytvořil objekt typu com.sun.jdi.VirtualMachine:
EventQueue eventQueue = virtualMachine.eventQueue();
Důležité je, že pro každý sledovaný virtuální stroj existuje pouze jedna fronta událostí, takže tato metoda vrátí vždy stejný objekt typu EventQueue. To znamená, že debugger většinou všechny události zpracovává v jediné části kódu a nemůže například získat dvě fronty pro různé typy událostí. Vzhledem k tomu, že se každý typ události typicky zpracovává zcela odlišným způsobem (například vstup na breakpoint versus detekce vzniku výjimky), nevyhneme se při implementaci debuggeru nutnosti implementovat nějaký typ rozeskoku.
3. Obecné události vztažené k celému sledovanému virtuálnímu stroji
V předchozí kapitole bylo naznačeno, že se jednotlivé události rozdělují do dvou skupin. V první skupině najdeme události reprezentující změnu stavu cílové JVM jako celku a ve druhé skupině najdeme události, které jsou vztaženy ke konkrétnímu vláknu a ke konkrétnímu místu kódu, kde k události došlo. Nejprve se budeme zabývat první skupinou obecných událostí. Celkem existuje sedm typů těchto událostí a všechny tyto typy jsou vypsány v tabulce níže:
| # | Rozhraní | Popis |
|---|---|---|
| 1 | com.sun.jdi.event.VMStartEvent | Tato událost vznikne těsně před okamžikem, kdy je spuštěno hlavní vlákno aplikace, tj. většinou metoda main(). |
| 2 | com.sun.jdi.event.VMDeathEvent | Tato událost informuje debugger o tom, že cílová JVM bude ukončena. V závislosti na nastavení debuggeru mohou vzniknout dvě události tohoto typu. |
| 3 | com.sun.jdi.event.VMDisconnectEvent | Tato událost informuje debugger o odpojení sledované JVM při jejím ukončování. |
| 4 | com.sun.jdi.event.ClassPrepareEvent | Událost vygenerovaná ve chvíli, kdy se v cílové JVM načítá a připravuje třída. |
| 5 | com.sun.jdi.event.ClassUnloadEvent | Událost vygenerovaná při odstraňování třídy ze sledované JVM (pokud JVM podporuje odstraňování tříd z paměti) |
| 6 | com.sun.jdi.event.ThreadStartEvent | Událost informující debugger o tom, že vzniklo vlákno, které se bude spouštět (událost je vygenerována ještě před vlastním spuštěním vlákna) |
| 7 | com.sun.jdi.event.ThreadDeathEvent | Událost informující debugger o ukončení nějakého vlákna |
Poznámka: události typu VMStartEvent, VMDeathEvent a VMDisconnectEvent jsou generovány vždy, tj. i tehdy, pokud nebyly explicitně povoleny!
4. Události, které vznikají v konkrétních vláknech a na známém místě v aplikaci
Následující události jsou, na rozdíl od událostí popsaných výše, naopak vztaženy ke konkrétnímu vláknu a debugger může díky tomu přesně zjistit, ve které části kódu k události došlo (což je mnohdy velmi důležité, například v případě použití breakpointů):
Zápis nové hodnoty do sledovaného atributu| # | Rozhraní | Popis |
|---|---|---|
| 1 | com.sun.jdi.event.BreakpointEvent | Vstup na breakpoint. |
| 2 | com.sun.jdi.event.ExceptionEvent | Vznik výjimky (generováno v javovském kódu; pokud výjimka vznikne v nativním kódu, dojde ke vzniku události až později) |
| 3 | com.sun.jdi.event.MethodEntryEvent | Vstup do metody |
| 4 | com.sun.jdi.event.MethodExitEvent | Výskok z metody (libovolný return, ne ovšem při vzniku výjimky – tehdy se generuje jiná událost) |
| 5 | com.sun.jdi.event.WatchpointEvent | Předek následující dvojice rozhraní: |
| 6 | com.sun.jdi.event.AccessWatchpointEvent | Čtení hodnoty sledovaného atributu |
| 7 | com.sun.jdi.event.ModificationWatchpointEvent | |
| 8 | com.sun.jdi.event.MonitorContendedEnteredEvent | Vstup do režimu čekání s právě uvolněným zámkem |
| 9 | com.sun.jdi.event.MonitorContendedEnterEvent | Vstup do režimu čekání se zámkem, který již vlastní jiné vlákno |
| 10 | com.sun.jdi.event.MonitorWaitedEvent | Vlákno ukončilo čekání na synchronizaci (i timeout čekání) |
| 11 | com.sun.jdi.event.MonitorWaitEvent | Vlákno vstoupí do režimu čekání na synchronizaci (monitor) |
| 12 | com.sun.jdi.event.StepEvent | Jeden krok při krokování aplikace |
5. Rozhraní deklarovaná v baličku com.sun.jdi.request
Kromě rozhraní z balíčku com.sun.jdi.event je nutné při práci s událostmi využívat i několik rozhraní, která najdeme v balíčku com.sun.jdi.request. Aby totiž debugger mohl ve smyčce událostí jednotlivé události získávat a dále zpracovávat, je nutné daný typ události nejprve povolit, a to právě přes zmíněná rozhraní. V balíčku com.sun.jdi.request nalezneme především tuto dvojici rozhraní:
| # | Rozhraní | Popis |
|---|---|---|
| 1 | com.sun.jdi.request.EventRequest | Předek všech rozhraní uvedených v další tabulce |
| 2 | com.sun.jdi.request.EventRequestManager | Objekt (existující pro sledovanou JVM jako singleton), přes nějž lze požadovat generování určitého typu událostí |
Objekt typu com.sun.jdi.request.EventRequestManager lze získat chvíli, kdy debugger vytvořil objekt typu com.sun.jdi.VirtualMachine:
EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
V předchozí tabulce bylo uvedeno, že rozhraní com.sun.jdi.request.EventRequest je předkem pro další rozhraní. Ta jsou uvedena nyní. Každé rozhraní z této tabulky odpovídá jednomu typu události:
| # | 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 |
Povšimněte si absence „povolovacích rozhraní“ pro události typu VMStartEvent a VMDisconnectEvent. Tyto události jsou generovány vždy, tudíž pro jejich řízení není zapotřebí žádných specializovaných objektů.
6. Získání objektu typu com.sun.jdi.request.EventRequestManager
Konečně známe všechna rozhraní, s nimiž bude náš demonstrační debugger pracovat, takže si můžeme vysvětlit, jak je možné po připojení k cílové JVM povolit některé typy událostí a jak implementovat smyčku událostí. Dnešní demonstrační příklad je založen na příkladech vysvětlených v předchozích částech tohoto seriálu, což znamená, že už si nebudeme vysvětlovat způsob připojení k cílové JVM, ale hned si uvedeme zdrojový kód metody zavolaný po připojení k této JVM. V této metodě je nejprve získán objekt typu EventRequestManager, což je pro danou instanci třídy virtualMachine singleton. Dále se s využitím tohoto objektu zaregistruje a povolí události typu VMDeathEvent a ihned poté se vstoupí do smyčky událostí:
/**
* 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);
// klasicka smycka pro zpracovani udalosti
eventLoop(virtualMachine, vmDeathRequest);
}
7. Registrace nového typu události a povolení generování událostí
Vlastní žádost o registraci události typu VMDeathEvent je velmi jednoduchá, protože využijeme metodu eventRequestManager.createVMDeathRequest() vracející objekt typu VMDeathRequest (viz též kapitola číslo pět). Jakmile tento objekt získáme, je důležité povolit generování událostí:
/**
* 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;
}
V rozhraní com.sun.jdi.request.EventRequestManager jsou předepsány tyto metody určené pro získání objektů typu com.sun.jdi.request.*Request:
| # | Návratový typ | Jméno metody | Parametry |
|---|---|---|---|
| 1 | com.sun.jdi.request.AccessWatchpointRequest | createAccessWatchpointRequest | (Field field) |
| 2 | com.sun.jdi.request.BreakpointRequest | createBreakpointRequest | (Location location) |
| 3 | com.sun.jdi.request.ClassPrepareRequest | createClassPrepareRequest | () |
| 4 | com.sun.jdi.request.ClassUnloadRequest | createClassUnloadRequest | () |
| 5 | com.sun.jdi.request.ExceptionRequest | createExceptionRequest | (ReferenceType refType, boolean notifyCaught, boolean notifyUncaught) |
| 6 | com.sun.jdi.request.MethodEntryRequest | createMethodEntryRequest | () |
| 7 | com.sun.jdi.request.MethodExitRequest | createMethodExitRequest | () |
| 8 | com.sun.jdi.request.ModificationWatchpointRequest | createModificationWatchpointRequest | (Field field) |
| 9 | com.sun.jdi.request.MonitorContendedEnteredRequest | createMonitorContendedEnteredRequest | () |
| 10 | com.sun.jdi.request.MonitorContendedEnterRequest | createMonitorContendedEnterRequest | () |
| 11 | com.sun.jdi.request.MonitorWaitedRequest | createMonitorWaitedRequest | () |
| 12 | com.sun.jdi.request.MonitorWaitRequest | createMonitorWaitRequest | () |
| 13 | com.sun.jdi.request.StepRequest | createStepRequest | (ThreadReference thread, int size, int depth) |
| 14 | com.sun.jdi.request.ThreadDeathRequest | createThreadDeathRequest | () |
| 15 | com.sun.jdi.request.ThreadStartRequest | createThreadStartRequest | () |
| 16 | com.sun.jdi.request.VMDeathRequest | createVMDeathRequest | () |
8. Smyčka událostí implementovaná s využitím objektu typu com.sun.jdi.event.EventQueue
Vlastní implementace smyčky událostí je 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:
/**
* 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(vmDeathRequest, eventQueue)) {
// jojo tady skutecne nic neni :)
}
}
9. Zpracování jednotlivých typů událostí a reakce na ukončení práce sledované JVM
Konečně se dostáváme k nejdůležitější části celého debuggeru – k vlastnímu čtení a zpracování jednotlivých událostí. Důležité je si uvědomit, že události se z fronty událostí čtou po skupinách uložených v objektu typu EventSet (což jsme si již ostatně řekli ve druhé kapitole). Vzhledem k tomu, že EventSet implementuje rozhraní Collection i Iterable, je snadné iterovat přes všechny události uložené v tomto objektu. Ve vnitřní smyčce je proveden rozeskok v závislosti na typu události, přičemž předpokládáme, že se budou číst pouze události typu VMStartEvent a VMDeathEvent (teoreticky též VMDisconnectEvent, ovšem my ukončíme debugger dříve, než tato událost nastane :-). Jakmile získáme první událost typu VMDeathEvent, dojde k ukončení debuggeru – ze smyčky se vrátí hodnota false, která ve funkci eventLoop (předchozí kapitola) povede k ukončení:
/**
* Precteni a zpracovani udalosti
*
* @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent
* @param eventQueue fronta udalosti
*
* @return true pokud se ma pokracovat ve zpracovavani udalosti
* false pokud se ma sledovana VM ukoncit
*/
private static boolean processEvents(VMDeathRequest vmDeathRequest, EventQueue eventQueue) {
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 {
System.out.println("other event");
}
}
// znovu postit vsechna vlakna
eventSet.resume();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
Volání metody EventSet.resume() slouží ke znovuspuštění vláken, které jsou při vzniku události ve sledované JVM pozastaveny. V našem případě sice není volání této metody nutné, protože zpracováváme pouze události platné pro celou JVM, ale pro jistotu zde tato metoda je uvedena (příště bude navíc přemístěna do bloku finally).
10. Kompletní zdrojový kód demonstračního příkladu JDIEventRequestManager
V předchozích kapitolách jsme si popsali důležité metody, které jsou využity v demonstračním příkladu JDIEventRequestManager. Kompletní zdrojový kód tohoto příkladu je vypsán pod tímto odstavcem, odkaz na poslední verzi zdrojového kódu uloženého do Mercurial repositáře naleznete v jedenácté kapitole:
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.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
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 zaregistruje udalost zavolana ve chvili, kdy dojde k ukonceni
* behu sledovaneho virtualniho stroje Javy.
*
* @author Pavel Tisnovsky
*/
public class JDIEventRequestManager {
/**
* 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);
// klasicka smycka pro zpracovani udalosti
eventLoop(virtualMachine, vmDeathRequest);
}
/**
* 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;
}
/**
* 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(vmDeathRequest, eventQueue)) {
// jojo tady skutecne nic neni :)
}
}
/**
* Precteni a zpracovani udalosti
*
* @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent
* @param eventQueue fronta udalosti
*
* @return true pokud se ma pokracovat ve zpracovavani udalosti
* false pokud se ma sledovana VM ukoncit
*/
private static boolean processEvents(VMDeathRequest vmDeathRequest, EventQueue eventQueue) {
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 {
System.out.println("other event");
}
}
// znovu postit vsechna vlakna
eventSet.resume();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
Pokud je tento jednoduchý debugger připojen k virtuálnímu stroji Javy s testovací aplikací Test4, měl by na standardní výstup vypsat následující text:
Connecting to virtual machine
Connected
Got 1 request:
VMStartEvent
Got 2 requests:
VMDeathEvent
Calling exit
Po výpisu těchto zpráv by debugger měl ukončit jak cílovou JVM, tak i sám sebe. Poznámka: testovací aplikace Test4 čeká na stisk libovolné klávesy, teprve poté dojde ke vzniku události typu VMDeathEvent:
import java.io.IOException;
public class Test4 {
public static void main(String[] args) throws IOException {
System.out.println("Press any key");
System.in.read();
}
}
11. Repositář se zdrojovými kódy demonstračního příkladu JDIEventRequestManager i podpůrných skriptů
Zdrojové kódy dnešního demonstračního příkladu JDIEventRequestManager, testovací třídy Test4 i skriptů použitých pro překlad i spuštění tohoto demonstračního příkladu, byly uloženy (podobně jako tomu bylo i v mnoha předchozích částech tohoto seriálu) do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze všech zmíněných zdrojových souborů a skriptů můžete najít na adresách:
| # | Zdrojový soubor/skript | Umístění souboru v repositáři |
|---|---|---|
| 1 | JDIEventRequestManager.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/b4d636160962/jdi/JDIEventRequestManager.java |
| 2 | compile_JDIEventRequestManager.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/b4d636160962/jdi/compile_JDIEventRequestManager.sh |
| 3 | Test4.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/b4d636160962/jdi/Test4.java |
| 4 | Test4.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/b4d636160962/jdi/Test4.sh |
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