Dieser Artikel wurde maschinell übersetzt.

Debugmodul-API

Schreiben von Debugtools für Windows Extension, Teil 3: Clients und Rückrufe

Andrew Richards

In diesem dritten Ratenzahlung über die Debugger-API werde ich tiefer in die Beziehung beschäftigen, die eine Debuggererweiterung mit dem Debugger haben kann.Ich gebe Ihnen einen Überblick über die Architektur von Debugger-Clients und Debugger Rückrufe.Dabei bekommen wir in die Nitty-Gritty der Konstanten DEBUG_OUTPUT_XXX und DEBUG_OUTCTL_XXX.

Ich werde diese Grundlage verwenden, um eine Kapselung von der Son of Strike (SOS)-Debuggererweiterung zu implementieren.Ich werde erhöhen die SOS-Ausgabe mit Debugger Markup Language (DML) und zeigen, wie Sie die integrierten Debugger-Befehle und andere Erweiterungen zum Abrufen von Informationen von Ihrer Erweiterungen benötigt nutzen können.

Bevor Sie fortfahren, werden Sie sicher, Sie haben die vorherigen zwei Raten, welche eine Debuggererweiterung zu verstehen ist (einschließlich der wie ich bin erstellen und testen die Beispiele in diesem Artikel) lesen und wie man richtig Ausgabe zu erzeugen.Sie finden Sie unter msdn.microsoft.com/magazine/gg650659 und msdn.microsoft.com/magazine/hh148143.

Ausgabesteuerung

Es gibt zwei Möglichkeiten um Ausgabe in eine Erweiterung zu erzeugen: die IDebugControl::Output *-Funktionen und die IDebugControl::ControlledOutput *-Funktionen.Die Ausgabe-Funktionen sind nur eine Vereinfachung der ControlledOuput Funktionen.Sie das aktuelle Ausgabe Steuerelement festlegen abrufen und an ControlledOutput als ersten Parameter übergeben:

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

Ich gab einen Überblick über die DEBUG_OUTCTL_XXX-Konstanten für den OutputControl-Parameter im zweiten Artikel verwendet. In diesem Artikel werde ich Fokus auf die unteren Bits, die den Anwendungsbereich der Ausgabe steuern. Eine der DEBUG_OUTCTL_XXX bereichsbasierte Konstanten muss für die unteren Bits verwendet werden. Der Wert leitet, wo die Ausgabe ist zu gehen. Dies kann für alle Debugger Clients (DEBUG_OUTCTL_ALL_CLIENTS), nur die IDebugClient der IDebugControl-Schnittstelle (DEBUG_OUTCTL_THIS_CLIENT), alle anderen Clients (DEBUG_OUTCTL_ALL_OTHER_CLIENTS), nirgendwo an alle (DEBUG_OUTCTL_IGNORE) oder nur die Protokolldatei (DEBUG_OUTCTL_LOG_ONLY) zugeordnet sein.

In Example06 im begleitenden Codedownload (die vorhergehenden Artikel identisch ist), der! Outctlpassed Befehl (finden Sie unter Abbildung 1) basiert auf der übergebene IDebugClient-Schnittstelle.

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

Die Funktion wird ein QueryInterface für die IDebugControl-Schnittstelle (des übergebenen IDebugClient). Anschließend wird ControlledOutput mit jedem Output Control Bereichen aufgerufen. Die Maske wird auf Normal (DEBUG_OUTPUT_NORMAL) festgelegt, so dass es ist nicht Ausgabe ausgeschlossen. Standardmäßig ist nur ein Wert von verbose (DEBUG_OUTPUT_VERBOSE) aus dem WinDBG-Ausgabefenster ausgeschlossen. Um den Befehl zu testen, verwende ich das test_windbg.cmd-Skript. In der gestarteten WinDBG, ich öffne das Log Datei (.logopen), führen Sie die! Outctlpassed Befehl und in der Nähe der Log Datei (.logclose), wie hier gezeigt:

0: 000 > .logopen outctlpassed.txt
Geöffnet-Log-Datei "outctlpassed.txt"
0: 000 > ! Outctlpassed
DEBUG_OUTCTL_THIS_CLIENT
DEBUG_OUTCTL_ALL_CLIENTS
0: 000 > .logclose
Schließen open Log-Datei outctlpassed.txt

