Machen Sie sich mit dem Fahrrad vertraut, Teil 1: Lernen Sie die Grundlagen zum Anpassen des Visual Studio-Debuggers mithilfe von Plugins

Eine der Neuerungen von Visual Studio 2012 war das Erscheinen eines neuen benutzerdefinierten Debuggers namens Concord. Dank seines Komponentensystems können VSIX-Plug-Ins das Verhalten des Debuggers anpassen und neue kontextsensitive Tools schreiben, die den Debugger für ihre Anforderungen verwenden können. Die API bietet viele QOL-Funktionen, z. B. das Zusammenführen von verwaltetem / nicht verwaltetem Code, die nahtlose Integration in einen remote / lokal debuggten Prozess und vieles mehr. Tatsächlich kann fast alles, was in der IDE getan werden kann, programmgesteuert mit der Concord-API durchgeführt werden! Ändern Sie die Werte bestimmter Variablen im laufenden Betrieb, rufen Sie Funktionen auf Anfrage auf (oder lassen Sie das Programm gezielt Aufrufe überspringen!). Plugins können nach PDB (!), Schrittweiser Umgehung und sogar Code-Änderung suchen! Öffnen Sie die Katze und lernen Sie diese wenig bekannten Innovationen im Bereich des Fahrradbaus kennen.

