Este artigo foi traduzido por máquina.

Mecanismo de API do depurador

Escrevendo uma extensão para as Ferramentas de depuração do Windows, Parte 3: Clientes e retornos de chamada

Andrew Richards

Baixar o código de exemplo

Nesta terceira edição sobre a API do depurador, eu estou indo para aprofundar o relacionamento que pode ter uma extensão de depurador com o depurador. Vou te dar uma visão geral da arquitetura do depurador clientes e retornos de chamada do depurador. Ao fazê-lo, nós vamos chegar em essenciais das constantes DEBUG_OUTPUT_XXX e DEBUG_OUTCTL_XXX.

Vou usar essa base para implementar um encapsulamento da extensão de depurador do filho de Strike (SOS). Vou melhorar a saída de SOS com DML (linguagem de marcação de depurador) e demonstrar como você pode aproveitar os comandos de depurador interno e outras extensões para recuperar informações exigidas por suas extensões.

Antes de continuar, certifique-se de que você leu as anteriores duas parcelas para compreender que uma extensão de depurador é (incluindo como eu estou construindo e testando os exemplos neste artigo) e como corretamente produzir saída. Eles podem ser encontrados em msdn.microsoft.com/magazine/gg650659 e msdn.microsoft.com/magazine/hh148143.

Controle de saída

Há duas maneiras para produzir saída em uma extensão: as funções IDebugControl::Output * e IDebugControl::ControlledOutput * funções. As funções de saída são apenas uma simplificação das funções ControlledOuput. Eles recuperar o configuração de controle de saída atual e passam isso para ControlledOutput como o primeiro parâmetro:

HRESULT ControlledOutput(
  [in]  ULONG OutputControl,
  [in]  ULONG Mask,
  [in]  PCSTR Format,
         ...
);

Eu dei uma visão geral das constantes DEBUG_OUTCTL_XXX usado para o parâmetro OutputControl no segundo artigo. Neste artigo, vou concentrar-se sobre os bits que controlam o escopo da saída. Uma das constantes DEBUG_OUTCTL_XXX baseados no escopo deve ser usada para os bits menos significativos. O valor instrui que a saída é ir. Isso pode ser para todos os clientes de depurador (DEBUG_OUTCTL_ALL_CLIENTS), apenas o IDebugClient associado com a interface de IDebugControl (DEBUG_OUTCTL_THIS_CLIENT), todos os outros clientes (DEBUG_OUTCTL_ALL_OTHER_CLIENTS), nowhere at all (DEBUG_OUTCTL_IGNORE) ou apenas o arquivo de log (DEBUG_OUTCTL_LOG_ONLY).

Em Example06 no download de código acompanhamento (que é o mesmo que o artigo anterior), o! outctlpassed comando (consulte Figura 1) é baseado na interface IDebugClient passado.

Figura 1 ! outctlpassed

HRESULT CALLBACK 
outctlpassed(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  UNREFERENCED_PARAMETER(args);

  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->
    QueryInterface(__uuidof(IDebugControl), (void **)&pDebugControl)))
  {
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_THIS_CLIENT,           
      DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_THIS_CLIENT\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_CLIENTS,       
      DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_ALL_CLIENTS\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_OTHER_CLIENTS,  
      DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_ALL_OTHER_CLIENTS\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_IGNORE,             
      DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_IGNORE\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_LOG_ONLY,          
      DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_LOG_ONLY\n");

    pDebugControl->Release();
  }
  return S_OK;
}

A função faz uma QueryInterface para a interface de IDebugControl (de IDebugClient passado). Em seguida, ControlledOutput é chamado com cada um dos escopos de controle de saída. A máscara é definida como normal (DEBUG_OUTPUT_NORMAL) para que ele não é excluído da saída. Por padrão, apenas um valor de máscara de verbose (DEBUG_OUTPUT_VERBOSE) é excluído da janela de saída WinDBG. Para testar o comando, eu uso o script test_windbg.cmd. No WinDBG lançado, abro o log file (.logopen), executar o! outctlpassed comando e fechar o log file (.logclose), conforme mostrado aqui:

0: 000 > .logopen outctlpassed.txt
Abriu o arquivo de log 'outctlpassed.txt'
0: 000 > ! outctlpassed
DEBUG_OUTCTL_THIS_CLIENT
DEBUG_OUTCTL_ALL_CLIENTS
0: 000 > .logclose
Fechando o log aberto arquivo outctlpassed.txt

A! outctlpassed comando só inclui saída DEBUG_OUTCTL_THIS_CLIENT e DEBUG_OUTCTL_ALL_CLIENTS. O DEBUG_OUTCTL_ALL_OTHER_CLIENTS, DEBUG_OUTCTL_IGNORE e DEBUG_OUTCTL_LOG_ONLY de saída de controles são omitidos na janela de saída. Mas se você olhar no arquivo de log, há um resultado diferente, conforme mostrado aqui:

Abriu o arquivo de log 'outctlpassed.txt'
0: 000 > ! outctlpassed
DEBUG_OUTCTL_THIS_CLIENT
DEBUG_OUTCTL_ALL_CLIENTS
DEBUG_OUTCTL_ALL_OTHER_CLIENTS
DEBUG_OUTCTL_LOG_ONLY
0: 000 > .logclose
Fechando o log aberto arquivo outctlpassed.txt

