踏上自行车的荆棘,第1部分:了解使用插件自定义Visual Studio调试器的基础知识

Visual Studio 2012的一项创新是伴随着一个名为Concord的新的自定义调试器的出现。 其组件系统允许VSIX插件自定义调试器的行为,并编写新的,上下文相关的工具,这些工具可以根据需要使用调试器。 其API提供了许多QOL功能,例如托管/非托管代码之间的封送处理,与远程/本地调试过程的无缝集成等。 实际上,几乎可以在IDE中完成的所有操作都可以使用Concord API以编程方式完成! 随时更改特定变量的值,根据要求调用函数(或专门使程序跳过对它们的调用!),插件可以按PDB(!),分步跳过甚至代码修改进行搜索! 打开猫,您将学到自行车制造领域的这些鲜为人知的创新。

您可能应该从头开始。 调试器通过从VSIX插件清单引用的vsdconfig文件中读取信息来检测组件。 反之, vsdconfig指示哪些接口由插件组件实现,以及如何找到这些组件(链接到.dll文件,指示类,或者,如果是本机实现,则指示CLSID。我将在C#中给出示例)。 而且,指示每个组件的唯一标识符(GUID)及其“级别”。 级别决定了将按什么顺序处理插件,以及在哪种过程的上下文中确定此实现将被加载到IDE进程或已调试应用程序的进程中。 这是由于以下事实:某些功能只能在IDE的上下文中起作用,反之亦然-仅在调试过程的上下文中才可以起作用。 一些API函数到处都是相同的方式。 而且,许多组件都有自己的布局规则,因为它们可能取决于位于其固定级别的现有调试器元素。 为避免发生事故,我建议在单独的沙箱中使用RTFM(https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.visualstudio.debugger.componentinterfaces?view=visualstudiosdk-2017)和独立实验,这并不可惜删除是否发生了什么(再次如此细微地联系在一起-在某些情况下,甚至还不清楚为什么不加载程序集或类型,因为我仍然找不到日志中出现的位置,可以稳定地指出问题。错误,例如,参考无法加载到目标进程的依赖项,它可能会出现在输出st中 二,还是不行。要akuratno,采取频繁的提交, 而不要坐在车轮后面醉)。

级别列表如下(我将提供英文文本,以使读者在执行RFTM行为时不会发生事件):

IDE组件级别(值> 100,000):
组成部分组件级别
AD7 AL1,000,000,000
拆卸提供商9,998,000
堆栈查询-希望查询调用堆栈的组件9,997,000
堆栈提供者9,996,000
堆栈过滤器-可以过滤和注释堆栈的级别9,995,000
断点经理9,994,000
IDE表达评估9,992,000
符号堆栈遍历器-需要访问符号的堆栈遍历器9,991,000
IDE SymbolProvider-为调试器的其余部分提供符号信息的组件。 在此级别以下不应使用符号路径。1,999,000

目标流程的组件级别(值<99.999):
组成部分组件级别
监视符号提供程序-在目标计算机上建立符号状态时的符号提供程序(例如:解释器,动态编译/发出的代码)75,000
断点条件处理器-此级别用于处理断点条件,例如条件表达式和命中计数。 在此点以下,无论是否具有错误条件,所有物理断点事件都将可见。70,000
监视任务提供者-这是目标流程中任务数据挖掘的级别65,500
监控表达评估器65,000
监视器协调-在各个监视器之间仲裁以进行步进,按本机地址的断点,堆栈遍历等的组件。60,000
监控堆垛机55,000
自定义调试监视器-保留给希望使用标准调试监视器提供的服务的第三方调试监视器。40,500
运行时调试监视器-为托管/本机/脚本代码提供数据检查和执行控制40,000
基本调试监视器10,000
基本调试监视器服务-为基本调试监视器(例如:进程创建)以及预调试服务(例如:进程枚举)提供实用程序服务1,000

接下来,依次介绍创建项目的过程。 如果没有任何重要的细微差别,我可以仅描述或略过此过程,但现实情况完全不同-我们将需要许多库依赖项,以及用于创建配置文件的工具,由于某种原因,该文件未随Visual Studio一起发布,但可以使用只能用nuget。 实际上,现在我们需要继续讲究本质。 创建和设置项目的过程的结构如下:

  1. 打开Visual Studio。 就我而言,2017年社区版
  2. VSIX项目( Visual C#->扩展性选项卡,或通过搜索)。 我们称之为“ HelloVSIX”
  3. 在解决方案中添加一个新的类库项目,并将其命名为“ DebuggeePlugin”
  4. 我们将引用放在项目“ HelloVSIX”中的项目“ DebuggeePlugin”上
  5. 我们在DebuggeePlugin项目中将对程序集“ Microsoft.VisualStudio.Debugger.Engine”的引用
  6. 将nuget包Microsoft.VSSDK.Debugger.VSDConfigTool的引用添加到项目“ DebuggeePlugin”。 这是我们用于生成VSD配置的工具。

现在,我们准备让我们的插件做一些有用的事情。 让我们做最简单的事情-当目标进程遇到入口点时,它显示一个MessageBox并显示“ Hello VSIX”。 为此,我们需要创建一个实现IDkmEntryPointNotification接口的类,并填写几个配置文件。 添加一个名为DkmEntryPointNotificationService的新公共类,并继承IDkmEntryPointNotification接口,并保留默认实现:

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(); } } } 

