Hlavní navigace

Pohled pod kapotu JVM – práce s breakpointy s využitím rozhraní JDI

11. 6. 2013
Doba čtení: 29 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si vysvětlíme způsob práce s breakpointy s využitím rozhraní JDI (Java Debugger Interface). Řekneme si, jak se breakpointy nastavují i jaké informace lze získat ve chvíli, kdy nějaké vlákno na breakpoint vstoupí.

Obsah

1. Pohled pod kapotu JVM – práce s breakpointy s využitím rozhraní JDI

2. Registrace události vyvolané při dosažení breakpointu

3. Zjištění přesné lokace, na níž má být breakpoint umístěn

4. Upravená smyčka událostí (event loop)

5. Výpis základních informací při dosažení breakpointu

6. Zdrojový kód prvního demonstračního příkladu JDIBreakpointDemo1

7. Zdrojový kód testovací třídy Test8

8. Výstup generovaný demonstračním příkladem JDIBreakpointDemo1

9. Přečtení podrobnějších informací o vláknu při vstupu na breakpoint

10. Hodnoty parametrů metod i lokálních proměnných

11. Zdrojový kód druhého demonstračního příkladu JDIBreakpointDemo2

12. Výstup generovaný demonstračním příkladem JDIBreakpointDemo2

13. Repositář se zdrojovými kódy demonstračních příkladů

14. Odkazy na Internetu

1. Pohled pod kapotu JVM – práce s breakpointy s využitím rozhraní JDI

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si vysvětlíme práci s breakpointy s využitím rozhraní JDI (Java Debugger Interface). Breakpointy, které jsou nedílnou součástí prakticky všech univerzálních debuggerů, určují místo v programovém kódu, po jehož dosažení většinou dojde k pozastavení vlákna, které na breakpoint vstoupilo, a následně k získání dalších informací souvisejících s breakpointem – například je možné si nechat zobrazit seznam lokálních proměnných apod. Díky rozhraní JDI je práce s breakpointy poměrně jednoduchá, což si ostatně ukážeme i na dvojici demonstračních příkladů. Jejich jedinou funkcí bude zobrazení informací o dosažení breakpointu a v případě druhého demonstračního příkladu se navíc zobrazí i informace o jménech a hodnotách všech viditelných lokálních proměnných, včetně argumentů metody, v níž došlo k zastavení na breakpointu.

Pro přehled si nejprve uveďme některé třídy, rozhraní a metody z JDI, s nimiž se setkáme v následujících kapitolách:

# Balíček Třída/rozhraní Metoda Popis
1 com.sun.jdi.request EventRequestManager createBreakpointRequest() registrace breakpointu na zadané lokaci
2 com.sun.jdi.request EventRequest setSuspendPolicy() určuje, která vlákna se mají zastavit, když dojde k zaregistrované události (zde vstupu na breakpoint)
3 com.sun.jdi Location lineNumber() vrátí číslo řádku pro danou lokaci
4 com.sun.jdi ReferenceType allLineLocations() vrátí seznam všech lokací pro danou třídu
5 com.sun.jdi VirtualMachine classesByName() vrátí všechny třídy s odpovídajícím jménem
6 com.sun.jdi.event LocatableEvent thread() vrátí thread, v němž došlo k události
7 com.sun.jdi ThreadReference frame() získá n-tý zásobníkový rámec vlákna
8 com.sun.jdi StackFrame visibleVariables() vrátí seznam všech viditelných proměnných

2. Registrace události vyvolané při dosažení breakpointu

První věcí, kterou je nutné při práci s breakpointy s využitím rozhraní JDI udělat, je zaregistrovat a taktéž povolit vytváření událostí typu BreakpointEvent. Tyto události jsou do fronty událostí (event queue) vloženy ve chvíli, kdy nějaké vlákno skutečně breakpointu dosáhne. Událost tohoto typu se registruje opět přes objekt typu EventRequestManager s využitím dalšího objektu, který je typu BreakpointRequest. Kromě události BreakpointEvent zaregistrujeme i událost typu VMDeathRequest, protože náš demonstrační debugger musí korektně ukončit svoji činnost ve chvíli, kdy je monitorovaný (sledovaný) virtuální stroj Javy zastaven. Jak se registrace obou zmíněných typů událostí provádí, zjistíme při pohledu na zdrojový kód uživatelské metody acquireAndUseEventRequestManager(), který je vypsán pod tímto odstavcem:

    /**
     * Ukazka pouziti EventRequestManageru.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
 
        // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
 
        // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint
        BreakpointRequest breakpointRequest = registerBreakpointEvent(virtualMachine, eventRequestManager);
        //System.out.println("Breakpoint set: " + breakpointRequest.toString());
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest);
    }

O vlastní registraci a povolení generování událostí typu BreakpointEvent se stará další uživatelská metoda nazvaná registerBreakpointEvent(). U registrovaného breakpointu musíme znát přesnou lokaci v kódu, kam má být breakpoint vložen. Tato lokace je představována objektem typu Location, jenž je získán v metodě computeBreakpointLocation(), jejíž popis bude uveden v navazující kapitole. Zajímavá je taktéž specifikace chování sledované JVM ve chvíli dosažení breakpointu – my zde vyžadujeme pozastavení všech vláken sledované aplikace, což je zajištěno zavolání metody breakpointRequest.setSuspen­dPolicy(EventRequest.SUSPEN­D_ALL):

    /**
     * Registrace udalosti typu BreakpointRequest.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param eventRequestManager
     *            objekt zajistujici praci s registraci udalosti
     * @return objekt typu BreakpointRequest
     */
    private static BreakpointRequest registerBreakpointEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        Location breakpointLocation;
        try {
            // potrebujeme zjistit lokaci, kde se ma breakpoint nastavit
            breakpointLocation = computeBreakpointLocation(virtualMachine, TEST_CLASS_NAME, BREAKPOINT_LINE_NUMBER);
            // ne vzdy se muze podarit zjistit lokaci
            if (breakpointLocation == null) {
                System.err.println("Can not find location to set breakpoint!");
                return null;
            }
            // registrace breakpointu
            BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(breakpointLocation);
            // pri vstupu na breakpoint se zastavi vsechna vlakna
            // ve sledovanem virtualnim stroji Javy
            breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
            breakpointRequest.enable();
            return breakpointRequest;
        }
        catch (AbsentInformationException e) {
            e.printStackTrace();
        }
        return null;
    }

