Débogage à distance de l'application Java

1. Vue d'ensemble

Le débogage d'une application Java distante peut être pratique dans plusieurs cas.

Dans ce didacticiel, nous découvrirons comment faire cela en utilisant les outils de JDK.

2. L'application

Commençons par écrire une application. Nous l'exécuterons sur un emplacement distant et le déboguerons localement via cet article:

public class OurApplication { private static String staticString = "Static String"; private String instanceString; public static void main(String[] args) { for (int i = 0; i < 1_000_000_000; i++) { OurApplication app = new OurApplication(i); System.out.println(app.instanceString); } } public OurApplication(int index) { this.instanceString = buildInstanceString(index); } public String buildInstanceString(int number) { return number + ". Instance String !"; } } 

3. JDWP: le protocole Java Debug Wire

Le Java Debug Wire Protocol est un protocole utilisé en Java pour la communication entre un débogueur et un débogueur . Le débogueur est l'application en cours de débogage tandis que le débogueur est une application ou un processus se connectant à l'application en cours de débogage.

Les deux applications s'exécutent sur la même machine ou sur des machines différentes. Nous allons nous concentrer sur ce dernier.

3.1. Options de JDWP

Nous utiliserons JDWP dans les arguments de ligne de commande JVM lors du lancement de l'application de débogage.

Son appel nécessite une liste d'options:

  • le transport est la seule option entièrement requise. Il définit le mécanisme de transport à utiliser. dt_shmem ne fonctionne que sous Windows et si les deux processus s'exécutent sur la même machine alors que dt_socket est compatible avec toutes les plates-formes et permet aux processus de s'exécuter sur des machines différentes
  • serveur n'est pas une option obligatoire. Cet indicateur, lorsqu'il est activé, définit la manière dont il s'attache au débogueur. Il expose soit le processus via l'adresse définie dans l' option d' adresse . Sinon, JDWP en expose une par défaut
  • suspend définit si la JVM doit suspendre et attendre qu'un débogueur se connecte ou non
  • adresse est l'option contenant l'adresse, généralement un port, exposée par le débogueur. Il peut également représenter une adresse traduite sous forme de chaîne de caractères (comme javadebug si on utilise server = y sans fournir d' adresse sous Windows)

3.2. Commande de lancement

Commençons par lancer l'application distante. Nous fournirons toutes les options énumérées précédemment:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 OurApplication 

Jusqu'à Java 5, l'argument JVM runjdwp devait être utilisé avec l'autre option de débogage :

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

Cette façon d'utiliser JDWP est toujours prise en charge mais sera abandonnée dans les versions futures. Nous préférerons l'utilisation de la nouvelle notation lorsque cela est possible.

3.3. Depuis Java 9

Enfin, l'une des options de JDWP a changé avec la sortie de la version 9 de Java. Il s'agit d'un changement assez mineur car il ne concerne qu'une seule option, mais fera une différence si nous essayons de déboguer une application distante.

Cette modification a un impact sur la façon dont l' adresse se comporte pour les applications distantes. L'ancienne adresse de notation = 8000 s'applique uniquement à localhost . Pour obtenir l'ancien comportement, nous utiliserons un astérisque avec deux points comme préfixe pour l'adresse (par exemple, adresse = *: 8000 ).

Selon la documentation, ce n'est pas sécurisé et il est recommandé de spécifier l'adresse IP du débogueur chaque fois que possible:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000

4. JDB: le débogueur Java

JDB, le débogueur Java, est un outil inclus dans le JDK conçu pour fournir un client de débogage pratique à partir de la ligne de commande.

Pour lancer JDB, nous utiliserons le mode attacher . Ce mode associe JDB à une JVM en cours d'exécution. D'autres modes d'exécution existent, comme écouter ou exécuter, mais sont surtout pratiques lors du débogage d'une application exécutée localement:

jdb -attach 127.0.0.1:8000 > Initializing jdb ... 

4.1. Points d'arrêt

Continuons en mettant quelques points d'arrêt dans l'application présentée dans la section 1.

Nous allons définir un point d'arrêt sur le constructeur:

> stop in OurApplication. 

Nous en définirons un autre dans la méthode statique main , en utilisant le nom complet de la classe String :

> stop in OurApplication.main(java.lang.String[]) 

Enfin, nous allons définir le dernier sur la méthode d'instance buildInstanceString :

> stop in OurApplication.buildInstanceString(int) 

Nous devrions maintenant remarquer l'arrêt de l'application serveur et l'impression suivante dans notre console de débogage:

> Breakpoint hit: "thread=main", OurApplication.(), line=11 bci=0 

Let's now add a breakpoint on a specific line, the one where the variable app.instanceString is being printed:

> stop at OurApplication:7 

We notice that at is used after stop instead of in when the breakpoint is defined on a specific line.

4.2. Navigate and Evaluate

Now that we've set our breakpoints, let's use cont to continue the execution of our thread until we reach the breakpoint on line 7.

We should see the following printed in the console:

> Breakpoint hit: "thread=main", OurApplication.main(), line=7 bci=17 

As a reminder, we've stopped on the line containing the following piece of code:

System.out.println(app.instanceString); 

Stopping on this line could have also been done by stopping on the main method and typing step twice. step executes the current line of code and stops the debugger directly on the next line.

Now that we've stopped, the debugee is evaluating our staticString, the app‘s instanceString, the local variable i and finally taking a look at how to evaluate other expressions.

Let's print staticField to the console:

> eval OurApplication.staticString OurApplication.staticString = "Static String" 

We explicitly put the name of the class before the static field.

Let's now print the instance field of app:

> eval app.instanceString app.instanceString = "68741. Instance String !" 

Next, let's see the variable i:

> print i i = 68741 

Unlike the other variables, local variables don't require to specify a class or an instance. We can also see that print has exactly the same behavior as eval: they both evaluate an expression or a variable.

We'll evaluate a new instance of OurApplication for which we've passed an integer as a constructor parameter:

> print new OurApplication(10).instanceString new OurApplication(10).instanceString = "10. Instance String !" 

Now that we've evaluated all the variables we needed to, we'll want to delete the breakpoints set earlier and let the thread continue its processing. To achieve this, we'll use the command clear followed by the breakpoint's identifier.

The identifier is exactly the same as the one used earlier with the command stop:

> clear OurApplication:7 Removed: breakpoint OurApplication:7 

Pour vérifier si le point d'arrêt a été correctement supprimé, nous utiliserons clear sans arguments. Cela affichera la liste des points d'arrêt existants sans celui que nous venons de supprimer:

> clear Breakpoints set: breakpoint OurApplication. breakpoint OurApplication.buildInstanceString(int) breakpoint OurApplication.main(java.lang.String[]) 

5. Conclusion

Dans cet article rapide, nous avons découvert comment utiliser JDWP avec JDB, deux outils JDK.

Plus d'informations sur l'outillage peuvent bien sûr être trouvées dans leurs références respectives: JDWP et JDB - pour approfondir l'outillage.