将文件“ DkmEntryPointNotificationService.vsdconfigxml”添加到“ DebuggeePlugin”项目。 对于应该通过Microsoft.VisualStudio.Debugger.ComponentInterfaces命名空间接口的实现接收通知的每个声明的类,您都应该具有这样的文件。 顺便说一句,可以在一类中一次实现多个这样的接口。 现在,我们需要更改“ .vsdconfigxml”文件的生成操作。 为此,您必须手动编辑项目文件(我很认真)。 我们卸载DebuggeePlugin项目,并使用Studio编辑器将其打开。 我们需要找到以下XLM标签:

 <None Include="DkmEntryPointNotificationService.vsdconfigxml" /> 

并将此标记移动到您自己的ItemGroup,将类型从“无”更改为VsdConfigXmlFiles:
 <ItemGroup> <VsdConfigXmlFiles Include="DkmEntryPointNotificationService.vsdconfigxml" /> </ItemGroup> 

您可以保存并重新加载项目。

现在,转到配置。 要做的第一件事:如果将vsdconfig.xsd文件添加到DebuggeePlugin项目,则应将其删除。 我们现在将替换它,因为它更容易处理原始文本。 打开DkmEntryPointNotificationService.vsdconfigxml并将文本替换为以下内容:

 <?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> 

在任何此类文件中,我们都需要指出以下内容:

  1. ComponentId-可以使用GUID生成工具(工具-> CreateGUID)生成此值
  2. ComponentLevel是组件在层次结构中的级别。 请参阅上表和MSDN上的帮助信息以选择所需的值范围。
  3. Assemblyname是我们的程序集的名称(不是解决方案!)。 在这种情况下,将有一个DebuggeePlugin
  4. 类名-应该指出,包括类所在的名称空间。 在这种情况下,DebuggeePlugin.DkmEntryPointNotificationService
  5. InterfaceGroup数组-其中的每个条目指示此组件实现的接口。 在每个InterfaceGroup节点内,应该有一个子节点,该子节点指示该组中每个人(过滤器)的通用接口,但后面有关过滤器的信息。 现在,我们只有一个Interface节点,并带有IdkmEntryPointNotification接口的名称。 如果我们有多个接口,那么将有多个Interface节点。

让我提醒您,对于每个应该从调试器接收通知的类,必须有一个这样的文件。 但是乐趣并没有就此结束。 随后,每个这样的文件都收集在项目输出目录中的.vsdconfig文件中。 并且应该在插件清单中引用它们。 这样做如下:

  1. 生成“ .vsdconfigxml”文件后,我们必须...收集一次解决方案,否则项目的输出目录中将没有任何.vsdconfig文件)
  2. 之后,打开source.extension.vsixmanifest文件的文本编辑器,并在关闭PackageManifest标记之前添加以下代码:

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

如果在完成操作后,“ DebuggeePlugin.vsdconfig”文件出现在HelloVSIX项目中,则应从项目中将其删除 ,并应再次收集解决方案,否则将不会更新。

准备工作结束了! 您可以开始调试我们的插件。 这是通过启动VisualStudio的实验实例来完成的(对于VSIX项目,这是默认的调试目标,因此不需要其他步骤)。 实际上,我们单击Debug-> StartDebugging,然后看到VisualStudio的实验实例。 在其中,默认情况下,我们的插件应已安装。 您可以通过“工具”->“扩展和更新”菜单进行验证。

由于我们实现了IDkmEntryPointNotification接口,因此我们将不得不在VisualStudio的实验实例中创建一个测试项目。 实际上,我们创建了一个新项目,选择C ++->控制台应用程序 (选择C ++,因为以下示例将包含C ++特定内容),将其称为VSIXTestApp ,无需进行任何更改即可运行,收集并看到我们的实验实例停止在DebuggeePlugin方法内引发异常。 DkmEntryPointNotificationService.OnEntryPoint。 太好了! 现在,您需要显示MessageBox。 为此,必须将以下引用添加到DebuggeePlugin项目:

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

在DkmEntryPointNotificationService.cs文件的开头添加两个用法:

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

并在DkmEntryPointNotificationService.OnEntryPoint方法中添加对VsShellUtilities.ShowMessageBox方法的调用:

 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); } } } 

我们重建,启动工作室的实验实例,启动测试项目,瞧!

我们看到工作室的测试实例创建了MessageBox!



而实际上有什么好处呢?

在这里,我们学习了如何设置VSIX项目,该项目包含用于Visual Studio调试器的插件,同时考虑了妨碍结果的大多数细微差别。 这是更详细工作的起点。 在下一篇文章中,我将向您展示另一个重要点:如何执行IDE与Debug目标组件之间的通信。

有关使用Concord API的进一步帮助,您不仅可以引用MSDN,还可以引用github上的以下Microsoft存储库:
github.com/microsoft/PTVS
github.com/Microsoft/ConcordExtensibilitySamples

Source: https://habr.com/ru/post/zh-CN476298/


All Articles