3. Zjištění přesné lokace, na níž má být breakpoint umístěn

Pokud se breakpoint nastavuje v nějakém skutečném debuggeru, ať již se jedná o standardní jdb ovládaný z příkazové řádky, nebo některý debugger zabudovaný do integrovaného vývojového prostředí (Eclipse, IDEA, Netbeans), může uživatel specifikovat, na kterém řádku se má breakpoint nacházet. Musí se tedy zadat dvojice Jméno_třídy:číslo_řádku; přičemž u vizuálních debuggerů, které jsou součástí IDE, se jméno třídy odvodí od pozice breakpointu v rámci zdrojového souboru (ten může obsahovat více tříd – neveřejných tříd, anonymních tříd, vnitřních tříd atd.). Získání přesné lokace v rozhraní JDI je poněkud složitější, protože číslo řádku je nutné vyhledat v seznamu obsahujícím objekty typu Location. Těmito objekty je nutné projít a vyhledat objekt, jehož číslo řádku se shoduje s číslem řádku zadaným uživatelem:

    /**
     * Zjisteni lokace, kde se ma breakpoint nastavit.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param className
     *            jmeno tridy, v jejimz ramci se pracuje s breakpointem
     * @param breakpointLineNumber
     *            cislo radku, na nemz ma byt breakpoint nastaven
     * @return lokace, kde se ma breakpoint nastavit nebo null
     * @throws AbsentInformationException
     */
    private static Location computeBreakpointLocation(VirtualMachine virtualMachine, String className, int breakpointLineNumber) throws AbsentInformationException {
        ReferenceType class1 = getClassInMonitoredVM(virtualMachine, className);
 
        // precist vsechny lokace ve zjistene tride
        List<Location> locations = class1.allLineLocations();
 
        // projit vsemi lokacemi
        // pro kazdou lokaci se pokusime zjistit cislo radku
        // a porovnat ji se zadanym cislem radku
        for (Location location : locations) {
            // pokud dojde ke shode cisel radku,
            // lze na teto lokaci nastavit breakpoint
            if (location.lineNumber() == breakpointLineNumber) {
                return location;
            }
        }
        // nepovedlo se najit lokaci pro dane cislo radku
        return null;
    }

V předchozí metodě se nejprve získala reference třídy nějakého jména (v našem případě je jméno nastaveno na „Test8“). Získání této třídy je relativně jednoduché, o čemž se můžete přesvědčit z následujícího úryvku kódu. Nejdříve se přes VirtualMachine.classesByName() získá seznam všech tříd, jejichž jméno odpovídá řetězci „Test6“. Vzhledem k tomu, že tato třída existuje pouze jedna, můžeme z vráceného seznamu následně jednoduše přečíst první prvek a vrátit ho volající metodě:

    /**
     * Precteni tridy zadaneho jmena v monitorovanem virtualnim stroji.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param className
     *            jmeno tridy, v jejimz ramci se pracuje s breakpointem
     * @return reference tridy v monitorovanem virtualnim stroji
     */
    private static ReferenceType getClassInMonitoredVM(VirtualMachine virtualMachine, String className) {
        // mela by se vratit jedina trida
        List<ReferenceType> allClasses = virtualMachine.classesByName(className);
        System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size());
 
        // vratila se pravdepodobne jedina trida, takze ji precteme
        ReferenceType class1 = allClasses.get(0);
        return class1;
    }

4. Upravená smyčka událostí (event loop)

