Hlavní navigace

Pohled pod kapotu JVM – připojení debuggeru k běžícímu virtuálnímu stroji přes rozhraní JDI

9. 4. 2013
Doba čtení: 19 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se již potřetí budeme zabývat rozhraním JDI (Java Debugger Interface). Řekneme si, jakým způsobem je možné toto rozhraní využít pro připojení debuggeru či jiného podobného nástroje k již běžícímu javovskému virtuálnímu stroji.

Obsah

1. Připojení debuggeru k běžícímu virtuálnímu stroji přes konektor typu AttachingConnector

2. První demonstrační příklad – výpis všech dostupných konektorů využitelných pro připojení k běžícímu JVM

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ů

12. Odkazy na Internetu

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.Attachin­gConnector 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.Connec­tor.name()), tak i jeho jednořádkový popis (metoda com.sun.jdi.connect.Connec­tor.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.Launchin­gConnector –, ovšem naprosto stejný postup lze použít i pro konektory implementující rozhraní com.sun.jdi.connect.Attachin­gConnector. 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.Attachin­gConnector. To se provede s využitím metody VirtualMachineManager.Attachin­gConnector(), 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.

root_podpora

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.or­g/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

  1. Breakpoint (Wikipedia)
    http://cs.wikipedia.org/wi­ki/Breakpoint
  2. JVM Tool Interface Version 1.2 Documentation
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  3. JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#SetBreakpoint
  4. JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#ClearBreakpoint
  5. JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#Breakpoint
  6. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  7. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  8. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  9. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  10. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  11. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  12. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  13. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  14. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  15. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  16. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  17. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  18. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  19. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html

Byl pro vás článek přínosný?