Hlavní navigace

Pohled pod kapotu JVM - sledování činnosti virtuálního stroje Javy přes rozhraní JVM TI (4.část: práce s výjimkami)

27. 11. 2012
Doba čtení: 42 minut

Sdílet

Dnes si řekneme, jak je možné přes rozhraní JVM TI detekovat vznik libovolné výjimky a současně i zjistit další podrobnější informace o tom, na jakém místě programu bude výjimka zachycena a o jakou výjimku se vlastně jedná. Při tvorbě nových demonstračních příkladů si popíšeme i mnohé další možnosti JVM TI.

Obsah

1. Detekce zachycení výjimky v javovském programu

2. Jedenáctý demonstrační agent – detekce zachycení výjimky

3. Získání jména a signatury metody, v níž k výjimce došlo

4. Získání jména třídy, v jejíž metodě k výjimce došlo

5. Úprava výpisu, přečtení a vypsání typu výjimky

6. Zjištění čísla řádku, kde se výjimka zachytí

7. Dvanáctý demonstrační agent – výpis podrobnějších informací o zachycované výjimce

8. Krátké shrnutí všech doposud popsaných a použitých callback funkcí

9. Archiv se zdrojovými kódy i skripty pro překlad

10. Odkazy na Internetu

1. Detekce zachycení výjimky v javovském programu

V dnešní části seriálu o programovacím jazyce Java i o vlastnostech JVM se již počtvrté budeme zabývat popisem využití rozhraní JVM TI (JVM Tool Interface) pro tvorbu agentů určených pro monitorování a taktéž pro řízení jak samotného virtuálního stroje Javy, tak i aplikací, které jsou v tomto virtuálním stroji provozovány. Dnes si řekneme, jakým způsobem lze v agentovi zjistit, že byla zachycena výjimka, přesněji řečeno okamžik těsně před zachycením výjimky. Tato funkcionalita může být velmi užitečná v těch případech, kdy nějaký programátor ve své aplikaci určitou výjimku zachytává do prázdného bloku catch (catch (Exception e) {}), což sice může být v odůvodněných případech správné, většinou se však jedná o pouhý pokus o zakrytí problémů před administrátory či uživateli (což se může dříve či později vymstít). S využitím agentů lze relativně snadno zjistit, kde všude byly výjimky zachyceny, a to dokonce bez znalosti zdrojových kódů aplikace i bez zásahu do zdrojových kódů a/nebo bajtkódu (což může být zakázáno v licenci).

Virtuální stroj Javy může před zachycením výjimky zavolat callback funkci zaregistrovanou v JVM TI agentovi. Tato callback funkce musí být zaregistrována pomocí SetEventCallbacks() následujícím způsobem:

jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
 
/* JVMTI_EVENT_EXCEPTION_CATCH */
callbacks.ExceptionCatch = &callback_on_exception_catch;
 
error_code = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));

Navíc se musí vhodným způsobem nastavit režim notifikace s využitím SetEventNotificationMode():

(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION_CATCH (jthread)NULL);

A samozřejmě je nutné taktéž nastavit a ověřit, zda virtuální stroj  touto vlastností vůbec oplývá:

jvmtiCapabilities capabilities;
 
memset(&capabilities, 0, sizeof(jvmtiCapabilities));
 
/* vyuzivame pouze jednu specialni schopnost agenta */
capabilities.can_generate_exception_events = 1;
 
(*jvmti)->AddCapabilities(jvmti, &capabilities);