A só falta a partir do arquivo de log de controle de saída é DEBUG_OUTCTL_IGNORE.

Arquitetura do depurador

Para entender o que aconteceu com a saída ausente na janela do WinDBG saída e o arquivo de log, precisamos aprofundar-se na arquitetura do depurador. Depuração baseia-se em quatro camadas: o auxiliar do depurador, o mecanismo de depuração, o depurador cliente e extensões do depurador.

Na parte inferior é o auxiliar do depurador (dbghelp.dll). Esta biblioteca contém todas as funcionalidades para resolver símbolos.

A próxima camada é o mecanismo de depuração (dbgeng.dll). Esta biblioteca manipula a sessão de depuração e, em particular, fornece o suporte para depurar um destino remoto. Incluídos nesta biblioteca é a manipulação das interfaces do depurador (por exemplo, IDebugClient, IDebugControl e assim por diante).

A próxima camada é a camada Debugger Client (cliente). Nesta camada, entrada e saída são processados como o cliente aprouver. Os depuradores WinDBG e NTSD estão nessa camada. Um cliente usa o depurador motor DebugCreate e DebugConnect funções para criar um objeto IDebugClient que é anexado para o destino desejado. O destino pode ser local ou remoto (através do apoio de proxy do mecanismo de depuração).

A última camada é depurador extensões (extensão). Um cliente chama comandos exportados a DLL de extensão de depurador. A interface IDebugClient que o cliente depurador recebe do mecanismo do depurador é a mesma interface que o cliente passa para extensões. Embora chamado por um cliente, uma extensão apenas interage com o mecanismo de depuração. Extensões podem compartilhar a interface IDebugClient com o cliente, enquanto eles não danificar sua configuração. Como alternativa, eles podem fazer seu próprio cliente (via IDebugClient::CreateClient) e configurá-lo conforme necessário.

Vários clientes de depurador

Vários clientes (objetos IDebugClient) podem ser anexados à mesma sessão de depuração; é a sessão que é singular, não os clientes. Cada cliente tem sua própria interface particular para o mecanismo de depuração, e é essa capacidade que nós estamos indo para aproveitar.

Inicialmente, quando você anexa WinDBG ou NTSD a um processo, há apenas um cliente — a interface de IDebugClient retornada por DebugCreate ou DebugConnect. Quando uma extensão chama IDebugClient::CreateClient contra essa interface retornada, o mecanismo de depuração faz outro cliente que tem associado com a sessão. O novo cliente é anexado à mesma sessão de depuração, mas ele está em um estado padrão. Retornos de chamada não são definidos sobre ela e a máscara de saída é o valor padrão (tudo exceto Verbose).

Em Figura 2, o! outctlcreate faz a mesma saída como! outctlpassed, mas é baseado em um objeto IDebugClient recém-criado.

Figura 2 ! outctlcreate

HRESULT CALLBACK 
outctlcreate(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  UNREFERENCED_PARAMETER(args);

  IDebugClient* pDebugCreated;
  if (SUCCEEDED(pDebugClient->CreateClient(&pDebugCreated)))
  {
    IDebugControl* pDebugControl;
    if (SUCCEEDED(pDebugCreated->QueryInterface(__uuidof(IDebugControl), 
      (void **)&pDebugControl)))
    {
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_THIS_CLIENT,        
        DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_THIS_CLIENT\n");
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_CLIENTS,       
        DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_ALL_CLIENTS\n");
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_OTHER_CLIENTS, 
        DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_ALL_OTHER_CLIENTS\n");
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_IGNORE,            
        DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_IGNORE\n");
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_LOG_ONLY,          
        DEBUG_OUTPUT_NORMAL, "DEBUG_OUTCTL_LOG_ONLY\n");
      pDebugControl->Release();
    }
    pDebugCreated->Release();
  }
  return S_OK;
}

Como antes, para testar o comando, eu uso o script test_windbg.cmd. No WinDBG lançado, abro o log file (.logopen), executar o! outctlcreate comando e fechar o log file (.logclose), conforme mostrado aqui:

0: 000 > .logopen outctlcreate.txt
Abriu o arquivo de log 'outctlcreate.txt'
0: 000 > ! outctlcreate
DEBUG_OUTCTL_ALL_CLIENTS
DEBUG_OUTCTL_ALL_OTHER_CLIENTS
0: 000 > .logclose
Fechando o log aberto arquivo outctlcreate.txt

A! outctlcreate comando inclui apenas a saída DEBUG_OUTCTL_ALL_CLIENTS e DEBUG_OUTCTL_ALL_OTHER_CLIENTS. A "todos os outros" saída substitui o "isso" porque o cliente WinDBG agora é o "outro" cliente de saída. Os resultados do arquivo de log ainda são os mesmos, conforme mostrado aqui:

Abriu o arquivo de log 'outctlcreate.txt'
0: 000 > ! outctlcreate
DEBUG_OUTCTL_THIS_CLIENT
DEBUG_OUTCTL_ALL_CLIENTS
DEBUG_OUTCTL_ALL_OTHER_CLIENTS
DEBUG_OUTCTL_LOG_ONLY
0: 000 > .logclose
Fechando o log aberto arquivo outctlcreate.txt

