Partager via


Injection dynamique du code avec l'API de débogage

Cette section décrit l'injection de code dynamique à l'aide du Common Language Runtime (CLR) qui débogue l'API. L'injection de code dynamique exécute une fonction qui n'était pas présente dans le fichier exécutable portable d'origine (PE). Par exemple, vous pouvez utiliser l'injection de code dynamique pour exécuter une expression dans la fenêtre Immédiat pendant que vous déboguez dans l'environnement de développement intégré Microsoft Visual Studio (IDE). Le CLR détourne un thread actif pour exécuter le code. Le débogueur peut demander au CLR d'exécuter ou de figer les threads restants. Dans la mesure où l'injection de code dynamique est fondée sur l'évaluation de fonctions, vous pouvez déboguer une fonction injectée dynamiquement comme s'il s'agissait de code normal. Autrement dit, vous pouvez appeler tous les services de débogage standard tels que la définition de points d'arrêt, l'exécution pas à pas, et ainsi de suite pour le code injecté dynamiquement.

Pour injecter du code dynamiquement, un débogueur doit effectuer les opérations suivantes :

  • Composer une fonction qui exécutera le code dynamique.

  • Injecter le code dans le programme débogué en utilisant Modifier & Continuer.

  • Exécuter le code, à plusieurs reprises le cas échéant, en appelant la fonction composée.

Les sous-sections suivantes décrivent ces étapes en détail.

Composition de la fonction

  1. Calculez la signature de la fonction qui exécutera le code dynamique. Pour cela, ajoutez la signature pour les variables locales à la signature pour la méthode qui est exécutée dans le frame terminal. Une signature qui est construite de cette manière ne requiert pas le calcul du sous-ensemble minimal des variables utilisées. Le runtime ignorera les variables inutilisées. Pour plus d'informations, consultez « Obtenir la signature des métadonnées », plus loin dans cette rubrique.

    Tous les arguments servant à la fonction doivent être déclarés comme étant ByRef. Cela permettra à l'évaluation de fonction de propager toutes modifications apportées aux variables dans la fonction injectée vers le frame terminal dans le programme débogué.

    Certaines variables ne peuvent pas être dans la portée lorsque le code dynamique est exécuté. Dans de telles situations, vous devez passer une référence null. Si vous référencez une variable hors de-portée, une NullReferenceException sera levée. Lorsque cela se produit, le débogueur peut effectuer l'évaluation de fonction en appelant le rappel ICorDebugManagedCallback::EvalException.

  2. Choisissez un nom unique pour la fonction. Vous devez préfixer le nom de fonction avec la chaîne « _Hidden » afin que le débogueur empêche un utilisateur de parcourir la fonction. Lorsque cette fonction est ajoutée, un indicateur est défini pour indiquer que le nom de fonction est spécial.

Injection de code

  1. Le débogueur doit demander au compilateur de générer une fonction dont le corps est le code qui sera injecté dynamiquement.

  2. Le débogueur calcule un fichier exécutable portable (PE) delta. Pour cela, le débogueur appelle la méthode ICorDebugModule::GetEditAndContinueSnapshot pour obtenir un objet ICorDebugEditAndContinueSnapshot. Le débogueur appelle les méthodes ICorDebugEditAndContinueSnapshot pour créer l'image PE delta et ICorDebugController::CommitChanges pour installer le PE delta dans l'image en cours.

    La fonction injectée dynamiquement doit être placée au même niveau de visibilité que le frame terminal dans lequel elle s'exécutera. Si le frame terminal est une méthode d'instance, la fonction injectée dynamiquement doit également être une méthode d'instance dans la même classe. Si le frame terminal est une méthode statique, la fonction injectée dynamiquement doit également être une méthode statique. Les méthodes globales sont des méthodes statiques qui appartiennent à une classe particulière.

    RemarqueRemarque

    La fonction existera dans le programme débogué même après l'achèvement du processus d'injection de code dynamique.Cela permet de réévaluer de façon répétée le code injecté précédemment sans devoir recomposer la fonction et réinjecter le code.Par conséquent, les étapes décrites de cette section et dans la section précédente peuvent être ignorées pour le code injecté précédemment.

