Hlavní navigace

Pohled pod kapotu JVM – detekce vstupu do metod s využitím rozhraní JDI

21. 5. 2013
Doba čtení: 33 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java si ukážeme způsob detekce vstupu do metod s využitím rozhraní JDI (Java Debugger Interface). Pro zjištění, jaké metody jsou ve sledovaném virtuálním stroji Javy volány, se při využití JDI používá systém událostí, s nímž jsme se seznámili minule.

Obsah

1. Pohled pod kapotu JVM – detekce vstupu do metod s využitím rozhraní JDI

2. Registrace události vyvolané při vstupu do libovolné metody

3. Výpis základních informací o volané metodě

4. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection1

5. Filtrace tříd, pro jejichž metody se vyvolá událost MethodEntryEvent

6. Zjištění podrobnějších informací o volané metodě

7. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection2

8. Zjištění modifikátorů, parametrů a návratových typů volaných metod

9. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection3

10. Zdrojový kód testovací třídy Test5

11. Repositář se zdrojovými kódy všech tří demonstračních příkladů

12. Odkazy na Internetu

1. Pohled pod kapotu JVM – detekce vstupu do metod s využitím rozhraní JDI

V předchozí části seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme si řekli základní informace o tom, jakým způsobem se v rozhraní JDI (Java Debugger Interface) registrují a následně zpracovávají takzvané „události“ (events), které jsou automaticky generovány ve chvíli, kdy dojde ke změně stavu sledovaného (monitorovaného) virtuálního stroje Javy. Připomeňme si, že každá událost se na straně debuggeru projeví vytvořením objektu typu com.sun.jdi.event.Event, který je ihned po svém vytvoření vložen do takzvané fronty událostí (event queue). Debugger může (resp. přesněji řečeno dokonce musí) události postupně z této fronty číst a nějakým způsobem na tyto události reagovat. Postupné čtení událostí je většinou implementováno formou programové smyčky (event loop), na jejímž začátku se přečte nová událost z fronty událostí a posléze se provede rozeskok na základě typu přečtené události.

Registrovat a zpracovávat lze následující typy událostí:

  1. Start sledovaného virtuálního stroje
  2. Ukončení práce sledovaného virtuálního stroje
  3. Vytvoření a spuštění nového vlákna
  4. Ukončení činnosti vlákna
  5. Vstup vlákna do stavu čekání na jiné vlákno (monitor)
  6. Vstup na breakpoint v nějakém vlákně běžícím ve sledované JVM
  7. Čtení či zápis do sledovaného atributu (watchpoint)
  8. Provedení jednoho kroku při krokování programu ve sledované JVM
  9. Vznik výjimky
  10. Vstup do metody
  11. Výskok z metody

Debugger většinou musí umět zareagovat minimálně na událost typu VMDeathEvent, která nastane ve chvíli, kdy je činnost sledovaného virtuálního stroje ukončována. Zpracování události tohoto typu jsme si ukázali minule v demonstračním příkladu JDIEventRequestManager.java. Dnes si řekneme, jak může debugger jednoduše sledovat volání jednotlivých metod v monitorovaném virtuálním stroji Javy; posléze si také ukážeme možnosti filtrace událostí na základě jména třídy, popř. jednoduchého regulárního výrazu popisujícího větší množinu jmen.

2. Registrace události vyvolané při vstupu do libovolné metody

Žádost o registraci události se provádí s využitím tříd implementujících rozhraní com.sun.jdi.request.EventRequest, které je předkem dalších rozhraní vypsaných v následující tabulce:

# Rozhraní Popis
1 com.sun.jdi.request.BreakpointRequest Žádost o generování událostí typu com.sun.jdi.event.BreakpointEvent
2 com.sun.jdi.request.ClassPrepareRequest Žádost o generování událostí typu com.sun.jdi.event.ClassPrepareEvent
3 com.sun.jdi.request.ClassUnloadRequest Žádost o generování událostí typu com.sun.jdi.event.ClassUnloadEvent
4 com.sun.jdi.request.ExceptionRequest Žádost o generování událostí typu com.sun.jdi.event.ExceptionEvent
5 com.sun.jdi.request.MethodEntryRequest Žádost o generování událostí typu com.sun.jdi.event.MethodEntryEvent
6 com.sun.jdi.request.MethodExitRequest Žádost o generování událostí typu com.sun.jdi.event.MethodExitEvent
7 com.sun.jdi.request.Monitor­ContendedEnteredRequest Žádost o generování událostí typu com.sun.jdi.event.Monitor­ContendedEnteredEvent
8 com.sun.jdi.request.Monitor­ContendedEnterRequest Žádost o generování událostí typu com.sun.jdi.event.Monitor­ContendedEnterEvent
9 com.sun.jdi.request.Monitor­WaitedRequest Žádost o generování událostí typu com.sun.jdi.event.MonitorWaitedEvent
10 com.sun.jdi.request.MonitorWaitRequest Žádost o generování událostí typu com.sun.jdi.event.MonitorWaitEvent
11 com.sun.jdi.request.StepRequest Žádost o generování událostí typu com.sun.jdi.event.StepEvent
12 com.sun.jdi.request.ThreadDeathRequest Žádost o generování událostí typu com.sun.jdi.event.ThreadDeathEvent
13 com.sun.jdi.request.ThreadStartRequest Žádost o generování událostí typu com.sun.jdi.event.ThreadStartEvent
14 com.sun.jdi.request.VMDeathRequest Žádost o generování událostí typu com.sun.jdi.event.VMDeathEvent
15 com.sun.jdi.request.WatchpointRequest Žádost o generování událostí typu com.sun.jdi.event.WatchpointEvent
16 com.sun.jdi.request.Acces­sWatchpointRequest Žádost o generování událostí typu com.sun.jdi.AccessWatchpointEvent
17 com.sun.jdi.request.Modifi­cationWatchpointRequest Žádost o generování událostí typu com.sun.jdi.event.Modifica­tionWatchpointEvent