Die! Outctlpassed Befehl enthält nur DEBUG_OUTCTL_THIS_CLIENT und DEBUG_OUTCTL_ALL_CLIENTS Ausgabe. Die DEBUG_OUTCTL_ALL_OTHER_CLIENTS, DEBUG_OUTCTL_IGNORE und DEBUG_OUTCTL_LOG_ONLY wird Steuerelemente im Fenster Ausgabe weggelassen werden. Aber wenn Sie in der Protokolldatei suchen, es gibt ein anderes Ergebnis, wie hier gezeigt:

Geöffnet-Log-Datei "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
Schließen open Log-Datei outctlpassed.txt

Die nur Ausgabekontrolle fehlt in der Protokolldatei DEBUG_OUTCTL_IGNORE ist.

Debugger-Architektur

Um zu verstehen, was die fehlende Ausgabe in das Ausgabefenster WinDBG und die Log-Datei geschehen, müssen wir in der Debugger-Architektur zu vertiefen. Debuggen basiert auf vier Schichten: die Debugger-Hilfsprogramm, das Debugger-Modul, den Debugger Client und Debugger-Erweiterungen.

Unten ist die Debugger-Hilfsprogramm (dbghelp.dll). Diese Bibliothek enthält die gesamte Funktionalität zum Auflösen Symbole verwendet.

Die nächste Schicht ist die Debugger-Engine (dbgeng.dll). Diese Bibliothek behandelt die Debugsitzung und sieht insbesondere die Unterstützung um ein remote-Ziel zu debuggen. In dieser Bibliothek enthalten ist die Handhabung der Debugger-Schnittstellen (z. B. IDebugClient, IDebugControl und So weiter).

Die nächste Schicht ist die Debugger-Client (Client) Schicht. Auf dieser Ebene werden ein- und Ausgabe verarbeitet wie der Client für hält richtig. Die WinDBG und NTSD-Debugger sind in dieser Ebene. Ein Client verwendet die Debugger-Engine DebugCreate und DebugConnect Funktionen zum Erstellen eines IDebugClient-Objekts, das die das gewünschte Ziel zugeordnet ist. Das Ziel kann lokal oder remote (durch den Debugger Engine Proxy-Unterstützung) sein.

Die letzte Schicht ist Debugger-Erweiterungen (Erweiterung). Ein Client Ruft Befehle durch den Debugger-Erweiterungs-DLL exportiert. Die IDebugClient-Schnittstelle, die der Debugger-Client, aus dem Debugger-Engine empfängt ist die gleiche Schnittstelle, die der Client-Erweiterungen übergibt. Obwohl durch einen Client aufgerufen wird, interagiert eine Erweiterung nur mit den Debugger-Engine. Erweiterungen können die IDebugClient-Schnittstelle mit dem Client freigeben, solange sie nicht Ihre Konfiguration Schaden. Alternativ können sie machen ihre eigenen Client (über IDebugClient::CreateClient) und konfigurieren Sie nach Bedarf.

Mehrere Debugger-Clients

Mehrere Clients (IDebugClient Objekte) können die gleichen Debugsitzung angebracht werden; Es ist die Sitzung, die Einzahl, ist nicht die Clients. Jeder Client verfügt über eine eigene private Schnittstelle der Debugger-Engine, und es ist diese Fähigkeit, den wir nutzen möchten.

Anfangs, wenn Sie WinDBG oder NTSD zu einem Prozess anfügen, es ist nur ein Client – die IDebugClient-Schnittstelle von DebugCreate oder DebugConnect zurückgegeben. Wenn eine Erweiterung IDebugClient::CreateClient für diese zurückgegebenen-Schnittstelle aufruft, macht die Debugger-Engine einen anderen Client, der die Sitzung zugeordnet ist. Der neue Client an die gleichen Debugsitzung angeschlossen ist, aber es ist im Standardzustand. Rückrufe sind nicht darauf legen und die Ausgabe-Maske ist der Standardwert (außer Verbose).

