22/01/2025
Vous êtes-vous déjà demandé comment votre IDE préféré arrive-t-il à afficher précisément la ligne de code PHP en cours d’exécution ? Et que se passe-t-il réellement quand on ajoute un point d’arrêt via l’interface ? Quelle magie opère derrière tout ça ?
Je vous propose dans cet article de plonger dans l’univers mystique de Xdebug et de vous familiariser avec sa gestion du débogage.
Notre quête ne sera pas facile ; armés du grimoire magique, nous invoquerons d’abord Xdebug pour observer ses interactions avec l’IDE. Puis on descendra dans les profondeurs de PHP pour observer les rouages de Xdebug.

Il était une fois Xdebug…
Avant de partir affronter les ténèbres tête baissée, rappelons-nous le sujet de notre quête.
Pour résumer, Xdebug est une extension PHP (créée par Derick Rethans en 2002) qui ajoute des fonctionnalités avancées de débogage, de profiling et de traçage d’exécution. En plus de “pimper” la fonction var_dump, elle permet d’obtenir des informations détaillées sur les performances et les comportements du code. Si vous souhaitez en savoir davantage sur son utilisation, je vous invite à regarder la présentation de Robin Colombier au Drupal Camp de Rennes 2024.
Toujours maintenue actuellement, elle est particulièrement appréciée par la communauté et s’intègre aux principaux IDE.
Mais vous l’avez compris, nous n’allons pas évoquer sa configuration ni sa mise en place. D’excellentes ressources existent déjà sur le web, comme cet article d’Arthur Gorden.
Abracadabra !
Vous fouillez d’abord dans votre besace et vous trouvez un bon vieux grimoire intitulé « DBGp debugging protocol ». En le parcourant rapidement, vous comprenez que la communication entre Xdebug et l’IDE repose sur un protocole particulier.
Sa documentation décrit un ensemble de commandes et de règles pour normaliser la communication entre un moteur de débogage et un IDE. Le protocole ne se veut pas spécifique à Xdebug pour autant.
Dans cet article, on considère que le moteur de débogage est Xdebug et que l’IDE désigne l’interface graphique et/ou le client de débogage intégré suivant le contexte. Nous avons configuré xdebug.start_with_request=yes pour déclencher l’activation de Xdebug. Les versions utilisées sont 8.4 pour PHP et 3.4 pour Xdebug. Le script PHP s’exécute en local, sans proxy. On s’intéresse uniquement au mode « debug » de Xdebug.
Le principe de base est le suivant : la connexion s’effectue par socket TCP/IP. Via cette interface, Xdebug transmet des informations au format XML (à noter que la valeur d’une variable y est encodée en base64) et l’IDE envoie des commandes au format ASCII.

On place des points d’arrêt dans notre IDE (on va revenir sur les points d’arrêt plus tard) et on commence le rituel en agitant les bras. En observant les effets du sortilège, nous distinguons plusieurs étapes clés auxquelles le protocole associe également des états pour Xdebug.
1. Invocation de la connexion
Une des particularités du protocole DBGp vient du fait que c’est Xdebug qui initialise la connexion vers l’IDE et non l’inverse.
En effet, vu qu’il s’agit d’une communication par socket, on pourrait s’attendre à une relation client-serveur où le client (IDE) demande un service au serveur (Xdebug).
Mais contrairement à d’autres langages comme Java ou Python, PHP est souvent exécuté via des requêtes web ou des scripts CLI qui ne sont pas des processus permanents. L’établissement de la connexion depuis l’IDE serait davantage complexe.