V případě, že virtuální stroj podporuje posílání informací o výjimkách agentovi, dojde k zaregistrování callback funkce nazvané callback_on_exception_catch() (toto jméno jsme použili při registraci s využitím SetEventCallbacks(). Zatímco většina doposud popisovaných callback funkcí měla velmi jednoduchou hlavičku s maximálně čtyřmi parametry, je tomu v případě funkce callback_on_exception_catch() jinak, protože do této funkce se předává hned šest parametrů nesoucích informace o typu výjimky, vlákně, v němž je výjimka zachycována, metodě, kde bude výjimka zachycena a taktéž informace o tom, kde přesně je výjimka ve funkci zachycena (na jaké instrukci). Hlavička callback funkce callback_on_exception_catch() vypadá následovně:

/*
 * Callback funkce zavolaná ve chvíli zachycení výjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception)

Význam jednotlivých parametrů je stručně popsán v následující tabulce:

# Parametr Typ Význam
1 jvmti_env jvmtiEnv* JVM TI prostředí agenta (je předáváno do většiny callback funkcí)
2 jni_env JNIEnv* JNI prostředí platné pro dané vlákno (je předáváno do některých callback funkcí)
3 thr jthread vlákno, v němž dojde k zachycení výjimky
4 method jmethodID metoda, ve které se výjimka zachycuje
5 location jlocation umístění kódu, který výjimku zachycuje (odpovídá indexu instrukce)
6 exception jobject identifikátor objektu, který nese informaci o výjimce

2. Jedenáctý demonstrační agent – detekce zachycení výjimky

Funkci agenta, jehož zdrojový kód bude postupně vznikat v následujících kapitolách, budeme ověřovat na jednoduché javovské třídě nazvané Test11. V této třídě, přesněji řečeno z metody main(), se postupně volá čtveřice statických metod, přičemž v každé metodě je vyvolána jedna z nejčastěji vyhazovaných výjimek a následně je tato výjimka přímo v dané metodě odchycena v bloku catch. Mezi testované výjimky patří ArrayIndexOutOfBoundsException (je použit špatný index pole), ArithmeticException (některá aritmetická výjimka, zde se konkrétně jedná o pokus o dělení nulou), ClassNotFoundException (nelze nalézt třídu v čase běhu aplikace) a konečně nechvalně známá NullPointerException:

/**
  * Testovaci trida pouzita pro test jedenacteho
  * demonstracniho JVM TI agenta.
  */
public class Test11 {
 
    /**
      * Test zachyceni vyjimky typu ArrayIndexOutOfBoundsException.
      */
    public static void method1() {
        int[] a = {1,2,3};
        try {
            a[-1] = 10;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
      * Test zachyceni vyjimky typu ArithmeticException.
      */
    public static int method2(int a, int b) {
        try {
            return a/b;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }
 
    /**
      * Test zachyceni vyjimky typu ClassNotFoundException.
      */
    public static Class method3(String className) {
        try {
            return Class.forName(className);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
      * Test zachyceni vyjimky typu NullPointerException.
      */
    public static String method4(Object object) {
        try {
            return object.toString();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
 
    /**
      * Spusteni testu.
      */
    public static void main(String[] args) {
        method1();
        method2(1, 0);
        method3("xyzzy");
        method4(null);
    }
}

Následuje výpis zdrojového kódu první verze agenta, který dokáže detekovat zachycení výjimky. Jedná se prozatím o pouhou kostru plnohodnotného agenta, protože se pouze vypíše informace o zachycení výjimky, ovšem bez dalších důležitých informací:

/*
 * Demonstracni agent, ktery dokaze zaregistrovat zachyceni vyjimky.
 */
 
#include <stdlib.h>
#include <string.h>
 
/* Nutno nastavit cestu k tomuto souboru
 * pres volbu -Icesta_k_jvm
 */
#include <jvmti.h>
 
/*
 * Jmeno agenta pouzite ve zpravach vypisovanych
 * na standardni vystup.
 */
#define AGENT_NAME "Agent11:"
 
/*
 * Vypis zpravy na standardni vystup.
 */
#define MSG(message) puts(AGENT_NAME " " message)
 
/*
 * Vypis kodu chyby a chybove zpravy na standardni vystup.
 */
static void print_jvmti_error(jvmtiEnv *jvmti, jvmtiError error_code, const char *str)
{
    char *error_code_str = NULL;
    const char *msg_str = str == NULL ? "" : str;
    char *msg_err = NULL;
 
    (*jvmti)->GetErrorName(jvmti, error_code, &error_code_str);
    msg_err = error_code_str == NULL ? "Unknown" : error_code_str;
    printf(AGENT_NAME " ERROR: JVMTI: %d(%s): %s\n", error_code, msg_err, msg_str);
}
 
/*
 * Pokud je predany navratovy kod chybovym kodem,
 * vypise se chybove hlaseni na standardni vystup.
 */
static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError error_code, const char *str)
{
    if ( error_code != JVMTI_ERROR_NONE )
    {
        print_jvmti_error(jvmti, error_code, str);
    }
}
 
/*
 * Nastaveni pozadovanych schopnosti agenta.
 */
jvmtiError set_capabilities(jvmtiEnv *jvmti)
{
    jvmtiCapabilities capabilities;
    jvmtiError error_code;
 
    memset(&capabilities, 0, sizeof(jvmtiCapabilities));
 
    /* vyuzivame pouze jednu specialni schopnost agenta */
    capabilities.can_generate_exception_events = 1;
 
    error_code = (*jvmti)->AddCapabilities(jvmti, &capabilities);
    check_jvmti_error(jvmti, error_code, "Unable to get necessary JVMTI capabilities.");
    return error_code;
}
 
/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    printf(AGENT_NAME " An exception is caught\n");
}
 
/*
 * Registrace callback funkce zavolane pri zachyceni vyjimky.
 */
jvmtiError register_all_callback_functions(jvmtiEnv *jvmti)
{
    jvmtiEventCallbacks callbacks;
    jvmtiError error_code;
 
    memset(&callbacks, 0, sizeof(callbacks));
 
    /* JVMTI_EVENT_EXCEPTION_CATCH */
    callbacks.ExceptionCatch = &callback_on_exception_catch;
 
    error_code = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
    check_jvmti_error(jvmti, error_code, "Cannot set JVM TI callbacks");
    return error_code;
}
 
/*
 * Nastaveni jedne udalosti, pro nez se ma zavolat callback funkce.
 */
jvmtiError set_event_notification_mode(jvmtiEnv *jvmti, int event)
{
    jvmtiError error_code;
 
    error_code = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, event, (jthread)NULL);
    check_jvmti_error(jvmti, error_code, "Cannot set event notification");
    return error_code;
}
 
/*
 * Nastaveni udalosti, pro nez se maji zavolat callback funkce.
 */
jvmtiError set_event_notification_modes(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    /* zachytavat pouze udalost zachyceni vyjimky */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_EXCEPTION_CATCH)) != JNI_OK)
    {
        return error_code;
    }
 
    return error_code;
}
 
/*
 * Funkce zavolana ve chvili nacitani agenta do JVM.
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
{
    jvmtiEnv *jvmti = NULL;
    jint result;
    jvmtiError error_code;
 
    MSG("Agent_OnLoad");
    result = (*jvm)->GetEnv(jvm, (void **) &jvmti, JVMTI_VERSION_1_0);
    if (result != JNI_OK || jvmti == NULL)
    {
        printf("ERROR: Unable to access JVMTI Version 1 (0x%x),"
                " is your J2SE a 1.5 or newer version? JNIEnv's GetEnv() returned %d\n",
                JVMTI_VERSION_1, (int)result);
        return result;
    }
    MSG("JVM TI version is correct");
 
    /* nastaveni pozadovanych schopnosti agenta */
    if ((error_code = set_capabilities(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    /* registrace vsech callback funkci */
    if ((error_code = register_all_callback_functions(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    /* nastaveni udalosti, pro nez se maji zavolat callback funkce */
    if ((error_code = set_event_notification_modes(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    return JNI_OK;
}
 
/*
 * Funkce zavolana ve chvili odstranovani agenta z JVM.
 */
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
    MSG("Agent_OnUnload");
}
 
/*
 * finito
 */

Po překladu a spuštění agenta by se na konzoli měl vypsat přibližně tento text:

Agent11: Agent_OnLoad
Agent11: JVM TI version is correct
Agent11: An exception is caught
Agent11: An exception is caught
Agent11: An exception is caught
Agent11: An exception is caught
Agent11: An exception is caught
Agent11: An exception is caught
Agent11: An exception is caught
Agent11: An exception is caught
java.lang.ArrayIndexOutOfBoundsException: -1
        at Test11.method1(Test11.java:13)
        at Test11.main(Test11.java:63)
Agent11: An exception is caught
java.lang.ArithmeticException: / by zero
        at Test11.method2(Test11.java:25)
        at Test11.main(Test11.java:64)
Agent11: An exception is caught
java.lang.ClassNotFoundException: xyzzy
        at java.net.URLClassLoader$1.run(URLClassLoader.java:217)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:205)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:321)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:266)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:186)
        at Test11.method3(Test11.java:38)
        at Test11.main(Test11.java:65)
Agent11: An exception is caught
java.lang.NullPointerException
        at Test11.method4(Test11.java:51)
        at Test11.main(Test11.java:66)
Agent11: Agent_OnUnload

Jedná se o hlášení agenta (standardní výstup) kombinované s hlášeními testovacího programu (chybový výstup). V navazujících kapitolách si ukážeme, jak je možné zařídit, aby agent vypisoval i další smysluplnější informace.

3. Získání jména a signatury metody, v níž k výjimce došlo

Jednou ze základních informací, které by se měly vypisovat, je jméno metody, v níž bude výjimka zachycena. Informaci o metodě dostaneme ve čtvrtém parametru funkce callback_on_exception_catch() – jedná se o parametr jmethodID method. My vlastně již víme, jak lze tento parametr využít, protože jsme si v předchozích částech tohoto seriálu popsali metodu GetMethodName(), která po předání identifikátoru metody dokáže vrátit jak jméno metody, tak i její signaturu, v níž jsou obsaženy jak typy parametrů, tak i návratový typ:

jvmtiError
GetMethodName(
            jvmtiEnv* env,
            jmethodID method,
            char** name_ptr,
            char** signature_ptr,
            char** generic_ptr);

Připomeňme si, že prvním parametrem této funkce je odkaz na strukturu jvmtiEnv, který je použit ve většině funkcí rozhraní JVM TI, takže se pro nás nejedná o žádnou novinku. Druhým parametrem funkce je identifikátor metody, který je typu jmethodID. Tento typ je většinou v hlavičkovém souboru jvmti.h deklarován jako long int či long long int v závislosti na architektuře počítače a použitém systému. Ve třetím parametru je vrácen řetězec se jménem metody, v parametru čtvrtém řetězec s plnou signaturou metody a konečně v parametru pátém je vrácen řetězec s generickou signaturou metody. Do třetího, čtvrtého či pátého parametru lze namísto ukazatele předat hodnotu NULL, čímž se rozhraní JVM TI naznačí, že tento údaj nemá naplňovat. Nesmíme zapomenout na to, že všechny řetězce vrácené funkcí GetMethodName() je nutné (ideálně co nejdříve) dealokovat s využitím funkce Deallocate(), kterou již taktéž známe.

Se znalostí GetMethodName() můžeme naši callback funkci upravit následujícím způsobem:

/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    char *method_name_ptr;
    char *method_signature_ptr;
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
 
    /* ziskat jmeno a signaturu metody */
    (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name_ptr, &method_signature_ptr, NULL);
 
    /* tisk informace o zachycene vyjimce */
    printf(AGENT_NAME " An exception is caught in %s%s\n",
            method_name_ptr,
            method_signature_ptr);
 
    /* dealokace vsech ziskanych pametovych struktur */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_signature_ptr);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}

Výstup agenta již bude poněkud čitelnější a dodá nám více informací:

Agent12: Agent_OnLoad
Agent12: JVM TI version is correct
Agent12: An exception is caught in loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in method1()V
Agent12: An exception is caught in method2(II)I
Agent12: An exception is caught in loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in method3(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in method4(Ljava/lang/Object;)Ljava/lang/String;
Agent12: Agent_OnUnload

4. Získání jména třídy, v jejíž metodě k výjimce došlo

Jméno a signaturu metody je vhodné doplnit o jméno třídy, v níž je daná metoda deklarována. Žádnou informaci o třídě sice při zavolání callback funkce callback_on_exception_catch() nedostaneme, to však není velký problém, protože pro každou metodu lze příslušnou třídu zjistit s využitím funkce GetMethodDeclaringClass(), jejíž hlavička vypadá následovně:

jvmtiError
GetMethodDeclaringClass(
            jvmtiEnv* env,
            jmethodID method,
            jclass* declaring_class_ptr);

Z této hlavičky je patrné, že do prvního parametru jednoduše předáme hodnotu jvmti_env, do parametru druhého pak identifikátor metody a v parametru třetím se nám v případě úspěchu vrátí identifikátor třídy, do níž daná metoda náleží (datové typy jmethodID a jclass jsou definovány rozhraním JNI, s nímž JVM TI dosti úzce souvisí). Jakmile máme naplněnou proměnnou typu jclass, můžeme již zcela jednoduše získat jméno této třídy s využitím funkce GetClassSignature(), které se hodnota proměnné typu jclass předá ve druhém parametru a v parametru třetím se vrátí řetězec s kýženou signaturou třídy:

jvmtiError
GetClassSignature(
            jvmtiEnv* env,
            jclass klass,
            char** signature_ptr,
            char** generic_ptr);

Pro řetězec či dvojici řetězců vrácených funkcí GetClassSignature() platí stejná pravidla, jako pro řetězce vrácené funkcí GetMethodName(), zejména pak nutnost provést jejich dealokaci s využitím funkce Deallocate(). Vše si ukážeme na třetí variantě naší callback funkce callback_on_exception_catch():

/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    char *method_name_ptr;
    char *method_signature_ptr;
    char *class_name_ptr;
    jclass method_class;
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
 
    /* ziskat jmeno a signaturu metody a signaturu tridy */
    (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name_ptr, &method_signature_ptr, NULL);
    (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &method_class);
    (*jvmti_env)->GetClassSignature(jvmti_env, method_class, &class_name_ptr, NULL);
 
    /* tisk informace o zachycene vyjimce */
    printf(AGENT_NAME " An exception is caught in %s%s%s\n",
            class_name_ptr,
            method_name_ptr,
            method_signature_ptr);
 
    /* dealokace vsech ziskanych pametovych struktur */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_signature_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}

Agent nyní po svém spuštění vypíše opět o něco více informací, než jeho předchozí verze:

Agent12: Agent_OnLoad
Agent12: JVM TI version is correct
Agent12: An exception is caught in Ljava/lang/ClassLoader;loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in Ljava/net/URLClassLoader;findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in Ljava/lang/ClassLoader;loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in LTest12;method1()V
Agent12: An exception is caught in LTest12;method2(II)I
Agent12: An exception is caught in Ljava/lang/ClassLoader;loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in Ljava/net/URLClassLoader;findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in Ljava/lang/ClassLoader;loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in Ljava/net/URLClassLoader;findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in LTest12;method3(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in LTest12;method4(Ljava/lang/Object;)Ljava/lang/String;
Agent12: Agent_OnUnload

5. Úprava výpisu, přečtení a vypsání typu výjimky

Z předchozího výpisu je patrné, že se jméno třídy vypisuje se znakem ‚L‘ na začátku a na konci je umístěn středník. To sice odpovídá způsobu zápisu signatur, ale bude čitelnější, když se prvního ‚L‘ zbavíme a namísto středníku budeme vypisovat tečku, která čitelně oddělí jméno třídy od jména metody. Úprava programu ve skutečnosti není příliš složitá, pouze přidáme jednu pomocnou funkci, které předáme původní signaturu (řetězec) a taktéž znak, který má nahradit původní středník:

/*
 * Uprava jmena tridy pro tisk.
 */
char* update_class_name(char *class_name_ptr, char replace_to)
{
    char *class_name_ptr_;
    if (class_name_ptr != NULL)
    {
        /* odstraneni pocatecniho L na zacatku jmena tridy */
        class_name_ptr_ = class_name_ptr;
        if (class_name_ptr_[0] == 'L')
        {
            class_name_ptr_++;
        }
        /* nahrada znaku ; za tecku */
        char *last_char = class_name_ptr_ + strlen(class_name_ptr_) - 1;
        if (*last_char == ';')
        {
            *last_char = replace_to;
        }
    }
    return class_name_ptr_;
}
 
/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    char *method_name_ptr;
    char *method_signature_ptr;
    char *class_name_ptr;
    char *class_name_ptr_;
    jclass method_class;
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
 
    /* ziskat jmeno a signaturu metody a signaturu tridy */
    (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name_ptr, &method_signature_ptr, NULL);
    (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &method_class);
    (*jvmti_env)->GetClassSignature(jvmti_env, method_class, &class_name_ptr, NULL);
 
    /* upravit jmeno tridy */
    class_name_ptr_ = update_class_name(class_name_ptr, '.');
 
    /* tisk informace o zachycene vyjimce */
    printf(AGENT_NAME " An exception is caught in %s%s%s\n",
            class_name_ptr_,
            method_name_ptr,
            method_signature_ptr);
 
    /* dealokace vsech ziskanych pametovych struktur */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_signature_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}

O tom, že je výpis čitelnější, se lze snadno přesvědčit:

Agent12: Agent_OnLoad
Agent12: JVM TI version is correct
Agent12: An exception is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in Test12.method1()V
Agent12: An exception is caught in Test12.method2(II)I
Agent12: An exception is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in Test12.method3(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception is caught in Test12.method4(Ljava/lang/Object;)Ljava/lang/String;
Agent12: Agent_OnUnload

Další vylepšení spočívá ve zjištění a následném výpisu typu výjimky. To je poměrně jednoduchá záležitost, protože posledním parametrem předávaným do callback funkce callback_on_exception_catch() je objekt představující výjimku a pro tento objekt lze získat příslušnou třídu s využitím funkce GetObjectClass(), která je ovšem dostupná přes rozhraní JNI a nikoli JVM TI. Jakmile známe třídu výjimky, můžeme pro získání jejího jména využít nám již známou a dnes již použitou funkci GetClassSignature:

jvmtiError
GetClassSignature(
            jvmtiEnv* env,
            jclass klass,
            char** signature_ptr,
            char** generic_ptr);

Úprava našeho agenta takovým způsobem, aby vypisoval i jméno výjimky je tedy relativně jednoduchá, jen nesmíme zapomenout na převod signatury třídy na „čitelné“ jméno:

/*
 * Uprava jmena tridy pro tisk.
 */
char* update_class_name(char *class_name_ptr, char replace_to)
{
    char *class_name_ptr_;
    if (class_name_ptr != NULL)
    {
        /* odstraneni pocatecniho L na zacatku jmena tridy */
        class_name_ptr_ = class_name_ptr;
        if (class_name_ptr_[0] == 'L')
        {
            class_name_ptr_++;
        }
        /* nahrada znaku ; za tecku */
        char *last_char = class_name_ptr_ + strlen(class_name_ptr_) - 1;
        if (*last_char == ';')
        {
            *last_char = replace_to;
        }
    }
    return class_name_ptr_;
}
 
/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    char *method_name_ptr;
    char *method_signature_ptr;
    char *class_name_ptr;
    char *class_name_ptr_;
    char *exception_name_ptr;
    char *exception_name_ptr_;
    jclass method_class;
    jclass exception_class;
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
    exception_class = (*jni_env)->GetObjectClass(jni_env, exception_object);
 
    /* ziskat jmeno a signaturu metody, signaturu tridy i signaturu vyjimky */
    (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name_ptr, &method_signature_ptr, NULL);
    (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &method_class);
    (*jvmti_env)->GetClassSignature(jvmti_env, method_class, &class_name_ptr, NULL);
    (*jvmti_env)->GetClassSignature(jvmti_env, exception_class, &exception_name_ptr, NULL);
 
    /* upravit jmeno tridy i jmeno vyjimky */
    class_name_ptr_ = update_class_name(class_name_ptr, '.');
    exception_name_ptr_ = update_class_name(exception_name_ptr, ' ');
 
    /* tisk informace o zachycene vyjimce */
    printf(AGENT_NAME " An exception %sis caught in %s%s%s\n",
            exception_name_ptr_,
            class_name_ptr_,
            method_name_ptr,
            method_signature_ptr);
 
    /* dealokace vsech ziskanych pametovych struktur */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_signature_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)exception_name_ptr);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}

Opět se podívejme na to, jak vypadá další verze výpisu provedeného agentem:

Agent12: Agent_OnLoad
Agent12: JVM TI version is correct
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception java/security/PrivilegedActionException is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception java/lang/ArrayIndexOutOfBoundsException is caught in Test12.method1()V
Agent12: An exception java/lang/ArithmeticException is caught in Test12.method2(II)I
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception java/security/PrivilegedActionException is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;
Agent12: An exception java/security/PrivilegedActionException is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception java/lang/ClassNotFoundException is caught in Test12.method3(Ljava/lang/String;)Ljava/lang/Class;
Agent12: An exception java/lang/NullPointerException is caught in Test12.method4(Ljava/lang/Object;)Ljava/lang/String;
Agent12: Agent_OnUnload

6. Zjištění čísla řádku, kde se výjimka zachytí

Nyní nám ještě zbývá do našeho demonstračního agenta přidat implementačně nejsložitější část – zobrazení čísla řádku, na němž je výjimka zachycena. Pro získání této informace lze využít pátý parametr předávaný do callback funkce callback_on_exception_catch(), který je typu jlocation. Je ovšem nutné pamatovat na to, že tato hodnota přímo neodpovídá číslu řádku, ale pozici instrukce v bajtkódu – konkrétní způsob reprezentace je již závislý na implementovaném virtuálním stroji Javy a není dobré předpokládat, že se všechny JVM budou chovat zcela stejně, tj. že budou pro stejnou zachycenou výjimku vždy vracet stejnou hodnotu typu jlocation. Musíme být tedy schopni převést hodnotu jlocation na číslo řádku odpovídající původnímu zdrojovému kódu. Taková funkce sice přímo v JNI ani v JVM TI neexistuje, ale my si můžeme takovou funkci sami napsat, a to s využitím obecnější funkce GetLineNumberTable(), která má hlavičku:

jvmtiError (JNICALL *GetLineNumberTable) (
            jvmtiEnv* env,
            jmethodID method,
            jint* entry_count_ptr,
            jvmtiLineNumberEntry** table_ptr);

S prvními dvěma parametry jsme se již několikrát setkali. Ve třetím parametru tato funkce vrací počet záznamů v tabulce, která je souběžně vrácena v parametru posledním (i zde se používá ukazatel naplněný přímo funkci GetLineNumberTable()). Obsah zmíněné tabulky je velmi jednoduchý – nachází se zde záznamy typu jvmtiLineNumberEntry, z nichž každý má tuto strukturu:

struct jvmtiLineNumberEntry {
            jlocation start_location;
            jint line_number;
};

Údaje by v tabulce měly být vzestupně seřazeny podle hodnoty jlocation a navíc by se zde neměly nacházet shodné hodnoty jlocation. Naproti tomu se hodnoty jint line_number mohou opakovat, protože je běžné, že se příkaz původně zapsaný na jediné řádce zdrojového kódu přeloží i do relativně dlouhé sekvence instrukcí. Nás však zajímá především mapování od jlocationline_number, které je vždy jednoznačné a pro získání čísla konkrétního řádku ve zdrojovém kódu lze využít programovou smyčku, například takovou, jaká je implementována v naší funkci get_line_number():

/*
 * Uprava jmena tridy pro tisk.
 */
char* update_class_name(char *class_name_ptr, char replace_to)
{
    char *class_name_ptr_;
    if (class_name_ptr != NULL)
    {
        /* odstraneni pocatecniho L na zacatku jmena tridy */
        class_name_ptr_ = class_name_ptr;
        if (class_name_ptr_[0] == 'L')
        {
            class_name_ptr_++;
        }
        /* nahrada znaku ; za tecku */
        char *last_char = class_name_ptr_ + strlen(class_name_ptr_) - 1;
        if (*last_char == ';')
        {
            *last_char = replace_to;
        }
    }
    return class_name_ptr_;
}
 
/*
 * Ziskani cisla radku pro zadanou metodu a index instrukce.
 */
int get_line_number(jvmtiEnv *jvmti_env, jmethodID method, jlocation location)
{
    int count;
    int line_number = 0;
    int i;
    jvmtiLineNumberEntry *location_table;
 
    /* nacteni tabulky s cisly radku a indexy instrukci */
    (*jvmti_env)->GetLineNumberTable(jvmti_env, method, &count, &location_table);
    /* projit celou tabulkou */
    for (i = 0; i < count - 1; i++)
    {
        jvmtiLineNumberEntry entry1 = location_table[i];
        jvmtiLineNumberEntry entry2 = location_table[i+1];
        /* pokud se lokace nachazi mezi entry1 (vcetne) a entry2 (krome) */
        /* nasli jsme spravny radek */
        if (location >= entry1.start_location && location < entry2.start_location)
        {
            line_number = entry1.line_number;
            break;
        }
    }
    /* take se muze jednat o uplne posledni instrukci v metode */
    if (location >= location_table[count-1].start_location)
    {
        line_number = location_table[count-1].line_number;
    }
 
    /* dealokace tabulky */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)location_table);
    return line_number;
}
 