In Abbildung 2, der! Outctlcreate ist die gleiche Ausgabe als! Outctlpassed, aber auf eine neu erstellte IDebugClient-Objekt basiert.

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

Wie vor, den Befehl testen ich das test_windbg.cmd-Skript verwenden. In der gestarteten WinDBG, ich öffne das Log Datei (.logopen), führen Sie die! Outctlcreate Befehl und in der Nähe der Log Datei (.logclose), wie hier gezeigt:

0: 000 > .logopen outctlcreate.txt
Geöffnet-Log-Datei "outctlcreate.txt"
0: 000 > ! Outctlcreate
DEBUG_OUTCTL_ALL_CLIENTS
DEBUG_OUTCTL_ALL_OTHER_CLIENTS
0: 000 > .logclose
Schließen open Log-Datei outctlcreate.txt

Die! Outctlcreate Befehl enthält nur die DEBUG_OUTCTL_ALL_CLIENTS und DEBUG_OUTCTL_ALL_OTHER_CLIENTS Ausgabe. Die "alle anderen" Ausgabe ersetzt, die die "dies" Ausgang da WinDBG-Client nun die "anderen" Client ist. Die Log Dateiergebnisse sind immer noch die gleichen, wie hier gezeigt:

Geöffnet-Log-Datei "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
Schließen open Log-Datei outctlcreate.txt

Innerhalb der Debugging-Engine ist der ControlledOutput-Aufruf bedingt auf ausgeführt wird. Das Modul ruft den Ausgabe-Rückruf auf allen Clients, die die Output-Kriterien entsprechen. So, wenn Sie Ihre eigenen Client machen und das DEBUG_OUTCTL_THIS_CLIENT Ausgabe Steuerelement verwenden, wird die Ausgabe Local bleiben (die nicht in WinDBG angezeigt,). Beachten Sie, dass die Ausgabe noch auf die Protokolldatei geht. Wenn Sie das "nicht angemeldet" high Bit (DEBUG_OUTCTL_NOT_LOGGED) hinzufügen, können Sie dies, zu beenden.

Wieder einmal die! Outctlthis Befehl führt nur "das" und "nicht protokollierten" Ausgabe (DEBUG_OUTCTL_THIS_CLIENT | DEBUG_OUTCTL_NOT_LOGGED) auf einem erstellten Client, wie in gezeigt Abbildung 3.

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

Wie vor, den Befehl testen ich das test_windbg.cmd-Skript verwenden. In der gestarteten WinDBG, ich öffne das Log Datei (.logopen), führen Sie die! Outctlthis Befehl und schließen Sie die Logdatei (.logclose), wie hier gezeigt:

0: 000 > .logopen outctlthis.txt
Geöffnet-Log-Datei "outctlthis.txt"
0: 000 > ! Outctlthis
0: 000 > .logclose
Schließen open Log-Datei outctlthis.txt

Die Log-Datei aufzuzeichnen nicht die Ausgabe des Befehls dieser Zeit. Beachten Sie, dass es die Ausführung aufnehmen, weil Prompt Ausgabe durch den WinDBG-Client vor dem Aufrufen der Erweiterung, erstellt wird, wie hier gezeigt:

Geöffnet-Log-Datei "outctlthis.txt"
0: 000 > ! Outctlthis
0: 000 > .logclose
Schließen open Log-Datei outctlthis.txt

In der Tat habe ich Ausgabe auf NULL weitergeleitet, da der Client erstellt alle dazugehörigen Ausgabedokument Rückruf nicht – nicht gerade eine nützliche Sache. Aber wenn ich den Code mithilfe die IDebugControl::Execute-Funktion ändern, kann ich nun alle Befehle ausführen, ohne es in WinDBG oder protokolliert werden, angezeigt wird, wie hier gezeigt:

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

Wenn Sie Execute auf diese Weise verwenden, müssen Sie die DEBUG_EXECUTE_NOT_LOGGED und DEBUG_EXECUTE_NO_REPEAT-Flags festgelegt, sodass die prompte Ausgabe ist nicht in der Protokolldatei generiert und der Befehl ist nicht für die Wiederholung (z.B. wenn der Benutzer einen blank-Befehl führt durch Drücken der EINGABETASTE in der Eingabeaufforderung) gespeichert.

