Dynamisches Einfügen von Code mit der Debug-API
In diesem Abschnitt wird das dynamische Einfügen von Code mit der Debug-API der Common Language Runtime (CLR) erläutert. Die dynamische Codeeinfügung führt eine Funktion aus, die in der ursprünglichen PE (Portable Executable)-Datei nicht vorhanden war. Sie können die dynamische Codeeinfügung zum Beispiel verwenden, um einen Ausdruck im Direktfenster auszuführen, während Sie in der integrierten Entwicklungsumgebung (IDE) von Microsoft Visual Studio das Debuggen durchführen. Die CLR übernimmt einen aktiven Thread, um den Code auszuführen. Der Debugger fordert möglicherweise an, dass die CLR die übrigen Threads ausführt oder sperrt. Da die dynamische Codeeinfügung auf der Funktionsauswertung aufbaut, können Sie eine dynamisch eingefügte Funktion so debuggen, als handelte es sich um normalen Code. Das heißt, Sie können alle standardmäßigen Debugdienste wie z. B. das Festlegen von Haltepunkten, Einspringen usw. für den dynamisch eingefügten Code aufrufen.
Ein Debugger muss wie folgt vorgehen, um Code dynamisch einzufügen:
Verfassen einer Funktion, die den dynamischen Code ausführt.
Einfügen des Codes in die zu debuggende Komponente durch Verwendung von Bearbeiten und Fortfahren.
Ausführen des Codes, auch wiederholt, durch das Aufrufen der verfassten Funktion.
In den folgenden Unterabschnitten werden diese Schritte ausführlicher beschrieben.
Verfassen der Funktion
Die Signatur für die Funktion, die den dynamischen Code ausführt, wird berechnet. Hängen Sie dazu die Signatur für die lokalen Variablen an die Signatur für die Methode an, die im Endframe ausgeführt wird. Für eine Signatur, die auf diese Weise erstellt wurde, ist die Berechnung des minimalen untergeordneten Satzes von verwendeten Variablen nicht notwendig. Die Laufzeit ignoriert die nicht verwendeten Variablen. Weitere Informationen erhalten Sie im Abschnitt "Abrufen der Signatur aus den Metadaten" weiter unten in diesem Kapitel.
Alle Argumente für die Funktion müssen als ByRef deklariert werden. Dadurch können mithilfe der Funktionsauswertung Änderungen an den Variablen der eingefügten Funktion an den Endframe der zu debuggenden Komponente weitergegeben werden.
Einige Variablen befinden sich beim Ausführen des dynamischen Codes möglicherweise nicht im Gültigkeitsbereich. In solchen Situationen sollten Sie eine null-Referenz übergeben. Wenn Sie auf eine Variable verweisen, die sich nicht im Gültigkeitsbereich befindet, wird eine NullReferenceException ausgelöst. Wenn dies auftritt, kann der Debugger die Funktionsauswertung abschließen, indem er den ICorDebugManagedCallback::EvalException-Rückruf aufruft.
Wählen Sie einen eindeutigen Namen für die Funktion. Sie müssen dem Funktionsnamen das Präfix "_Hidden:" voranstellen, damit der Debugger verhindern kann, dass ein Benutzer die Funktion durchsucht. Wenn diese Funktion hinzugefügt wird, wird ein Flag gesetzt, das den Funktionsnamen als speziellen Namen kennzeichnet.
Einfügen des Codes
Der Debugger sollte den Compiler anweisen, eine Funktion zu erstellen, deren Text der dynamisch einzufügende Code ist.
Der Debugger berechnet eine Delta-PE (Portable Executable)-Datei. Hierzu ruft der Debugger die ICorDebugModule::GetEditAndContinueSnapshot-Methode auf, um ein ICorDebugEditAndContinueSnapshot-Objekt abzurufen. Der Debugger ruft die ICorDebugEditAndContinueSnapshot-Methode zum Erstellen des Delta-PE-Abbilds und die ICorDebugController::CommitChanges-Methode zum Installieren der Delta-PE im laufenden Abbild auf.
Die dynamisch eingefügte Funktion sollte auf der gleichen Sichtbarkeitsebene platziert werden wie der Endframe, in dem sie ausgeführt wird. Wenn es sich beim Endframe um eine Instanzmethode handelt, sollte die dynamisch eingefügte Funktion ebenfalls eine Instanzmethode der gleichen Klasse sein. Wenn es sich beim Endframe um eine statische Methode handelt, sollte die dynamisch eingefügte Funktion ebenfalls eine statische Methode sein. Globale Methoden sind statische Methoden, die zu einer bestimmten Klasse gehören.
Hinweis Die Funktion ist sogar dann noch in der zu debuggenden Komponente vorhanden, nachdem das Einfügen des dynamischen Codes abgeschlossen ist.Hierdurch kann zuvor eingefügter Code wiederholt neu ausgewertet werden, ohne dass die Funktion erneut verfasst und der Code erneut eingefügt werden muss.Daher können die in diesem Abschnitt und im vorherigen Abschnitt beschriebenen Schritte bei zuvor eingefügtem Code übersprungen werden.
Ausführen von eingefügtem Code
Mithilfe der Debugüberprüfungsroutinen wird der Wert (ein ICorDebugValue-Objekt) der einzelnen Variablen in der Signatur abgerufen. Sie können entweder die ICorDebugThread::GetActiveFrame-Methode oder die ICorDebugChain::GetActiveFrame-Methode zum Abrufen des Endframes und QueryInterface für ICorDebugILFrame verwenden. Rufen Sie die Methoden ICorDebugILFrame::EnumerateLocalVariables, ICorDebugILFrame::EnumerateArguments, ICorDebugILFrame::GetLocalVariable oder ICorDebugILFrame::GetArgument auf, um die eigentlichen Variablen abzurufen.
Hinweis Wenn der Debugger an eine zu debuggende Komponente angehängt ist, die nicht über einen CORDBG_ENABLE-Satz verfügt (d. h. eine zu debuggende Komponente, die keine Debugdaten erfasst hat), kann der Debugger kein ICorDebugILFrame-Objekt abrufen und daher keine Werte für die Funktionsauswertung erfassen.
Es ist nicht wichtig, ob Objekte, bei denen es sich um Argumente der dynamisch eingefügten Funktion handelt, schwach oder stark dereferenziert werden. Wenn die Funktion ausgewertet wird, übernimmt die Laufzeit den Thread, in dem die Einfügung auftrat. Hierbei verbleiben der ursprüngliche Endframe und alle ursprünglichen starken Verweise auf dem Stapel. Wenn jedoch alle Verweise in der zu debuggenden Komponente schwach sind, kann beim Ausführen der dynamischen Codeeinfügung eine Garbage Collection ausgeführt werden, von der das Objekt möglicherweise erfasst werden kann.
Verwenden Sie die ICorDebugThread::CreateEval-Methode, um ein ICorDebugEval-Objekt zu erstellen. ICorDebugEval stellt Methoden zum Auswerten einer Funktion bereit. Rufen Sie eine dieser Methoden auf. Eine Methode wie ICorDebugEval::CallFunction richtet nur die Funktionsauswertung ein. Der Debugger muss ICorDebugController::Continue aufrufen, um die zu debuggende Komponente auszuführen und die Funktion auszuwerten. Wenn die Auswertung abgeschlossen ist, rufen die Debugdienste die ICorDebugManagedCallback::EvalComplete-Methode oder die ICorDebugManagedCallback::EvalException-Methode auf, um den Debugger über die Funktionsauswertung zu benachrichtigen.
Wenn die Funktionsauswertung ein Objekt zurückgibt, wird auf das Objekt stark verwiesen.
Wenn der dynamisch eingefügte Code versucht, einen null-Verweis zu dereferenzieren, der an die den Code umschließende Funktion übergeben wird, rufen die Debugdienste der CLR ICorDebugManagedCallback::EvalException auf. Als Antwort darauf kann der Debugger den Benutzer benachrichtigen, dass der eingefügte Code nicht ausgewertet werden kann.
Beachten Sie, dass die ICorDebugEval::CallFunction-Methode keine virtuellen Dispatches durchführt; verwenden Sie stattdessen die ICorDebugObjectValue::GetVirtualMethod-Methode.
Wenn die zu debuggende Komponente eine Multithreadkomponente ist und keiner der anderen Threads ausgeführt werden soll, sollte der Debugger ICorDebugController::SetAllThreadsDebugState aufrufen und den Status aller Threads auf THREAD_SUSPEND setzen, ausgenommen des Threads, der für die Funktionsauswertung verwendet wird. Diese Einstellung verursacht möglicherweise einen Deadlock, abhängig vom Vorgehen des eingefügten Codes.
Verschiedene Probleme
Da die .NET Framework-Sicherheit durch Kontextrichtlinien bestimmt wird, erfolgt die dynamische Codeeinfügung mit den gleichen Sicherheitsberechtigungen und -funktionen wie die des Endframes, es sei denn, Sie ändern die Sicherheitseinstellungen ausdrücklich.
Dynamisch eingefügte Funktionen können dann hinzugefügt werden, wenn die Funktionen zum Bearbeiten und Fortfahren verwendet werden können. Das Hinzufügen dieser Funktionen zum Endframe stellt eine logische Wahl dar.
Die Anzahl der Felder, Instanzmethoden oder statischen Methoden, die Sie mit den Verfahren zum Bearbeiten und Fortfahren zu einer Klasse hinzufügen können, ist unbegrenzt. Die maximal zulässige Menge von statischen Daten ist vordefiniert und derzeit auf 1 MB pro Modul beschränkt.
Nicht lokale goto-Anweisungen sind in dynamisch eingefügtem Code unzulässig.
Abrufen der Signatur aus den Metadaten
Abrufen eines Metadatenverteilers
Der Debugger ruft die ICorDebugModule::GetMetaDataInterface-Methode mit dem REFIID IID_IMetaDataDispenser auf, um einen Metadatenverteiler abzurufen.
Der Debugger ruft die IMetaDataDispenser::OpenScope-Methode mit dem REFIID IID_IMetaDataImport auf, um die IMetaDataImport-Schnittstelle abzurufen.
Suchen der Methode mit IMetaDataImport
Der Debugger ruft IMetaDataImport::GetMethodProps auf, um eine Methode anhand ihres Tokens zu suchen. Das Methodentoken kann mit der ICorDebugFunction::GetToken-Methode abgerufen werden. IMetaDataImport::GetMethodProps gibt die Signatur der Methode zurück.
Der Debugger ruft die IMetaDataImport::GetSigFromToken-Methode auf, um die lokale Signatur abzurufen, d. h. die Signatur der lokalen Variablen. Der Debugger muss das Token für die Signatur der lokalen Variablen angeben. Der Debugger kann dieses Token abrufen, indem er die ICorDebugFunction::GetLocalVarSigToken-Methode aufruft.
Erstellen der Funktionssignatur
Das Format der Signatur ist in der Spezifikation "Type and Signature Encoding in Metadata" festgelegt und hat Vorrang vor den Informationen in dieser Übersicht.
Im Abschnitt zur Methodendeklaration in der Spezifikation wird das Format für die Methodensignatur beschrieben. Das Format ist ein Einzelbyte für die Aufrufkonvention, gefolgt von einem Einzelbyte für die Argumentanzahl, gefolgt von einer Typenliste. Jeder Typ kann eine andere Größe aufweisen. Wenn eine Aufrufkonvention mit variablen Argumenten festgelegt ist, ist die Argumentanzahl die Gesamtzahl der Argumente, d. h. feste Argumente plus variable Argumente. Ein ELEMENT_TYPE_SENTINEL-Byte markiert, wo die festen Argumente enden und die variablen Argumente beginnen.
Im Abschnitt zu eigenständigen Signaturen in der Spezifikation wird das Format von lokalen Signaturen beschrieben. Eigenständige Signaturen verwenden keine variable Argumentaufrufkonvention.
Nach Abruf der Methodensignatur und der lokalen Signatur weist der Debugger Speicher für eine neue Signatur zu. Der Debugger sollte dann die Methodensignatur durchlaufen. Für jeden Typ sollte der Debugger das ELEMENT_TYPE_BYREF-Byte gefolgt vom Typ der neuen Signatur festlegen. Der Prozess sollte so lange wiederholt werden, bis entweder die Signatur für das Methodenende oder ein als ELEMENT_TYPE_SENTINEL markierter Typ erreicht wird. Danach sollte der Debugger die lokalen Signaturtypen kopieren und jeden Typ als ELEMENT_TYPE_BYREF markieren. Wenn die Methodensignatur über eine variable Argumentaufrufkonvention verfügt, sollte der Debugger diese Typen kopieren und als ELEMENT_TYPE_BYREF markieren. Zum Schluss muss der Debugger die Anzahl von Argumenten aktualisieren.
Siehe auch
Konzepte
Übersicht über das Debugging in der CLR