Percorra os espinhos das bicicletas, parte um: aprenda o básico da personalização do depurador do Visual Studio usando plug-ins

Uma das inovações do Visual Studio 2012 foi acompanhada pelo aparecimento de um novo depurador personalizado chamado Concord. Seu sistema de componentes permite que os plug-ins do VSIX personalizem o comportamento do depurador e gravem novas ferramentas sensíveis ao contexto que podem usar o depurador para suas necessidades. Sua API fornece muitos recursos de QV, como empacotamento entre código gerenciado / não gerenciado, integração perfeita com um processo remoto / depurado localmente e muito mais. De fato, quase tudo o que pode ser feito no IDE pode ser programaticamente usando a API Concord! Altere os valores de variáveis ​​específicas em tempo real, chame as funções mediante solicitação (ou faça o programa ignorar as chamadas para eles!). Os plug-ins podem pesquisar por PDB (!), Desvio passo a passo e até mesmo modificação de código! Abra o gato e você aprenderá sobre essas inovações pouco conhecidas no campo da construção de bicicletas.

Você provavelmente deve começar do começo. O depurador detecta os componentes lendo informações do arquivo vsdconfig referenciado pelo manifesto do plug-in VSIX. Por sua vez, vsdconfig indica quais interfaces são implementadas pelos componentes do plug-in e como encontrá-los (link para um arquivo .dll, indicando a classe ou, no caso da implementação nativa, indicando CLSID. Vou dar exemplos em C #). Além disso, um identificador exclusivo (GUID) para cada componente é indicado, bem como seu "nível". Um nível é o que determina em que ordem os plug-ins serão processados, bem como no contexto de qual processo essa implementação será carregada - no processo IDE ou no processo do aplicativo depurado. Isso ocorre porque algumas funcionalidades podem funcionar apenas no contexto de um IDE e vice-versa - apenas no contexto de um processo depurado. Algumas funções da API funcionam da mesma maneira lá e ali. Além disso, vários componentes têm suas próprias regras de layout, porque podem depender dos elementos depuradores existentes localizados em seus níveis fixos. Para evitar incidentes, recomendo o RTFM (https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.debugger.componentinterfaces?view=visualstudiosdk-2017) e experimentos independentes em uma sandbox separada, que não será uma pena excluir se algo acontecer (isso está conectado, novamente, com essa nuance - em alguns casos, nem está claro por que o assembly ou o tipo não está carregando, porque ainda não consegui encontrar onde os logs apareceriam que sinalizariam o problema de forma estável. por exemplo, com referência a uma dependência que não pode ser carregada no processo de destino, ela pode aparecer na saída st di-, ou não. Seja akuratno, tomar commits freqüentes, e não sentar-se atrás do bêbado roda).

A lista de níveis é a seguinte (darei o texto em inglês para que o leitor não tenha incidentes ao cometer atos de RFTM):

Níveis do componente IDE (valores> 100.000):
ComponenteNível de componente
AD7 AL1.000.000.000
Provedor de desmontagem9.998.000
Consulta de pilha - Componentes que desejam consultar a pilha de chamadas9.997.000
Fornecedor da pilha9.996.000
Filtro de pilha - nível em que as pilhas podem ser filtradas e anotadas9.995.000
Gerenciador de ponto de interrupção9.994.000
Avaliação de Expressão IDE9.992.000
Caminhantes de pilha de símbolos - caminhantes de pilha que precisam acessar símbolos9.991.000
IDE SymbolProvider - Componentes que fornecem informações de símbolo para o restante do depurador. O caminho do símbolo não deve ser usado abaixo deste nível.1.999.000

Níveis de componentes do processo de destino (valores <99.999):
ComponenteNível de componente
Provedor de símbolo do monitor - Provedores de símbolo quando o estado simbólico é construído no computador de destino (por exemplo: intérprete, código compilado / emitido dinamicamente)75.000
Processador de condição de ponto de interrupção - Esse nível é para processar condições de ponto de interrupção, como expressões de condição e contagem de ocorrências. Abaixo deste ponto, todos os eventos físicos do ponto de interrupção serão visíveis, independentemente de terem ou não condições falsas.70.000
Monitorar provedor de tarefas - este é o nível de mineração de dados da tarefa no processo de destino65.500
Monitor Expression Evaluator65.000
Coordenação do Monitor - Componentes que arbitram entre os vários monitores para pisar, pontos de interrupção por endereço nativo, caminhada na pilha, etc.60.000
Monitore os caminhantes da pilha55.000
Monitor de depuração personalizado - Reservado para monitores de depuração de terceiros que desejam fazer uso dos serviços fornecidos pelos monitores de depuração padrão.40.500
Monitor de Depuração de Tempo de Execução - Fornece inspeção de dados e controle de execução para código gerenciado / nativo / script40.000
Monitor de depuração base10.000
Serviços de Monitoramento de Depuração Base - fornece serviços utilitários para monitores de depuração base (por exemplo: criação de processos), bem como serviços de pré-depuração (por exemplo: enumeração de processos)1.000

Em seguida, em ordem, o processo de criação de um projeto. Se não houvesse nuances importantes, eu poderia descrever esse processo minimamente ou ignorá-lo, mas as realidades são completamente diferentes - precisamos de várias dependências da biblioteca, além de uma ferramenta para criar arquivos de configuração, que por algum motivo não são distribuídos com o VisualStudio, mas estão disponíveis somente com pepita. De fato, agora precisamos chegar ao ponto. O processo de criação e configuração de um projeto está estruturado da seguinte maneira:

  1. Abra o Visual Studio. No meu caso, 2017 Community Edition
  2. Projeto VSIX ( Visual C # -> guia Extensibilidade ou via pesquisa). Vamos chamá-lo de "HelloVSIX"
  3. Adicione um novo projeto de biblioteca de classes na solução e chame-o de "DebuggeePlugin"
  4. Colocamos a referência no projeto "DebuggeePlugin" no projeto "HelloVSIX"
  5. Colocamos a referência ao assembly "Microsoft.VisualStudio.Debugger.Engine" no projeto DebuggeePlugin
  6. Adicione a referência ao pacote de nuget Microsoft.VSSDK.Debugger.VSDConfigTool ao projeto "DebuggeePlugin". Esta é a nossa ferramenta para gerar configurações VSD.

Agora, estamos prontos para fazer nosso plug-in fazer algo útil. Vamos fazer a coisa mais simples que você pode fazer - deixe exibir uma MessageBox que diz "Hello VSIX" quando o processo de destino se deparar com um ponto de entrada. Para fazer isso, precisamos criar uma classe que implemente a interface IDkmEntryPointNotification , além de preencher vários arquivos de configuração. Adicione uma nova classe pública chamada DkmEntryPointNotificationService e herde a interface IDkmEntryPointNotification e deixe a implementação padrão por enquanto:

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

Adicione o arquivo "DkmEntryPointNotificationService.vsdconfigxml" ao projeto "DebuggeePlugin". Para cada classe declarada que deve receber notificações por meio de implementações das interfaces de espaço para nome Microsoft.VisualStudio.Debugger.ComponentInterfaces, você deve ter esse arquivo. A propósito, é possível implementar várias dessas interfaces ao mesmo tempo em uma classe. Agora precisamos alterar a ação de compilação do nosso arquivo ".vsdconfigxml". Para fazer isso, você deve editar manualmente o arquivo do projeto (seriamente). Nós descarregamos o projeto DebuggeePlugin e o abrimos com o editor do estúdio. Precisamos encontrar a seguinte tag XLM:

 <None Include="DkmEntryPointNotificationService.vsdconfigxml" /> 

e mova essa tag para seu próprio ItemGroup, alterando o tipo de None para VsdConfigXmlFiles:
 <ItemGroup> <VsdConfigXmlFiles Include="DkmEntryPointNotificationService.vsdconfigxml" /> </ItemGroup> 

Você pode salvar e recarregar o projeto.

Agora, vá para as configurações. A primeira coisa a fazer: se o arquivo vsdconfig.xsd for adicionado ao projeto DebuggeePlugin, ele deverá ser excluído. Vamos substituí-lo agora, porque é mais fácil trabalhar com texto bruto. Abra DkmEntryPointNotificationService.vsdconfigxml e substitua o texto pelo seguinte:

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

Nesse arquivo, seremos obrigados a indicar o seguinte:

  1. ComponentId - esse valor pode ser gerado usando a ferramenta de geração de GUID (Ferramentas -> CreateGUID)
  2. ComponentLevel é o nível do nosso componente na hierarquia. Consulte a tabela acima e ajude as informações no MSDN para selecionar o intervalo de valores desejado.
  3. Assemblyname é o nome da nossa montagem (não é uma solução!). Nesse caso, haverá um DebuggeePlugin
  4. Nome da classe - deve ser indicado, incluindo o espaço para nome no qual a classe está localizada. Nesse caso, DebuggeePlugin.DkmEntryPointNotificationService
  5. Matriz InterfaceGroup - cada entrada indica uma interface implementada por este componente. Dentro de cada nó do InterfaceGroup, deve haver um subnó indicando as interfaces comuns a todos neste grupo, o filtro, mas sobre os filtros posteriormente. Agora, temos apenas um nó de interface e carrega o nome da interface IdkmEntryPointNotification. Se tivéssemos várias interfaces, haveria vários nós de interface.

Deixe-me lembrá-lo que, para cada classe que deve receber notificações do depurador, deve haver esse arquivo. Mas a diversão não termina aí. Cada um desses arquivos, posteriormente, é coletado em um arquivo .vsdconfig no diretório de saída do projeto. E eles devem ser referenciados no manifesto do plug-in. Isso é feito da seguinte maneira:

  1. Depois de gerar o arquivo ".vsdconfigxml", precisamos ... coletar a solução uma vez, caso contrário não teremos nenhum arquivo .vsdconfig no diretório de saída do projeto)
  2. Depois disso, abra o editor de texto para o arquivo source.extension.vsixmanifest e adicione o seguinte código antes da tag PackageManifest de fechamento:

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

Se, após as ações concluídas, o arquivo “DebuggeePlugin.vsdconfig” aparecer no projeto HelloVSIX, ele deverá ser REMOVIDO DO PROJETO e a solução deverá ser coletada novamente, caso contrário, não será atualizado.

O trabalho preparatório acabou! Você pode começar a depurar nosso plugin. Isso é feito iniciando uma instância experimental do VisualStudio (para projetos VSIX, esse é o destino de depuração padrão, portanto, nenhuma etapa adicional é necessária). Na verdade, clicamos em Debug-> StartDebugging e vemos uma instância experimental do VisualStudio. Nele, por padrão, nosso plugin já deve estar instalado. Você pode verificar isso no menu Ferramentas-> Extensões e atualizações.

Devido ao fato de termos implementado a interface IDkmEntryPointNotification, teremos que criar um projeto de teste em uma instância experimental do VisualStudio. Na verdade, criamos um novo projeto, selecione C ++ -> Aplicativo de console (escolha C ++, porque os exemplos a seguir conterão detalhes em C ++), chame-o de VSIXTestApp , execute sem nenhuma alteração, colete e veja se nossa instância experimental parou de lançar uma exceção dentro do método DebuggeePlugin. DkmEntryPointNotificationService.OnEntryPoint. Ótimo! Agora você precisa mostrar a MessageBox. Para fazer isso, é necessário adicionar as seguintes referências ao projeto DebuggeePlugin:

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

Adicione duas utilizações no início do arquivo DkmEntryPointNotificationService.cs:

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

E adicione uma chamada ao método VsShellUtilities.ShowMessageBox no método DkmEntryPointNotificationService.OnEntryPoint:

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

Nós reconstruímos, lançamos uma instância experimental do estúdio, lançamos um projeto de teste e pronto!

Vemos que a instância de teste do estúdio criou o MessageBox!



E o que, de fato, beneficia?

Aqui, aprendemos como configurar um projeto VSIX contendo um plug-in para o depurador do Visual Studio, levando em consideração a maioria das nuances que atrapalharam o resultado. Este é o ponto de partida para um trabalho mais detalhado. No próximo artigo, mostrarei outro ponto importante: como a comunicação entre o IDE e os componentes de destino de Depuração é realizada.

Para obter mais ajuda sobre o uso da API Concord, você pode consultar não apenas o MSDN, mas também os seguintes repositórios da Microsoft no github:
github.com/microsoft/PTVS
github.com/Microsoft/ConcordExtensibilitySamples

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


All Articles