V dnešních demonstračních příkladech využijeme pouze rozhraní VMDeathRequest a MethodEntryRequest. Registrace příslušných typů událostí se provádí v uživatelské metodě acquireAndUseEventRequestManager(), která nejprve zaregistruje oba typy událostí a posléze zavolá smyčku událostí implementovanou v samostatné metodě:

    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
        MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager);
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest);
    }

V metodě registerMethodEntryRequest() se vyžádá generování událostí typu MethodEntryEvent a posléze se generování těchto typů událostí ještě musí explicitně povolit:

    /**
     * Registrace udalosti typu MethodEntry
     */
    private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) {
        MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest();
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        methodEntryRequest.enable();
        return methodEntryRequest;
    }

3. Výpis základních informací o volané metodě

Vlastní implementace smyčky událostí je, stejně jako v demonstračním příkladu popsaném minule, rozdělená do dvou uživatelských metod. V metodě nazvané příznačně eventLoop() se nejprve získá objekt typu EventQueue a následně se v programové smyčce opakovaně volá uživatelská metoda processEvents() vracející pravdivostní hodnotu true v případě, že se má pokračovat ve čtení a zpracování dalších událostí a hodnotu false ve chvíli, kdy přišla událost typu VMDeathEvent a kdy má tedy debugger ukončit svoji činnost. Metodu eventLoop() si dnes popisovat nebudeme, protože ji známe již z minula, ovšem v metodě processEvents() došlo k rozšíření „rozeskoku“ o další typ události:

    /**
     * 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 MethodEntryEvent) {
                    printMethodEntryInfo((MethodEntryEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

Při přečtení události typu MethodEntryEvent se zavolá uživatelská metoda printMethodEntryInfo(), která ve své první verzi pouze vytiskne jméno zavolané metody:

    /**
     * Zavolano pri vyskytu udalosti typu MethodEntry
     */
    private static void printMethodEntryInfo(MethodEntryEvent event) {
        String methodName = event.method().name();
 
        // nyni mame vsechny informace, lze je tedy vypsat
        System.out.format("    entry into method: %s\n", methodName);
    }

4. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection1

Metody popsané v předchozích dvou kapitolách jsou součástí dnešního demonstračního příkladu nazvaného JDIMethodEntryDetection1. Tento příklad po svém spuštění vytiskne jména všech volaných metod, a to nezávisle na tom, ve kterém vláknu jsou tyto metody volány či ke které třídě metody náleží:

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.Bootstrap;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
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
 *
 *
 * @author Pavel Tisnovsky
 */
public class JDIMethodEntryDetection1 {
 
    /**
     * Navratovy kod pouzity pri ukoncovani sledovane JVM.
     */
    private static final int EXIT_VALUE = 0;
 
    /**
     * Jmeno konektoru, ktery pro pripojeni pouziva sockety.
     */
    private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
 
    /**
     * Vstupni metoda debuggeru.
     */
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
        List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
 
        // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
        AttachingConnector connector = getSocketAttachConnector(connectors);
 
        if (connector == null) {
            System.out.println("Socket connector is not available");
            return;
        }
 
        // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni
        debugVirtualMachineUsingSocket(connector);
    }
 
    /**
     * Ziskat konektor pouzivajici pro pripojeni sockety
     */
    private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
        for (AttachingConnector connector : connectors) {
            if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
                return connector;
            }
        }
        // nenasli jsme zadny vhodny konektor
        return null;
    }
 
    /**
     * Pripojeni k bezicimu virtualnimu stroji pres socket.
     * @throws InterruptedException 
     */
    private static void debugVirtualMachineUsingSocket(AttachingConnector connector) {
        // nastaveni argumentu pouzivanych konektorem
        Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector);
 
        try {
            // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
            VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments);
 
            // spustit sledovany virtualni stroj Javy
            runVirtualMachine(virtualMachine);
 
            // ukazka pouziti EventRequestManageru
            acquireAndUseEventRequestManager(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni.
     *
     * @param connector konektor pouzity pro pripojeni
     * @return mapa obsahujici parametry konektoru
     */
    private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) {
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue("6502");
        return arguments;
    }
 
    /**
     * Pripojeni debuggeru ke sledovanemu virtualnimu stroji.
     *
     * @param connector konektor vyuzivajici pro spojeni sockety
     * @param arguments mapa obsahujici parametry pripojeni
     * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     *
     * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari
     * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru
     */
    private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments)
        throws IOException, IllegalConnectorArgumentsException {
        System.out.println("Connecting to virtual machine");
        VirtualMachine virtualMachine = connector.attach(arguments);
        System.out.println("Connected");
        return virtualMachine;
    }
 
    /**
     * Spustit sledovany virtualni stroj
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void runVirtualMachine(VirtualMachine virtualMachine) {
        virtualMachine.resume();
    }
 
    /**
     * Ukonceni prace beziciho sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void shutdownVirtualMachine(VirtualMachine virtualMachine) {
        System.out.println("Calling exit");
        virtualMachine.exit(EXIT_VALUE);
    }
 
    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
        MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager);
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest);
    }
 
    /**
     * Registrace udalosti typu VMDeathEvent
     */
    private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) {
        VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest();
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        vmDeathRequest.enable();
        return vmDeathRequest;
    }
 
    /**
     * Registrace udalosti typu MethodEntry
     */
    private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) {
        MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest();
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        methodEntryRequest.enable();
        return methodEntryRequest;
    }
 
    /**
     * 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
     * @param methodEntryRequest objekt pro rizeni udalosti typu MethodEntry
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, MethodEntryRequest methodEntryRequest) {
        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 MethodEntryEvent) {
                    printMethodEntryInfo((MethodEntryEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Zavolano pri vyskytu udalosti typu MethodEntry
     */
    private static void printMethodEntryInfo(MethodEntryEvent event) {
        String methodName = event.method().name();
 
        // nyni mame vsechny informace, lze je tedy vypsat
        System.out.format("    entry into method: %s\n", methodName);
    }
 
}

Příklad výstupu generovaného tímto demonstračním příkladem (výstup je zkrácen):

Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: getClassSignature
...
...
...
Got 1 request:
    entry into method: hook
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: run
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: toArray
Got 1 request:
    entry into method: size
Got 1 request:
    entry into method: size
Got 1 request:
    entry into method: iterator
Got 1 request:
    entry into method: keySet
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: iterator
Got 1 request:
    entry into method: newKeyIterator
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: access$100
Got 1 request:
    entry into method: hasNext
Got 1 request:
    entry into method: access$100
Got 1 request:
    entry into method: getClass
Got 1 request:
    entry into method: reverse
Got 1 request:
    entry into method: size
Got 1 request:
    entry into method: iterator
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: hasNext
Got 1 request:
    entry into method: size
Got 1 request:
    entry into method: hasNext
Got 1 request:
    entry into method: size
Got 2 requests:
    VMDeathEvent
Calling exit

5. Filtrace tříd, pro jejichž metody se vyvolá událost MethodEntryEvent

Pokud jste si vyzkoušeli přeložit a spustit debugger z předchozí kapitoly, zajisté jste si všimli, že počet událostí generovaných při vstupu do metod, je značný, a to i pro jednoduché aplikace typu „Hello world!“. Většinou však nepotřebujeme detekovat vstup do všech metod, ale jen do metod náležejících nějaké třídě nebo několika vybraným třídám. Samozřejmě je možné pro každou událost typu MethodEntryEvent zjistit jméno třídy příslušné volané metody, to je však poměrně náročné z hlediska zpracování – filtrace se totiž provádí v poměrně pozdním zpracování události (po vytvoření události a jejím vložení do fronty událostí).

Namísto toho je možné pro události typu MethodEntryEvent definovat filtr, který povolí vytvoření událostí pouze pro metody tříd, jejichž jména odpovídají zadanému regulárnímu výrazu. Pro tento účel lze využít com.sun.jdi.request.Metho­dEntryRequest.addClassFil­ter(java.lang.String) popř. com.sun.jdi.request­.MethodEntryRequest.addClas­sFilter(com.sun.jdi.Referen­ceType) (zde se již regulární výraz samozřejmě nepoužívá). Kromě „pozitivního“ filtru, je možné použít i filtr „negativní“, který se zadává s využitím com.sun.jdi.request.Metho­dEntryRequest.addClassExclu­sionFilter(java.lang.Strin­g). Upravme nyní uživatelskou metodu registerMethodEntryRequest() takovým způsobem, aby se zjišťovaly vstupy do metod třídy „Test5“:

    /**
     * Registrace udalosti typu MethodEntry
     */
    private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) {
        MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest();
 
        // pridame jeste filtr
        methodEntryRequest.addClassFilter("Test5");
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        methodEntryRequest.enable();
        return methodEntryRequest;
    }