Dentro do mecanismo de depuração, a chamada ControlledOutput está sendo condicionalmente posta em prática. O mecanismo invoca o retorno de chamada de saída em todos os clientes que correspondam aos critérios de controle de saída. Assim, se você fizer seu próprio cliente e usa o controle de saída DEBUG_OUTCTL_THIS_CLIENT, a saída vai ficar local (isto é, não exibido no WinDBG). Observe que a saída ainda vai para o arquivo de log. Se você adicionar o bit alto "não conectado" (DEBUG_OUTCTL_NOT_LOGGED), você pode parar isso, também.

Mais uma vez, o! outctlthis comando faz exatamente "isso" e "não conectado" de saída (DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED) em um cliente criado, como mostrado na Figura 3.

Figura 3 ! outctlthis

HRESULT CALLBACK 
outctlthis(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  UNREFERENCED_PARAMETER(args);

  IDebugClient* pDebugCreated;
  if (SUCCEEDED(pDebugClient->CreateClient(&pDebugCreated)))
  {
    IDebugControl* pDebugControl;
    if (SUCCEEDED(pDebugCreated->QueryInterface(__uuidof(IDebugControl), 
      (void **)&pDebugControl)))
    {
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_THIS_CLIENT |  
        DEBUG_OUTCTL_NOT_LOGGED, DEBUG_OUTPUT_NORMAL, 
        "DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED\n");
      pDebugControl->Release();
    }
    pDebugCreated->Release();
  }
  return S_OK;
}

Como antes, para testar o comando, eu uso o script test_windbg.cmd. No WinDBG lançado, abro o log file (.logopen), executar o! outctlthis de comando e fechar o arquivo de log (.logclose), conforme mostrado aqui:

0: 000 > .logopen outctlthis.txt
Abriu o arquivo de log 'outctlthis.txt'
0: 000 > ! outctlthis
0: 000 > .logclose
Fechando o log aberto arquivo outctlthis.txt

O arquivo de log não grava a saída do comando desta vez. Observe que ele gravar a execução porque rápida saída está sendo criada pelo cliente WinDBG antes de chamar de extensão, como mostrado aqui:

Abriu o arquivo de log 'outctlthis.txt'
0: 000 > ! outctlthis
0: 000 > .logclose
Fechando o log aberto arquivo outctlthis.txt

Com efeito, eu tenho Redirecionado saída para NULL porque o cliente criado não tem qualquer retorno de chamada de saída associados — não é exatamente uma coisa útil. Mas se eu alterar o código para usar a função IDebugControl::Execute, agora pode executar qualquer comando sem ele sendo exibido em WinDBG ou sendo registrados, conforme mostrado aqui:

pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED, 
  args, DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);

Ao usar Execute desta forma, você precisará definir os sinalizadores DEBUG_EXECUTE_NOT_LOGGED e DEBUG_EXECUTE_NO_REPEAT para que a saída imediata não é gerada no arquivo de log e o comando não é salvo de repetição (por exemplo, para quando o usuário executa um comando em branco pressionando Enter no prompt de comando).

Como mostrado na Figura 4, no final o! outctlexecute comando executa silenciosamente o argumento fornecido.

Figura 4 ! outctlexecute

HRESULT CALLBACK 
outctlexecute(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugClient* pDebugCreated;
  if (SUCCEEDED(pDebugClient->CreateClient(&pDebugCreated)))
  {
    IDebugControl* pDebugControl;
    if (SUCCEEDED(pDebugCreated->QueryInterface(__uuidof(IDebugControl), 
      (void **)&pDebugControl)))
  {
    pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED, 
      args, DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);
    pDebugControl->Release();
  }
  pDebugCreated->Release();
  }
  return S_OK;
}

Eu estou executando uma recarga forçada de símbolos (. Reload /f). Isso normalmente iria produzir muita saída de símbolo, mas! outctlexecute oculta a saída, conforme mostrado aqui:

0: 000 > ! outctlexecute. Reload /f.

Retornos de chamada de saída

Quando a saída de rotas do mecanismo de depuração solicita aos clientes (indicados pelo controle de saída), há duas restrições adicionais aplicadas:

  • É a saída de um tipo (máscara) que é procurado?
  • Há um retorno de chamada de saída associados?

Quando é chamado IDebugControl::Output ou IDebugControl::ControlledOutput, o mecanismo de depuração compara a máscara fornecida com máscara de saída do cliente (IDebugClient::GetOutputMask). As duas máscaras precisam corresponder para o retorno de chamada de saída ser chamado.

Em seguida, o mecanismo de depuração chama IDebugClient::GetOutputCallbacks para obter uma interface de retorno de chamada de saída (IDebugOutputCallbacks, IDebugOutputCallbacks2 ou IDebugOutputCallbacksWide). Se houver um conjunto de retorno de chamada de saída no cliente (anteriormente feito via IDebugClient::SetOutputCallbacks), o mecanismo de depuração invoca o retorno de chamada — por exemplo, IDebugOutputCallbacks::Output.