Siehe Abbildung 4, am Ende der! Outctlexecute Befehl im Hintergrund führt das angegebene Argument.

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

Ich bin erzwungenen erneuten Laden von Symbolen (.reload/f) ausführen. Dies würde in der Regel produzieren viele Symbol Ausgaben, aber! Outctlexecute hat die Ausgabe, versteckt, wie hier gezeigt:

0: 000 > ! Outctlexecute .reload/f

Ausgabe-Rückrufe

Wenn der Debugger Routen Motorleistung anfordert an die Clients (gekennzeichnet durch die Ausgabesteuerung), gibt es zwei zusätzliche Einschränkungen angewendet:

  • Ist die Ausgabe eines Typs (Mask), die wollte ist?
  • Gibt es ein dazugehörigen Ausgabedokument-Rückruf?

Wenn IDebugControl::Output oder IDebugControl::ControlledOutput aufgerufen wird, vergleicht die Debuggen-Engine die angegebene Maske mit der Client Ausgabe Maske (IDebugClient::GetOutputMask). Die zwei Masken entsprechen für die Ausgabe-Rückruf aufgerufen werden müssen.

Anschließend ruft die Debuggen-Engine IDebugClient::GetOutputCallbacks, ein Ausgabe-Callback-Interface (IDebugOutputCallbacks, IDebugOutputCallbacks2 oder IDebugOutputCallbacksWide) zu erhalten. Wenn es gibt einen Ausgabe-Rückruf Satz auf dem Client (vorher über IDebugClient::SetOutputCallbacks), das Debugging-Modul ruft den Rückruf – z. B. IDebugOutputCallbacks::Output.

In Example07 im begleitenden Codedownload habe ich eine IDebugOutputCallbacks Implementierung codiert, die normale Ausgabe von 1 MB und 1 MB Fehlerausgabe unterstützt. (Der Header ist Abbildung 5 und der Code ist in Abbildung 6.)

Abbildung 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__

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

Die-Klasse fügt die Zeichenfolge zur normalen oder Fehler Puffer nach jedem Output-Aufruf. Abgesehen von Funktionen für die Schnittstellen IUnknown und IDebugOutputCallbacks gibt es einige zusätzlichen Funktionen für den Zugriff auf die Puffer (BufferNormal und BufferError), (klar) sie zu entfernen, und eine Funktion, die unterstützten Ausgabetypen (SupportedMask) zu erhalten.

Jedes Mal, Ausgabe oder ControlledOutput wird aufgerufen (und die IDebugClient Kriterien erfüllt sind), Ausgabe der Callback-Funktion wird aufgerufen. Die Maske übergeben ist die gleiche Maske, die auf den ursprünglichen Ausgabe-Aufruf festgelegt ist. Meine Ausgabe-Funktion erledigt Bitmaske Vergleich auf der übergebenen Maske, so dass der Text die richtige Puffergröße zugeordnet ist. Es liegt an der Umsetzung des Rückrufs zu entscheiden, ob die Ausgabe ist separat gespeichert, kombiniert oder ignoriert. Ein einzelner IDebugClient::Execute-Aufruf kann hunderte von Ausgabe Aufrufe von unterschiedlichen Typen führen. Weil ich, die Normal und Fehler Ausgabe des gesamten Execute-Vorgangs zu interpretieren will, verketten ich diese Zeichenfolgen zu zwei zugeordneten Puffer.

Die IDebugOutputCallbacks-Schnittstelle ist in ANSI und TEXT-Format die Zeichenfolge übergeben. Ist der ursprüngliche Text in Unicode oder in DML, wird das Modul eine Konvertierung vor an den Rückruf tun. Um Unicode (breit) Inhalte aufzuzeichnen, verwenden Sie stattdessen die IDebugOutCallbacksWide-Schnittstelle. Der einzige Unterschied zwischen diesen zwei Schnittstellen ist der Text-Parameter als PCWSTR anstelle von PCSTR übergeben wird. Beide Schnittstellen unterstützen nur die Benachrichtigung der textbasierten Ausgabe. Sie erhalten keine DML-formatierten Text.