Po přidání filtru se výstup debuggeru výrazným způsobem zkrátí:

Connecting to virtual machine
Connected
Got 1 request:
    VMStartEvent
Got 1 request:
    entry into method: main
Got 1 request:
    entry into method: <init>
Got 1 request:
    entry into method: run
Got 1 request:
    entry into method: foo
Got 1 request:
    entry into method: bar
Got 1 request:
    entry into method: baz
Got 2 requests:
    VMDeathEvent
Calling exit

6. Zjištění podrobnějších informací o volané metodě

Uživatelskou metodu printMethodEntryInfo() je samozřejmě možné různými způsoby rozšiřovat. Kromě jména metody (což je zcela základní informace) lze zjistit i jméno příslušné třídy a dokonce i pozici volané metody v rámci zdrojového souboru příslušné třídy. První úprava metody printMethodEntryInfo() využívá objekt typu Location, s nímž jsme se již v tomto seriálu setkali:

    /**
     * Zavolano pri vyskytu udalosti typu MethodEntry
     */
    private static void printMethodEntryInfo(MethodEntryEvent event) {
        Location location = event.location();
        // ziskat vsechny informace o pozici v pozastavenem vlaknu
        String className = getClassName(location);
        String methodName = getMethodName(location);
        String sourceName = getSourceName(location);
        String lineNumber = getLineNumber(location);
 
        // nyni mame vsechny informace, lze je tedy vypsat
        System.out.format("    entry into method: %s.%s (%s:%s)\n", className, methodName, sourceName, lineNumber);
    }

Pro získání jména třídy, ve které je volaná metoda deklarována, se používá volání com.sun.jdi.Method.declaringType() (přesněji řečeno com.sun.jdi.TypeComponent­.declaringType()) vracející objekt typu ReferenceType:

    /**
     * Jmeno tridy, jejiz metoda byla zavolana.
     */
    private static String getClassName(Location location) {
        return location.method().declaringType().name();
    }

Jako ukázku toho, že jméno volané metody je možné získat více způsoby, je zde ukázáno použití Location.method() namísto MethodEntryEvent.method():

    /**
     * Jmeno volane metody.
     */
    private static String getMethodName(Location location) {
        return location.method().name();
    }

Zjištění jména zdrojového kódu, v němž je volaná metoda a samozřejmě i příslušná třída deklarována, je velmi jednoduché, protože pro tento účel lze použít volání com.sun.jdi.Location.sourceName(). V případě potřeby je možné zjistit i cestu k tomuto souboru s využitím com.sun.jdi.Location.sourcePath() (tuto informaci jsme v rozhraní JVM TI zjišťovali jen velmi složitým způsobem):

    /**
     * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci.
     */
    private static String getSourceName(Location location) {
        try {
            return location.sourceName();
        }
        catch (AbsentInformationException e) {
            return "unknown";
        }
    }

Informaci o čísle řádku v rámci zdrojového souboru (pokud je ovšem tato informace vůbec dostupná) získáme s využitím com.sun.jdi.Location.lineNumber(), která vrátí kladné číslo v případě, že je číslo řádku známé a zápornou či nulovou hodnotu ve chvíli, kdy číslo řádku není možné z nějakého důvodu zjistit (typicky se jedná o nativní metody).

    /**
     * Prevod cisla radku na retezec, pokud je to mozne.
     */
    private static String getLineNumber(Location location) {
        int lineNumber = location.lineNumber();
        // u nativnich metod nelze zjistit cisla radku
        return lineNumber >= 0 ? "" + lineNumber : "<native method>";
    }

7. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection2

Vylepšení popsaná v páté a šesté kapitole jsou součástí dnešního druhého demonstračního příkladu nazvaného JDIMethodEntryDetection2, jehož zdrojový kód je zobrazen pod tímto odstavcem:

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.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
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
 *
 *
 * @author Pavel Tisnovsky
 */
public class JDIMethodEntryDetection2 {
 
    /**
     * Navratovy kod pouzity pri ukoncovani sledovane JVM.
     */
    private static final int EXIT_VALUE = 0;
 
    /**
     * Jmeno konektoru, ktery pro pripojeni pouziva sockety.
     */
    private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
 
    /**
     * Vstupni metoda debuggeru.
     */
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
        List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
 
        // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
        AttachingConnector connector = getSocketAttachConnector(connectors);
 