Implementace smyčky událostí je zcela stejná, jako smyčka použitá v demonstračních příkladech popsaných minule a předminule:

    /**
     * Klasicka smycka pro postupne zpracovani udalosti.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) {
        EventQueue eventQueue = virtualMachine.eventQueue();
 
        // precist a zpracovat udalosti
        while (processEvents(eventQueue, vmDeathRequest)) {
            // jojo tady skutecne nic neni :)
        }
    }

Změnil se však kód další uživatelské metody nazvané processEvents(), protože v této metodě je nutné reagovat na výskyt dvou typů událostí. První událost typu VMDeathEvent je vygenerována ve chvíli ukončování práce sledovaného virtuálního stroje Javy a událost typu BreakpointEvent vznikne ve chvíli, kdy nějaké vlákno vstoupí na nastavený breakpoint. Povšimněte si toho, že se všechna vlákna sledovaného virtuálního stroje opět spustí příkazem eventSet.resume():

    /**
     * Precteni a zpracovani udalosti.
     * 
     * @param eventQueue
     *            fronta udalosti
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     * 
     * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud
     *         se ma sledovana VM ukoncit
     */
    private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) {
        EventSet eventSet;
        try {
            // precist udalosti z fronty
            eventSet = eventQueue.remove();
            int events = eventSet.size();
            System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":"));
 
            // projit vsemi udalostmi
            for (Event event : eventSet) {
                if (event instanceof VMStartEvent) {
                    System.out.println("    VMStartEvent");
                }
                else if (event instanceof VMDeathEvent) {
                    System.out.println("    VMDeathEvent");
                    // zakazat dalsi generovani udalosti
                    // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) 
                    vmDeathRequest.disable();
                    // posleze se zavola shutdownVirtualMachine()
                    return false;
                }
                else if (event instanceof BreakpointEvent) {
                    // nejake vlakno se zastavilo na breakpointu
                    printInfoAboutBreakpoint((BreakpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

5. Výpis základních informací při dosažení breakpointu

Pokud je ve smyčce událostí popsané v předchozí kapitole přečtena z fronty událostí (event queue) událost typu BreakpointEvent, je zavolána uživatelská metoda nazvaná printInfoAboutBreakpoint(), jejímž úkolem je informovat uživatele formou zprávy vypsané na standardní výstup. První verze této metody je velmi jednoduchá, protože se zde pouze vypíše, na jakém řádku bylo dosaženo breakpointu (breakpointů lze totiž samozřejmě zaregistrovat větší množství). Dopředu si řekněme, že značně vylepšená varianta uživatelské metody printInfoAboutBreakpoint() bude popsána v deváté a taktéž v desáté kapitole:

    /**
     * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu.
     * 
     * @param event udalost s informacemi o dosazenem breakpointu
     */
    private static void printInfoAboutBreakpoint(BreakpointEvent event) {
        // precist cislo radku, na kterem doslo k zastaveni vlakna
        int lineNumber = event.location().lineNumber();
        System.out.println("Breakpoint at line " + lineNumber + ": ");
    }
}

6. Zdrojový kód prvního demonstračního příkladu JDIBreakpointDemo1

Celý dnešní první demonstrační příklad nazvaný JDIBreakpointDemo1 pracuje tak, že se nejdříve zaregistrují oba výše zmíněné typy zpracovávaných událostí (včetně nastavení breakpointu) a posléze se spustí smyčka událostí (event loop). Jakmile nějaké vlákno dosáhne breakpointu, vypíše se na standardní výstup informace o řádku (ve zdrojovém kódu), kde se breakpoint nacházel a posléze se vlákno (resp. i všechna ostatní vlákna) ve sledované JVM opět spustí:

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.VMDeathRequest;
 
 
 
/**
 * Pripojeni k bezicimu virtualnimu stroji Javy, ktery byl spusten s parametry:
 * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida
 * 
 * Po pripojeni se nastavi breakpoint a pri jeho dosazeni se vypisou zakladni
 * informace o sledovane aplikaci.
 * 
 * @author Pavel Tisnovsky
 */
public class JDIBreakpointDemo1 {
 
    /**
     * Navratovy kod pouzity pri ukoncovani sledovane JVM.
     */
    private static final int EXIT_VALUE = 0;
 
    /**
     * Jmeno konektoru, ktery pro pripojeni pouziva sockety.
     */
    private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
 
    /**
     * Jmeno testovane tridy.
     */
    private static final String TEST_CLASS_NAME = "Test8";
 
    /**
     * Cislo radku, na kterem bude nastaveny breakpoint.
     */
    private static int BREAKPOINT_LINE_NUMBER = 6;
 
    /**
     * Vstupni metoda tohoto demonstracniho debuggeru.
     */
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
        List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
 
        // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
        AttachingConnector connector = getSocketAttachConnector(connectors);
 
        if (connector == null) {
            System.out.println("Socket connector is not available");
            return;
        }
 
        // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni
        debugVirtualMachineUsingSocket(connector);
    }
 
    /**
     * Ziskat konektor pouzivajici pro pripojeni sockety
     * 
     * @param connectors
     *            seznam vsech dostupnych konektoru pro pripojeni
     * @return konektor pouzivajici pro pripojeni sockety
     */
    private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
        for (AttachingConnector connector : connectors) {
            if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
                return connector;
            }
        }
        // nenasli jsme zadny vhodny konektor
        return null;
    }
 
    /**
     * Pripojeni k bezicimu virtualnimu stroji pres socket.
     * 
     * @param connector
     *            konektor pouzivajici pro pripojeni sockety
     * @throws InterruptedException
     */
    private static void debugVirtualMachineUsingSocket(AttachingConnector connector) {
        // nastaveni argumentu pouzivanych konektorem
        Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector);
 
        try {
            // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
            VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments);
 
            // spustit sledovany virtualni stroj Javy
            runVirtualMachine(virtualMachine);
 
            // ukazka pouziti EventRequestManageru
            acquireAndUseEventRequestManager(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani
     * spojeni.
     * 
     * @param connector
     *            konektor pouzity pro pripojeni
     * @return mapa obsahujici parametry konektoru
     */
    private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) {
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue("6502");
        return arguments;
    }
 
    /**
     * Pripojeni debuggeru ke sledovanemu virtualnimu stroji.
     * 
     * @param connector
     *            konektor vyuzivajici pro spojeni sockety
     * @param arguments
     *            mapa obsahujici parametry pripojeni
     * @return sledovany virtualni stroj, k nemuz je debugger vzdalene
     *         pripojen
     * 
     * @throws IOException
     *             vyvolane v pripade, ze se pripojeni k JVM nepodari
     * @throws IllegalConnectorArgumentsException
     *             vyvolane v pripade spatne zadanych parametru
     */
    private static VirtualMachine connectToVirtualMachine(
            AttachingConnector connector,
            Map<String, Connector.Argument> arguments) throws IOException,
            IllegalConnectorArgumentsException {
        System.out.println("Connecting to virtual machine");
        VirtualMachine virtualMachine = connector.attach(arguments);
        System.out.println("Connected");
        return virtualMachine;
    }
 
    /**
     * Spustit sledovany virtualni stroj.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void runVirtualMachine(VirtualMachine virtualMachine) {
        virtualMachine.resume();
    }
 
    /**
     * Ukonceni prace beziciho sledovaneho virtualniho stroje.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void shutdownVirtualMachine(VirtualMachine virtualMachine) {
        System.out.println("Calling exit");
        virtualMachine.exit(EXIT_VALUE);
    }
 
    /**
     * Ukazka pouziti EventRequestManageru.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
 
        // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
 
        // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint
        BreakpointRequest breakpointRequest = registerBreakpointEvent(virtualMachine, eventRequestManager);
        //System.out.println("Breakpoint set: " + breakpointRequest.toString());
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest);
    }
 
    /**
     * Registrace udalosti typu VMDeathEvent.
     * 
     * @param eventRequestManager
     *            objekt zajistujici praci s registraci udalosti
     * @return objekt typu VMDeathRequest
     */
    private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) {
        VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest();
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        vmDeathRequest.enable();
        return vmDeathRequest;
    }
 
    /**
     * Registrace udalosti typu BreakpointRequest.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param eventRequestManager
     *            objekt zajistujici praci s registraci udalosti
     * @return objekt typu BreakpointRequest
     */
    private static BreakpointRequest registerBreakpointEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        Location breakpointLocation;
        try {
            // potrebujeme zjistit lokaci, kde se ma breakpoint nastavit
            breakpointLocation = computeBreakpointLocation(virtualMachine, TEST_CLASS_NAME, BREAKPOINT_LINE_NUMBER);
            // ne vzdy se muze podarit zjistit lokaci
            if (breakpointLocation == null) {
                System.err.println("Can not find location to set breakpoint!");
                return null;
            }
            // registrace breakpointu
            BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(breakpointLocation);
            // pri vstupu na breakpoint se zastavi vsechna vlakna
            // ve sledovanem virtualnim stroji Javy
            breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
            breakpointRequest.enable();
            return breakpointRequest;
        }
        catch (AbsentInformationException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
     * Zjisteni lokace, kde se ma breakpoint nastavit.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param className
     *            jmeno tridy, v jejimz ramci se pracuje s breakpointem
     * @param breakpointLineNumber
     *            cislo radku, na nemz ma byt breakpoint nastaven
     * @return lokace, kde se ma breakpoint nastavit nebo null
     * @throws AbsentInformationException
     */
    private static Location computeBreakpointLocation(VirtualMachine virtualMachine, String className, int breakpointLineNumber) throws AbsentInformationException {
        ReferenceType class1 = getClassInMonitoredVM(virtualMachine, className);
 
        // precist vsechny lokace ve zjistene tride
        List<Location> locations = class1.allLineLocations();
 
        // projit vsemi lokacemi
        // pro kazdou lokaci se pokusime zjistit cislo radku
        // a porovnat ji se zadanym cislem radku
        for (Location location : locations) {
            // pokud dojde ke shode cisel radku,
            // lze na teto lokaci nastavit breakpoint
            if (location.lineNumber() == breakpointLineNumber) {
                return location;
            }
        }
        // nepovedlo se najit lokaci pro dane cislo radku
        return null;
    }
 
    /**
     * Precteni tridy zadaneho jmena v monitorovanem virtualnim stroji.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param className
     *            jmeno tridy, v jejimz ramci se pracuje s breakpointem
     * @return reference tridy v monitorovanem virtualnim stroji
     */
    private static ReferenceType getClassInMonitoredVM(VirtualMachine virtualMachine, String className) {
        // mela by se vratit jedina trida
        List<ReferenceType> allClasses = virtualMachine.classesByName(className);
        System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size());
 
        // vratila se pravdepodobne jedina trida, takze ji precteme
        ReferenceType class1 = allClasses.get(0);
        return class1;
    }
 
    /**
     * Klasicka smycka pro postupne zpracovani udalosti.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) {
        EventQueue eventQueue = virtualMachine.eventQueue();
 
        // precist a zpracovat udalosti
        while (processEvents(eventQueue, vmDeathRequest)) {
            // jojo tady skutecne nic neni :)
        }
    }
 
    /**
     * Precteni a zpracovani udalosti.
     * 
     * @param eventQueue
     *            fronta udalosti
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     * 
     * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud
     *         se ma sledovana VM ukoncit
     */
    private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) {
        EventSet eventSet;
        try {
            // precist udalosti z fronty
            eventSet = eventQueue.remove();
            int events = eventSet.size();
            System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":"));
 
            // projit vsemi udalostmi
            for (Event event : eventSet) {
                if (event instanceof VMStartEvent) {
                    System.out.println("    VMStartEvent");
                }
                else if (event instanceof VMDeathEvent) {
                    System.out.println("    VMDeathEvent");
                    // zakazat dalsi generovani udalosti
                    // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) 
                    vmDeathRequest.disable();
                    // posleze se zavola shutdownVirtualMachine()
                    return false;
                }
                else if (event instanceof BreakpointEvent) {
                    // nejake vlakno se zastavilo na breakpointu
                    printInfoAboutBreakpoint((BreakpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu.
     * 
     * @param event udalost s informacemi o dosazenem breakpointu
     */
    private static void printInfoAboutBreakpoint(BreakpointEvent event) {
        // precist cislo radku, na kterem doslo k zastaveni vlakna
        int lineNumber = event.location().lineNumber();
        System.out.println("Breakpoint at line " + lineNumber + ": ");
    }
}

7. Zdrojový kód testovací třídy Test8

Oba dva dnešní demonstrační příklady budeme testovat proti sledovanému virtuálnímu stroji Javy, v němž bude spuštěna velmi jednoduchá aplikace představovaná třídou Test8. V této třídě se mj. nachází i metoda run() s programovou smyčkou. Pokud se podíváte na hodnotu konstant TEST_CLASS_NAME a BREAKPOINT_LINE_NUMBER uvedených ve zdrojovém kódu prvního demonstračního příkladu, snadno zjistíte, že breakpoint bude nastaven na příkaz y+=x; tvořícího tělo zmíněné programové smyčky:

public class Test8 {
 
    public void run(int x) {
        int y = 0;
        for (int i = 1; i < 10; i++) {
            y += x;
        }
    }
 
    public static void main(String[] args) {
        System.out.println("Press any key");
        try {
            System.in.read();
        }
        catch (java.io.IOException e) {
            // . //
        }
        new Test8().run(10);
    }
 
}

8. Výstup generovaný demonstračním příkladem JDIBreakpointDemo1

Podívejme se nyní na to, jak vypadá výstup demonstračního příkladu JDIBreakpointDemo1 ve chvíli, kdy je tento jednoduchý debugger spuštěn oproti výše popsané třídě Test8 v monitorovaném virtuálním stroji Javy:

Connecting to virtual machine
Connected
Number of classes with name 'Test8': 1
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 1 request:
Breakpoint at line 6:
Got 2 requests:
    VMDeathEvent
Calling exit

Vidíme, že na registrovaný breakpoint se přistoupilo celkem devětkrát, což velmi dobře koresponduje s programovou smyčkou implementovanou v testovací metodě Test8.run():

        for (int i = 1; i < 10; i++) {
            y += x;
        }

9. Přečtení podrobnějších informací o vláknu při vstupu na breakpoint

V předchozí kapitole byly vypsány zprávy vytvořené dnešním prvním demonstračním příkladem. Zajisté uznáte, že informace o tom, že nějaké vlákno vstoupilo na breakpoint, mohou sice být velmi zajímavé (například při trasování), ovšem většinou od debuggerů vyžadujeme mnohem větší množství informací. Zejména se jedná o informace vztažené k vláknu, které na breakpoint vstoupilo: hodnoty lokálních proměnných a parametrů aktuální metody, popř. i obsah všech zásobníkových rámců. Tyto informace je samozřejmě možné s využitím rozhraní JDI relativně snadné získat, musí se však dodržet jedna podmínka – testované Javovské třídy musí být přeloženy společně s ladicími informacemi, tj. při překladu je nutné použít přepínač -g. Pokud by ladicí informace nebyly součástí bajtkódu, tedy souborů s koncovkou .class, nebylo by možné namapovat hodnoty lokálních proměnných a argumentů na jejich jména. V další kapitole bude ukázáno, jakým způsobem lze získat již zmíněné informace o argumentech a lokálních proměnných metody, v níž byl breakpoint zaregistrován.

10. Hodnoty parametrů metod i lokálních proměnných

Ve vylepšené variantě uživatelské metody nazvané printInfoAboutBreakpoint() se nejprve vypíše číslo řádku, na němž nějaké vlákno vstoupilo na breakpoint (tuto část kódu již známe). Následně se s využitím metody Event.thread() získá reference vlákna a z něho posléze i objekt typu StackFrame představující jeden zásobníkový rámec. Povšimněte si, že lze získat libovolný zásobníkový rámec, protože jednotlivé rámce jsou zpětně číslovány on nuly (nejvyšší rámec) směrem ke kladným hodnotám. Důležité je, že zásobníkový rámec je možné získat pouze pro zastavená vlákna, což je však v našem případě splněno. Následně se z objektu typu StackFrame načte seznam všech viditelných lokálních proměnných a argumentů (nejedná se tedy o všechny lokální proměnné, nemusí se například vrátit lokální počitadla smyček). U každé proměnné se přečte a následně i vypíše její jméno a hodnota převedená na řetězec:

    /**
     * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu.
     * 
     * @param event udalost s informacemi o dosazenem breakpointu
     */
    private static void printInfoAboutBreakpoint(BreakpointEvent event) {
        // precist cislo radku, na kterem doslo k zastaveni vlakna
        int lineNumber = event.location().lineNumber();
        System.out.println("Breakpoint at line " + lineNumber + ": ");
 
        // ziskat referenci na vlakno 
        ThreadReference threadRef = event.thread();
        try {
            // ziskat posledni zasobnikovy ramec vlakna
            StackFrame stackFrame = threadRef.frame(0);
            // a projit vsemi viditelnymi promennymi
            List<LocalVariable> visibleVars = stackFrame.visibleVariables();
            for (LocalVariable visibleVar : visibleVars) {
                Value val = stackFrame.getValue(visibleVar);
                System.out.println("    Local variable: " + visibleVar.name() + " = " + val);
            }
        }
        catch (IncompatibleThreadStateException e) {
            e.printStackTrace();
        }
        catch (AbsentInformationException e) {
            e.printStackTrace();
        }
    }

11. Zdrojový kód druhého demonstračního příkladu JDIBreakpointDemo2

Upravená uživatelská metoda printInfoAboutBreakpoint() se stala součástí dnešního druhého demonstračního příkladu pojmenovaného JDIBreakpointDemo2. Celý zdrojový kód tohoto příkladu je vypsán pod tímto odstavcem:

cyber23

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.VMDeathRequest;
 
 
 
/**
 * Pripojeni k bezicimu virtualnimu stroji Javy, ktery byl spusten s parametry:
 * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida
 * 
 * Po pripojeni se nastavi breakpoint a pri jeho dosazeni se vypisou zakladni
 * informace o sledovane aplikaci.
 * 
 * @author Pavel Tisnovsky
 */
public class JDIBreakpointDemo2 {
 
    /**
     * Navratovy kod pouzity pri ukoncovani sledovane JVM.
     */
    private static final int EXIT_VALUE = 0;
 
    /**
     * Jmeno konektoru, ktery pro pripojeni pouziva sockety.
     */
    private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
 
    /**
     * Jmeno testovane tridy.
     */
    private static final String TEST_CLASS_NAME = "Test8";
 
    /**
     * Cislo radku, na kterem bude nastaveny breakpoint.
     */
    private static int BREAKPOINT_LINE_NUMBER = 6;
 
    /**
     * Vstupni metoda tohoto demonstracniho debuggeru.
     */
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
        List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
 
        // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
        AttachingConnector connector = getSocketAttachConnector(connectors);
 
        if (connector == null) {
            System.out.println("Socket connector is not available");
            return;
        }
 
        // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni
        debugVirtualMachineUsingSocket(connector);
    }
 
    /**
     * Ziskat konektor pouzivajici pro pripojeni sockety
     * 
     * @param connectors
     *            seznam vsech dostupnych konektoru pro pripojeni
     * @return konektor pouzivajici pro pripojeni sockety
     */
    private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
        for (AttachingConnector connector : connectors) {
            if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
                return connector;
            }
        }
        // nenasli jsme zadny vhodny konektor
        return null;
    }
 
    /**
     * Pripojeni k bezicimu virtualnimu stroji pres socket.
     * 
     * @param connector
     *            konektor pouzivajici pro pripojeni sockety
     * @throws InterruptedException
     */
    private static void debugVirtualMachineUsingSocket(AttachingConnector connector) {
        // nastaveni argumentu pouzivanych konektorem
        Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector);
 
        try {
            // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
            VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments);
 
            // spustit sledovany virtualni stroj Javy
            runVirtualMachine(virtualMachine);
 
            // ukazka pouziti EventRequestManageru
            acquireAndUseEventRequestManager(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani
     * spojeni.
     * 
     * @param connector
     *            konektor pouzity pro pripojeni
     * @return mapa obsahujici parametry konektoru
     */
    private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) {
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue("6502");
        return arguments;
    }
 
    /**
     * Pripojeni debuggeru ke sledovanemu virtualnimu stroji.
     * 
     * @param connector
     *            konektor vyuzivajici pro spojeni sockety
     * @param arguments
     *            mapa obsahujici parametry pripojeni
     * @return sledovany virtualni stroj, k nemuz je debugger vzdalene
     *         pripojen
     * 
     * @throws IOException
     *             vyvolane v pripade, ze se pripojeni k JVM nepodari
     * @throws IllegalConnectorArgumentsException
     *             vyvolane v pripade spatne zadanych parametru
     */
    private static VirtualMachine connectToVirtualMachine(
            AttachingConnector connector,
            Map<String, Connector.Argument> arguments) throws IOException,
            IllegalConnectorArgumentsException {
        System.out.println("Connecting to virtual machine");
        VirtualMachine virtualMachine = connector.attach(arguments);
        System.out.println("Connected");
        return virtualMachine;
    }
 
    /**
     * Spustit sledovany virtualni stroj.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void runVirtualMachine(VirtualMachine virtualMachine) {
        virtualMachine.resume();
    }
 
    /**
     * Ukonceni prace beziciho sledovaneho virtualniho stroje.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void shutdownVirtualMachine(VirtualMachine virtualMachine) {
        System.out.println("Calling exit");
        virtualMachine.exit(EXIT_VALUE);
    }
 
    /**
     * Ukazka pouziti EventRequestManageru.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
 
        // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
 
        // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint
        BreakpointRequest breakpointRequest = registerBreakpointEvent(virtualMachine, eventRequestManager);
        //System.out.println("Breakpoint set: " + breakpointRequest.toString());
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest);
    }
 
    /**
     * Registrace udalosti typu VMDeathEvent.
     * 
     * @param eventRequestManager
     *            objekt zajistujici praci s registraci udalosti
     * @return objekt typu VMDeathRequest
     */
    private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) {
        VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest();
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        vmDeathRequest.enable();
        return vmDeathRequest;
    }
 
    /**
     * Registrace udalosti typu BreakpointRequest.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param eventRequestManager
     *            objekt zajistujici praci s registraci udalosti
     * @return objekt typu BreakpointRequest
     */
    private static BreakpointRequest registerBreakpointEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        Location breakpointLocation;
        try {
            // potrebujeme zjistit lokaci, kde se ma breakpoint nastavit
            breakpointLocation = computeBreakpointLocation(virtualMachine, TEST_CLASS_NAME, BREAKPOINT_LINE_NUMBER);
            // ne vzdy se muze podarit zjistit lokaci
            if (breakpointLocation == null) {
                System.err.println("Can not find location to set breakpoint!");
                return null;
            }
            // registrace breakpointu
            BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(breakpointLocation);
            // pri vstupu na breakpoint se zastavi vsechna vlakna
            // ve sledovanem virtualnim stroji Javy
            breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
            breakpointRequest.enable();
            return breakpointRequest;
        }
        catch (AbsentInformationException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
     * Zjisteni lokace, kde se ma breakpoint nastavit.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param className
     *            jmeno tridy, v jejimz ramci se pracuje s breakpointem
     * @param breakpointLineNumber
     *            cislo radku, na nemz ma byt breakpoint nastaven
     * @return lokace, kde se ma breakpoint nastavit nebo null
     * @throws AbsentInformationException
     */
    private static Location computeBreakpointLocation(VirtualMachine virtualMachine, String className, int breakpointLineNumber) throws AbsentInformationException {
        ReferenceType class1 = getClassInMonitoredVM(virtualMachine, className);
 
        // precist vsechny lokace ve zjistene tride
        List<Location> locations = class1.allLineLocations();
 
        // projit vsemi lokacemi
        // pro kazdou lokaci se pokusime zjistit cislo radku
        // a porovnat ji se zadanym cislem radku
        for (Location location : locations) {
            // pokud dojde ke shode cisel radku,
            // lze na teto lokaci nastavit breakpoint
            if (location.lineNumber() == breakpointLineNumber) {
                return location;
            }
        }
        // nepovedlo se najit lokaci pro dane cislo radku
        return null;
    }
 
    /**
     * Precteni tridy zadaneho jmena v monitorovanem virtualnim stroji.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param className
     *            jmeno tridy, v jejimz ramci se pracuje s breakpointem
     * @return reference tridy v monitorovanem virtualnim stroji
     */
    private static ReferenceType getClassInMonitoredVM(VirtualMachine virtualMachine, String className) {
        // mela by se vratit jedina trida
        List<ReferenceType> allClasses = virtualMachine.classesByName(className);
        System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size());
 
        // vratila se pravdepodobne jedina trida, takze ji precteme
        ReferenceType class1 = allClasses.get(0);
        return class1;
    }
 
    /**
     * Klasicka smycka pro postupne zpracovani udalosti.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) {
        EventQueue eventQueue = virtualMachine.eventQueue();
 
        // precist a zpracovat udalosti
        while (processEvents(eventQueue, vmDeathRequest)) {
            // jojo tady skutecne nic neni :)
        }
    }
 
    /**
     * Precteni a zpracovani udalosti.
     * 
     * @param eventQueue
     *            fronta udalosti
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     * 
     * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud
     *         se ma sledovana VM ukoncit
     */
    private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) {
        EventSet eventSet;
        try {
            // precist udalosti z fronty
            eventSet = eventQueue.remove();
            int events = eventSet.size();
            System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":"));
 
            // projit vsemi udalostmi
            for (Event event : eventSet) {
                if (event instanceof VMStartEvent) {
                    System.out.println("    VMStartEvent");
                }
                else if (event instanceof VMDeathEvent) {
                    System.out.println("    VMDeathEvent");
                    // zakazat dalsi generovani udalosti
                    // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) 
                    vmDeathRequest.disable();
                    // posleze se zavola shutdownVirtualMachine()
                    return false;
                }
                else if (event instanceof BreakpointEvent) {
                    // nejake vlakno se zastavilo na breakpointu
                    printInfoAboutBreakpoint((BreakpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Vypis informaci o tom, ze se nejake vlakno zastavilo na breakpointu.
     * 
     * @param event udalost s informacemi o dosazenem breakpointu
     */
    private static void printInfoAboutBreakpoint(BreakpointEvent event) {
        // precist cislo radku, na kterem doslo k zastaveni vlakna
        int lineNumber = event.location().lineNumber();
        System.out.println("Breakpoint at line " + lineNumber + ": ");
 
        // ziskat referenci na vlakno 
        ThreadReference threadRef = event.thread();
        try {
            // ziskat posledni zasobnikovy ramec vlakna
            StackFrame stackFrame = threadRef.frame(0);
            // a projit vsemi viditelnymi promennymi
            List<LocalVariable> visibleVars = stackFrame.visibleVariables();
            for (LocalVariable visibleVar : visibleVars) {
                Value val = stackFrame.getValue(visibleVar);
                System.out.println("    Local variable: " + visibleVar.name() + " = " + val);
            }
        }
        catch (IncompatibleThreadStateException e) {
            e.printStackTrace();
        }
        catch (AbsentInformationException e) {
            e.printStackTrace();
        }
    }
}

12. Výstup generovaný demonstračním příkladem JDIBreakpointDemo2

Při spuštění druhého demonstračního příkladu JDIBreakpointDemo2 oproti virtuálnímu stroji Javy s běžící testovací aplikací Test8 dostaneme už mnohem podrobnější informaci o vláknu, které vstoupilo na breakpoint. Vypíše se totiž nejenom číslo řádku, ale i hodnoty argumentů a lokálních proměnných funkce run(), takže můžeme snadno sledovat hodnotu počitadla programové smyčky (i) i postupnou změnu hodnoty lokální proměnné y:

Connecting to virtual machine
Connected
Number of classes with name 'Test8': 1
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 0
    Local variable: i = 1
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 10
    Local variable: i = 2
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 20
    Local variable: i = 3
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 30
    Local variable: i = 4
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 40
    Local variable: i = 5
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 50
    Local variable: i = 6
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 60
    Local variable: i = 7
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 70
    Local variable: i = 8
Got 1 request:
Breakpoint at line 6:
    Local variable: x = 10
    Local variable: y = 80
    Local variable: i = 9
Got 2 requests:
    VMDeathEvent
Calling exit

13. Repositář se zdrojovými kódy demonstračních příkladů

Zdrojové kódy obou dnes popsaných demonstračních příkladů JDIBreakpointDemo1 a JDIBreakpointDemo2 jsou společně s dalšími pomocnými soubory uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze těchto zdrojových kódů:

14. Odkazy na Internetu

  1. Class com.sun.jdi.Bootstrap
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­package-tree.html
  2. Interface com.sun.jdi.VirtualMachine
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­VirtualMachine.html
  3. Interface com.sun.jdi.Field
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­Field.html
  4. Interface com.sun.jdi.ReferenceType
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­ReferenceType.html
  5. Interface com.sun.jdi.TypeComponent
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­TypeComponent.html
  6. Interface com.sun.jdi.Accessible
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­Accessible.html
  7. Breakpoint (Wikipedia)
    http://cs.wikipedia.org/wi­ki/Breakpoint
  8. JVM Tool Interface Version 1.2 Documentation
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  9. JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#SetBreakpoint
  10. JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#ClearBreakpoint
  11. JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#Breakpoint
  12. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  13. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  14. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  15. 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
  16. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  17. 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
  18. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  19. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  20. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  21. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  22. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  23. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  24. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  25. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html

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