/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    char *method_name_ptr;
    char *method_signature_ptr;
    char *class_name_ptr;
    char *class_name_ptr_;
    char *exception_name_ptr;
    char *exception_name_ptr_;
    jclass method_class;
    jclass exception_class;
    int line_number;
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
    exception_class = (*jni_env)->GetObjectClass(jni_env, exception_object);
 
    /* ziskat jmeno a signaturu metody, signaturu tridy i signaturu vyjimky */
    (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name_ptr, &method_signature_ptr, NULL);
    (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &method_class);
    (*jvmti_env)->GetClassSignature(jvmti_env, method_class, &class_name_ptr, NULL);
    (*jvmti_env)->GetClassSignature(jvmti_env, exception_class, &exception_name_ptr, NULL);
 
    /* upravit jmeno tridy i jmeno vyjimky */
    class_name_ptr_ = update_class_name(class_name_ptr, '.');
    exception_name_ptr_ = update_class_name(exception_name_ptr, ' ');
 
    /* ziskat cislo radku */
    line_number = get_line_number(jvmti_env, method, location);
 
    /* tisk informace o zachycene vyjimce */
    printf(AGENT_NAME " An exception %sis caught in %s%s%s at line %d\n",
            exception_name_ptr_,
            class_name_ptr_,
            method_name_ptr,
            method_signature_ptr,
            line_number);
 
    /* dealokace vsech ziskanych pametovych struktur */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_signature_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)exception_name_ptr);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}