        if (connector == null) {
            System.out.println("Socket connector is not available");
            return;
        }
 
        // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni
        debugVirtualMachineUsingSocket(connector);
    }
 
    /**
     * Ziskat konektor pouzivajici pro pripojeni sockety
     */
    private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
        for (AttachingConnector connector : connectors) {
            if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
                return connector;
            }
        }
        // nenasli jsme zadny vhodny konektor
        return null;
    }
 
    /**
     * Pripojeni k bezicimu virtualnimu stroji pres socket.
     * @throws InterruptedException 
     */
    private static void debugVirtualMachineUsingSocket(AttachingConnector connector) {
        // nastaveni argumentu pouzivanych konektorem
        Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector);
 
        try {
            // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
            VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments);
 
            // spustit sledovany virtualni stroj Javy
            runVirtualMachine(virtualMachine);
 
            // ukazka pouziti EventRequestManageru
            acquireAndUseEventRequestManager(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni.
     *
     * @param connector konektor pouzity pro pripojeni
     * @return mapa obsahujici parametry konektoru
     */
    private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) {
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue("6502");
        return arguments;
    }
 
    /**
     * Pripojeni debuggeru ke sledovanemu virtualnimu stroji.
     *
     * @param connector konektor vyuzivajici pro spojeni sockety
     * @param arguments mapa obsahujici parametry pripojeni
     * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     *
     * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari
     * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru
     */
    private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments)
        throws IOException, IllegalConnectorArgumentsException {
        System.out.println("Connecting to virtual machine");
        VirtualMachine virtualMachine = connector.attach(arguments);
        System.out.println("Connected");
        return virtualMachine;
    }
 
    /**
     * Spustit sledovany virtualni stroj
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void runVirtualMachine(VirtualMachine virtualMachine) {
        virtualMachine.resume();
    }
 
    /**
     * Ukonceni prace beziciho sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void shutdownVirtualMachine(VirtualMachine virtualMachine) {
        System.out.println("Calling exit");
        virtualMachine.exit(EXIT_VALUE);
    }
 
    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
        MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager);
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest);
    }
 
    /**
     * Registrace udalosti typu VMDeathEvent
     */
    private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) {
        VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest();
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        vmDeathRequest.enable();
        return vmDeathRequest;
    }
 
    /**
     * Registrace udalosti typu MethodEntry
     */
    private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) {
        MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest();
 
        // pridame filtr na jmeno tridy
        methodEntryRequest.addClassFilter("Test5");
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        methodEntryRequest.enable();
        return methodEntryRequest;
    }
 
    /**
     * 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
     * @param methodEntryRequest objekt pro rizeni udalosti typu MethodEntry
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, MethodEntryRequest methodEntryRequest) {
        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 MethodEntryEvent) {
                    printMethodEntryInfo((MethodEntryEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Zavolano pri vyskytu udalosti typu MethodEntry
     */
    private static void printMethodEntryInfo(MethodEntryEvent event) {
        Location location = event.location();
        // ziskat vsechny informace o pozici v pozastavenem vlaknu
        String className = getClassName(location);
        String methodName = getMethodName(location);
        String sourceName = getSourceName(location);
        String lineNumber = getLineNumber(location);
 
        // nyni mame vsechny informace, lze je tedy vypsat
        System.out.format("    entry into method: %s.%s (%s:%s)\n", className, methodName, sourceName, lineNumber);
    }
 
    /**
     * Jmeno tridy, jejiz metoda byla zavolana.
     */
    private static String getClassName(Location location) {
        return location.method().declaringType().name();
    }
 
    /**
     * Jmeno volane metody.
     */
    private static String getMethodName(Location location) {
        return location.method().name();
    }
 
    /**
     * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci.
     */
    private static String getSourceName(Location location) {
        try {
            return location.sourceName();
        }
        catch (AbsentInformationException e) {
            return "unknown";
        }
    }
 
    /**
     * Prevod cisla radku na retezec, pokud je to mozne.
     */
    private static String getLineNumber(Location location) {
        int lineNumber = location.lineNumber();
        // u nativnich metod nelze zjistit cisla radku
        return lineNumber >= 0 ? "" + lineNumber : "<native method>";
    }
 
}

Po spuštění tohoto příkladu již získáme zajímavější výstup:

Connecting to virtual machine
Connected
Got 1 request:
    VMStartEvent
Got 1 request:
    entry into method: Test5.<init> (Test5.java:1)
Got 1 request:
    entry into method: Test5.run (Test5.java:4)
Got 1 request:
    entry into method: Test5.foo (Test5.java:10)
Got 1 request:
    entry into method: Test5.bar (Test5.java:15)
Got 1 request:
    entry into method: Test5.baz (Test5.java:19)
Got 2 requests:
    VMDeathEvent
Calling exit

8. Zjištění modifikátorů, parametrů a návratových typů volaných metod