Exécution du code injecté

  1. Obtenez la valeur (objet ICorDebugValue) de chaque variable dans la signature à l'aide des routines d'inspection du débogage. Vous pouvez utiliser la méthode ICorDebugThread::GetActiveFrame ou ICorDebugChain::GetActiveFrame pour obtenir le frame terminal, et QueryInterface pour ICorDebugILFrame. Appelez la méthode ICorDebugILFrame::EnumerateLocalVariables, ICorDebugILFrame::EnumerateArguments, ICorDebugILFrame::GetLocalVariableou ICorDebugILFrame::GetArgument pour obtenir les variables réelles.

    RemarqueRemarque

    Si le débogueur est attaché à un programme débogué qui n'a pas CORDBG_ENABLE défini (autrement dit, un programme débogué qui n'a pas rassemblé d'informations de débogage), le débogueur ne sera pas en mesure d'obtenir un objet ICorDebugILFrame, et par conséquent ne pourra pas rassembler des valeurs pour l'évaluation de fonction.

    Il importe peu que les objets qui sont des arguments pour la fonction injectée dynamiquement soient déréférencés faiblement ou fortement. Lorsque la fonction est évaluée, le runtime détourne le thread dans lequel l'injection s'est produite. Cela laisse le frame terminal d'origine et toutes les références fortes d'origine sur la pile. Toutefois, si toutes les références dans le programme débogué sont faibles, l'exécution de l'injection de code dynamique peut déclencher un garbage collection qui peut, à son tour, provoquer la récupération de l'objet par le garbage collector.

  2. Utilisez la méthode ICorDebugThread::CreateEval pour créer un objet ICorDebugEval. ICorDebugEval fournit des méthodes pour évaluer une fonction. Appelez l'une de ces méthodes. Une méthode comme ICorDebugEval::CallFunction installe seulement l'évaluation de fonction. Le débogueur doit appeler ICorDebugController::Continue pour exécuter le programme débogué et évaluer la fonction. Une fois l'évaluation terminée, les services de débogage appellent la méthode ICorDebugManagedCallback::EvalComplete ou ICorDebugManagedCallback::EvalException pour notifier au débogueur l'évaluation de fonction.

    Si l'évaluation de fonction retourne un objet, l'objet est référencé fortement.

    Si le code injecté dynamiquement tente de déréférencer une référence null qui est passée à la fonction qui encapsule le code, les services de débogage du CLR appellent ICorDebugManagedCallback::EvalException. En réponse, le débogueur peut informer l'utilisateur qu'il ne peut pas évaluer le code injecté.

    Notez que la méthode ICorDebugEval::CallFunction n'effectue pas de distributions virtuelles ; utilisez ICorDebugObjectValue::GetVirtualMethod à la place si vous souhaitez des distributions virtuelles.

    Si le programme débogué est multithread et que le débogueur ne souhaite aucun des autres threads en exécution, le débogueur doit appeler ICorDebugController::SetAllThreadsDebugState et définir l'état de tous les threads à THREAD_SUSPEND, sauf le thread qui est utilisé pour l'évaluation de fonction. Ce paramètre peut provoquer un interblocage, selon ce que fait le code injecté dynamiquement.

Problèmes divers

  • Dans la mesure où la sécurité du .NET Framework est déterminée par des stratégies de contexte, l'injection de code dynamique fonctionnera avec les mêmes fonctions et autorisations de sécurité que le frame terminal, à moins que vous modifiiez spécifiquement les paramètres de sécurité.

  • Les fonctions injectées dynamiquement peuvent être ajoutées partout où la fonctionnalité Modifier & Continuer permet l'ajout de fonctions. Un choix logique est de les ajouter au frame terminal.

  • Il n'existe pas de limites quant au nombre de champs, de méthodes d'instance ou de méthodes statiques que vous pouvez ajouter à une classe en utilisant des opérations Modifier & Continuer. Le volume maximal de données statiques autorisé est prédéfini et il est limité actuellement à 1 Mo par module.

  • Les instructions goto non locales ne sont pas autorisées dans le code injecté dynamiquement.

Obtention de la signature des métadonnées

Obtention d'un distributeur de métadonnées

Recherche de la méthode en utilisant IMetaDataImport

Génération de la signature de la fonction

Le format de la signature est documenté dans la spécification relative au type et encodage de signature dans les métadonnées, qui a priorité sur les informations fournies dans cette vue d'ensemble.

La section traitant de la déclaration de méthode dans la spécification décrit le format de la signature de méthode. Le format est un octet unique pour la convention d'appel, suivi d'un octet unique pour le nombre d'arguments, suivi d'une liste de types. Chaque type peut avoir une taille différente. Si une convention d'appel d'arguments variables est spécifiée, le nombre d'arguments sera égal au nombre total des arguments, autrement dit, des arguments fixes plus les arguments variables. Un octet ELEMENT_TYPE_SENTINEL marque l'emplacement où les arguments fixes se terminent et où les arguments variables commencent.

La section des signatures autonomes dans la spécification décrit le format des signatures locales. Les signatures autonomes n'utilisent pas de convention d'appel d'argument variable.

Après avoir obtenu la signature de méthode et la signature locale, le débogueur doit allouer de l'espace pour une nouvelle signature. Le débogueur doit ensuite itérer au sein de la signature de méthode. Pour chaque type, il doit mettre l'octet ELEMENT_TYPE_BYREF, suivi du type, dans la nouvelle signature. Le processus doit être répété jusqu'à ce que la signature fin-de-méthode ou un type a marqué ELEMENT_TYPE_SENTINEL soit atteint. Ensuite, le débogueur doit copier les types de signature locale et marquer chaque type ELEMENT_TYPE_BYREF. Si la signature de méthode a une convention d'appel d'argument variable, le débogueur doit copier ces types et les marquer avec ELEMENT_TYPE_BYREF. Enfin, le débogueur doit mettre à jour le nombre d'arguments.

Voir aussi

Concepts

Vue d'ensemble du débogage CLR

Autres ressources

Débogage (Référence des API non managées)