Agent12: Agent_OnLoad
Agent12: JVM TI version is correct
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class; at line 318
Agent12: An exception java/security/PrivilegedActionException is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class; at line 221
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class; at line 318
Agent12: An exception java/lang/ArrayIndexOutOfBoundsException is caught in Test12.method1()V at line 15
Agent12: An exception java/lang/ArithmeticException is caught in Test12.method2(II)I at line 27
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class; at line 318
Agent12: An exception java/security/PrivilegedActionException is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class; at line 221
Agent12: An exception java/lang/ClassNotFoundException is caught in java/lang/ClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class; at line 318
Agent12: An exception java/security/PrivilegedActionException is caught in java/net/URLClassLoader.findClass(Ljava/lang/String;)Ljava/lang/Class; at line 221
Agent12: An exception java/lang/ClassNotFoundException is caught in Test12.method3(Ljava/lang/String;)Ljava/lang/Class; at line 40
Agent12: An exception java/lang/NullPointerException is caught in Test12.method4(Ljava/lang/Object;)Ljava/lang/String; at line 53
Agent12: Agent_OnUnload

7. Dvanáctý demonstrační agent – výpis podrobnějších informací o zachycované výjimce

Pokud jste se v předchozích kapitolách trošku ztratili a není vám úplně jasné, jak má vlastně přesně vypadat finální verze JVM TI agenta, je na konci této kapitoly zobrazen celý jeho zdrojový kód. Překlad agenta i testovací javovské třídy se provede pomocí příkazu, u nějž je pouze zapotřebí upravit cestu ke konkrétní nainstalované JVM:

gcc -Wall -ansi -I/usr/lib/jvm/java-1.6.0-openjdk/include/ -shared -o libagent12.so agent12.c
javac -g Test12.java

Spuštění nového virtuálního stroje i se zaregistrovaným agentem se provádí příkazem:

java -agentpath:./libagent12.so Test12 2> /dev/null

Nyní již následuje slíbený zdrojový kód demonstračního agenta číslo 12:

/*
 * Demonstracni agent, ktery dokaze zaregistrovat zachyceni vyjimky
 * a soucasne vypsat i podrobnejsi informace o vyjimce.
 */
 
#include <stdlib.h>
#include <string.h>
 
/* Nutno nastavit cestu k tomuto souboru
 * pres volbu -Icesta_k_jvm
 */
#include <jvmti.h>
 
/*
 * Jmeno agenta pouzite ve zpravach vypisovanych
 * na standardni vystup.
 */
#define AGENT_NAME "Agent12:"
 
/*
 * Vypis zpravy na standardni vystup.
 */
#define MSG(message) puts(AGENT_NAME " " message)
 
/* Zamek pouzivany agentem */
jrawMonitorID  global_lock;
 
/*
 * Vypis kodu chyby a chybove zpravy na standardni vystup.
 */
static void print_jvmti_error(jvmtiEnv *jvmti, jvmtiError error_code, const char *str)
{
    char *error_code_str = NULL;
    const char *msg_str = str == NULL ? "" : str;
    char *msg_err = NULL;
 
    (*jvmti)->GetErrorName(jvmti, error_code, &error_code_str);
    msg_err = error_code_str == NULL ? "Unknown" : error_code_str;
    printf(AGENT_NAME " ERROR: JVMTI: %d(%s): %s\n", error_code, msg_err, msg_str);
}
 