Sie sollten wahrscheinlich von vorne beginnen. Der Debugger erkennt die Komponenten, indem er Informationen aus der Datei vsdconfig liest , auf die im Manifest des VSIX-Plugins verwiesen wird. Vsdconfig gibt wiederum an, welche Schnittstellen von den Plug-in- Komponenten implementiert werden und wie diese Komponenten zu finden sind (Verknüpfung mit einer DLL-Datei, die die Klasse angibt, oder, im Fall der nativen Implementierung, CLSID. Ich werde Beispiele in C # angeben). Außerdem wird eine eindeutige Kennung (GUID) für jede Komponente sowie deren "Ebene" angegeben. Eine Ebene bestimmt, in welcher Reihenfolge Plugins verarbeitet werden und in welchem ​​Kontext diese Implementierung geladen wird - in den IDE-Prozess oder in den Prozess der debuggten Anwendung. Dies liegt an der Tatsache, dass einige Funktionen nur im Kontext einer IDE und umgekehrt funktionieren können - nur im Kontext eines debuggten Prozesses. Einige API-Funktionen arbeiten dort und dort auf die gleiche Weise. Außerdem haben einige Komponenten ihre eigenen Layoutregeln, da sie möglicherweise von vorhandenen Debugger-Elementen abhängen, die sich auf ihren festgelegten Ebenen befinden. Um Vorfälle zu vermeiden, empfehle ich RTFM (https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.debugger.componentinterfaces?view=visualstudiosdk-2017) und unabhängige Experimente in einer separaten Sandbox, was nicht schade sein wird Löschen, wenn etwas passiert (dies ist wiederum mit einer solchen Nuance verbunden - in einigen Fällen ist nicht einmal klar, warum die Assembly oder der Typ nicht geladen wird, da ich immer noch nicht finden konnte, wo die Protokolle erscheinen, die das Problem stabil signalisieren würden. Fehler, In Bezug auf eine Abhängigkeit, die nicht in den Zielprozess geladen werden kann, kann sie beispielsweise in der Ausgabe st erscheinen Di- oder nicht. Seien Sie akuratno, nehmen häufig Commits und sitzen nicht betrunken hinter dem Steuer).

Die Liste der Ebenen ist wie folgt (ich werde den Text in Englisch geben, damit der Leser keine Zwischenfälle beim Begehen von RFTM-Handlungen hat):

IDE-Komponentenebenen (Werte> 100.000):
KomponenteKomponentenebene
AD7 AL1.000.000.000
Demontageanbieter9.998.000
Stapelabfrage - Komponenten, die den Aufrufstapel abfragen möchten9,997,000
Stack-Anbieter9.996.000
Stapelfilter - Ebene, auf der Stapel gefiltert und mit Anmerkungen versehen werden können9.995.000
Haltepunkt-Manager9.994.000
IDE Expression Evaluation9.992.000
Symbolstapelwanderer - Stapelwanderer, die Zugriff auf Symbole benötigen9,991,000
IDE SymbolProvider - Komponenten, die Symbolinformationen für den Rest des Debuggers bereitstellen. Der Symbolpfad sollte nicht unterhalb dieser Ebene verwendet werden.1,999,000

Ebenen der Komponenten des Zielprozesses (Werte <99,999):
KomponenteKomponentenebene
Monitor Symbol Provider - Symbolanbieter, wenn der symbolische Status auf dem Zielcomputer erstellt wurde (Beispiel: Interpreter, dynamisch kompilierter / ausgegebener Code)75.000
Haltepunktbedingungsprozessor - Auf dieser Ebene werden Haltepunktbedingungen wie Bedingungsausdrücke und Trefferzahlen verarbeitet. Unterhalb dieses Punktes sind alle physischen Haltepunktereignisse sichtbar, unabhängig davon, ob sie falsche Bedingungen haben oder nicht.70.000
Monitor Task Provider - Dies ist die Ebene für das Task Data Mining im Zielprozess65.500
Ausdrucksauswertung überwachen65.000
Monitorkoordination - Komponenten, die zwischen den verschiedenen Monitoren nach Schritt, Haltepunkt nach nativer Adresse, Stackwalk usw. vermitteln.60.000
Überwachen Sie Stack-Walker55.000
Benutzerdefinierter Debug-Monitor - Reserviert für Debug-Monitore von Drittanbietern, die die von den Standard-Debug-Monitoren bereitgestellten Dienste nutzen möchten.40.500
Runtime Debug Monitor - Bietet Datenüberprüfung und Ausführungskontrolle für verwalteten / systemeigenen / Skriptcode40.000
Basis-Debug-Monitor10.000
Base Debug Monitor Services - Bietet Utility-Services für Base Debug-Monitore (z. B. Prozesserstellung) sowie Pre-Debug-Services (z. B. Prozessauflistung).1.000

Als nächstes in der Reihenfolge, den Prozess der Erstellung eines Projekts. Wenn es keine wichtigen Nuancen gäbe, könnte ich diesen Prozess minimal beschreiben oder überspringen, aber die Realitäten sind völlig anders - wir benötigen eine Reihe von Bibliotheksabhängigkeiten sowie ein Tool zum Erstellen von Konfigurationsdateien, die aus irgendeinem Grund nicht mit VisualStudio verteilt werden, aber verfügbar sind nur mit nuget. Tatsächlich müssen wir uns jetzt der Essenz zuwenden. Der Prozess zum Erstellen und Einrichten eines Projekts ist wie folgt aufgebaut:

  1. Öffnen Sie Visual Studio. In meinem Fall 2017 Community Edition
  2. VSIX-Projekt (Registerkarte Visual C # -> Erweiterbarkeit oder über die Suche). Nennen wir es "HelloVSIX"
  3. Fügen Sie ein neues Klassenbibliotheksprojekt in die Projektmappe ein und nennen Sie es "DebuggeePlugin".
  4. Wir setzen den Verweis auf das Projekt "DebuggeePlugin" im Projekt "HelloVSIX"
  5. Wir haben den Verweis auf die Assembly "Microsoft.VisualStudio.Debugger.Engine" in das DebuggeePlugin-Projekt eingefügt
  6. Fügen Sie dem Projekt „DebuggeePlugin“ den Verweis für das Nuget-Paket Microsoft.VSSDK.Debugger.VSDConfigTool hinzu. Dies ist unser Tool zum Generieren von VSD-Konfigurationen.

Jetzt sind wir bereit, unser Plugin dazu zu bringen, etwas Nützliches zu tun. Lassen Sie uns das einfachste tun, was Sie tun können - lassen Sie es eine MessageBox anzeigen, die "Hallo VSIX" sagt, wenn der Zielprozess auf einen Einstiegspunkt stößt. Dazu müssen wir eine Klasse erstellen, die die IDkmEntryPointNotification- Schnittstelle implementiert, sowie mehrere Konfigurationsdateien ausfüllen. Fügen Sie eine neue öffentliche Klasse mit dem Namen DkmEntryPointNotificationService hinzu, erben Sie die IDkmEntryPointNotification- Schnittstelle und belassen Sie die Standardimplementierung für den Moment :

using Microsoft.VisualStudio.Debugger; using Microsoft.VisualStudio.Debugger.ComponentInterfaces; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DebuggeePlugin { class DkmEntryPointNotificationService : IDkmEntryPointNotification { public void OnEntryPoint(DkmProcess process, DkmThread thread, DkmEventDescriptor eventDescriptor) { throw new NotImplementedException(); } } } 

Fügen Sie die Datei "DkmEntryPointNotificationService.vsdconfigxml" zum Projekt "DebuggeePlugin" hinzu. Für jede deklarierte Klasse, die Benachrichtigungen über Implementierungen der Namespace-Schnittstellen Microsoft.VisualStudio.Debugger.ComponentInterfaces erhalten soll, sollte eine solche Datei vorhanden sein. Übrigens ist es möglich, mehrere solcher Schnittstellen gleichzeitig in einer Klasse zu implementieren. Jetzt müssen wir die Build-Aktion unserer ".vsdconfigxml" -Datei ändern. Dazu müssen Sie die Projektdatei manuell bearbeiten (ich meine es ernst). Wir entladen das DebuggeePlugin-Projekt und öffnen es mit dem Studio-Editor. Wir müssen das folgende XLM-Tag finden:

 <None Include="DkmEntryPointNotificationService.vsdconfigxml" /> 

und verschieben Sie dieses Tag in Ihre eigene ItemGroup, indem Sie den Typ von None in VsdConfigXmlFiles ändern:
 <ItemGroup> <VsdConfigXmlFiles Include="DkmEntryPointNotificationService.vsdconfigxml" /> </ItemGroup> 

Sie können das Projekt speichern und neu laden.

Nun gehe zu den Einstellungen. Das erste, was zu tun ist: Wenn die Datei vsdconfig.xsd zum DebuggeePlugin-Projekt hinzugefügt wird, sollte sie gelöscht werden. Wir werden es jetzt ersetzen, da es einfacher ist, mit Rohtext zu arbeiten. Öffnen Sie DkmEntryPointNotificationService.vsdconfigxml und ersetzen Sie den Text durch den folgenden:

 <?xml version="1.0" encoding="utf-8"?> <Configuration xmlns="http://schemas.microsoft.com/vstudio/vsdconfig/2008"> <ManagedComponent ComponentId="422413E1-450E-40A6-AE24-7E80A81CC668" ComponentLevel=^_^quot𘙮quot^_^ AssemblyName="DebuggeePlugin"> <Class Name="DebuggeePlugin.DkmEntryPointNotificationService"> <Implements> <InterfaceGroup> <NoFilter/> <Interface Name="IDkmEntryPointNotification"/> </InterfaceGroup> </Implements> </Class> </ManagedComponent> </Configuration> > <?xml version="1.0" encoding="utf-8"?> <Configuration xmlns="http://schemas.microsoft.com/vstudio/vsdconfig/2008"> <ManagedComponent ComponentId="422413E1-450E-40A6-AE24-7E80A81CC668" ComponentLevel=^_^quot𘙮quot^_^ AssemblyName="DebuggeePlugin"> <Class Name="DebuggeePlugin.DkmEntryPointNotificationService"> <Implements> <InterfaceGroup> <NoFilter/> <Interface Name="IDkmEntryPointNotification"/> </InterfaceGroup> </Implements> </Class> </ManagedComponent> </Configuration> 

In einer solchen Datei müssen wir Folgendes angeben:

  1. ComponentId - Dieser Wert kann mit dem GUID-Generierungstool (Tools -> CreateGUID) generiert werden.
  2. ComponentLevel ist die Ebene unserer Komponente in der Hierarchie. In der obigen Tabelle und den Hilfeinformationen zu MSDN finden Sie Informationen zum Auswählen des gewünschten Wertebereichs.
  3. Assemblyname ist der Name unserer Assembly (keine Lösung!). In diesem Fall gibt es ein DebuggeePlugin
  4. Klassenname - sollte einschließlich des Namespaces angegeben werden, in dem sich die Klasse befindet. In diesem Fall DebuggeePlugin.DkmEntryPointNotificationService
  5. Array InterfaceGroup - Jeder Eintrag in ihm gibt eine von dieser Komponente implementierte Schnittstelle an. Innerhalb jedes InterfaceGroup-Knotens sollte sich ein Unterknoten befinden, der die Schnittstellen angibt, die allen Filtern in dieser Gruppe gemeinsam sind, aber später über Filter. Jetzt haben wir nur einen Schnittstellenknoten und tragen den Namen der IdkmEntryPointNotification-Schnittstelle. Wenn wir mehrere Schnittstellen hätten, gäbe es mehrere Schnittstellenknoten.

Lassen Sie mich daran erinnern, dass es für jede Klasse, die Benachrichtigungen vom Debugger erhalten soll, eine solche Datei geben muss. Aber der Spaß hört hier nicht auf. Jede solche Datei wird anschließend in einer .vsdconfig-Datei im Ausgabeverzeichnis des Projekts gesammelt. Und auf sie sollte im Plugin-Manifest verwiesen werden. Dies geschieht wie folgt:

  1. Nachdem wir die ".vsdconfigxml" -Datei generiert haben, müssen wir ... die Lösung einmal sammeln, da wir sonst keine .vsdconfig -Datei im Ausgabeverzeichnis des Projekts haben.)
  2. Öffnen Sie anschließend den Texteditor für die Datei source.extension.vsixmanifest und fügen Sie den folgenden Code vor dem schließenden PackageManifest-Tag hinzu:

  <Assets> <Asset Type="DebuggerEngineExtension" d:Source="File" Path="DebuggeePlugin.vsdconfig" /> </Assets> 

Wenn nach den abgeschlossenen Aktionen die Datei „DebuggeePlugin.vsdconfig“ im HelloVSIX-Projekt angezeigt wird, sollte sie AUS DEM PROJEKT ENTFERNT und die Lösung erneut gesammelt werden, andernfalls wird sie nicht aktualisiert.

Die Vorarbeiten sind beendet! Sie können mit dem Debuggen unseres Plugins beginnen. Dies erfolgt durch Starten einer experimentellen Instanz von VisualStudio (für VSIX-Projekte ist dies das Standard-Debug-Ziel, sodass keine zusätzlichen Schritte erforderlich sind). Tatsächlich klicken wir auf Debug-> StartDebugging und sehen eine experimentelle Instanz von VisualStudio. Standardmäßig sollte unser Plugin bereits installiert sein. Sie können dies über das Menü Extras-> Erweiterungen und Updates überprüfen.

Aufgrund der Tatsache, dass wir die IDkmEntryPointNotification-Schnittstelle implementiert haben, müssen wir ein Testprojekt in einer experimentellen Instanz von VisualStudio erstellen. Tatsächlich erstellen wir ein neues Projekt, wählen C ++ -> Konsolenanwendung (wählen Sie C ++, da die folgenden Beispiele C ++ - Besonderheiten enthalten), nennen es VSIXTestApp , führen es ohne Änderungen aus, sammeln und sehen, dass unsere experimentelle Instanz keine Ausnahme mehr innerhalb der DebuggeePlugin-Methode auslöst. DkmEntryPointNotificationService.OnEntryPoint. Großartig! Nun müssen Sie die MessageBox anzeigen. Dazu müssen Sie dem DebuggeePlugin-Projekt die folgenden Verweise hinzufügen:

  • Microsoft.VisualStudio.Shell.15.0
  • Microsoft.VisualStudio.Shell.Interop
  • Microsoft.VisualStudio.Shell.Interop.8.0
  • Microsoft.VisualStudio.OLE.Interop

Fügen Sie am Anfang der Datei DkmEntryPointNotificationService.cs zwei Verwendungen hinzu:

 using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; 

Fügen Sie der VsShellUtilities.ShowMessageBox- Methode in der DkmEntryPointNotificationService.OnEntryPoint-Methode einen Aufruf hinzu:

 using Microsoft.VisualStudio.Debugger; using Microsoft.VisualStudio.Debugger.ComponentInterfaces; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DebuggeePlugin { class DkmEntryPointNotificationService : IDkmEntryPointNotification { public void OnEntryPoint(DkmProcess process, DkmThread thread, DkmEventDescriptor eventDescriptor) { VsShellUtilities.ShowMessageBox(Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider, "Hello VSIX", "Hello VSIX", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); } } } 

Wir bauen um, starten eine experimentelle Instanz des Studios, starten ein Testprojekt und voila!

Wir sehen, dass die Testinstanz des Studios MessageBox erstellt hat!



Und was nützt das eigentlich?

Hier haben wir gelernt, wie ein VSIX-Projekt mit einem Plug-In für den Visual Studio-Debugger eingerichtet wird, wobei die meisten Nuancen berücksichtigt werden, die dem Ergebnis im Wege standen. Dies ist der Ausgangspunkt für detailliertere Arbeiten. Im nächsten Artikel zeige ich Ihnen einen weiteren wichtigen Punkt: Wie wird die Kommunikation zwischen der IDE und den Debug-Zielkomponenten durchgeführt?

Für weitere Hilfe bei der Verwendung der Concord-API können Sie nicht nur auf MSDN verweisen, sondern auch auf die folgenden Microsoft-Repositorys auf dem Github:
github.com/microsoft/PTVS
github.com/Microsoft/ConcordExtensibilitySamples

Source: https://habr.com/ru/post/de476298/


All Articles