Este artigo foi traduzido por máquina.

API do mecanismo de depuração

Escrevendo uma extensão para as Ferramentas de depuração do Windows, Parte 2: Saída

Andrew Richards

Baixar o exemplo de código

Nesta segunda parte da minha série sobre a API do depurador, vou mostrar como você pode aprimorar a saída gerada pela extensão do mecanismo de depuração (DbgEng). Você pode cair em um número de desvios ao fazer isso, no entanto. Espero realçar todas as armadilhas para você.

Antes de ler, recomendo que você vai querer leu a edição anterior entender é uma extensão de depurador (e como estou criando e testando os exemplos neste artigo). Você pode lê-lo no msdn.microsoft.com/magazine/gg650659.

Linguagem de marcação do depurador

DML (linguagem de marcação de depurador) é uma linguagem de marcação inspirada em HTML. Ele oferece suporte a ênfase via itálico/negrito/sublinhado e navegação por meio de hiperlinks. DML foi adicionado à API do depurador na versão 6. 6.

O SDK do Windows para o Windows Vista primeiro enviados versão 6.6.7.5 desta API e suporte para x86, x64 e IA64. O Windows 7 /.NET 3. 5 SDK/WDK fornecido a próxima versão (versão 6.11.1.404). O Windows 7 /.NET 4 SDK/WDK é fornecida a versão atual (versão 6.12.2.633). O Windows 7 /.NET 4 veículo de lançamento é a única maneira de obter a versão mais recente de Debugging Tools for Windows da Microsoft. Não há nenhum download direto de x86, x64 ou IA64 pacotes disponíveis. Observe que esses lançamentos subseqüentes não expandir sobre as APIs relacionadas a DML definidas na versão 6. 6. No entanto, eles, têm vale a pena correções relacionadas ao suporte DML.

'DML de hello World'

Como você provavelmente pode adivinhar, a marcação usada pelo DML para dar ênfase é a mesma marcação conforme usado por HTML. Para marcar o texto com negrito, use "<b> … </b>"; para uso em itálico, "<i> … </i>"; e para sublinhado, use "<u> … </u>". Figura 1 mostra um exemplo de comando que produz "Hello DML mundo!" com esses três tipos de marcação.

Figura 1 ! hellodml implementação

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

  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL,  
      "<b>Hello</b> <i>DML</i> <u>World!</u>\n");
    pDebugControl->Release();
  }
  return S_OK;
}

Para testar a extensão, eu tenho um script chamado test_windbg.cmd na pasta Example04 do código para download que acompanha este artigo. O script copia a extensão para a pasta C:\Debuggers_x86. Em seguida, o script inicia o WinDbg, carrega a extensão e inicia uma nova instância do bloco de notas (como o destino de depuração). Se tudo o que passou de acordo com a plano, eu digito "! hellodml" no depurador do prompt de comando e ver uma resposta "DML de Hello World!" com negrito, marcação de itálico e sublinhado:

0: 000 > ! hellodml
Hello DML World!

Também tenho um script chamado test_ntsd.cmd que faz as mesmas etapas, mas carrega o depurador NTSD. Se eu digito "! hellodml" no prompt de comando do depurador, verei a resposta "DML de Hello World!", mas com nenhuma marcação. O DML é convertido em texto como NTSD é um cliente de depuração somente texto. Todas as marcações será removido quando DML é saída para um texto (apenas)-com base no cliente:

0: 000 > ! hellodml
DML de Hello World!

Marcação

Assim como com HTML, você precisa tomar cuidado para iniciar e encerrar qualquer marcação. Figura 2 tem um comando simples extensão (! echoasdml) que ecoa o argumento comando como DML com marcadores antes e após a saída DML como saída de texto.

Figura 2 ! echoasdml implementação

HRESULT CALLBACK 
echoasdml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[Start DML]\n");
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_NORMAL, "%s\n", args);
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[End DML]\n");
    pDebugControl->Release();
  }
  return S_OK;
}

Este exemplo mostra o que acontece se você não fechar a marcação de seqüência:

0: 000 > ! echoasdml Hello World
[Iniciar DML]
Olá mundo
[End DML]

0: 000 > ! echoasdml Hello World </b> <b>
[Iniciar DML]
Olá mundo
[End DML]
 