Em Example07 no download de código que acompanha, eu tenho codificado uma implementação de IDebugOutputCallbacks que suporta 1MB de saída normal e 1 MB de saída de erro. (O cabeçalho é em Figura 5 e o código está no Figura 6.)

Figura 5 OutputCallbacks.h

#ifndef __OUTPUTCALLBACKS_H__
#define __OUTPUTCALLBACKS_H__

#include "dbgexts.h"

class COutputCallbacks : public IDebugOutputCallbacks
{
  private:
    long m_ref;
    PCHAR m_pBufferNormal;
    size_t m_nBufferNormal;
    PCHAR m_pBufferError;
    size_t m_nBufferError;

  public:
    COutputCallbacks()
     {
      m_ref = 1;
      m_pBufferNormal = NULL;
      m_nBufferNormal = 0;
      m_pBufferError = NULL;
      m_nBufferError = 0;
    }

    ~COutputCallbacks()
    {
      Clear();
    }

    // IUnknown
    STDMETHOD(QueryInterface)(__in REFIID InterfaceId, __out PVOID* Interface);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    // IDebugOutputCallbacks
    STDMETHOD(Output)(__in ULONG Mask, __in PCSTR Text);

    // Helpers
    ULONG SupportedMask() { return DEBUG_OUTPUT_NORMAL | DEBUG_OUTPUT_ERROR; }
    PCHAR BufferNormal() { return m_pBufferNormal; }
    PCHAR BufferError() { return m_pBufferError; }
    void Clear();   
};

#endif // #ifndef __OUTPUTCALLBACKS_H__

Figura 6 OutputCallbacks.cpp

#include "dbgexts.h"
#include "outputcallbacks.h"

#define MAX_OUTPUTCALLBACKS_BUFFER 0x1000000  // 1Mb
#define MAX_OUTPUTCALLBACKS_LENGTH 0x0FFFFFF  // 1Mb - 1

STDMETHODIMP COutputCallbacks::QueryInterface(__in REFIID InterfaceId, 
  __out PVOID* Interface)
{
  *Interface = NULL;
  if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) || IsEqualIID(InterfaceId, 
    __uuidof(IDebugOutputCallbacks)))
  {
    *Interface = (IDebugOutputCallbacks *)this;
    InterlockedIncrement(&m_ref);
    return S_OK;
  }
  else
  {
    return E_NOINTERFACE;
  }
}

STDMETHODIMP_(ULONG) COutputCallbacks::AddRef()
{
  return InterlockedIncrement(&m_ref);
}

STDMETHODIMP_(ULONG) COutputCallbacks::Release()
{
  if (InterlockedDecrement(&m_ref) == 0)
  {
    delete this;
    return 0;
  }
  return m_ref;
}

STDMETHODIMP COutputCallbacks::Output(__in ULONG Mask, __in PCSTR Text)
{
  if ((Mask & DEBUG_OUTPUT_NORMAL) == DEBUG_OUTPUT_NORMAL)
  {
    if (m_pBufferNormal == NULL)
    {
      m_nBufferNormal = 0;
      m_pBufferNormal = (PCHAR)malloc(sizeof(CHAR)*(MAX_OUTPUTCALLBACKS_BUFFER));
      if (m_pBufferNormal == NULL) return E_OUTOFMEMORY;
      m_pBufferNormal[0] = '\0';
      m_pBufferNormal[MAX_OUTPUTCALLBACKS_LENGTH] = '\0';
    }
    size_t len = strlen(Text);
    if (len > (MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferNormal))
    {
      len = MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferNormal;
    }
    if (len > 0)
    {
      memcpy(&m_pBufferNormal[m_nBufferNormal], Text, len);
      m_nBufferNormal += len;
      m_pBufferNormal[m_nBufferNormal] = '\0';
    }
  }
  if ((Mask & DEBUG_OUTPUT_ERROR) == DEBUG_OUTPUT_ERROR)
  {
    if (m_pBufferError == NULL)
    {
      m_nBufferError = 0;
      m_pBufferError = (PCHAR)malloc(sizeof(CHAR)*(MAX_OUTPUTCALLBACKS_BUFFER));
      if (m_pBufferError == NULL) return E_OUTOFMEMORY;
      m_pBufferError[0] = '\0';
      m_pBufferError[MAX_OUTPUTCALLBACKS_LENGTH] = '\0';
    }
    size_t len = strlen(Text);
    if (len >= (MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferError))
    {
      len = MAX_OUTPUTCALLBACKS_LENGTH-m_nBufferError;
    }
    if (len > 0)
    {
      memcpy(&m_pBufferError[m_nBufferError], Text, len);
      m_nBufferError += len;
      m_pBufferError[m_nBufferError] = '\0';
    }
  }
  return S_OK;
}

void COutputCallbacks::Clear()
{
  if (m_pBufferNormal)
  {
    free(m_pBufferNormal);
    m_pBufferNormal = NULL;
    m_nBufferNormal = 0;
  }
  if (m_pBufferError)
  {
    free(m_pBufferError);
    m_pBufferError = NULL;
    m_nBufferError = 0;
  }
}

A classe anexa a Cadeia de caracteres para o normal ou buffer de erro após cada chamada de saída. Além das funções necessárias para o IUnknown e IDebugOutputCallbacks interfaces, existem algumas funções adicionais para fornecer acesso aos buffers (BufferNormal e BufferError), para limpar (claro)-los e uma função para obter os tipos de saída suportados (SupportedMask).