/*
 * Pokud je predany navratovy kod chybovym kodem,
 * vypise se chybove hlaseni na standardni vystup.
 */
static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError error_code, const char *str)
{
    if ( error_code != JVMTI_ERROR_NONE )
    {
        print_jvmti_error(jvmti, error_code, str);
    }
}
 
/*
 * Vytvoreni zamku.
 */
jvmtiError create_raw_monitor(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    error_code = (*jvmti)->CreateRawMonitor(jvmti, "agent data", &global_lock);
    check_jvmti_error(jvmti, error_code, "Cannot create raw monitor");
 
    return error_code;
}
 
/*
 * Vstup do kriticke sekce.
 */
static void enter_critical_section(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    error_code = (*jvmti)->RawMonitorEnter(jvmti, global_lock);
    check_jvmti_error(jvmti, error_code, "Cannot enter with raw monitor");
}
 
/*
 * Vystup z kriticke sekce
 */
static void exit_critical_section(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    error_code = (*jvmti)->RawMonitorExit(jvmti, global_lock);
    check_jvmti_error(jvmti, error_code, "Cannot exit with raw monitor");
}
 
/*
 * Nastaveni pozadovanych schopnosti agenta.
 */
jvmtiError set_capabilities(jvmtiEnv *jvmti)
{
    jvmtiCapabilities capabilities;
    jvmtiError error_code;
 
    memset(&capabilities, 0, sizeof(jvmtiCapabilities));
 
    /* vyuzivame dve specialni schopnosti agenta */
    capabilities.can_generate_exception_events = 1;
    capabilities.can_get_line_numbers = 1;
 
    error_code = (*jvmti)->AddCapabilities(jvmti, &capabilities);
    check_jvmti_error(jvmti, error_code, "Unable to get necessary JVMTI capabilities.");
    return error_code;
}
 
/*
 * Uprava jmena tridy pro tisk.
 */
char* update_class_name(char *class_name_ptr, char replace_to)
{
    char *class_name_ptr_;
    if (class_name_ptr != NULL)
    {
        /* odstraneni pocatecniho L na zacatku jmena tridy */
        class_name_ptr_ = class_name_ptr;
        if (class_name_ptr_[0] == 'L')
        {
            class_name_ptr_++;
        }
        /* nahrada znaku ; za tecku */
        char *last_char = class_name_ptr_ + strlen(class_name_ptr_) - 1;
        if (*last_char == ';')
        {
            *last_char = replace_to;
        }
    }
    return class_name_ptr_;
}
 
/*
 * Ziskani cisla radku pro zadanou metodu a index instrukce.
 */
int get_line_number(jvmtiEnv *jvmti_env, jmethodID method, jlocation location)
{
    int count;
    int line_number = 0;
    int i;
    jvmtiLineNumberEntry *location_table;
 
    /* nacteni tabulky s cisly radku a indexy instrukci */
    (*jvmti_env)->GetLineNumberTable(jvmti_env, method, &count, &location_table);
    /* projit celou tabulkou */
    for (i = 0; i < count - 1; i++)
    {
        jvmtiLineNumberEntry entry1 = location_table[i];
        jvmtiLineNumberEntry entry2 = location_table[i+1];
        /* pokud se lokace nachazi mezi entry1 (vcetne) a entry2 (krome) */
        /* nasli jsme spravny radek */
        if (location >= entry1.start_location && location < entry2.start_location)
        {
            line_number = entry1.line_number;
            break;
        }
    }
    /* take se muze jednat o uplne posledni instrukci v metode */
    if (location >= location_table[count-1].start_location)
    {
        line_number = location_table[count-1].line_number;
    }
 
    /* dealokace tabulky */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)location_table);
    return line_number;
}
 
/*
 * Callback funkce zavolana pri zachyceni vyjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv *jni_env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception_object)
{
    char *method_name_ptr;
    char *method_signature_ptr;
    char *class_name_ptr;
    char *class_name_ptr_;
    char *exception_name_ptr;
    char *exception_name_ptr_;
    jclass method_class;
    jclass exception_class;
    int line_number;
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
    exception_class = (*jni_env)->GetObjectClass(jni_env, exception_object);
 
    /* ziskat jmeno a signaturu metody, signaturu tridy i signaturu vyjimky */
    (*jvmti_env)->GetMethodName(jvmti_env, method, &method_name_ptr, &method_signature_ptr, NULL);
    (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &method_class);
    (*jvmti_env)->GetClassSignature(jvmti_env, method_class, &class_name_ptr, NULL);
    (*jvmti_env)->GetClassSignature(jvmti_env, exception_class, &exception_name_ptr, NULL);
 
    /* upravit jmeno tridy i jmeno vyjimky */
    class_name_ptr_ = update_class_name(class_name_ptr, '.');
    exception_name_ptr_ = update_class_name(exception_name_ptr, ' ');
 
    /* ziskat cislo radku */
    line_number = get_line_number(jvmti_env, method, location);
 
    /* tisk informace o zachycene vyjimce */
    printf(AGENT_NAME " An exception %sis caught in %s%s%s at line %d\n",
            exception_name_ptr_,
            class_name_ptr_,
            method_name_ptr,
            method_signature_ptr,
            line_number);
 
    /* dealokace vsech ziskanych pametovych struktur */
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)method_signature_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
    (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)exception_name_ptr);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}
 
/*
 * Registrace callback funkce zavolane pri zachyceni vyjimky.
 */
jvmtiError register_all_callback_functions(jvmtiEnv *jvmti)
{
    jvmtiEventCallbacks callbacks;
    jvmtiError error_code;
 
    memset(&callbacks, 0, sizeof(callbacks));
 
    /* JVMTI_EVENT_EXCEPTION_CATCH */
    callbacks.ExceptionCatch = &callback_on_exception_catch;
 
    error_code = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
    check_jvmti_error(jvmti, error_code, "Cannot set JVM TI callbacks");
    return error_code;
}
 
/*
 * Nastaveni jedne udalosti, pro nez se ma zavolat callback funkce.
 */
jvmtiError set_event_notification_mode(jvmtiEnv *jvmti, int event)
{
    jvmtiError error_code;
 
    error_code = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, event, (jthread)NULL);
    check_jvmti_error(jvmti, error_code, "Cannot set event notification");
    return error_code;
}
 