Lorsqu’on active le débogage dans notre IDE, on démarre en vérité le client de débogage intégré pour recevoir les informations envoyées par Xdebug. Lors du démarrage du script PHP, Xdebug démarre une session de débogage de son côté et cherche à se connecter à ce client.
Xdebug envoie un premier paquet init, ouvrant ainsi un portail vers une communication magique et interrompt immédiatement l’exécution du code pour attendre les commandes de l’IDE. C’est l’état « starting ».
<init
xmlns="urn:debugger_protocol_v1"
xmlns:xdebug="https://xdebug.org/dbgp/xdebug"
fileuri="file:///Wizards/Gandalf/XdebugTower/Celebration.php"
language="PHP"
xdebug:language_version="8.4.1"
protocol_version="1.0"
appid="82633">
<engine version="3.4.0">
<![CDATA[Xdebug]]>
</engine>
<author>
<![CDATA[Derick Rethans]]>
</author>
<url>
<![CDATA[https://xdebug.org]]>
</url>
<copyright>
<![CDATA[Copyright (c) 2002-2024 by Derick Rethans]]>
</copyright>
</init>
2. Runes et cercles de protection
Une fois la connexion établie, l’IDE et Xdebug établissent un pacte.
Le protocole prévoie que certaines fonctionnalités ne sont peut-être pas disponibles des deux côtés. En utilisant l’ensemble des commandes features à ce moment-là, l’IDE communique ce qu’il peut gérer et modifie également la configuration de Xdebug (type de points d’arrêt supportés ou encodage utilisé par exemple).

Durant cette étape, l’IDE enregistre et envoie également les points d’arrêt que nous avons définis auparavant. Ils deviennent en quelque sorte gravés dans le code pour arrêter le temps !
Les points d’arrêt sont au cœur du processus de débogage. Ils permettent de stopper l’exécution du script à des lignes de code spécifiques pour examiner l’état des variables et la pile d’appel à ce moment précis.
Il est aussi possible de définir des nouveaux points d’arrêt une fois le script interrompu. Dans tous les cas, la commande breakpoint_set est utilisée.
A la fin de cette étape, l’IDE envoie la commande run et Xdebug reprend l’exécution du code PHP.
3. Manipulation des forces invisibles
Rappelez-vous que lors de l’étape précédente, les points d’arrêt ont été transmis à Xdebug.
La magie opère et le script s’arrête sous l’effet du charme.
Dès qu’un point d’arrêt est rencontré, Xdebug interrompt l’exécution et se met de nouveau en “attente” pour recevoir des commandes de l’IDE. Cela correspond à l’état “break”. Elle envoie alors les données (variables, points d’arrêts, pile d’appel etc.) en mémoire dans le contexte actuel d’exécution.

L’IDE reçoit ensuite les informations de débogage et met à jour son interface pour les afficher en temps réel. Cela se traduit concrètement par l’affichage du focus sur le fichier et la ligne correspondante. C’est ça !
Un sentiment de pouvoir temporel immense traverse votre esprit.

Nous pouvons inspecter le contexte, une propriété, réaliser du débogage pas à pas ou définir de nouveaux points d’arrêt. Par le biais de l’interface graphique, l’IDE envoie ainsi les commandes correspondantes (context_get, property_get et step_into par exemple) à Xdebug et nous affiche le résultat.
On notera que le code est interrompu avant l’exécution de la ligne sur laquelle se situe le point d’arrêt.
On peut également continuer l’exécution du script (run). Dans ce cas-là, Xdebug change d’état et passe à “running”.
4. Dissipation du sortilège
Sachez que ce pouvoir n’est malheureusement pas éternel.

Lors de la fin du script PHP, Xdebug passe à l’état « stopping » et informe l’IDE que la session se termine. L’IDE envoie alors la commande detach et rend le focus. Xdebug nettoie sa session de débogage (mémoire), l’arrête et clôture la connexion.
Voilà, vous venez d’analyser ce que fait votre IDE avec son client de débogage intégré. Il nous aide et permet de nous épargner d’entrer manuellement toutes les différentes commandes du protocole DBGp. Il suffit de tester le client mis à disposition pour se rendre compte que l’IDE nous collecte en un clic beaucoup d’informations. Les séances de débogage seraient davantage laborieuses sans l’interface graphique !
Nous sommes prêts ! Il est temps de s’aventurer dans la montagne PHP…
Descente dans les profondeurs du volcan
Nous voulons aller plus loin et explorer encore un peu plus Xdebug.
Xdebug repose sur une architecture complexe et sophistiquée en interagissant avec le moteur de PHP. L’extension est écrite en C et ses sources sont disponibles ici.
Voici une vision d’ensemble.

Dès que Xdebug est activée, elle s’intègre au moteur d’exécution Zend de PHP en tant qu’extension.
Souvenez-vous que le code PHP est précompilé en opcodes avant d’être exécuté par le moteur. Avec le scénario de l’IDE, nous savons aussi que Xdebug récupère et enregistre les points d’arrêt lors du démarrage de la session de débogage.
Lorsque le moteur Zend exécute les opcodes, il le fait séquentiellement. C’est là que Xdebug se greffe. Lorsqu’un script PHP est lancé, Xdebug intercepte et analyse chaque opcode. Si cela correspond à un point d’arrêt enregistré, elle transmet les informations à l’IDE et se met en attente des commandes comme nous l’avons vu précédemment.
L’atmosphère est brulante non ? Des torrents de lave en fusion nous entourent…
En fait le moteur Zend dispose de hooks pour que les extensions puissent se greffer sur son fonctionnement interne. Xdebug utilise zend_execute_ex pour altérer l’exécution des opcodes (xdebug_execute_ex).

L’extension détecte alors les modes activés et dans notre cas, lors du mode “debug”, cela déclenche le xdebug_init_debugger. C’est en passant par cette fonction qu’on retrouve dans les logs le fameux “Connected to debugging client: …” et que l’exécution des opcodes est mis en pause.
Cela active ensuite une vérification sur les points d’arrêts ( xdebug_debugger_handle_breakpoints & handle_breakpoints). Le code devient encore un peu plus complexe et il est impossible de tout détailler en quelques phrases. Cependant, ce qui est intéressant ici, c’est que si une correspondance est trouvée, cela déclenche l’appel à un handler pour le protocole DBGp (handler_dbgp.c) qui va générer puis envoyer la réponse au format XML (et écrire dans les logs).
On a maintenant une petite idée du scénario de base !
Fin de l’aventure
L’aventure avec Xdebug touche à sa fin, mais ses secrets resteront à jamais gravés dans votre grimoire de développeur. À vous de créer vos propres enchantements !
J’en profite pour évoquer la fonction PHP xdebug_info(), qui existe depuis la version 3. Elle affiche une page qui ressemble fortement au rendu de la fonction phpinfo() mais avec quelques petits plus (liens docs, logs) non négligeables. Libre à vous de l’utiliser.
Une dernière astuce aussi qui peut vous faire gagner du temps : veillez à ce que l’extension Xdebug soit chargée après Zend OpCache. Comme expliqué dans la documentation officielle, le fonctionnement peut être altéré si Xdebug est initialisée avant. La raison vient du fait qu’à la fois Xdebug et Zend OPcache peuvent modifier les headers renvoyés.
J’espère que cet article vous aura permis d’y voir un peu plus clair dans la façon dont votre IDE et Xdebug fonctionnent ensemble.
Cet article n’aurait jamais vu le jour sans l’aide de mes collègues chez ekino. Merci à eux !
Et merci à vous de m’avoir lu jusqu’ici !
Sources :
- https://xdebug.org/docs/step_debug
- https://hassansin.github.io/understanding-xdebug-using-mscgen
- https://dev.to/servbay/use-xdebug-for-php-project-debugging-2i4o
- https://medium.com/@jdesrig/xdebug-a-deeper-look-9cea47707c06
- https://www.honeybadger.io/blog/php-debugger/
- https://dev.to/younup/deboguer-comme-un-chef-en-php-avec-xdebug-2bka
- Xdebug source code
La magie de Xdebug was originally published in ekino-france on Medium, where people are continuing the conversation by highlighting and responding to this story.