Cada vez que saída ou ControlledOutput é invocado (e os critérios IDebugClient), o retorno de chamada de saída é chamada de função. A máscara de passado é a mesma máscara que é definir na chamada saída original. Minha função de saída faz comparação de máscara de bits na máscara de passado, para que o texto está associado com o buffer correto. Cabe a implementação de retorno de chamada para decidir se a saída é armazenada separadamente, combinada ou ignorada. Uma única chamada de IDebugClient::Execute pode resultar em centenas de chamadas de saída dos tipos de variáveis. Porque eu quero interpretar a saída normal e erro de toda a operação de executar, eu concatenar essas cadeias de caracteres em dois buffers associados.

A interface IDebugOutputCallbacks é passada a Cadeia de caracteres no formato ANSI e texto. Se o texto original é em Unicode ou em DML, o motor fará uma conversão antes para o retorno de chamada. Para capturar o Unicode (wide) conteúdo, use a interface IDebugOutCallbacksWide. A única diferença entre estas duas interfaces é o parâmetro de texto sendo passado como PCWSTR em vez de PCSTR. Ambas essas interfaces somente oferecem suporte a notificação de saída com base em texto. Eles não recebem texto formatado DML.

Para obter conteúdo DML, você precisará usar a interface IDebugOutputCallbacks2. Este dá interface você controle de como o texto recebido pelo retorno de chamada é formatado: texto, DML ou qualquer um. A função GetInterestMask é usada para definir isso. Ele é chamado quando a interface é definida em um cliente via IDebugClient::SetOutputCallbacks. Você retorna se você deseja receber qualquer tipo de saída (DEBUG_OUTCBI_ANY_FORMAT), DML (DEBUG_OUTCBI_DML) ou texto (DEBUG_OUTCBI_TEXT). Note que este não é o mesmo que a máscara de saída.

A função de saída não tem nenhum propósito nesta interface. é um artefato da herança de interface (falsa) de IDebugCallbacks. Todas as notificação de retorno de chamada de saída é através da função Output2:

STDMETHOD(Output2)(ULONG Which, ULONG Flags, ULONG64 Arg, PCWSTR Text)

O qual parâmetro especifica se o texto (DEBUG_OUTCB_TEXT) ou DML (DEBUG_OUTCB_DML) está sendo passado, ou que é desejado um flush (DEBUG_OUTCB_EXPLICIT_FLUSH) (IDebugClient::FlushCallbacks). Note que estes são os CB * constantes. não as CBI * constantes usadas para a máscara de interesse.

O parâmetro Flags especifica informações adicionais em uma máscara de bits. Em particular, para o conteúdo DML especifica se o conteúdo contém hiperlinks (DEBUG_OUTCBF_DML_HAS_TAGS) ou caracteres especiais (", &, < e >) que são codificados (DEBUG_OUTCBF_DML_HAS_SPECIAL_CHARACTERS). O parâmetro também pode indicar que um flush (DEBUG_OUTCBF_COMBINED_EXPLICIT_FLUSH) é desejado após a saída foi processada. Isso ocorre quando chamadas de saída e FlushCallbacks são combinadas pelo mecanismo.

O parâmetro Arg contém a máscara de saída DML, texto e FLUSH retornos de chamada.

Finalmente, o parâmetro de texto é o texto que está sendo outputted. Isso será NULL quando o retorno de chamada está sendo chamado para um flush (DEBUG_OUTCB_EXPLICIT_FLUSH). O texto sempre é passado como Unicode.

Execução

Não é preciso ser muito código para adicionar o retorno de chamada para o! outctlexecute comando (consulte Figura 4). As etapas adicionais são para alocar o retorno de chamada e associá-lo com a interface de IDebugClient criada antes da chamada para executar.

Eu implementar minhas retornos de chamada como objetos de heap contados referenciado. Se você estiver usando o ambiente de compilação do Windows Driver Kit, você precisará desativar "PREfast para Drivers" aviso 28197 para que você não receber um aviso de OACR (Microsoft Auto revisão de código) sobre a "nova" alocação de cada vez que você compilar o projeto. (PREfast não detecta que a classe é referência contada.)

Há duas partes para a associação de retorno de chamada para o cliente: a máscara de saída (SetOutputMask) e o retorno de chamada de saída (SetOutputCallbacks). Eu tenho uma função auxiliar em minha classe de retorno de chamada para retornar a máscara de saída suportados (COutputCallbacks::SupportedMask) para que eu não tenho que codificá-lo. Se não definir uma máscara, iria correr o risco de não receber a saída esperada.

A! callbackecho comando (consulte Figura 7) é implementado no projeto Example07 no download de acompanhamento.

Figura 7 ! callbackecho

HRESULT CALLBACK 
callbackecho(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugClient* pDebugCreated;
  if (SUCCEEDED(pDebugClient->CreateClient(&pDebugCreated)))
  {
    IDebugControl* pDebugControl;
    if (SUCCEEDED(pDebugCreated->QueryInterface(__uuidof(IDebugControl), 
      (void **)&pDebugControl)))
    {
      // Create our IDebugOutputCallbacks-based object
      #pragma warning( disable : 28197 ) // PreFAST sees the 'new' as a leak due to 
                                     // the use of AddRef/Release
      COutputCallbacks* pOutputCallbacks = new COutputCallbacks;
      if (pOutputCallbacks != NULL)
      {
        // Apply IDebugOutputCallbacks to the IDebugClient
        if (SUCCEEDED(pDebugCreated->
          SetOutputMask(pOutputCallbacks->SupportedMask())) &&   
          SUCCEEDED(pDebugCreated->SetOutputCallbacks(pOutputCallbacks)))
      {
        // Execute and display 'vertarget'
        pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED, 
          "vertarget", DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);
        pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_OTHER_CLIENTS, 
          DEBUG_OUTPUT_NORMAL, "[Normal]\n%s\n", pOutputCallbacks->BufferNormal() ?
pOutputCallbacks->BufferNormal() : "");
        pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_OTHER_CLIENTS, 
          DEBUG_OUTPUT_ERROR , "[Error] \n%s\n", pOutputCallbacks->BufferError() ?
pOutputCallbacks->BufferError() : "");

        // Clear the callback buffers
        pOutputCallbacks->Clear();

        // Execute and display the passed command
        pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED, 
          args, DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);
        pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_OTHER_CLIENTS, 
          DEBUG_OUTPUT_NORMAL, "[Normal]\n%s\n", pOutputCallbacks->BufferNormal() ?
pOutputCallbacks->BufferNormal() : "");
        pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_OTHER_CLIENTS, 
          DEBUG_OUTPUT_ERROR , "[Error] \n%s\n", pOutputCallbacks->BufferError() ?
pOutputCallbacks->BufferError() : "");

        pDebugCreated->SetOutputCallbacks(NULL); // It's a best practice to 
                                                // release callbacks 
                                                // before the client release
        }
        pOutputCallbacks->Release();
      }
      pDebugControl->Release();
    }
    pDebugCreated->Release();
  }
  return S_OK;
}