0: 000 > ! echoasdml <b> saudação
[Iniciar DML]
Olá
[End DML]

0: 000 > ! echoasdml World </b>
[Iniciar DML]
Mundo
[End DML]

"<b> Hello" comando folhas negrito ativadas, fazendo com que todos os seguinte extensão e prompt de saída a ser exibida como negrito. Isso ocorre independentemente de se o texto foi saído no modo DML ou texto. Como você poderia esperar, o fechamento subseqüente da marcação negrito reverte o estado.

Outro problema comum é quando a seqüência tem marcas de formatação XML dentro dele. A saída pode ser tanto truncada, como acontece no primeiro exemplo aqui ou as marcas XML poderiam ficaria assim:

0: 000 > ! echoasdml < xml
[Iniciar DML]
 
0: 000 > ! echoasdml Hello World <xml> </xml>
[Iniciar DML]
Olá mundo
[End DML]

Tratar isso da mesma forma como faria com HTML: a seqüência de caracteres antes da saída de seqüência de escape. Você pode fazer isso sozinho ou ter o depurador faça isso por você. Os quatro caracteres que precisam de saída são &, <,> e ". As versões de escape equivalentes são: "& amp;", "& lt;", "& gt;" e "& quot;".

Figura 3 mostra um exemplo de seqüenciamento de escape.

Figura 3 ! echoasdmlescape implementação

HRESULT CALLBACK 
echoasdmlescape(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[Start DML]\n");
    if ((args != NULL) && (strlen(args) > 0))
    {
      char* szEscape = (char*)malloc(strlen(args) * 6);
      if (szEscape == NULL)
      {
        pDebugControl->Release();
        return E_OUTOFMEMORY;
      }
      size_t n=0; size_t e=0;
      for (; n<strlen(args); n++)
      {
        switch (args[n])
        {
          case '&':
                memcpy(&szEscape[e], "&amp;", 5);
                e+=5;
                break;
          case '<':
                memcpy(&szEscape[e], "&lt;", 4);
                e+=4;
                break;
          case '>':
                memcpy(&szEscape[e], "&gt;", 4);
                e+=4;
                break;
          case '"':
                memcpy(&szEscape[e], "'", 6);
                e+=6;
                break;
          default:
                szEscape[e] = args[n];
                e+=1;
                break;
        }
      }
      szEscape[e++] = '\0';
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML,  
        DEBUG_OUTPUT_NORMAL, "%s\n", szEscape);
      free(szEscape);
    }
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[End DML]\n");
    pDebugControl->Release();
  }
  return S_OK;
}

O comando echoasdmlescape aloca um novo buffer é seis vezes o tamanho do original. Este é o espaço suficiente para lidar com uma seqüência de caracteres do argumento com puramente" caracteres. A função itera sobre a seqüência do argumento (que é sempre ANSI) e adiciona o texto apropriado para o buffer. Depois, ele usa o buffer de seqüência de escape com o formatador de %s passado para a função IDebugClient::ControlledOutput. O! echoasdmlescape comando exibe o argumento sem a seqüência seja interpretada como marcação DML:

0: 000 > ! echoasdmlescape < xml
[Iniciar DML]
< xml
[End DML]
 
0: 000 > ! echoasdmlescape Hello World <xml> </xml>
[Iniciar DML]
Olá, mundo <xml> </xml>
[End DML]

Observe que algumas seqüências de caracteres, talvez você ainda não obtenha a saída esperada, dada a entrada fornecida. Essas inconsistências não têm nada a ver com o escape seqüenciamento (ou DML); eles estão causados pelo analisador do depurador. Os dois casos de observação são as " caractere (seqüência de conteúdo) e o caractere ";" (término do comando):

0: 000 > ! "Hello World" de echoasdmlescape
[Iniciar DML]
Olá mundo
[End DML]
 
0: 000 > ! echoasdmlescape Hello World;
[Iniciar DML]
Olá mundo
[End DML]
 
0: 000 > ! echoasdmlescape "Hello World";
[Iniciar DML]
Olá, tudo bem;
[End DML]

Você não precisa passar por esse esforço de seqüências de escape por conta própria, no entanto. O depurador oferece suporte a um formatador especial para esta ocorrência. Em vez de gerar o escape seqüenciados seqüência e, em seguida, usando o formatador de %s, você pode simplesmente usar o formatador %y {t} na seqüência de caracteres original.

