Obsah
1. Připojení debuggeru k běžícímu virtuálnímu stroji přes konektor typu AttachingConnector
3. Výstup vygenerovaný prvním demonstračním příkladem
4. Druhý demonstrační příklad – výpis argumentů všech nalezených konektorů typu AttachingConnector
5. Výstup vygenerovaný druhým demonstračním příkladem
6. Realizace připojení k již běžícímu virtuálnímu stroji Javy
7. Spuštění „cílové“ JVM a čekání na připojení debuggeru
8. Výběr konektoru využívajícího pro připojení sockety
9. Obnovení a ukončení běhu „cílové“ JVM
10. Třetí demonstrační příklad – praktické využití konektoru typu AttachingConnector
11. Zdrojové kódy všech demonstračních příkladů i podpůrných skriptů
1. Připojení debuggeru k běžícímu virtuálnímu stroji přes konektor typu AttachingConnector
V předchozí části seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme si popsali a na jednoduchém demonstračním příkladu i prakticky ukázali, jakým způsobem je možné s využitím rozhraní JDI (Java Debugger Interface) spustit přímo z debuggeru novou instanci takzvané „cílové“ JVM s testovanou aplikací a jak lze zachytit standardní výstup (popř. naprosto stejným způsobem i chybový výstup) této aplikace. Tento postup využívají například mnohá integrovaná vývojová prostředí, která díky tomu, že přes rozhraní JDI přímo spustí cílovou JVM, mají nad tímto virtuálním strojem velkou kontrolu a mohou například získat přehled o běžících vláknech aplikace, zásobníkových rámcích jednotlivých vláken, breakpointech apod. Ovšem ne vždy je tento způsob sledování a ladění javovské aplikace nejvýhodnější. V některých případech může být mnohem lepší, když se „cílová“ JVM spustí zcela samostatně a debugger se k již inicializovanému virtuálnímu stroji připojí později, třeba i vzdáleně.
I tento režim práce debuggerů a dalších podobných nástrojů rozhraní JDI samozřejmě podporuje. Připomeňme si krátce, že v JDI mají vývojáři debuggerů k dispozici celkem tři skupiny takzvaných konektorů sloužících pro připojení k cílové JVM. První skupina konektorů implementuje rozhraní LaunchingConnector a je typická tím, že konektor sám spustí cílovou JVM i s laděnou aplikací. Využití tohoto typu konektorů jsme si ukázali minule. Druhá skupina konektorů implementuje rozhraní AttachingConnector. Jedná se o konektory, které se dokážou připojit k již běžící JVM a způsob práce těchto typů konektorů si popíšeme v navazujících kapitolách. Třetí typ konektorů implementuje rozhraní ListeningConnector. Tyto konektory slouží k tomu, aby se cílová JVM sama aktivně připojila k debuggeru, který již musí být spuštěn. Všechny tři skupiny konektorů jsou pro větší přehled vypsány v další tabulce i s příslušnými implementacemi:
| # | Typ konektoru | Metoda propojení debuggeru a JVM | Implementace |
|---|---|---|---|
| 1 | LaunchingConnector | debugger spouští JVM | com.sun.jdi.CommandLineLaunch, com.sun.jdi.RawCommandLineLaunch |
| 2 | AttachingConnector | debugger se připojuje k běžící JVM | com.sun.jdi.SocketAttach, com.sun.jdi.SharedMemoryAttach, com.sun.jdi.ProcessAttach |
| 3 | ListeningConnector | debugger akceptuje žádost JVM o připojení | com.sun.jdi.SocketListen, com.sun.jdi.SharedMemoryListen |
2. První demonstrační příklad – výpis všech dostupných konektorů využitelných pro připojení k běžícímu JVM
Ze všech tří typů konektorů popsaných v předchozí kapitole si dnes popíšeme konektory, které se dokážou připojit k již běžícímu virtuálnímu stroji Javy. Seznam těchto konektorů nabízených v daném okamžiku běžícím virtuálním strojem Javy je možné získat s využitím metody
List<AttachingConnector> VirtualMachineManager.attachingConnectors()
vracející všechny dostupné (prozatím nenakonfigurované) JDI konektory implementující rozhraní com.sun.jdi.connect.AttachingConnector a tím pádem implementující i rozhraní com.sun.jdi.connect.Connector. Počet těchto konektorů je závislý na operačním systému, na němž je JVM spuštěna. Na Linuxu, který nás samozřejmě zajímá nejvíce, je dostupný konektor SocketAttach, jenž se k cílové JVM připojuje přes sockety; na operačním systému Microsoft Windows je navíc k dispozici i konektor SharedMemoryAttach využívající pro komunikaci s cílovým virtuálním strojem Javy sdílenou paměť.
Jaké konektory implementující rozhraní AttachingConnector jsou na právě používaném operačním systému k dispozici, lze zjistit pomocí dnešního prvního demonstračního příkladu, který u každého konektoru vypíše jak jeho jméno (metoda com.sun.jdi.connect.Connector.name()), tak i jeho jednořádkový popis (metoda com.sun.jdi.connect.Connector.description()) a třídu, která tento konektor implementuje. Na tomto místě je vhodné upozornit na fakt, že tato třída se může v různých verzích či implementacích JVM lišit – nejedná se totiž o třídy zahrnuté do standardního API Javy:
import java.util.List;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
/**
* Vypis vsech konektoru, ktere lze pouzit pro pripojeni k bezici JVM.
*
* @author Pavel Tisnovsky
*/
public class JDIListAttachingConnectors {
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();
// vypis informaci o vsech konektorech
listConnectors(connectors);
}
/**
* Vypis informaci o vsech konektorech pouzitelnych
* pro pripojeni k bezici JVM.
*/
private static void listConnectors(List<AttachingConnector> connectors) {
for (AttachingConnector connector : connectors) {
System.out.println("Connector: " + connector.name());
System.out.println(" class: " + connector.getClass().getName()); // systemove zavisle
System.out.println(" description: " + connector.description()); // popis pro lidske operatory
System.out.println();
}
}
}
3. Výstup vygenerovaný prvním demonstračním příkladem
Podobně jako tomu bylo v demonstračních příkladech uvedených předminule a minule, i dnešní první demonstrační příklad je nutné překládat s volbou -classpath, v níž bude uvedena cesta k java archivu tools.jar. Pokud se například pro překlad použije OpenJDK 6 nainstalovaná ze standardních balíčků, může být překlad proveden následujícím příkazem:
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIListAttachingConnectors.java
I při spuštění demonstračního příkladu je nutné uvést cestu k java archivu tools.jar:
java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIListAttachingConnectors
Při spuštění dnešního prvního demonstračního příkladu na operačním systému Microsoft Windows získáme seznam tří konektorů implementujících rozhraní AttachingConnector:
Connector: com.sun.jdi.SocketAttach
class: com.sun.tools.jdi.SocketAttachingConnector
description: Attaches by socket to other VMs
Connector: com.sun.jdi.SharedMemoryAttach
class: com.sun.tools.jdi.SharedMemoryAttachingConnector
description: Attaches by shared memory to other VMs
Connector: com.sun.jdi.ProcessAttach
class: com.sun.tools.jdi.ProcessAttachingConnector
description: Attaches to debuggee by process-id (pid)
Na Linuxu může naproti tomu nabídka konektorů typu AttachingConnector vypadat následovně:
Connector: com.sun.jdi.SocketAttach
class: com.sun.tools.jdi.SocketAttachingConnector
description: Attaches by socket to other VMs
Connector: com.sun.jdi.ProcessAttach
class: com.sun.tools.jdi.ProcessAttachingConnector
description: Attaches to debuggee by process-id (pid)
Z výpisů je patrné, že na obou operačních systémech jsou k dispozici dva shodné konektory – „com.sun.jdi.SocketAttach“ a „com.sun.jdi.ProcessAttach“. To tedy znamená, že konektor využívající pro připojení k cílové JVM sockety lze bez větších problémů použít vždy.
4. Druhý demonstrační příklad – výpis argumentů všech nalezených konektorů typu AttachingConnector
Konektorům, které umožňují připojení k již běžícímu virtuálnímu stroji Javy, je nutné předat i některé důležité parametry potřebné pro provedení vlastního připojení. Příkladem může být konektor zajišťující připojení přes sockety, kterému je nutné předat číslo portu, na němž cílová JVM připojení očekává, popř. i adresu počítače s cílovou JVM. V předchozí části jsme si ukázali, jak lze vypsat a posléze i změnit parametry implicitního konektoru – ten implementuje rozhraní com.sun.jdi.connect.LaunchingConnector –, ovšem naprosto stejný postup lze použít i pro konektory implementující rozhraní com.sun.jdi.connect.AttachingConnector. Výpis všech implicitně nastavených parametrů zvoleného konektoru zajistí následující programová smyčka, která je i součástí dnešního druhého demonstračního příkladu:
// vypsat vsechny parametry konektoru
Map<String, Argument> arguments = connector.defaultArguments();
for (Map.Entry<String, Connector.Argument> argument : arguments.entrySet()) {
System.out.format(" %-12s %s\n",
argument.getKey(),
argument.getValue());
}
Úplný zdrojový kód druhého demonstračního příkladu vypadá následovně:
import java.util.List;
import java.util.Map;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.Connector.Argument;
/**
* Vypis vsech konektoru, ktere lze pouzit pro pripojeni k bezici JVM.
*
* @author Pavel Tisnovsky
*/
public class JDIListAttachingConnectorsArguments {
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();
// vypis informaci o vsech konektorech
listConnectors(connectors);
}
/**
* Vypis informaci o vsech konektorech pouzitelnych
* pro pripojeni k bezici JVM.
*/
private static void listConnectors(List<AttachingConnector> connectors) {
for (AttachingConnector connector : connectors) {
System.out.println("Connector: " + connector.name());
System.out.println(" class: " + connector.getClass().getName());
System.out.println(" description: " + connector.description());
System.out.println(" arguments:");
// vypsat vsechny parametry konektoru
Map<String, Argument> arguments = connector.defaultArguments();
for (Map.Entry<String, Connector.Argument> argument : arguments.entrySet()) {
System.out.format(" %-12s %s\n",
argument.getKey(),
argument.getValue());
}
System.out.println();
}
}
}
5. Výstup vygenerovaný druhým demonstračním příkladem
Překlad druhého demonstračního příkladu se provede pomocí javac, jemuž se opět předá cesta k tools.jar:
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIListAttachingConnectorsArguments.java
I při spuštění demonstračního příkladu je nutné uvést cestu k java archivu tools.jar:
java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIListAttachingConnectorsArguments
Výstup druhého demonstračního příkladu na operačním systému Microsoft Windows může vypadat následovně (lišit se bude především hodnota parametru „hostname“):
Connector: com.sun.jdi.SocketAttach
class: com.sun.tools.jdi.SocketAttachingConnector
description: Attaches by socket to other VMs
arguments:
timeout timeout=
hostname hostname=bender6502
port port=
Connector: com.sun.jdi.SharedMemoryAttach
class: com.sun.tools.jdi.SharedMemoryAttachingConnector
description: Attaches by shared memory to other VMs
arguments:
timeout timeout=
name name=
Connector: com.sun.jdi.ProcessAttach
class: com.sun.tools.jdi.ProcessAttachingConnector
description: Attaches to debuggee by process-id (pid)
arguments:
pid pid=
timeout timeout=
Výstup druhého demonstračního příkladu na Linuxu bude podle očekávání zkrácen, protože zde není k dispozici konektor typu „com.sun.jdi.SharedMemoryAttach“:
Connector: com.sun.jdi.SocketAttach
class: com.sun.tools.jdi.SocketAttachingConnector
description: Attaches by socket to other VMs
arguments:
timeout timeout=
hostname hostname=bender
port port=
Connector: com.sun.jdi.ProcessAttach
class: com.sun.tools.jdi.ProcessAttachingConnector
description: Attaches to debuggee by process-id (pid)
arguments:
pid pid=
timeout timeout=
Důležité jsou pro nás především argumenty s názvem hostname a port, protože ty se musí nastavit vhodným způsobem tak, aby se debugger skutečně mohl připojit k cílovému virtuálnímu stroji Javy. Při lokálním spojení v rámci jednoho počítače není zapotřebí argument s názvem hostname měnit, ovšem port je nutné explicitně specifikovat vždy, což si ostatně ukážeme v navazujících kapitolách.
6. Realizace připojení k již běžícímu virtuálnímu stroji Javy
V této kapitole si teoreticky řekneme, jakým způsobem lze realizovat připojení k běžící cílové (laděné) JVM s využitím spojení pomocí socketů. Aplikace, která má být spuštěna v cílovém virtuálním stroji Javy, se přeloží zcela běžným způsobem – není tedy zapotřebí provádět žádné zásahy do zdrojového kódu ani měnit parametry překladu. Ovšem při spuštění cílové JVM je nutné použít parametr -agentlib, kterému se předá jméno JVM TI agenta, což je v našem případě jdwp. Jak již víme z předchozích dílů, znamená tato zkratka Java Debug Wire Protocol, z čehož vyplývá, že JVM TI agent s názvem jdwp tento protokol implementuje. jdwp je samozřejmě současně i název nativní knihovny, v níž je komunikační část implementována – na Linuxu se jedná o knihovnu pojmenovanou libjdwp.so a na systému Microsoft Windows pak o dynamickou knihovnu jdwp.dll (jeden z těchto souborů je součástí standardní instalace JRE).
Dále je nutné tomuto agentovi předat několik parametrů, které jsou vypsány v následující tabulce:
| # | Parametr | Hodnota | Význam |
|---|---|---|---|
| 1 | transport | dt_socket | spojení bude provedeno přes sockety, tj. startovaná (cílová) JVM otevře socket na specifikovaném portu a bude očekávat, že debugger tento port využije |
| 2 | server | y | cílová JVM bude spuštěna v režimu serveru, tj. aktivně připojovat se bude agent, nikoli naopak |
| 3 | address | 6502 | v režimu serveru je zde specifikováno jen číslo portu (adresa odpovídá stroji, na němž je cílová JVM nastartována), 6502 je jen „téměř náhodně“ zvolená konstanta; lze využít jakýkoli port, který může uživatel otevřít |
| 4 | suspend | y | cílová JVM je ihned po spuštění pozastavena a čeká na připojení debuggeru – aplikace tedy není ihned spuštěna, což je pro ladění výhodné |
Všechny parametry jdwp agenta se na příkazovou řádku zapisují následujícím (možná poněkud matoucím) způsobem, kdy se za -agentlib:jdwp přidá znak „=“ a za ním vždy dvojice „jméno_parametru=hodnota_parametru“, přičemž se jednotlivé dvojice od sebe oddělují čárkami:
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test
(„Test“ je samozřejmě jméno spouštěné třídy).
7. Spuštění „cílové“ JVM a čekání na připojení debuggeru
Podívejme se nyní na velmi jednoduchý příklad spuštění javovské aplikace v cílovém (laděném) virtuálním stroji Javy. Samotná aplikace po svém spuštění začne v nekonečné programové smyčce na standardní výstup vypisovat text „Hello world!“, skutečně se tedy nejedná o nejgeniálnější program, který byl kdy naprogramován :-) :
public class Test {
public static void main(String[] args) {
while (true) {
System.out.println("Hello world!");
}
}
}
Cílovou JVM i s testovanou aplikací spustíme následujícím způsobem:
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test
Po spuštění se stane zajímavá věc. Na standardní výstup se namísto nekonečné sekvence „Hello world!“ vypíše pouze jediný řádek, který by měl vypadat přibližně takto:
Listening for transport dt_socket at address: 6502
Proč tomu tak je by mělo být po přečtení šesté kapitoly zřejmé – díky parametru suspend=y je cílová JVM ihned po své inicializaci pozastavena a očekává připojení debuggeru.
8. Výběr konektoru využívajícího pro připojení sockety
Samotný debbuger (či jiný podobný nástroj), který se bude připojovat k cílové JVM přes sockety, musí nejprve získat všechny konektory implementující rozhraní com.sun.jdi.connect.AttachingConnector. To se provede s využitím metody VirtualMachineManager.AttachingConnector(), která vrátí seznam všech konektorů umožňujících připojení k již běžícímu virtuálnímu stroji Javy. Následně je nutné ze seznamu těchto konektorů vybrat konektor umožňující spojení s cílovou JVM přes sockety:
// 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;
}
...
...
...
Samotná metoda getSocketAttachConnector() je implementována sice jednoduše, ovšem (alespoň zdánlivě) neefektivně. Porovnáváme zde totiž jména jednotlivých konektorů s řetězcovou konstantou a pokud je nalezen konektor se jménem „com.sun.jdi.SocketAttach“, je tento konektor metodou ihned vrácen. Na tomto místě je vhodné poznamenat, že vzhledem k současnému stavu JDI zde není možné použít například operátor instanceof, protože konkrétní jméno třídy či rozhraní, kterou musí konektor „SocketAttach“ implementovat, není přesně zdokumentováno a stabilizováno mezi různými implementacemi JVM:
/**
* Ziskat konektor pouzivajici pro pripojeni sockety
*/
private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
for (AttachingConnector connector : connectors) {
if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
return connector;
}
}
return null;
}
V metodě getSocketAttachConnector() je použita řetězcová konstanta SOCKET_ATTACH_CONNECTOR_NAME, která musí obsahovat řetězec „com.sun.jdi.SocketAttach“. Deklarace této konstanty vypadá následovně:
private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
9. Obnovení a ukončení běhu „cílové“ JVM
Ve chvíli, kdy již máme vybrán správný konektor, je možné se připojit k běžícímu virtuálnímu stroji Javy. Aby se připojení skutečně provedlo, musí se nejprve nastavit port, přes který se debugger k cílové JVM připojí. Změna portu je ve skutečnosti velmi jednoduchá, jak ostatně ukazuje následující úryvek kódu:
Map<String, Connector.Argument> arguments = connector.defaultArguments();
arguments.get("port").setValue("6502");
Vlastní připojení k cílové JVM zajišťuje metoda AttachingConnector.attach(), které se předá mapa obsahující nové argumenty (parametry) konektoru:
VirtualMachine virtualMachine = connector.attach(arguments);
Následně je nutné cílový virtuální stroj přepnout ze stavu „suspend“, do něhož byl nastaven volbou „suspend=y“ při startu JVM. Znovuspuštění cílové JVM je ve skutečnosti velmi snadné:
virtualMachine.resume();
Kdykoli lze cílovou JVM násilně ukončit, a to zavoláním metody:
virtualMachine.exit(exit_kód);
Všechna funkcionalita popsaná v předešlém textu je použita v metodě nazvané connectToVirtualMachine(). Nejprve se nastaví argumenty konektoru tak, aby se připojoval na port číslo 6502, poté se provede vlastní připojení, cílový virtuální stroj Javy se znovuspustí a po cca desetině vteřiny dojde k jeho násilnému ukončení:
/**
* Pripojeni k bezicimu virtualnimu stroji pres socket.
* @throws InterruptedException
*/
private static void connectToVirtualMachine(AttachingConnector connector) {
// zmena hodnoty argumentu se jmenem "port"
Map<String, Connector.Argument> arguments = connector.defaultArguments();
arguments.get("port").setValue("6502");
// pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
try {
System.out.println("Connecting to virtual machine");
VirtualMachine virtualMachine = connector.attach(arguments);
// zmena stavu suspend->resume
System.out.println("Connected, calling resume");
virtualMachine.resume();
// nechame vzdalenou JVM bezet priblizne 100ms
Thread.sleep(100);
// ukonceni behu vzdaleneho virtualniho stroje
System.out.println("Calling exit");
virtualMachine.exit(0);
}
catch (IOException e) {
e.printStackTrace();
}
catch (IllegalConnectorArgumentsException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
10. Třetí demonstrační příklad – praktické využití konektoru typu AttachingConnector
Nyní si již můžeme ukázat celý zdrojový text dnešního třetího demonstračního příkladu a následně si ukázat, jakým způsobem lze tento příklad použít:
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;
/**
* 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 JDIAttachingConnectorTest {
private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
public static void main(String[] args) {
// ziskat (jedinou) instanci tridy VirtualMachineManager
VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
// ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
// potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
AttachingConnector connector = getSocketAttachConnector(connectors);
if (connector == null) {
System.out.println("Socket connector is not available");
return;
}
connectToVirtualMachine(connector);
}
/**
* Ziskat konektor pouzivajici pro pripojeni sockety
*/
private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
for (AttachingConnector connector : connectors) {
if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
return connector;
}
}
return null;
}
/**
* Pripojeni k bezicimu virtualnimu stroji pres socket.
* @throws InterruptedException
*/
private static void connectToVirtualMachine(AttachingConnector connector) {
// zmena hodnoty argumentu se jmenem "port"
Map<String, Connector.Argument> arguments = connector.defaultArguments();
arguments.get("port").setValue("6502");
// pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
try {
System.out.println("Connecting to virtual machine");
VirtualMachine virtualMachine = connector.attach(arguments);
// zmena stavu suspend->resume
System.out.println("Connected, calling resume");
virtualMachine.resume();
// nechame vzdalenou JVM bezet priblizne 100ms
Thread.sleep(100);
// ukonceni behu vzdaleneho virtualniho stroje
System.out.println("Calling exit");
virtualMachine.exit(0);
}
catch (IOException e) {
e.printStackTrace();
}
catch (IllegalConnectorArgumentsException e) {
e.printStackTrace();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Pokud tento demonstrační příklad přeložíme a přímo spustíme pomocí skriptu
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIAttachingConnectorTest.java java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIAttachingConnectorTest
záleží chování programu na tom, zda je či není spuštěná i cílová JVM (viz též sedmou kapitolu). V případě, že cílová JVM spuštěna není, dopadneme neslavně, neboť se do terminálu pouze vypíše hlášení o výjimce způsobené tím, že se program nedokázal připojit:
Connecting to virtual machine
java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:519)
at com.sun.tools.jdi.SocketTransportService.attach(SocketTransportService.java:204)
at com.sun.tools.jdi.GenericAttachingConnector.attach(GenericAttachingConnector.java:98)
at com.sun.tools.jdi.SocketAttachingConnector.attach(SocketAttachingConnector.java:72)
at JDIAttachingConnectorTest.connectToVirtualMachine(JDIAttachingConnectorTest.java:68)
at JDIAttachingConnectorTest.main(JDIAttachingConnectorTest.java:41)
Pokud však nejprve spustíme cílovou JVM již dvakrát zmíněným příkazem
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test
mělo by se připojení (=demonstrační příklad spuštěný z jiné konzole) podařit a měli bychom získat následující výstup:
Connecting to virtual machine Connected, calling resume Calling exit
Současně by se měl na konzoli s testovacím příkladem několikrát objevit text „Hello world!“. Pokud se neobjeví, může to znamenat, že doba sta milisekund použitá v demonstračním příkladu je příliš krátká na znovuspuštění cílové JVM a následný vstup do nekonečné programové smyčky while – proto můžete tuto hodnotu zvýšit například na půl sekundy.
Může taktéž nastat případ, že se k cílovému virtuálnímu stroji budeme snažit připojit dvěma instancemi třetího demonstračního příkladu. To není možné, protože k JVM se může připojit jen jeden debugger a navíc je i port 6502 již použit pro připojení prvního debuggeru:
ERROR: transport error 202: bind failed: Adresa je užívána ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510) JDWP exit error AGENT_ERROR_TRANSPORT_INIT(197): No transports initialized [../../../src/share/back/debugInit.c:708] FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197) ./Test.sh: řádek 2: 1919 Neúspěšně ukončen (SIGABRT) java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test
11. Zdrojové kódy všech demonstračních příkladů i podpůrných skriptů
Zdrojové kódy všech tří dnes popsaných demonstračních příkladů, testovací třídy i skriptů použitých pro překlad a spuštění demonstračních příkladů, byly uloženy 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:
12. Odkazy na Internetu
- Breakpoint (Wikipedia)
http://cs.wikipedia.org/wiki/Breakpoint - JVM Tool Interface Version 1.2 Documentation
http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html - JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#SetBreakpoint - JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#ClearBreakpoint - JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#Breakpoint - The JVM Tool Interface (JVM TI): How VM Agents Work
http://www.oracle.com/technetwork/articles/javase/jvm-ti-141370.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html