Ele executa dois comandos ("vertarget" e o argumento passado) e exibe a saída normal e erro de cada um. Ao usar a função ControlledOutput, você notará que eu use o controle de saída DEBUG_OUTCTL_ALL_OTHER_CLIENTS. Isso direciona a saída para todos os outros clientes e evita a saída está sendo capturada pelo meu retorno de chamada de saída associados. Se você está um loop que capturou a saída e estão produzindo saída da mesma máscara, certifique-se de que você usar o controle de saída DEBUG_OUTCTL_ALL_OTHER_CLIENTS, caso contrário você pode facilmente obter-se em um loop infinito.

Como antes, eu uso o script test_windbg.cmd para testar o comando. No WinDBG lançado, executo o! callbackecho comando e ver a saída normal de "vertarget" e o erro de saída de "r @ rip". (O comando register falha porque este é um destino de x 86 e "rip" é um registo de 64 x). Observe que, entre as chamadas Execute, eu chamo minha função auxiliar Clear para redefinir os buffers. Isso é o que faz com que a saída de "vertarget" estar ausentes da saída do comando "r @ rip", conforme mostrado aqui:

0: 000 > ! callbackecho r @ rip
[Normal]
Compatível com o Windows 7 versão 7600 MP (2 procs) livre x86
Produto: WinNt, suite: SingleUserTS
Kernel32. dll versão: 6.1.7600.16385 (win7_rtm.090713-1255)
Nome da máquina:
Tempo de sessão de depuração: Sat 29 de Jan 22:01:59.080 2011 (UTC - 8: 00)
Tempo de atividade do sistema: 0 dias 6:22:48.483
Tempo de atividade do processo: 0 dias 0:01:05.792

Tempo de kernel: 0 dias 0:00:00.015
Tempo de usuário: 0 dias 0:00:00.000
 
[Error]

[Normal]
 
[Error]**
^ Bad registrar o erro em 'r @ rip'

Na verdade, você pode implementar a mesma funcionalidade sem fazer um cliente de seu próprio. Se você trocar cuidadosamente a máscara de saída e o retorno de chamada de saída do cliente passado com seus próprios valores, você pode obter o mesmo resultado. No entanto, você precisa ser muito precisas ao fazê-lo. O IDebugClient passado é o cliente que usa o depurador (WinDBG) para saída para a janela de saída. Se você não devolvê-lo para o Estado que iniciou-se em, o depurador deixarão de funcionar corretamente.

Figura 8 (de Example07 no download de código) mostra a abordagem toggling usando o! commandtoggle comando.

Figura 8 ! callbacktoggle

