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