Você também pode evitar o esforço de seqüências de escape, se você usar um formatador de memória. O ma %, % mu, msa % e % msu formatadores permitem que você para saída de uma Cadeia de caracteres diretamente do espaço de endereço do destino; o mecanismo de depuração manipula a leitura de seqüência de caracteres e a exibição, como mostrado na Figura 4.

Figura 4 lendo a Cadeia de caracteres e a exibição de um formatador de memória

0: 000 > ! test02 de memorydml! g_ptr1
[Iniciar DML]
Erro (% ma): Arquivo não encontrado
Erro (%y {t}): Arquivo não encontrado
Erro (% s): Arquivo não encontrado
[End DML]
 
0: 000 > ! test02 de memorydml! g_ptr2
[Iniciar DML]
Erro (% ma): Valor é < 0
Erro (%y {t}): Valor é < 0
Erro (% s): Valor é [End DML]
 
0: 000 > ! test02 de memorydml! g_ptr3
[Iniciar DML]
Erro (% ma): Falta <xml> elemento
Erro (%y {t}): Falta <xml> elemento
Erro (% s): Elemento ausente
[End DML]

Nos exemplos de segundo e terceiros Figura 4, as seqüências de caracteres impressas com %s são truncadas ou omitidas devido ao < e > caracteres, mas o ma % e %y {t} de saída está correta. O aplicativo Test02 é Figura 5.

Figura 5 Test02 implementação

// Test02.cpp : Defines the entry point for the console application.
//

#include <windows.h>

void* g_ptr1;
void* g_ptr2;
void* g_ptr3;

int main(int argc, char* argv[])
{
  g_ptr1 = "File not found";
  g_ptr2 = "Value is < 0";
  g_ptr3 = "Missing <xml> element";
  Sleep(10000);
  return 0;
}

A! memorydml implementação está em Figura 6.

Figura 6 ! memorydml implementação

HRESULT CALLBACK 
memorydml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugDataSpaces* pDebugDataSpaces;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugDataSpaces), 
    (void **)&pDebugDataSpaces)))
  {
    IDebugSymbols* pDebugSymbols;
    if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugSymbols), 
      (void **)&pDebugSymbols)))
    {
      IDebugControl* pDebugControl;
      if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
        (void **)&pDebugControl)))
      {
        // Resolve the symbol
        ULONG64 ulAddress = 0;
        if ((args != NULL) && (strlen(args) > 0) && 
          SUCCEEDED(pDebugSymbols->GetOffsetByName(args, &ulAddress)))
        {   // Read the value of the pointer from the target address space
          ULONG64 ulPtr = 0;
          if (SUCCEEDED(pDebugDataSpaces->
            ReadPointersVirtual(1, ulAddress, &ulPtr)))
          {
            char szBuffer[256];
            ULONG ulBytesRead = 0;
            if (SUCCEEDED(pDebugDataSpaces->ReadVirtual(
              ulPtr, szBuffer, 255, &ulBytesRead)))
            {
              szBuffer[ulBytesRead] = '\0';

              // Output the value via %ma and %s
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_TEXT,
                DEBUG_OUTPUT_NORMAL, "[Start DML]\n");
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
                DEBUG_OUTPUT_ERROR, "<b>Error</b> (  %%ma): %ma\n", ulPtr);
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
                DEBUG_OUTPUT_ERROR, "<b>Error</b> (%%Y{t}): %Y{t}\n", szBuffer);
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
                DEBUG_OUTPUT_ERROR, "<b>Error</b> (   %%s): %s\n", szBuffer);
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_TEXT, 
                DEBUG_OUTPUT_NORMAL, "[End DML]\n");
            }
          }
        }
        pDebugControl->Release();
      }
      pDebugSymbols->Release();
    }
    pDebugDataSpaces->Release();
  }
  return S_OK;
}

Os scripts de teste (na pasta Example05) foram alterados para carregar um despejo do aplicativo Test02 em vez de iniciar o bloco de notas, para que você tenha uma seqüência de saída.