Pro zajímavost ještě debugger rozšíříme o výpis prakticky všech informací, které lze zjistit pro volané metody. Již jsme si ukázali, jak se zjistí a zobrazí jméno třídy, jméno metody, jméno zdrojového souboru s třídou a popř. i číslo řádku, na němž metodu nalezneme. Tyto informace lze doplnit o plnou hlavičku metody, včetně jejích modifikátorů, návratového typu i typů argumentů metody. Uživatelská metoda printMethodEntryInfo() je tedy náležitým způsobem rozšířena:

    /**
     * Zavolano pri vyskytu udalosti typu MethodEntry
     * 
     * @param event
     *            udalost typu MethodEntry
     */
    private static void printMethodEntryInfo(MethodEntryEvent event) {
        Method method = event.method();
        Location location = event.location();
 
        // ziskat vsechny informace o pozici v pozastavenem vlaknu
        String className = getClassName(location);
        String methodName = getMethodName(location);
        String sourceName = getSourceName(location);
        String lineNumber = getLineNumber(location);
 
        // typ navratove hodnoty reprezentovany jako retezec
        String type = method.returnTypeName();
        // priznaky pristupovych prav
        String accessibilityStr = getAccessibility(method);
        String staticStr = method.isStatic() ? "static " : "";
        String finalStr = method.isFinal() ? "final " : "";
        String nativeStr = method.isNative() ? "native " : "";
        String synchronizedStr = method.isSynchronized() ? "synchronized " : "";
 
        // nyni mame vsechny informace, lze je tedy vypsat
        System.out.format("    entry into method: %s%s%s%s%s%s %s.%s",
                accessibilityStr, staticStr, finalStr,
                nativeStr, synchronizedStr, type,
                className, methodName);
        printMethodArguments(method);
        // po vypisu argumentu se jeste vypise jmeno zdrojoveho
        // souboru a cislo radku
        System.out.format("   (%s:%s)\n", sourceName, lineNumber);
    }

U volaných metod je možné zjistit a vypsat další důležitou informaci. Jedná se o seznam argumentů metody. V závislosti na tom, zda byl bajtkód zkoumané třídy přeložen s přepínačem -g či bez tohoto modifikátoru, lze o argumentech zjistit buď pouze informaci o typech jednotlivých argumentů, popř. i jména argumentů – v tomto případě je však nutné provést překlad s přepínačem -g, jinak se tato informace do bajtkódu neuloží, protože ji virtuální stroj Javy ve skutečnosti pro spuštění metody nepotřebuje. S využitím rozhraní JDI nelze snadno zjistit, které informace o argumentech metod jsou k dispozici, proto je kód metody nazvané printMethodArguments() rozdělen na dvě části. V první části umístěné v bloku try {} se zjišťují jak jména, tak i typy argumentů, v části umístěné v bloku catch {} pak pouze typy argumentů metody, neboť tyto informace má JVM k dispozici vždy. Výjimka, která rozhodne o použití prvního či druhého bloku, je vyhozena z metody Method.arguments() umístěné jako první příkaz v bloku try{} (tuto část kódu již vlastně známe z předchozích částí tohoto seriálu):

    /**
     * Vypis argumentu metody.
     *
     * @param method metoda ve sledovanem virtualnim stroji
     */
    private static void printMethodArguments(Method method) {
        System.out.print("(");
        boolean first = true;
 
        try {
            // idealni je zjistit jmena i typy argumentu
            // pokud je to samozrejme mozne
            List<LocalVariable> arguments = method.arguments();
            // vypsat typy a jmena vsech argumentu metody
            for (LocalVariable argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument.typeName() + " " + argument.name());
            }
        }
        catch (AbsentInformationException e) {
            // pokud jmena argumentu nelze zjistit,
            // alespon typy jsou vzdy k dispozici
            List<String> arguments = method.argumentTypeNames();
            // vypsat typy vsech argumentu metody
            for (String argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument);
            }
        }
        System.out.print(");");
    }

Poslední důležitou informací o volané metodě jsou její přístupová práva, která lze zjistit velmi snadno:

    /**
     * Pristupova prava k atributu ci k metode (my zde tuto metodu
     * vyuzijeme pouze pro ziskani pristupovych prav k volanym metodam)
     */
    private static String getAccessibility(Accessible methodOrField) {
        if (methodOrField.isPublic()) {
            return "public ";
        }
        if (methodOrField.isProtected()) {
            return "protected ";
        }
        if (methodOrField.isPrivate()) {
            return "private ";
        }
        return "";
    }

9. Zdrojový kód demonstračního příkladu JDIMethodEntryDetection3