HRESULT CALLBACK 
callbacktoggle(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    // Remember the original mask and callback
    ULONG ulOriginalMask;
    IDebugOutputCallbacks* pOriginalCallback;
    if (SUCCEEDED(pDebugClient->GetOutputMask(&ulOriginalMask)) &&
      SUCCEEDED(pDebugClient->GetOutputCallbacks(&pOriginalCallback)))
    {
      // Create our IDebugOutputCallbacks based object
      #pragma warning( disable : 28197 ) // PreFAST sees the 'new' as a leak due to 
                                     // the use of AddRef/Release
      COutputCallbacks* pOutputCallbacks = new COutputCallbacks;
      if (pOutputCallbacks != NULL)
      {
        // Apply IDebugOutputCallbacks to the IDebugClient
        if (SUCCEEDED(pDebugClient->SetOutputMask(pOutputCallbacks->
          SupportedMask())) && (pDebugClient->
          SetOutputCallbacks(pOutputCallbacks)))
        {
          // Execute 'vertarget'
          pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | 
            DEBUG_OUTCTL_NOT_LOGGED, "vertarget", 
            DEBUG_EXECUTE_NOT_LOGGED | DEBUG_EXECUTE_NO_REPEAT);

          // Revert the mask and callback so we can do some output
          pDebugClient->SetOutputMask(ulOriginalMask);
          pDebugClient->SetOutputCallbacks(pOriginalCallback);

          // Display 'vertarget'
          pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_CLIENTS,  
            DEBUG_OUTPUT_NORMAL, "[Normal]\n%s\n", pOutputCallbacks->
            BufferNormal() ?
pOutputCallbacks->BufferNormal() : "");
          pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_CLIENTS, 
            DEBUG_OUTPUT_ERROR , "[Error] \n%s\n", pOutputCallbacks->
            BufferError() ?
pOutputCallbacks->BufferError() : "");

          // Go back to our mask and callback so we can do the private callback
          pDebugClient->SetOutputMask(pOutputCallbacks->SupportedMask());
          pDebugClient->SetOutputCallbacks(pOutputCallbacks);

          // Clear the callback buffers
          pOutputCallbacks->Clear();

          // Execute the passed command
          pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT | 
            DEBUG_OUTCTL_NOT_LOGGED, args, DEBUG_EXECUTE_NOT_LOGGED |  
            DEBUG_EXECUTE_NO_REPEAT);

          // Revert the mask and callback so we can do some output
          pDebugClient->SetOutputMask(ulOriginalMask);
          pDebugClient->SetOutputCallbacks(pOriginalCallback);

          // Display the passed command
          pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_CLIENTS, 
            DEBUG_OUTPUT_NORMAL, "[Normal]\n%s\n", pOutputCallbacks->
            BufferNormal() ?
pOutputCallbacks->BufferNormal() : "");
          pDebugControl->ControlledOutput(DEBUG_OUTCTL_ALL_CLIENTS, 
            DEBUG_OUTPUT_ERROR , "[Error] \n%s\n", pOutputCallbacks->
            BufferError() ?
pOutputCallbacks->BufferError() : "");
        }

        // Revert the mask (again) for the case 
        // where the mask was set but the callback wasn't
        pDebugClient->SetOutputMask(ulOriginalMask);
        pOutputCallbacks->Release();
      }
    }
    pDebugControl->Release();
  }
  return S_OK;
}

Ele implementa exatamente a mesma funcionalidade como! commandecho.Para ser capaz de chamar Execute e ControlledOutput, que eu precisava freqüentemente para alternar os valores de retorno de chamada de máscara e saída de saída entre os valores passados e meus valores para obter o resultado desejado.Note que o ControlledOutput é passado a DEBUG_OUTCTL_ALL_CLIENTS de saída controle nesse cenário, como o cliente associado agora é um destino de saída desejado.Além disso, você notará que implementei somente (para abreviar) erro de manipulação em chamadas iniciais.

Na maioria dos casos, a reutilização de um cliente pode tornar-se bastante complicada; Essa abordagem deve ser usada com cautela.

O que a executar?

A principal razão de que eu usar essa técnica de saída privada é implementar versões DML de comandos baseados em texto, onde eu não tenho conhecimento de suas partes internas.Em particular, I've "valor agregado" alguns dos comandos de depurador SOS com hiperlinks.Passando por como eu analisar a saída para decidir o que Marcar está além do escopo deste artigo.No entanto, falarei sobre como determinar o comando correto para executar para produzir a saída que necessitam para a análise.

A enumeração de extensões de depurador carregado e suas habilidades associadas não faz parte da API do depurador.Para piorar a situação, a mesma extensão de depurador pode ser carregada várias vezes no mesmo local no disco.Se você executar o script test_windbg.cmd com o. Load comandos myext e .chain, você verá um exemplo desta situação.A extensão MyExt é carregada duas vezes, uma vez como myext e novamente como myext.dll:

0: 000 > . Load myext
0: 000 > .Chain
Caminho de busca de extensão DLL:
C:\Debuggers_x86\WINXP;C:\Debuggers_x86\winext;...
Cadeia DLL de extensão:
myext: imagem 6.1.7600.16385, API 1.0.0, construído Sat 29 de Jan 22: 00: 48 2011
[caminho: C:\Debuggers_x86\myext.dll]
myext.dll: imagem 6.1.7600.16385, API 1.0.0, construído Sat 29 de Jan 22: 00: 48 2011
[caminho: C:\Debuggers_x86\myext.dll]
dbghelp: imagem 6.12.0002.633, API 6.1.6, construído em 01 de fevereiro de Mon 12: 08: 26 2010
[caminho: C:\Debuggers_x86\dbghelp.dll]
   ...

Ao chamar um comando de extensão para análise, você quer ter certeza que você está chamando o comando pretendido, não um comando com o mesmo nome em outra extensão (ou outra instância).