Assim, a maneira mais fácil de implementar a exibição de seqüências de caracteres de espaço de endereço de destino é simplesmente usar ma % e assim por diante. Se você precisar manipular uma seqüência de caracteres que foi lido antes para exibição ou acabou de criar uma seqüência de caracteres de sua preferência, em seguida, aplica a seqüência de escape via %y {t}. Se você precisar passar a seqüência de caracteres como a seqüência de caracteres de formato, em seguida, aplique o escape de seqüenciamento de si mesmo. Como alternativa, dividir a saída em várias chamadas de IDebugControl::ControlledOutput e usar apenas o controle de saída DML (DEBUG_OUTCTL_AMBIENT_DML) na parte do conteúdo, saindo do resto como texto (DEBUG_OUTCTL_AMBIENT_TEXT) com nenhuma seqüência de escape DML.

Outra restrição nessa área é o limite de comprimento de IDebugClient::ControlledOutput e IDebugClient::Output; eles só tiver saída cerca de 16000 caracteres por vez. Descobri que eu regularmente atingido esse limite com ControlledOutput quando fazer DML de saída. A marcação e a seqüência de escape podem facilmente inchar últimos 16000 caracteres em uma seqüência de caracteres enquanto ainda procurando relativamente pequeno na janela Saída.

Quando você está criando cadeias de caracteres grandes e está fazendo o escape de seqüenciamento por conta própria, você precisará certificar-se de que você recorta a cadeia de caracteres em momentos apropriados. Não recorte-las a marcação DML ou em uma seqüência de escape. Caso contrário, eles não seja interpretados corretamente.

Existem duas maneiras de obter um hiperlink no depurador: você pode usar <link> ou <exec> marcação. Em ambas as marcações, o texto delimitado é exibido com sublinhado e coloração de hipertexto (geralmente o azul). O comando é executado em ambos os casos é o membro "cmd". É semelhante ao membro "href" do <a> marcação de HTML:

    <link cmd="dps @$csp @$csp+0x80">Stack</link>
    <exec cmd="dps @$csp @$csp+0x80">Stack</exec>

A diferença entre <link> e <exec> pode ser difícil ver. Na verdade, demorei um pouco de pesquisa para chegar ao final dela. A diferença é que só são observáveis na janela do navegador de comando (Ctrl + N), não a janela de saída (Alt + 1). Em ambos os tipos de janela, a saída de <link> ou <exec> link será exibido na janela Saída associados. A diferença é o que acontece com o prompt de comando de cada janela.

Na janela de saída, não altera o conteúdo do prompt de comando. Se houver algum texto lá, permanecerá inalterado.

Na janela do navegador de comando, quando <link> é chamado, o comando é adicionado à lista de comandos e o prompt de comando é definido para o comando. Mas quando o <exec> é invocado, o comando não será adicionado à lista e o prompt de comando não será alterado. Não alterando o histórico de comandos, é possível criar uma seqüência de hyperlinks pode guiar o usuário através de um processo de decisão. O exemplo mais comum é a exibição da Ajuda. A navegação em torno de ajuda é adequada para o hiperlink, ainda é desejável fazer a navegação.

Preferência de usuário

Então, como você sabe se o usuário ainda deseja ver DML? Em alguns cenários, dados podem ocorrer problemas quando a saída é convertida em texto, seja pela ação de salvar o texto em um arquivo de log (.logopen), copiar e colar na janela Saída. Dados podem ser perdidos devido a abreviação DML ou os dados podem ser supérfluos devido à incapacidade de fazer a navegação via a versão de texto de saída.

Da mesma forma, se for oneroso para gerar a saída DML, esse esforço deve ser evitado se ela for conhecida que essa saída será convertido em conteúdo. Operações demoradas normalmente envolvem varreduras de memória e a resolução do símbolo.

Igualmente, os usuários podem apenas prefere não têm DML na sua saída.

Neste exemplo de abreviação é <link>-com base, deseja apenas obter o "Objeto encontrado em 0x0C876C32" saída e perder a informação importante (o tipo de dados do endereço):

Objeto encontrado em < vincular cmd = "logon dt!CSession 0x0C876C32 "> 0x0C876C32 </link>

A maneira correta de lidar com isso é ter uma condição que evita a abreviação quando DML não está habilitado. Aqui está um exemplo de como você pode corrigir isso:

if (DML)
  Object found at <link cmd="dt login!CSession 0x0C876C32">0x0C876C32</link>
else
  Object found at 0x0C876C32 (login!CSession)

A configuração de .prefer_dml é o mais próximo que você pode obter uma preferência de usuário (para que você pode tomar essa decisão condicional sobre saída abreviada ou supérflua). A configuração é usada para controlar se o depurador executa versões DML avançada das operações de comandos internos e por padrão. Embora ele não deve explicitamente para especificar se DML deve ser usado globalmente (dentro de extensões), é um bom substituto.

A única desvantagem essa preferência é que a preferência padrão é desativado, e depuração mais engenheiros não sabem que existe o comando .prefer_dml.

Lembre-se de que tem uma extensão, não é o mecanismo de depuração, para que o código para detectar a preferência de ".prefer_dml" ou "capacidade" (explicarei "capacidade" em breve). O mecanismo de depuração não ajustar a saída DML com base nesta opção. Se o depurador é capaz de DML, resultará em DML independentemente.

Para obter a preferência atual do ".prefer_dml", você precisa fazer uma QueryInterface na interface IDebugClient passada para a interface IDebugControl. Em seguida, você pode usar a função GetEngineOptions para obter a bitmask DEBUG_ENGOPT_XXX atual. Se o bit DEBUG_ENGOPT_PREFER_DML for definido, .prefer_dml está habilitado. Figura 7 tem uma implementação de exemplo de uma função de preferência do usuário.

Figura 7 PreferDML implementação

BOOL PreferDML(PDEBUG_CLIENT pDebugClient)
{
  BOOL bPreferDML = FALSE;
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)& pDebugControl)))
  {
    ULONG ulOptions = 0;
    if (SUCCEEDED(pDebugControl->GetEngineOptions(&ulOptions)))
    {
      bPreferDML = (ulOptions & DEBUG_ENGOPT_PREFER_DML);
    }
  pDebugControl->Release();
  }
  return bPreferDML;
}

Você deve estar pensando que não deseja chamar a função GetEngineOptions em todos os comandos para determinar a preferência. Nós não pode ser notificados da alteração? Afinal, ela provavelmente não altera com muita freqüência. Sim, pode ser melhor, mas há um problema.

Você pode fazer é registrar uma implementação de IDebugEventCallbacks via IDebugClient::SetEventCallbacks. Na implementação, você pode registrar um interesse na notificação DEBUG_EVENT_CHANGE_ENGINE_STATE. Quando IDebugControl::SetEngineOptions é chamado, o depurador chama IDebugEventCallbacks::ChangeEngineState com o bit DEBUG_CES_ENGINE_OPTIONS definido no parâmetro Flags. O parâmetro de argumento contém uma máscara de bits DEBUG_ENGOPT_XXX como GetEngineOptions retorna.

O problema é que o retorno de chamada que somente um evento pode ser registrado em qualquer momento para um objeto IDebugClient. Se desejarem que as extensões de dois (ou mais) registrar para retornos de chamada de evento (que inclui notificações mais importantes, como o carregamento de módulo/descarregamento, thread iniciar/parar, iniciar/interromper o processo e exceções), alguém irá perca. E se você modificar o objeto IDebugClient passado, por pessoa será o depurador!

Para implementar o retorno de chamada de IDebugEventCallbacks, você precisará fazer o seu próprio objeto IDebugClient via IDebugClient::CreateClient. Em seguida, associar seu retorno de chamada este objeto de IDebugClient (novo) e se tornem responsáveis pelo tempo de vida do que o IDebugClient.

Para simplificar, é melhor chamar GetEngineOptions toda vez que você precisa determinar o valor DEBUG_ENGOPT_PREFER_DML. Como mencionado anteriormente, chamar QueryInterface na interface IDebugClient passada para a interface IDebugControl e, em seguida, chame GetEngineOptions para certificar-se de que você tem a preferência atual (e correta).

Capacidade do cliente de depuração

Então, como você sabe se o depurador suporta até mesmo DML?

Se o depurador não oferece suporte a DML, dados podem ser perdidos, supérfluo ou o esforço pode ser oneroso, muito semelhante a preferência do usuário. Como mencionado, NTSD é um depurador somente texto e se DML saída-se a ele, o mecanismo de depuração conversão para remover o DML da saída de conteúdo.