Konečně se dostáváme ke zdrojovému kódu dnešního posledního demonstračního příkladu nazvaného JDIMethodEntryDetection3. Tento příklad – debugger – dokáže vypsat všechny dostupné informace o volaných metodách náležejících do třídy Test5. Kromě jmen metod se vypíšou i jejich modifikátory, návratový typ a argumenty (včetně jmen argumentů, pokud je ovšem tato informace dostupná):

CS24 tip temata

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Accessible;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
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
 *
 * Detekce vstupu do metod deklarovanych ve tride Test5.
 * U kazde metody se vypise jeji typ, jmeno i typy parametru, popr.
 * i jmena parametru, pokud je ovsem lze zjistit.
 *
 * @author Pavel Tisnovsky
 */
public class JDIMethodEntryDetection3 {
 
    /**
     * Navratovy kod pouzity pri ukoncovani sledovane JVM.
     */
    private static final int EXIT_VALUE = 0;
 
    /**
     * Jmeno konektoru, ktery pro pripojeni pouziva sockety.
     */
    private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
 
    /**
     * Vstupni metoda debuggeru.
     */
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
        List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
 
        // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
        AttachingConnector connector = getSocketAttachConnector(connectors);
 
        if (connector == null) {
            System.out.println("Socket connector is not available");
            return;
        }
 
        // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni
        debugVirtualMachineUsingSocket(connector);
    }
 
    /**
     * Ziskat konektor pouzivajici pro pripojeni sockety
     */
    private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
        for (AttachingConnector connector : connectors) {
            if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
                return connector;
            }
        }
        // nenasli jsme zadny vhodny konektor
        return null;
    }
 
    /**
     * Pripojeni k bezicimu virtualnimu stroji pres socket.
     * @throws InterruptedException 
     */
    private static void debugVirtualMachineUsingSocket(AttachingConnector connector) {
        // nastaveni argumentu pouzivanych konektorem
        Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector);
 
        try {
            // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
            VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments);
 
            // spustit sledovany virtualni stroj Javy
            runVirtualMachine(virtualMachine);
 
            // ukazka pouziti EventRequestManageru
            acquireAndUseEventRequestManager(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni.
     *
     * @param connector konektor pouzity pro pripojeni
     * @return mapa obsahujici parametry konektoru
     */
    private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) {
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue("6502");
        return arguments;
    }
 
    /**
     * Pripojeni debuggeru ke sledovanemu virtualnimu stroji.
     *
     * @param connector konektor vyuzivajici pro spojeni sockety
     * @param arguments mapa obsahujici parametry pripojeni
     * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     *
     * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari
     * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru
     */
    private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments)
        throws IOException, IllegalConnectorArgumentsException {
        System.out.println("Connecting to virtual machine");
        VirtualMachine virtualMachine = connector.attach(arguments);
        System.out.println("Connected");
        return virtualMachine;
    }
 
    /**
     * Spustit sledovany virtualni stroj
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void runVirtualMachine(VirtualMachine virtualMachine) {
        virtualMachine.resume();
    }
 
    /**
     * Ukonceni prace beziciho sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void shutdownVirtualMachine(VirtualMachine virtualMachine) {
        System.out.println("Calling exit");
        virtualMachine.exit(EXIT_VALUE);
    }
 
    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
        // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
        MethodEntryRequest methodEntryRequest = registerMethodEntryRequest(eventRequestManager);
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, methodEntryRequest);
    }
 
    /**
     * Registrace udalosti typu VMDeathEvent
     */
    private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) {
        VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest();
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        vmDeathRequest.enable();
        return vmDeathRequest;
    }
 
    /**
     * Registrace udalosti typu MethodEntry
     */
    private static MethodEntryRequest registerMethodEntryRequest(EventRequestManager eventRequestManager) {
        MethodEntryRequest methodEntryRequest = eventRequestManager.createMethodEntryRequest();
 
        // pridame filtr na jmeno tridy
        methodEntryRequest.addClassFilter("Test5");
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        methodEntryRequest.enable();
        return methodEntryRequest;
    }
 
    /**
     * 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
     * @param methodEntryRequest
     *            objekt pro rizeni udalosti typu MethodEntry
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, MethodEntryRequest methodEntryRequest) {
        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 MethodEntryEvent) {
                    printMethodEntryInfo((MethodEntryEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Zavolano pri vyskytu udalosti typu MethodEntry
     * 
     * @param event
     *            udalost typu MethodEntry
     */
    private static void printMethodEntryInfo(MethodEntryEvent event) {
        Method method = event.method();
        Location location = event.location();
 
        // ziskat vsechny informace o pozici v pozastavenem vlaknu
        String className = getClassName(location);
        String methodName = getMethodName(location);
        String sourceName = getSourceName(location);
        String lineNumber = getLineNumber(location);
 
        // typ navratove hodnoty reprezentovany jako retezec
        String type = method.returnTypeName();
        // priznaky pristupovych prav
        String accessibilityStr = getAccessibility(method);
        String staticStr = method.isStatic() ? "static " : "";
        String finalStr = method.isFinal() ? "final " : "";
        String nativeStr = method.isNative() ? "native " : "";
        String synchronizedStr = method.isSynchronized() ? "synchronized " : "";
 
        // nyni mame vsechny informace, lze je tedy vypsat
        System.out.format("    entry into method: %s%s%s%s%s%s %s.%s",
                accessibilityStr, staticStr, finalStr,
                nativeStr, synchronizedStr, type,
                className, methodName);
        printMethodArguments(method);
        // po vypisu argumentu se jeste vypise jmeno zdrojoveho
        // souboru a cislo radku
        System.out.format("   (%s:%s)\n", sourceName, lineNumber);
    }
 
    /**
     * Jmeno tridy, jejiz metoda byla zavolana.
     */
    private static String getClassName(Location location) {
        return location.method().declaringType().name();
    }
 
    /**
     * Jmeno volane metody.
     */
    private static String getMethodName(Location location) {
        return location.method().name();
    }
 
    /**
     * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci.
     */
    private static String getSourceName(Location location) {
        try {
            return location.sourceName();
        }
        catch (AbsentInformationException e) {
            return "unknown";
        }
    }
 
    /**
     * Prevod cisla radku na retezec, pokud je to mozne.
     */
    private static String getLineNumber(Location location) {
        int lineNumber = location.lineNumber();
        // u nativnich metod nelze zjistit cisla radku
        return lineNumber >= 0 ? "" + lineNumber : "<native method>";
    }
 
    /**
     * Vypis argumentu metody.
     *
     * @param method metoda ve sledovanem virtualnim stroji
     */
    private static void printMethodArguments(Method method) {
        System.out.print("(");
        boolean first = true;
 
        try {
            // idealni je zjistit jmena i typy argumentu
            // pokud je to samozrejme mozne
            List<LocalVariable> arguments = method.arguments();
            // vypsat typy a jmena vsech argumentu metody
            for (LocalVariable argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument.typeName() + " " + argument.name());
            }
        }
        catch (AbsentInformationException e) {
            // pokud jmena argumentu nelze zjistit,
            // alespon typy jsou vzdy k dispozici
            List<String> arguments = method.argumentTypeNames();
            // vypsat typy vsech argumentu metody
            for (String argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument);
            }
        }
        System.out.print(");");
    }
 
    /**
     * Pristupova prava k atributu ci k metode (my zde tuto metodu
     * vyuzijeme pouze pro ziskani pristupovych prav k volanym metodam)
     */
    private static String getAccessibility(Accessible methodOrField) {
        if (methodOrField.isPublic()) {
            return "public ";
        }
        if (methodOrField.isProtected()) {
            return "protected ";
        }
        if (methodOrField.isPrivate()) {
            return "private ";
        }
        return "";
    }
 
}