Um DML Inhalt zu erhalten, müssen Sie die IDebugOutputCallbacks2-Schnittstelle verwenden. Dieses Interface bietet Sie Kontrolle über der Text wie vom Rückruf empfangen wird formatiert: TEXT, DML- oder einem. Die GetInterestMask-Funktion wird verwendet, um dies zu definieren. Es wird aufgerufen, wenn die Schnittstelle auf einem Client via IDebugClient::SetOutputCallbacks festgelegt ist. Sie zurückkehren, ob TEXT (DEBUG_OUTCBI_TEXT), DML (DEBUG_OUTCBI_DML) oder beide Typen von Ausgabe (DEBUG_OUTCBI_ANY_FORMAT) erhalten sollen. Beachten Sie, dass dies nicht das gleiche wie die Ausgabe-Maske.

Die Ausgabe-Funktion hat keinen Zweck auf dieser Schnittstelle; Es ist ein Artefakt der Schnittstellenvererbung (gefälschte) des IDebugCallbacks. Alle Ausgabe Rückrufbenachrichtigungen ist über die Ausgangt2-Funktion:

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

Die welche Parameter gibt an, ob TEXT (DEBUG_OUTCB_TEXT) oder DML (DEBUG_OUTCB_DML) übergeben wird, oder dass ein Flush (DEBUG_OUTCB_EXPLICIT_FLUSH) gewünscht wird (IDebugClient::FlushCallbacks). Beachten Sie, dass dies die CB * Konstanten sind; nicht die CBI * Konstanten für die Maske Interesse.