Para obter a capacidade de depuração do cliente, você precisa fazer uma QueryInterface na interface IDebugClient passada para a interface IDebugAdvanced2. Em seguida, você pode usar a função de solicitação com o tipo de solicitação DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE. O HRESULT contém S_OK quando pelo menos um retorno de chamada de saída está ciente de DML, caso contrário retorna S_FALSE. Para reiterar, a bandeira não significa que todos os retornos de chamada são conscientes; Isso significa que na pelo menos um é.

Em ambientes aparentemente somente texto (como NTSD) ainda que os problemas de saída condicional. Se uma extensão registra um retorno de chamada de saída que está ciente de DML (retornando DEBUG_OUTCBI_DML ou DEBUG_OUTCBI_ANY_FORMAT de IDebugOutputCallbacks2::GetInterestMask) dentro de NTSD, fará com que a função de solicitação retornar S_OK. Felizmente, essas extensões são bastante raras. Se existirem, eles devem verificar o estado de DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE e a configuração de sua capacidade de acordo (anterior da sua Proclamação da sua capacidade de DML). Confira a próxima parte desta série para obter mais informações sobre reconhecimento DML retornos de chamada.

Figura 8 tem uma implementação de exemplo de uma função de capacidade.

Figura 8 AbilityDML implementação

BOOL AbilityDML(PDEBUG_CLIENT pDebugClient)
{
  BOOL bAbilityDML = FALSE;
  IDebugAdvanced2* pDebugAdvanced2;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugAdvanced2), 
    (void **)& pDebugAdvanced2)))
  {
    HRESULT hr = 0;
    if (SUCCEEDED(hr = pDebugAdvanced2->Request(
      DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE, 
      NULL, 0, NULL, 0, NULL)))
    {
      if (hr == S_OK) bAbilityDML = TRUE;
    }
    pDebugAdvanced2->Release();
  }
  return bAbilityDML;
}

Observe que o tipo de solicitação DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE e a interface IDebugOutputCallbacks2 não estão documentados na biblioteca MSDN ainda.

Tendo as insuficiências potenciais em mente, é a melhor maneira de lidar com capacidade de cliente e de preferência do usuário:

if (PreferDML(IDebugClient) && AbilityDML(IDebugClient))
  Object found at <link cmd="dt login!CSession 0x0C876C32">0x0C876C32</link>
else
  Object found at 0x0C876C32 (login!CSession)

A! ifdml implementação (em Figura 9) mostra as funções PreferDML e AbilityDML em ação para que condicional DML saída é gerada. Observe que a grande maioria dos casos, não é necessário ter uma instrução condicional, como isso. é possível confiar tranqüilamente sobre a conversão de conteúdo de mecanismo do depurador.

Figura 9 ! ifdml implementação

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

  PDEBUG_CONTROL pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    // A condition is usually not required;
    // Rely on content conversion when there isn't 
    // any abbreviation or superfluous content
    if (PreferDML(pDebugClient) && AbilityDML(pDebugClient))
    {
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
        DEBUG_OUTPUT_NORMAL, "<b>Hello</b> <i>DML</i> <u>World!</u>\n");
    }
    else
    {
      pDebugControl->ControlledOutput(
        DEBUG_OUTCTL_AMBIENT_TEXT, DEBUG_OUTPUT_NORMAL, 
        "Hello TEXT World!
\n");
    }
    pDebugControl->Release();
  }
  return S_OK;
}

Usando o script de teste de test_windbg.cmd para carregar o WinDbg, a saída de! ifdml é:

0: 000 > .prefer_dml 0
Versões DML dos comandos são desativados por padrão
0: 000 > ! ifdml
TEXTO de Hello World!

0: 000 > .prefer_dml 1
Versões DML dos comandos por padrão
0: 000 > ! ifdml
Hello DML World!

Usando o script de teste de test_ntsd.cmd para carregar o NTSD, a saída de! ifdml é:

0: 000 > .prefer_dml 0
Versões DML dos comandos são desativados por padrão
0: 000 > ! ifdml
TEXTO de Hello World!
 
0: 000 > .prefer_dml 1
Versões DML dos comandos por padrão
0: 000 > ! ifdml
TEXTO de Hello World!