/*
 * Nastaveni udalosti, pro nez se maji zavolat callback funkce.
 */
jvmtiError set_event_notification_modes(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    /* zachytavat pouze udalost zachyceni vyjimky */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_EXCEPTION_CATCH)) != JNI_OK)
    {
        return error_code;
    }
 
    return error_code;
}
 
/*
 * Funkce zavolana ve chvili nacitani agenta do JVM.
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
{
    jvmtiEnv *jvmti = NULL;
    jint result;
    jvmtiError error_code;
 
    MSG("Agent_OnLoad");
    result = (*jvm)->GetEnv(jvm, (void **) &jvmti, JVMTI_VERSION_1_0);
    if (result != JNI_OK || jvmti == NULL)
    {
        printf("ERROR: Unable to access JVMTI Version 1 (0x%x),"
                " is your J2SE a 1.5 or newer version? JNIEnv's GetEnv() returned %d\n",
                JVMTI_VERSION_1, (int)result);
        return result;
    }
    MSG("JVM TI version is correct");
 
    /* nastaveni pozadovanych schopnosti agenta */
    if ((error_code = set_capabilities(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    /* registrace vsech callback funkci */
    if ((error_code = register_all_callback_functions(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    /* nastaveni udalosti, pro nez se maji zavolat callback funkce */
    if ((error_code = set_event_notification_modes(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    /* vytvoreni zamku */
    if ((error_code = create_raw_monitor(jvmti)) != JNI_OK)
    {
        return error_code;
    }
 
    return JNI_OK;
}
 
/*
 * Funkce zavolana ve chvili odstranovani agenta z JVM.
 */
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
    MSG("Agent_OnUnload");
}
 
/*
 * finito
 */

8. Krátké shrnutí všech doposud popsaných a použitých callback funkcí

V této kapitole jsou pro přehlednost uvedeny hlavičky všech callback funkcí, které jsme si popsali a současně i použili dnes a v předchozích třech částech tohoto seriálu. Některým callback funkcím se předávají pouze základní informace, což je případ funkcí používaných například pro sledování činnosti správce paměti. Na druhou stranu zde však máme callback funkce volané ve chvíli, kdy vznikne výjimka, popř. ve chvíli, kdy je nějaká výjimka zachycena – tyto callback funkce mají již poměrně složitou hlavičku se šesti resp. dokonce s osmi předávanými parametry:

CS24_early

/*
 * Callback funkce zavolaná při inicializaci virtuálního stroje Javy.
 */
static void JNICALL callback_on_vm_init(
            jvmtiEnv *jvmti_env,
            JNIEnv* env,
            jthread thread);
/*
 * Callback funkce zavolaná při ukončení činnosti virtuálního stroje Javy.
 */
static void JNICALL callback_on_vm_death(
            jvmtiEnv *jvmti_env,
            JNIEnv* env);
/*
 * Callback funkce zavolaná při zavolání javovské metody v libovolném vláknu.
 */
static void JNICALL callback_on_method_entry(
            jvmtiEnv *jvmti,
            JNIEnv* env,
            jthread thread,
            jmethodID method);
/*
 * Callback funkce zavolaná při začátku běhu správce paměti (GC).
 */
static void JNICALL callback_on_gc_start(
            jvmtiEnv *jvmti_env);
/*
 * Callback funkce zavolaná při ukončení běhu správce paměti (GC).
 */
static void JNICALL callback_on_gc_finish(
            jvmtiEnv *jvmti_env);
/*
 * Callback funkce zavolaná ve chvíli zachycení výjimky.
 */
static void JNICALL callback_on_exception_catch(
            jvmtiEnv *jvmti_env,
            JNIEnv* env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception);

Pro úplnost ještě doplníme hlavičku callback funkce, která může být zavolána ve chvíli, kdy v aplikaci vznikne výjimka, a to nezávisle na tom, zda se jedná o výjimku zachycovanou nebo nezachycovanou. Touto callback funkcí se budeme podrobněji zabývat v následující části tohoto seriálu:

/*
 * Callback funkce zavolaná ve chvíli vzniku výjimky, resp. detekce výjimky virtuálním strojem.
 */
static void JNICALL callback_on_exception(
            jvmtiEnv *jvmti_env,
            JNIEnv* env,
            jthread thr,
            jmethodID method,
            jlocation location,
            jobject exception,
            jmethodID catch_method,
            jlocation catch_location);

9. Archiv se zdrojovými kódy i skripty pro překlad

Obě varianty demonstračních agentů jsou společně se skripty použitými pro jejich překlad a následné spuštění uloženy v tomto archivu.

10. Odkazy na Internetu

  1. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  2. JVM Tool Interface Version 1.2
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  3. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  4. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  5. 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
  6. ClojureScript One: Index
    http://clojurescriptone.com/in­dex.html
  7. ClojureScript One: Documentation
    http://clojurescriptone.com/do­cumentation.html
  8. ClojureScript One: Wiki
    https://github.com/brento­nashworth/one/wiki
  9. ClojureScript: Quick Start
    https://github.com/clojure/clo­jurescript/wiki/Quick-Start
  10. Getting Started with ClojureScript (and FW/1)
    http://corfield.org/entry/getting-started-with-clojurescript-and-fw-1
  11. First ClojureScript experiences: using Raphaël
    http://maurits.wordpress.com/2012/02/13/fir­st-clojurescript-experiences-using-raphael/
  12. Raphaël-JavaScript Library
    http://raphaeljs.com/
  13. A detailed installation Guide for VimClojure 2.2
    http://www.duenas.at/new_ho­mepage/vimclojure
  14. VimClojure : A filetype, syntax and indent plugin for Clojure
    http://www.vim.org/scripts/scrip­t.php?script_id=2501
  15. Nailgun server
    http://www.martiansoftware­.com/nailgun/background.html
  16. SLIME (Wikipedia)
    http://en.wikipedia.org/wiki/SLIME
  17. slime.vim
    http://s3.amazonaws.com/mps/slime.vim
  18. Textový editor Vim jako IDE: 1. část
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide/
  19. Textový editor Vim jako IDE: 2. část
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-2-cast/
  20. Textový editor Vim jako IDE: 3. část (omni completion)
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-3-cast/
  21. Textový editor Vim jako IDE: 4. část
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-4-cast/
  22. Textový editor Vim jako IDE: 5. část
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-5-cast/
  23. Textový editor Vim jako IDE: 6. část – Vim Script
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-6-cast-vim-script/
  24. Textový editor Vim jako IDE: 7. část – Vim Script
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-7-cast-vim-script/
  25. Textový editor Vim jako IDE: 8. část
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-8-cast/
  26. Textový editor Vim jako IDE: 9. část – pluginy Netrw a snipMate
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-9-cast-pluginy-netrw-a-snipmate/
  27. Textový editor Vim jako IDE: 10. část – různé tipy a triky
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-10-cast-ruzne-tipy-a-triky/
  28. Přenos textů mezi Vimem a dalšími aplikacemi
    http://www.root.cz/clanky/prenos-textu-mezi-vimem-a-dalsimi-aplikacemi/
  29. Textový editor Vim: konfigurace pravítka a stavového řádku
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-12-cast-konfigurace-pravitka-a-stavoveho-radku/
  30. Textový editor Vim: automatické formátování textů
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-14-cast-automaticke-formatovani-textu/
  31. Textový editor Vim: automatické formátování textů
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-automaticke-formatovani-textu-dokonceni/
  32. Textový editor Vim: editace XML a HTML
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-15-cast-editace-xml-a-html/
  33. Textový editor Vim: kooperace mezi Vimem a skripty
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-16-cast-kooperace-mezi-vimem-a-skriptovacimi-jazyky/
  34. Textový editor Vim: kooperace mezi Vimem a jazykem Perl
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-17-cast-kooperace-mezi-vimem-a-jazykem-perl/
  35. Textový editor Vim: konfigurace a překlad Vimu
    http://www.root.cz/clanky/textovy-editor-vim-jako-ide-18-cast-konfigurace-a-preklad-vimu/
  36. Counterclockwise
    http://code.google.com/p/cou­nterclockwise/
  37. Clojure IDEs – The Grand Tour
    http://www.bestinclass.dk/in­dex.clj/2010/03/clojure-ides-the-grand-tour-getting-started.html
  38. Light Table – a new IDE concept
    http://www.chris-granger.com/2012/04/12/light-table---a-new-ide-concept/
  39. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  40. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  41. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  42. Čistě funkcionální (datové struktury, jazyky, programování)
    http://cs.wikipedia.org/wi­ki/Čistě_funkcionální
  43. Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-i-getting.html
  44. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  45. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  46. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  47. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  48. Eulerovo číslo
    http://cs.wikipedia.org/wi­ki/Eulerovo_číslo
  49. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  50. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  51. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  52. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  53. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  54. Clojure.org: Clojure home page
    http://clojure.org/downloads
  55. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  56. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  57. Clojure.org: Atoms
    http://clojure.org/Atoms
  58. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  59. A Couple of Clojure Agent Examples
    http://lethain.com/a-couple-of-clojure-agent-examples/
  60. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  61. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  62. 4Clojure
    http://www.4clojure.com/
  63. ClojureDoc
    http://clojuredocs.org/
  64. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  65. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  66. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  67. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  68. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  69. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  70. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/
  71. JSR 223: Scripting for the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=223
  72. JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=292
  73. Java 7: A complete invokedynamic example
    http://niklasschlimm.blog­spot.com/2012/02/java-7-complete-invokedynamic-example.html
  74. InvokeDynamic: Actually Useful?
    http://blog.headius.com/2007/01/in­vokedynamic-actually-useful.html
  75. A First Taste of InvokeDynamic
    http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html
  76. Java 6 try/finally compilation without jsr/ret
    http://cliffhacks.blogspot­.com/2008/02/java-6-tryfinally-compilation-without.html
  77. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  78. 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
  79. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  80. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  81. Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
    http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/
  82. Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
    http://www.root.cz/clanky/jamvm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/
  83. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  84. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  85. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  86. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  87. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  88. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  89. BCEL Home page
    http://commons.apache.org/bcel/
  90. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  91. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  92. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  93. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  94. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  95. ASM Home page
    http://asm.ow2.org/
  96. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  97. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  98. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  99. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  100. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  101. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  102. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  103. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  104. Cobertura
    http://cobertura.sourceforge.net/
  105. FindBugs
    http://findbugs.sourceforge.net/
  106. GNU Classpath
    www.gnu.org/s/classpath/
  107. Java VMs Compared
    http://bugblogger.com/java-vms-compared-160/
  108. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp.org/en/jsr/de­tail?id=223
  109. Scripting for the Java Platform
    http://java.sun.com/develo­per/technicalArticles/J2SE/Des­ktop/scripting/
  110. Scripting for the Java Platform (Wikipedia)
    http://en.wikipedia.org/wi­ki/Scripting_for_the_Java_Plat­form
  111. Java Community Process
    http://en.wikipedia.org/wi­ki/Java_Specification_Requ­est
  112. Java HotSpot VM Options
    http://www.oracle.com/technet­work/java/javase/tech/vmop­tions-jsp-140102.html
  113. Great Computer Language Shootout
    http://c2.com/cgi/wiki?Gre­atComputerLanguageShootout
  114. Java performance
    http://en.wikipedia.org/wi­ki/Java_performance
  115. Trying the prototype
    http://mail.openjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  116. Better closures (for Java)
    http://blogs.sun.com/jrose/en­try/better_closures
  117. Lambdas in Java: An In-Depth Analysis
    http://www.infoq.com/articles/lambdas-java-analysis
  118. Class ReflectiveOperationException
    http://download.java.net/jdk7/doc­s/api/java/lang/Reflective­OperationException.html
  119. Scala Programming Language
    http://www.scala-lang.org/
  120. Run Scala in Apache Tomcat in 10 minutes
    http://www.softwaresecret­weapons.com/jspwiki/run-scala-in-apache-tomcat-in-10-minutes
  121. Fast Web Development With Scala
    http://chasethedevil.blog­spot.cz/2007/09/fast-web-development-with-scala.html
  122. Top five scripting languages on the JVM
    http://www.infoworld.com/d/developer-world/top-five-scripting-languages-the-jvm-855
  123. Proposal: Indexing access syntax for Lists and Maps
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  124. Proposal: Elvis and Other Null-Safe Operators
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  125. Java 7 : Oracle pushes a first version of closures
    http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/
  126. Groovy: An agile dynamic language for the Java Platform
    http://groovy.codehaus.org/Operators
  127. Better Strategies for Null Handling in Java
    http://www.slideshare.net/Step­han.Schmidt/better-strategies-for-null-handling-in-java
  128. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  129. Java Virtual Machine
    http://en.wikipedia.org/wi­ki/Java_virtual_machine
  130. ==, .equals(), compareTo(), and compare()
    http://leepoint.net/notes-java/data/expressions/22com­pareobjects.html
  131. New JDK7 features
    http://openjdk.java.net/pro­jects/jdk7/features/
  132. Project Coin: Bringing it to a Close(able)
    http://blogs.sun.com/darcy/en­try/project_coin_bring_clo­se
  133. CloseableFinder source code
    http://blogs.sun.com/darcy/re­source/ProjectCoin/Closea­bleFinder.java
  134. Joe Darcy blog about JDK
    http://blogs.sun.com/darcy
  135. Java 7 – more dynamics
    http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/
  136. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/index.html

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

Autor článku

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