Der Flags-Parameter gibt zusätzliche Informationen in eine Bitmaske. Insbesondere für DML Inhalt es angibt, ob der Inhalt Hyperlinks (DEBUG_OUTCBF_DML_HAS_TAGS enthält) oder Sonderzeichen führt (", &, < und >) sind codiert (DEBUG_OUTCBF_DML_HAS_SPECIAL_CHARACTERS). Der Parameter kann auch angeben, dass ein Flush (DEBUG_OUTCBF_COMBINED_EXPLICIT_FLUSH) erwünscht ist, nachdem die Ausgabe verarbeitet wurde. Dies tritt auf, wenn Output und FlushCallbacks Anrufe vom Modul kombiniert werden.

Der Arg-Parameter enthält die Ausgabe-Maske für DML, TEXT und FLUSH Rückrufe.

Schließlich ist der Text-Parameter der Text ausgegeben wird. Dies wird NULL sein, wenn der Rückruf für ein Flush (DEBUG_OUTCB_EXPLICIT_FLUSH) aufgerufen wird. Der Text wird immer als Unicode übergeben.

Execution

Es braucht nicht viel Code zum Hinzufügen des Rückrufs in die! Outctlexecute Befehl (siehe Abbildung 4). Die zusätzlichen Schritte sind den Rückruf zugeordnet werden und es die erstellte Schnittstelle IDebugClient vor dem Aufruf von Execute zugeordnet.

Ich implementieren meine Rückrufe als referenzierte gezählten Heapobjekte. Wenn Sie die Windows-Treiberkit Build-Umgebung verwenden, müssen Sie deaktivieren "PREfAst für Treiber" Warning 28197 so dass Sie keine Warnung OACR (Microsoft automatische Code-Review) über die "neue" Zuweisung bekommen jedes Mal, wenn Sie das Projekt kompilieren. (PREfAst nicht erkennt, dass die Klasse Verweis gezählt.)

Es gibt zwei Teile der Association des Rückrufs an den Client: die Ausgabe Maske (SetOutputMask) und der Ausgabe Rückruf (SetOutputCallbacks). Ich habe eine Hilfsfunktion auf meine Rückrufklasse die unterstützten Ausgabe Maske (COutputCallbacks::SupportedMask) zurück, so dass ich zu hartcodieren es nicht. Wenn ich die Maske nicht, würde ich das Risiko nicht die erwartete Ausgabe empfangen werden.

Die! Callbackecho Befehl (siehe Abbildung 7) in das Example07-Projekt im begleitenden Download implementiert ist.

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

Es führt zwei Befehle ("Vertarget" und das übergebene Argument) und zeigt die Normal und Fehler-Ausgabe der einzelnen. Wenn Sie die ControlledOutput-Funktion verwenden, werden Sie feststellen, dass ich das DEBUG_OUTCTL_ALL_OTHER_CLIENTS Ausgabe-Steuerelement verwenden. Dies leitet die Ausgabe in allen anderen Clients und vermeidet die Ausgabe von meinem zugehörigen Callback gefangen genommen. Wenn Sie Schleifen sind obwohl Ausgabe erfasst und sind Ausgabe der gleichen Maske zu erstellen, werden Sie sicher, dass Sie das DEBUG_OUTCTL_ALL_OTHER_CLIENTS Ausgabe Steuerelement verwenden, andernfalls Sie könnten leicht holen Sie sich in eine Endlosschleife Gerät.

Als Sie bevor, ich das test_windbg.cmd-Skript testen, den Befehl. In der gestarteten WinDBG, ich verkehren die! Callbackecho Befehl und sehen die normale Ausgabe des "Vertarget" und der Fehler-Ausgabe von "R @ Rip." (Der Register-Befehl schlägt fehl, da dies ein X 86-Ziel ist und "Rip" ein X 64 Register ist.) Beachten Sie, dass zwischen den Execute-Aufrufe, ich nenne meine klar Hilfsfunktion die Puffer zurücksetzen. Dies ist, was verursacht die Ausgabe "Vertarget" in der Befehlsausgabe "R @ Rip" fehlen, wie hier gezeigt:

0: 000 > ! Callbackecho R @ Rip
[Normal]
Windows 7-Version 7600 MP (2 Procs) Free X 86 kompatible
Produkt: WinNt, suite: SingleUserTS
Kernel32.dll-Version: 6.1.7600.16385 (win7_rtm.090713-1255)
Computername:
Debuggen Sitzungsdauer: Sat Jan 29 22:01:59.080 2011 (UTC - 8: 00)
Verfügbarkeit der Systeme: 0 Tage 6:22:48.483
Prozessbetriebszeit: 0 Tage 0:01:05.792

Kernelzeit: 0 Tage 0:00:00.015
Benutzerzeit: 0 Tage 0:00:00.000
 
[Fehler]

[Normal]
 
[Fehler]**
↑ Bad registrieren Fehler in 'R @ Rip'

Sie können tatsächlich die gleiche Funktionalität implementieren, ohne dass einen Client eigene. Wenn Sie mit Ihren eigenen Werten sorgfältig in die Ausgabe-Maske und die Ausgabe-Rückruf des übergebenen Clients swap, können Sie dasselbe Ergebnis erreichen. Jedoch müssen Sie ganz genau dabei sein. Der übergebene IDebugClient ist den Client, den der Debugger (WinDBG) für die Ausgabe an das Ausgabefenster verwendet. Wenn Sie es in den Zustand nicht, denen es zurückgeben bei gestartet, wird der Debugger nicht mehr ordnungsgemäß.

Abbildung 8 (von Example07 im Codedownload) zeigt die Umschaltfläche Ansatz mit der! Commandtoggle Befehl.

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

Es implementiert genau die gleiche Funktionalität wie! Commandecho. Um Execute und ControlledOutput aufrufen zu können, musste ich häufig wechseln die Maske und Ausgabe-Rückruf Ausgabewerte zwischen den übergebenen Werten und meine Werte, das gewünschte Ergebnis zu erhalten. Beachten Sie, dass die ControlledOutput die DEBUG_OUTCTL_ALL_CLIENTS übergeben wird Ausgangssteuerung in diesem Szenario, wie der damit verbundenen Client jetzt das Ziel eine gewünschte Ausgabe ist. Darüber hinaus werden Sie feststellen, dass ich nur (für Kürze) implementiert Fehlerbehandlung auf den anfänglichen aufrufen.

In den meisten Fällen kann die Wiederverwendung eines Clients recht schwerfällig geworden; Dieser Ansatz sollte mit Vorsicht verwendet werden.

Was ausführen?

Der wesentliche Grund, dass ich verwenden diese Technik private Ausgabe ist implementieren DML-Versionen der textbasierte Befehle, wo habe ich keine Kenntnis von ihrem Internals. Vor allem habe ich "Mehrwert" einige SOS Debuggerbefehle mit Hyperlinks. Durchlaufen wie ich die Ausgabe entscheiden, was zu markieren zu analysieren ist über den Rahmen dieses Artikels hinaus. Ich reden wird, jedoch, wie ich, den korrekten Befehl feststellen ausführen, um die Ausgabe zu erzeugen, dass ich für die Analyse erfordern.

Die Aufzählung der geladenen Debugger-Erweiterungen und ihre zugeordneten Fähigkeiten ist nicht Teil der Debugger-API. Erschwerend kommt hinzu, kann auf die gleiche Debuggererweiterung mehrmals vom gleichen Speicherort auf dem Datenträger geladen werden. Wenn Sie das test_windbg.cmd-Skript mit der .load Befehle Myext und .chain ausführen, sehen Sie ein Beispiel für diese Situation. Die MyExt Erweiterung ist zweimal, einmal als Myext und wieder als myext.dll geladen:

0: 000 > .Load myext
0: 000 > .Chain
Erweiterungs-DLL Suche Pfad:

C:\Debuggers_x86\WINXP;C:\Debuggers_x86\winext;...
Erweiterung DLL Kette:

Myext: Bild 6.1.7600.16385, API 1.0.0, gebaut Sat Jan 29 22: 00: 48 2011
[Pfad: C:\Debuggers_x86\myext.dll]

myext.dll: Bild 6.1.7600.16385, API 1.0.0, gebaut Sat Jan 29 22: 00: 48 2011
[Pfad: C:\Debuggers_x86\myext.dll]

Dbghelp: Bild 6.12.0002.633, API 6.1.6, gebaut Mon Feb 01 12: 08: 26 2010
[Pfad: C:\Debuggers_x86\dbghelp.dll]
   ...

Wenn einen Erweiterung-Befehl für die Analyse aufrufen, möchten Sie sicher sein, dass Sie den beabsichtigten Befehl, keinen Befehl mit dem gleichen Namen auf eine weitere Erweiterung (oder einer anderen Instanz) anrufen.

Wenn Sie einen Befehl voll qualifizieren, führt das Modul einfach den Befehl angegeben. Aber wenn Sie nicht voll ein Befehls qualifizieren, der Motor hat eine Suche zu tun. Das Modul befasst sich mit der Exporttabelle jeder Erweiterung in der Ladereihenfolge und führt die erste Erweiterung mit einer passenden Befehlsnamen. Der .extmatch-Befehl zeigt die aktuelle Suchergebnisse für den angegebenen Befehl. Den .extmatch-Befehl unterstützt Platzhalter den Namen extension und den Befehl über die "/ e" wechseln, wie hier gezeigt:

0: 000 > .extmatch callbackecho
! myext.callbackecho
! myext.dll.callbackecho
0: 000 > .extmatch/e *myext.dll* * Rückseite *
! myext.dll.callbackecho
! myext.dll.callbacktoggle

Wenn Sie die SOS-Erweiterung über einen .loadby sos.dll Mscorwks Aufruf laden, wird ein voll qualifizierter Pfad für es registriert werden; Das heißt, es wird nicht bekannt als "! sos.dll. <command>." Im folgenden Beispiel ich lief die Beispielanwendung Test03 auf Windows 7 x 64 und es (F6) den Debugger an. Die SOS-Erweiterung geladen wurde vom Ordner Mscorwks (C:\Windows\Microsoft.NET\Framework64\v2.0.50727):

0: 004 > .loadby sos.dll mscorwks
0: 004 > .Chain
Erweiterungs-DLL Suche Pfad:
C:\debuggers\WINXP;C:\debuggers\winext;...
Erweiterung DLL Kette:
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos.dll:
Bild 2.0.50727.4952, API 1.0.0, gebauten Do Mai 13 05: 15: 18 2010
[Pfad: C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos.dll]
Dbghelp: Bild 6.12.0002.633, API 6.1.6, gebaut Mon Feb 01 12: 15: 44 2010
[Pfad: C:\debuggers\dbghelp.dll]
    ...

Die einzige Möglichkeit, einen SOS-Befehl nicht sicher aufrufen soll Vollqualifizierung verwenden. So, wenn die SOS-Erweiterung (oder andere Erweiterung) wird genutzt, es gibt zwei Tatsachen zu bestimmen:

  • Werden die SOS-Erweiterung wird geladen?
  • Wo wird es geladen?

Beide können über eine Erweiterung Übereinstimmung (.extmatch/e *sos.dll* <command>) bestimmt werden. In meiner LoadSOS-Funktion (Teil von Example08; der Code ist nicht hier aufgeführt) ich erster Blick für den Befehl in die Versionsangabe Position (\v2.0.50727\sos.dll. <command>). Wenn ich den Versionsangaben Befehl finden kann, zurückgreifen ich auf auf der Suche nach den Befehl ohne eine Version (\sos.dll. <command>). Wenn ich es jetzt finden kann, zurück ich noch Erfolg, sondern als S_FALSE (im Gegensatz zu S_OK). Es liegt an den Aufrufer des LoadSOS zu bestimmen, ob es, eine falsche parsing Risiko dauern wird.

Wenn ich eine SOS-Version des Befehls nicht finden kann, ich nehme an Sie ist nicht geladen (oder es ist geladen, aber nicht in einer Weise, die ich bin glücklich mit) und tun eine Private ".loadby." Wenn dies Fehler generieren nicht, wiederhole ich die Suche. Wenn ich nicht werden SOS geladen kann oder nicht meinen Befehl finden, zurück ich Fehler (E_FAIL).

Einmal habe ich den Befehl, ich ausführen es, schreiben Sie es (z. B. ParseDSO) und Ausgabe die DML-Version (siehe Example08 im Codedownload).

In meinem DML-Wrappern der SOS-Befehle ich diese Überprüfung vor dem Ausführen eines Befehls tun um sicherzustellen, dass SOS zwischen Aufrufen entladen wurde nicht und dass der erforderliche Befehl vorhanden ist. Die Abbildungen 9 und 10 zeigen mein! Dsox Befehl in Aktion mit X 86 und X 64 Ziele.

Test03 x86 !dsox Wrapping !dumpstackobjects

Abbildung 9 Test03 x 86! Dsox einwickeln! Dumpstackobjects

Test03 x64 !dsox Wrapping !dumpstackobjects

Abbildung 10 Test03 x 64! Dsox einwickeln! Dumpstackobjects

Die Befehle hinzufügen eine! Dumpobj Hyperlink an jede Adresse in aufgeführten! Dumpstackobjects; der Code für! Dsox ist in Example08.

Break!

Mit ein wenig Infrastrukturcode ist es möglich, die Logik eines integrierten oder Erweiterung Befehles nutzbar zu machen. Meine Analyse der SOS-Erweiterung über das Hinzufügen von DML ist, aber es könnte leicht habe über die Erhebung von verwalteten Klasse Informationen. Ich nicht wissen nichts über die internen Strukturen der CLR, mein Ziel zu erreichen. Ich musste einfach wissen, wie man navigieren auf die Daten, die ich wollte, um anzuzeigen.

Debugger-Erweiterungen vereinfachen Debug Analyse durch die Automatisierung beim Datenabruf und die Bereicherung der Anzeige. Ich hoffe, dieser Serie inspiriert hat Sie Ihre eigenen Erweiterungen vornehmen, die vereinfachen das Debuggen, die, das Sie tun.

Wenn Sie gerade Debuggen und wollen mehr erfahren interessiert sind, sollten Sie das Advanced Windows Debuggen und Troubleshooting-Blog (blogs.msdn.com/b/ntdebugging) – es hat viele Schulungs- und Fallstudie Artikel zu lesen.

Andrew Richards   ist Microsoft senior Escalation Engineer für Windows OEM. Er hat eine Leidenschaft für Support-Tools und ständig schafft Debugger Erweiterungen und Anwendungen, die die Arbeit der Supportingenieure vereinfachen.

Dank der folgenden technischen Experten für die Überprüfung dieses Artikels: Drew Bliss