Saída controlada

DML de saída, você precisa usar a função IDebugControl::ControlledOutput:

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

A diferença entre ControlledOutput e a saída é o parâmetro OutputControl. Este parâmetro baseia-se sobre as constantes DEBUG_OUTCTL_XXX. Esse parâmetro de duas partes: bits inferiores representam o escopo da saída e os bits mais altos representam as opções. É um pouco maior que permite DML.

Um — e somente um — da DEBUG_OUTCTL_XXX as constantes de escopo devem ser usadas para bits inferiores. O valor direciona a saída é onde ir. Isso pode ser a 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), não em todos os (DEBUG_OUTCTL_IGNORE) ou somente o arquivo de log (DEBUG_OUTCTL_LOG_ONLY).

Os bits mais altos são uma máscara de bits e também são definidos em constantes de DEBUG_OUTCTL_XXX. Há constantes para especificar a saída baseada em texto ou DML (DEBUG_OUTCTL_DML), se a saída não está conectado (DEBUG_OUTCTL_NOT_LOGGED) e se a máscara de saída do cliente é homenageada (DEBUG_OUTCTL_OVERRIDE_MASK).

Controle de saída

Todos os exemplos, defini o parâmetro ControlledOutput para DEBUG_OUTCTL_AMBIENT_DML. Ler a documentação no MSDN, você pode dizer que eu poderia também usei DEBUG_OUTCTL_ALL_CLIENTS | DEBUG_OUTCTL_DML. No entanto, isso não seria honrar a preferência de controle de saída de IDebugControl.

Se o comando da extensão foi invocado pelo IDebugControl::Execute, o parâmetro OutputControl da chamada de execução deve ser usado para qualquer saída relacionada. IDebugControl::Output faz isso por natureza, mas ao usar IDebugControl::ControlledOutput, a responsabilidade de saber o valor de OutputControl é o chamador. O problema é que não há nenhuma maneira para recuperar o valor atual de controle de saída a partir da interface de IDebugControl (ou qualquer outra interface). Nem tudo está perdido, porém; Há constantes de "ambiente" de DEBUG_OUTCTL_XXX especiais para lidar com a alternância do bit DEBUG_OUTCTL_DML. Quando você usa uma constante de temperatura ambiente, o controle de saída atual é respeitado e o bit de DEBUG_OUTCTL_DML será definido de acordo.

Em vez de passar uma das constantes inferiores com a constante DEBUG_OUTCTL_DML superior, você simplesmente passar DEBUG_OUTCTL_AMBIENT_DML para habilitar a saída DML ou DEBUG_OUTCTL_AMBIENT_TEXT para desativar DML de saída:

pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, ...);

O parâmetro de máscara

Outro parâmetro que eu tenha sido definindo nos exemplos é a máscara. Você deve definir a máscara como uma constante DEBUG_OUTPUT_XXX apropriada com base no texto sendo saído. Observe que o parâmetro de máscara baseia-se sobre as constantes DEBUG_OUTPUT_XXX; Isso não deve ser confundida com as constantes DEBUG_OUTCTL_XXX.

Os valores mais comuns que você usaria são DEBUG_OUTPUT_NORMAL para saída normal (geral), DEBUG_OUTPUT_WARNING para a saída de aviso e DEBUG_OUTPUT_ERROR para a saída de erro. Quando sua extensão tem um problema, você deve usar DEBUG_OUTPUT_EXTENSION_WARNING.

Os sinalizadores de saída DEBUG_OUTPUT_XXX são semelhantes para stdout e stderr usado com a saída do console. Cada sinalizador de saída é um canal de saída individuais. Cabe para receptores de (retornos de chamada) para decidir quais desses canais serão ouvidos, como eles são a serem combinados (se tudo) e como eles são exibidos. Por exemplo, o WinDbg por padrão exibe todos os sinalizadores de saída, exceto o sinalizador de saída DEBUG_OUTPUT_VERBOSE na janela Saída. Você pode alternar entre esse comportamento por meio do modo de exibição | Saída detalhada (Ctrl + Alt + V).

A! maskdml implementação (consulte Figura 10) produz uma descrição estilizada com o sinalizador de saída associados.