Se você qualificar totalmente um comando, o mecanismo de simplesmente executa o comando especificado.Mas quando você não qualifica totalmente um comando, o motor tem de fazer uma pesquisa.O mecanismo examina a tabela de exportação de cada extensão na ordem de carga e executa a primeira extensão com um nome de comando correspondente.O comando .extmatch exibe os resultados de pesquisa para o comando determinado.O comando .extmatch oferece suporte a caracteres curinga do nome de extension e o comando via o "/ e" Alternar, conforme mostrado aqui:

0: 000 > .extmatch callbackecho
! myext.callbackecho
! myext.dll.callbackecho
0: 000 > .extmatch /e *myext.dll* * volta *
! myext.dll.callbackecho
! myext.dll.callbacktoggle

Quando você carrega a extensão de SOS por meio de uma chamada de mscorwks .loadby SOS. dll, um caminho totalmente qualificado será registrado para ele. ou seja, ele não vai ser conhecido como "! SOS. dll. <command>." No exemplo a seguir, eu corria o aplicativo de exemplo Test03 no Windows 7 x 64 e anexado ao depurador a ele (F6).A extensão SOS foi carregada a partir da pasta de mscorwks (C:\Windows\Microsoft.NET\Framework64\v2.0.50727):

0: 004 > .loadby SOS. dll mscorwks
0: 004 > .Chain
Caminho de busca de extensão DLL:
C:\debuggers\WINXP;C:\debuggers\winext;...
Cadeia DLL de extensão:
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos.dll:
imagem 2.0.50727.4952, API 1.0.0, construído Thu 13 de Maio 05: 15: 18 2010
[caminho: C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos.dll]
dbghelp: imagem 6.12.0002.633, API 6.1.6, construído 01 de fevereiro de Mon 12: 15: 44 2010
[caminho: C:\debuggers\dbghelp.dll]
    ...

A única maneira de chamar um comando de SOS com segurança é usar a qualificação completa.Assim, quando utilizando a extensão SOS (ou qualquer outra extensão), existem dois fatos para determinar:

  • É a extensão SOS carregada?
  • Onde ele está carregado?

Ambos podem ser determinados através de uma correspondência de extensão (.extmatch /e *sos.dll* <command>).Na minha função de LoadSOS (parte do Example08. o código não está listado aqui) eu olhar primeiro para o comando no local com versão (\v2.0.50727\sos.dll. <command>).Se não encontrar o comando versionado, eu cair de volta a olhar para o comando sem uma versão (\Framework \sos.dll. <command>).Se eu agora pode encontrá-lo, eu ainda retornar com êxito, mas como S_FALSE (ao contrário de S_OK).Cabe ao chamador de LoadSOS para determinar se ele terá um risco de análise incorreto.

Se não encontrar uma versão de SOS do comando, presumo que ele não é carregado (ou é carregado, mas não de uma forma que estou feliz com) e fazer um privado ".loadby". Se isso não gera erros, volto a repetir a pesquisa.Se eu não pode carregar o SOS ou não consigo encontrar meu comando, eu retorno falha (E_FAIL).

Assim que eu tiver o comando, eu executá-lo, reescrevê-lo (por exemplo, ParseDSO) e a versão DML de saída (consulte Example08 no download de código).

No meus invólucros DML de comandos SOS, eu faço essa verificação antes para executar um comando para ter certeza que SOS não foi descarregado entre chamadas e que existe o comando necessário.Figuras 9 e 10 mostrar meu! dsox comando em ação com metas x 86 e x 64.

Test03 x86 !dsox Wrapping !dumpstackobjects

Figura 9 Test03 x 86! dsox quebra automática! dumpstackobjects

Test03 x64 !dsox Wrapping !dumpstackobjects

Figura 10 Test03 x 64! dsox quebra automática! dumpstackobjects

Os comandos adicionam um! dumpobj hiperlink para cada endereço listado em! dumpstackobjects; o código para! dsox é em Example08.

Quebre!

Com um pouco de código de infra-estrutura, é possível aproveitar a lógica de qualquer comando interno ou extensão.Minha análise da extensão SOS é sobre a adição de DML, mas poderia ter sido facilmente sobre a recolha de informações de classe gerenciado.Eu não tive que sabe alguma coisa sobre as estruturas internas do CLR para atingir meu objetivo.Eu tinha que saber como Navegue para os dados que eu queria mostrar.

Depurador extensões simplificam a análise de depuração através da automação de recuperação de dados e o enriquecimento da exibição.Espero que esta série tem inspirado a fazer suas próprias extensões que simplificam a depuração que você fazer.

Se você está apenas interessado em depuração e deseja saber mais, você deve verificar se o blog Advanced Windows depuração e solução de problemas (blogs.msdn.com/b/ntdebugging) — tem lotes de artigos de formação e estudo de caso para ler.

Andrew Richards é um engenheiro de escalonamento sênior da Microsoft para Windows OEM.Ele tem uma paixão por ferramentas de suporte e é continuamente criando depurador extensões e aplicações que simplificam o trabalho dos engenheiros de suporte.

Graças aos seguinte perito técnico para revisão deste artigo: Drew Bliss