Po spuštění tohoto demonstračního debuggeru získáme podrobnou informaci o všech volaných metodách třídy Test5 (tato třída musela být přeložena s volbou -g):

Connecting to virtual machine
Connected
Got 1 request:
    VMStartEvent
Got 1 request:
    entry into method: public static void Test5.main(java.lang.String[] args);   (Test5.java:23)
Got 1 request:
    entry into method: public void Test5.<init>();   (Test5.java:1)
Got 1 request:
    entry into method: public void Test5.run(int value, char znak);   (Test5.java:4)
Got 1 request:
    entry into method: public void Test5.foo(float value);   (Test5.java:10)
Got 1 request:
    entry into method: public void Test5.bar(float value, float[] array, java.lang.String message);   (Test5.java:15)
Got 1 request:
    entry into method: public void Test5.baz();   (Test5.java:19)
Got 2 requests:
    VMDeathEvent
Calling exit

10. Zdrojový kód testovací třídy Test5

public class Test5 {
 
    public void run(int value, char znak) {
        int int_value = 10;
        Object object_value = null;
        foo(1.0f/value);
    }
 
    public void foo(float value) {
        Object object_value = this;
        bar(value, new float[] {value, value}, "Hello world!");
    }
 
    public void bar(float value, float[] array, String message) {
        baz();
    }
 
    public void baz() {
        System.out.println("baz");
    }
 
    public static void main(String[] args) {
        System.out.println("Press any key");
        try {
            System.in.read();
        }
        catch (java.io.IOException e) {
        }
        new Test5().run(42, 'a');
    }
 
}

11. Repositář se zdrojovými kódy všech tří demonstračních příkladů

Zdrojové kódy všech třech demonstračních příkladů popsaných v předchozích kapitolách byly uloženy (podobně jako tomu bylo i v předešlých částech tohoto seriálu) do Mercurial repositáře dostupného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze zmíněných zdrojových souborů můžete najít na adresách:

12. 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

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.