Figura 10 ! maskdml implementação

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

  PDEBUG_CONTROL pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, 
      "<b>DEBUG_OUTPUT_NORMAL</b> - Normal output.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_ERROR, "<b>DEBUG_OUTPUT_ERROR</b> - Error output.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_WARNING, "<b>DEBUG_OUTPUT_WARNING</b> - Warnings.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_VERBOSE, "<b>DEBUG_OUTPUT_VERBOSE</b> 
      - Additional output.
\n");
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_PROMPT, 
      "<b>DEBUG_OUTPUT_PROMPT</b> - Prompt output.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_PROMPT_REGISTERS, "<b>DEBUG_OUTPUT_PROMPT_REGISTERS</b> 
      - Register dump before prompt.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_EXTENSION_WARNING, 
      "<b>DEBUG_OUTPUT_EXTENSION_WARNING</b> 
      - Warnings specific to extension operation.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_DEBUGGEE, "<b>DEBUG_OUTPUT_DEBUGGEE</b> 
      - Debug output from the target (for example, OutputDebugString or  
      DbgPrint).
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML,  
      DEBUG_OUTPUT_DEBUGGEE_PROMPT, "<b>DEBUG_OUTPUT_DEBUGGEE_PROMPT</b> 
      - Debug input expected by the target (for example, DbgPrompt).
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_SYMBOLS, "<b>DEBUG_OUTPUT_SYMBOLS</b> 
      - Symbol messages (for example, !sym noisy).
\n");
    pDebugControl->Release();
  }
  return S_OK;
}

Se você alternar saída detalhada depois que o comando foi executado, a saída DEBUG_OUTPUT_VERBOSE omitida não será mostrado. a saída é perdida.

WinDbg oferece suporte a colorização de cada sinalizador de saída. No modo de exibição | Caixa de diálogo Opções, você pode especificar uma cor de primeiro plano e plano de fundo para cada sinalizador de saída. As configurações de cores são salvas no espaço de trabalho. Defini-las globalmente, inicie o WinDbg, excluir todos os espaços de trabalho, definir as cores (e qualquer outra configuração que você gostaria que) e salve o espaço de trabalho. Eu gosto de definir a cor de primeiro plano do erro para vermelho, o aviso para verde, detalhado para azul, o aviso de extensão como púrpura e símbolos de cinza. O espaço de trabalho padrão será o modelo para todas as futuras sessões de depuração.

Figura 11 mostra a saída de! maskdml sem (topo) e com (inferior) habilitado verbose.

!maskdml with Color Scheme

Figura 11 ! maskdml com o esquema de cores

Quebre!

É fácil aprimorar qualquer extensão com DML. E, com um pouco de código de infra-estrutura, também é fácil respeitar a preferência do usuário. Definitivamente vale a pena gastar algum tempo extra para gerar a saída corretamente. Em particular, nos empenhamos para sempre fornecer tanto saída baseada em texto e DML quando a saída é abreviado ou supérfluas e direcionar essa saída apropriadamente.

No próximo artigo sobre a API do depurador Engine, vou me aprofundar o relacionamento de que uma extensão de depurador pode ter com o depurador. Posso dar uma visão geral da arquitetura dos clientes do depurador e retornos de chamada do depurador. Ao fazer isso, vamos nos pequenos detalhes das constantes DEBUG_OUTPUT_XXX e DEBUG_OUTCTL_XXX.

Usarei essa base para implementar um encapsulamento da extensão de depurador Son of Strike ou SOS. Vou aprimorar a saída de SOS com DML e demonstrar como você pode utilizar os comandos do depurador internos e outras extensões para recuperar as informações necessárias para suas extensões.

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" (NTDebugging) em blogs.msdn.com/b/ntdebugging— há uns lotes de artigos de formação e estudo de caso para ler.

Microsoft está sempre procurando talentosos engenheiros de depuração. Se você estiver interessado em se juntar à equipe, o título do trabalho para procurar é "Engenheiro de escalonamento" Microsoft carreiras (careers.microsoft.com).

Andrew Richards é um engenheiro de escalonamento sênior da Microsoft para Exchange Server. Ele é apaixonado por ferramentas de suporte e está criando continuamente o depurador extensões e aplicativos que simplificam a tarefa de engenheiros de suporte.

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