Vue d’ensemble

Déboguer une application Java distante peut être pratique dans plus d’un cas.

Dans ce tutoriel, nous allons découvrir comment faire cela en utilisant les outils du JDK.

L’application

Débutons en écrivant une application. Nous allons l’exécuter sur un emplacement distant et la déboguer localement grâce à 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 !"; }}

JDWP : Le protocole Java Debug Wire

Le protocole Java Debug Wire 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 soit sur la même machine, soit sur des machines différentes. Nous allons nous concentrer sur cette dernière.

3.1. Les options de JDWP

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

Son invocation nécessite une liste d’options :

  • transport est la seule option entièrement requise. Elle 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 plateformes et permet aux processus de s’exécuter sur des machines différentes
  • server n’est pas une option obligatoire. Ce drapeau, lorsqu’il est activé, définit la façon dont il s’attache au débogueur. Soit il expose le processus à travers l’adresse définie dans l’option address. Sinon, JDWP en expose un par défaut
  • suspend définit si la JVM doit suspendre et attendre qu’un débogueur s’attache ou non
  • address est l’option contenant l’adresse, généralement un port, exposée par le débogueur. Elle peut aussi représenter une adresse traduite comme une 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 listé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 debug :

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

Cette façon d’utiliser JDWP est toujours supportée mais sera abandonnée dans les prochaines versions. Nous préférerons l’utilisation de la notation plus récente lorsque cela est possible.

3.3. Depuis Java 9

Enfin, une des options de JDWP a changé avec la sortie de la version 9 de Java. C’est un changement assez mineur puisqu’il ne concerne qu’une seule option mais qui fera une différence si nous essayons de déboguer une application distante.

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

Selon la documentation, ceci n’est pas sécurisé et il est recommandé de spécifier l’adresse IP du débogueur lorsque c’est possible :

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

JDB : Le débogueur Java

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

Pour lancer JDB, nous utiliserons le mode attach. Ce mode attache JDB à une JVM en cours d’exécution. D’autres modes d’exécution existent, comme listen ou run, mais ils sont surtout pratiques pour déboguer une application s’exécutant localement :

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

4.1. Points d’arrêt

Poursuivons en mettant des points d’arrêt dans l’application présentée dans la section 1.

Nous placerons un point d’arrêt sur le constructeur :

> stop in OurApplication.<init>

Nous en placerons un autre dans la méthode statique main, en utilisant le nom entièrement qualifié de la classe String :

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

Enfin, nous placerons le dernier sur la méthode d’instance buildInstanceString :

> stop in OurApplication.buildInstanceString(int)

Nous devrions maintenant remarquer que l’application serveur s’arrête et que ce qui suit est imprimé dans la console de notre débogueur:

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

Ajoutons maintenant un point d’arrêt sur une ligne spécifique, celle où la variable app.instanceString est imprimée:

> stop at OurApplication:7

Nous remarquons que at est utilisé après stop au lieu de in lorsque le point d’arrêt est défini sur une ligne spécifique.

4.2. Naviguer et évaluer

Maintenant que nous avons défini nos points d’arrêt, utilisons cont pour poursuivre l’exécution de notre thread jusqu’à ce que nous atteignions le point d’arrêt de la ligne 7.

Nous devrions voir ce qui suit imprimé dans la console:

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

Pour rappel, nous nous sommes arrêtés sur la ligne contenant le morceau de code suivant:

System.out.println(app.instanceString);

S’arrêter sur cette ligne aurait également pu être fait en s’arrêtant sur la méthode main et en tapant deux fois step. step exécute la ligne de code actuelle et arrête le débogueur directement sur la ligne suivante.

Maintenant que nous nous sommes arrêtés, le débogueur évalue notre staticString, l’instanceString de l’app, la variable locale i et enfin jette un coup d’œil à la façon d’évaluer d’autres expressions.

Imprimons staticField à la console:

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

Nous avons explicitement mis le nom de la classe avant le static field.

Imprimons maintenant le champ d’instance de app:

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

Puis, voyons la variable i:

> print ii = 68741

Contrairement aux autres variables, les variables locales ne nécessitent pas de spécifier une classe ou une instance. Nous pouvons également voir que print a exactement le même comportement que eval : ils évaluent tous les deux une expression ou une variable.

Nous allons évaluer une nouvelle instance de OurApplication pour laquelle nous avons passé un entier comme paramètre de constructeur:

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

Maintenant que nous avons évalué toutes les variables dont nous avions besoin, nous voudrons supprimer les points d’arrêt définis précédemment et laisser le thread continuer son traitement. Pour ce faire, nous utiliserons la commande clear suivie de l’identifiant du point d’arrêt.

L’identifiant est exactement le même que celui utilisé précédemment avec la commande stop:

> clear OurApplication:7Removed: 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:

> clearBreakpoints set: breakpoint OurApplication.<init> breakpoint OurApplication.buildInstanceString(int) breakpoint OurApplication.main(java.lang.String)

Conclusion

I:n cet article rapide, nous avons découvert comment utiliser JDWP conjointement avec JDB, deux outils du JDK.