Architecture du débogage CLR
L'API de débogage du Common Language Runtime (CLR) est destinée à être utilisés comme si elle fait partie du noyau de système d'exploitation. Dans le code non managé, lorsqu'un programme génère une exception, le noyau interrompt l'exécution du processus et passe les informations sur les exceptions au débogueur en utilisant l'API de débogage Win32. L'API de débogage CLR fournit les mêmes fonctionnalités pour le code managé. Lorsque le code managé génère une exception, l'API de débogage CLR interrompt l'exécution du processus et passe les informations sur les exceptions au débogueur.
Cette rubrique décrit comment et quand l'API de débogage CLR devient impliquée et quels services elle fournit.
Architecture de processus
L'API de débogage CLR inclut les deux composants majeurs suivants :
La DLL de débogage, qui est toujours chargée dans le même processus que le programme qui est débogué. Le contrôleur runtime est chargé de communiquer avec le CLR et d'effectuer le contrôle d'exécution et l'inspection des threads qui exécutent le code managé.
L'interface de débogueur, qui est chargée dans un processus différent à partir du programme qui est débogué. L'interface de débogueur est chargée de communiquer avec le contrôleur runtime au nom du débogueur. Elle est également chargée de gérer les événements de débogage Win32 qui proviennent du processus qui est débogué, et soit de gérer ces événements soit de les passer à un débogueur de code non managé. L'interface de débogueur est la seule partie de l'API de débogage CLR qui possède une API exposée.
L'API du débogage du CLR ne prend pas en charge l'utilisation distante interordinateur ou interprocessus ; autrement dit, un débogueur qui utilise l'API doit le faire à partir de son propre processus, comme illustré dans le diagramme de l'architecture de l'API suivant. Cette illustration montre où les différents composants de l'API de débogage CLR sont localisés et comment ils interagissent avec le CLR et le débogueur.
Architecture de l'API de débogage du CLR
Débogueur de code managé
Il est possible de générer un débogueur qui prend en charge le code managé uniquement. L'API de débogage CLR permet à un tel débogueur de s'attacher à un processus à la demande en utilisant un mécanisme de type attachement partiel. Un débogueur qui est partiellement attaché à un processus peut se détacher par la suite du processus.
Synchronisation des threads
L'API de débogage CLR a des spécifications conflictuelles qui se rapportent à l'architecture des processus. D'un côté, il existe de nombreuses raisons incontestables de conserver la logique du débogage dans le même processus que le programme qui est débogué. Par exemple, les structures de données sont complexes, et elles sont fréquemment manipulées par les fonctions à la place d'une disposition de mémoire fixe. Il est beaucoup plus facile d'appeler directement les fonctions au lieu d'essayer de décoder les structures de données de l'extérieur du processus. Une autre raison de conserver la logique du débogage dans le même processus est l'amélioration des performances par la suppression de charge liée à la communication interprocessus. Enfin, une fonctionnalité importante du débogage CLR est la possibilité d'exécuter le code utilisateur dans le processus avec le programme débogué, ce qui évidemment requiert une coopération avec le processus du programme débogué.
D'un autre côté, le débogage CLR doit coexister avec le débogage de code non managé, qui peut être exécuté correctement seulement à partir d'un processus extérieur. Un débogueur hors processus (out-of-process) est mieux sécurisé qu'un débogueur en cours de processus (in-process) car les interférences entre l'opération du débogueur et le processus du programme débogué sont réduites dans un débogueur hors processus (out-of-process).
Compte tenu de ces spécifications conflictuelles, l'API de débogage CLR combine un peu de chaque approche. L'interface de débogage principale est hors processus (out-of-process) et coexiste avec les services de débogage Win32 natifs. Toutefois, l'API de débogage CLR ajoute la capacité de se synchroniser avec le processus du programme débogué afin qu'il puisse exécuter le code de façon sécurisée dans le processus utilisateur. Pour exécuter cette synchronisation, l'API collabore avec le système d'exploitation et le CLR pour interrompre tous les threads dans le processus à un emplacement où ils ne peuvent pas interrompre une opération et laisser le runtime dans un état incohérent. Le débogueur est ensuite en mesure d'exécuter le code dans un thread spécial qui peut examiner l'état du runtime et appeler le code utilisateur si nécessaire.
Lorsque le code managé exécute une instruction de point d'arrêt ou génère une exception, le contrôleur runtime en est notifié. Ce composant détermine quels threads exécutent le code managé et quels threads exécutent le code non managé. En principe, les threads qui exécutent le code managé seront autorisés à continuer de s'exécuter jusqu'à ce qu'ils atteignent un état où ils peuvent être interrompus de façon sécurisée. Ils peuvent, par exemple, avoir à terminer une opération de garbage collection qui est en cours. Lorsque les threads de code managé ont atteint des états sécurisés, tous les threads sont interrompus. L'interface de débogueur informe alors le débogueur qu'un point d'arrêt ou qu'une exception a été reçu(e).
Lorsque le code non managé exécute une instruction de point d'arrêt ou génère une exception, l'interface de débogueur interface en reçoit une notification via l'API de débogage Win32. Cette notification est transmise à un débogueur non managé. Si le débogueur décide qu'il souhaite exécuter la synchronisation (par exemple, afin que les frames de pile de code managé puissent être inspectés), l'interface de débogueur doit redémarrer en premier le processus du programme débogué arrêté, puis d'indiquer au contrôleur runtime d'exécuter la synchronisation. Puis l'interface de débogueur est notifiée quand une la synchronisation terminée. Cette synchronisation est transparente pour le débogueur non managé.
Le thread qui a généré l'instruction du point d'arrêt ou l'exception ne doit pas être autorisé à s'exécuter pendant le processus de synchronisation. Pour faciliter ceci, l'interface de débogueur prend le contrôle du thread en plaçant un filtre d'exceptions spécial dans la chaîne de filtre du thread. Lorsque le thread est redémarré, il accède au filtre d'exceptions, qui place alors le thread sous le contrôle du contrôleur runtime. Lorsque c'est le moment de poursuivre le traitement des exception (ou le moment d'annuler l'exception), le filtre retourne le contrôle à la chaîne de filtre d'exceptions normale du thread ou retourne le résultat correct pour reprendre l'exécution.
Dans de rares cas, le thread qui génère l'exception native peut maintenir des verrouillages importants qui doivent être débloqués pour que la synchronisation du runtime puisse aboutir. (En général, il s'agit de verrouillages bibliothèque de bas niveau, tels que les verrouillages sur le tas malloc.) Dans pareils cas, l'opération de synchronisation doit expirer et la synchronisation échouer. Cela entraîne du coup l'échec de certaines opérations qui requièrent la synchronisation.
Thread d'assistance in-process
Un seul thread d'assistance de débogueur est utilisé dans chaque processus CLR afin de s'assurer que l'API de débogage CLR fonctionne correctement. Ce thread d'assistance est chargé de gérer de nombreux services d'inspection fournis par l'API de débogage, en plus d'aider à la synchronisation des threads dans certaines circonstances. Vous pouvez utiliser la méthode ICorDebugProcess::GetHelperThreadID pour identifier le thread d'assistance.
Interactions avec les compilateurs JIT
Pour permettre à un débogueur de déboguer le code compilé juste-à-temps (JIT), l'API de débogage CLR doit être en mesure de mapper les informations de mappage de la version MSIL (Microsoft Intermediate Language) d'une fonction vers la version native de la fonction. Ces informations incluent des points de séquence dans le code ainsi que des informations sur l'emplacement des variables locales. Dans le .NET Framework versions 1.0 et 1.1, ces informations étaient produites uniquement quand le runtime était en mode de débogage. Dans le .NET Framework 2.0, ces informations sont produites tout le temps.
De même, le code compilé JIT peut être hautement optimisé. Des optimisations telles que l'élimination de sous-expressions courantes, l'expansion inline de fonctions, le déroulement de boucles, la levée de code, et ainsi de suite, peuvent conduire à la perte de corrélation entre le code MSIL d'une fonction et le code natif appelé pour l'exécuter. Ainsi, l'aptitude du compilateur JIT à fournir des informations de mappage correctes est sévèrement affectée par ces techniques agressives d'optimisation du code. Par conséquent, lorsque le runtime est exécuté en mode débogage, le compilateur JIT n'exécute pas certaines optimisations. Cette restriction permet aux débogueurs de déterminer correctement le mappage de ligne source et l'emplacement de tous les arguments et variables locales.
Modes de débogage
L'API de débogage CLR fournit des modes spécifiques pour le débogage dans deux cas :
Mode Modifier & Continuer. Dans ce cas, le runtime fonctionne différemment pour permettre au code d'être changé ultérieurement. Cela tient au fait que la disposition de certaines structures de données à l'exécution doivent être différentes pour prendre en charge Modifier & Continuer. Dans la mesure où cela a un effet négatif sur les performances, n'utilisez ce mode que si vous voulez disposer de la fonctionnalité Modifier & Continuer.
Mode débogage. Ce mode permet au compilateur JIT d'omettre des optimisations. Par conséquent, il fait en sorte que l'exécution du code natif corresponde de façon plus proche à la source de langage de haut niveau. N'utilisez ce mode qu'en cas de nécessité car il a également un effet négatif sur les performances.
Si vous déboguez un programme hors du mode Modifier & Continuer, la fonctionnalité Modifier & Continuer n'est pas prise en charge. Si vous déboguez un programme hors du mode débogage, la plupart des fonctions de débogage seront encore prises en charge, mais les optimisations peuvent provoquer un comportement inhabituel. Par exemple, l'exécution pas à pas peut sembler sauter aléatoirement de ligne en ligne dans la méthode, et les méthodes inline peuvent ne pas apparaître dans une trace de pile.
Un débogueur peut activer les deux modes Modifier & Continuer et débogage par programme via l'API de débogage CLR si le débogueur prend le contrôle d'un processus avant que le runtime ne se réinitialise. Cela est suffisant dans de nombreux cas. Toutefois, un débogueur qui s'attache à un processus qui s'exécute déjà depuis quelque temps (par exemple, pendant le débogage JIT) ne pourra pas démarrer ces modes.
Pour mieux négocier ces problèmes, il est possible d'exécuter un programme en mode JIT ou en mode débogage indépendamment d'un débogueur. Pour plus d'informations sur les façons d'activer le débogage, consultez Débogage, traçage et profilage.
Les optimisations JIT peuvent rendre une application moins facile à déboguer. L'API de débogage CLR active l'inspection des frames de pile et des variables locales avec le code compilé JIT qui a été optimisé. L'exécution pas à pas est prise en charge mais peut être imprécise. Vous pouvez exécuter un programme qui ordonne au compilateur JIT de désactiver toutes les optimisations JIT afin de produire du code pouvant être débogué. Pour plus d'informations, consultez Simplification du débogage d'une image.
Voir aussi
Concepts
Vue d'ensemble du débogage CLR