Dieser Artikel wurde maschinell übersetzt.

Debug-APIs

Schreiben von Debugtools für Windows Extension

Andrew Richards

Downloaden des Codebeispiels

Problembehandlung bei der Produktion möglich der meisten frustrierend Jobs, die alle Engineer durchführen können. Es kann auch eine der am häufigsten lohnende Jobs sein. Im Support von Microsoft arbeiten, bin ich mit diesem täglich konfrontiert. Warum bringen die Anwendung zum Absturz? Warum hängt es? Warum ist es ein Leistungsproblem haben?

Lernen das Debuggen kann eine schwierige Aufgabe sein und ist eine von diesen Fähigkeiten, die der regelmäßigen Praxis geübt bleiben viele Stunden erfordert. Aber es ist eine wichtige Fähigkeit für eine Allround-Entwickler werden. Dennoch, durch die Abfuellung der Fähigkeiten von ein paar Debuggen Experten, Debugger-Ingenieure aller Fähigkeitsstufen Code ausführen können äußerst komplexe debugging als to Run-Befehle.

Unzählige Problembehandlungsverfahren kann verwendet werden, um die Hauptursache für einen Absturz, aber die wichtigsten zu erhalten – und am häufigsten zu Technikern mit Debuggen Fähigkeiten sehr ergiebigen – ist ein Speicherabbild des Prozesses. Prozess-Dumps enthalten einen Snapshot von einem Prozessspeicher zum Zeitpunkt der Erfassung. Je nach Dumpingspanne Tool könnte dies die gesamte Adressraum oder nur eine Teilmenge sein.

Windows erstellt automatisch ein Minidump über Windows Error Reporting (WER), wenn eine Anwendung eine unbehandelte Ausnahme auslöst. Darüber hinaus können Sie eine Dumpdatei über das Dienstprogramm "Userdump.exe" manuell erstellen. Das Sysinternals-tool () ProcDumpTechNet.Microsoft.com/Sysinternals/dd996900) wird immer das bevorzugte Prozess dumping-Tool von Microsoft Support Services, da Sie einen Dump basierend auf einer Vielzahl von Triggern erfassen können und Dumps mit verschiedenen Größen generieren kann. Aber sobald Sie die Sicherungsdaten haben, was Sie tun können, um Debuggen zu unterstützen?

Verschiedene Versionen von Visual Studio-Unterstützung, Öffnen von Dumpdateien (.dmp), aber das beste Tool zu verwenden ist ein Debugging Tools for Windows-Debugger. Diese Tools basieren alle auf einem einzigen debugging-Modul, die zwei Debuggererweiterung APIs unterstützt. In diesem Artikel werde ich die Grundlagen der Erstellung einer benutzerdefinierten Debuggererweiterung analysieren diese Dumpdateien (und auch live Systeme) mit Leichtigkeit zu decken.

Die Tools einrichten

Debugtools für Windows)Microsoft.com/whdc/devtools/Debugging) ist eine installierbare und verteilbare Komponente des Windows SDK und des Windows Driver Kit (WDK). Wie ich dies schreibe, die aktuelle Version 6.12 ist, und ist in Version 7.1 des Windows SDK oder das WDK verfügbar. Ich empfehle die Verwendung der neuesten Version wie Debugmoduls viele nützliche Ergänzungen, einschließlich besserer Stapel zu durchlaufen hat.

Die Debugging Tools for Windows-Richtlinien sagen, dass der Debugger-Erweiterungen, die mit die WDK-Build-Umgebung kompiliert werden soll. Ich verwende die neueste Version von WDK (Version 7.1.0 Build 7600.16385.1), aber eine beliebige Version von WDK oder der vorherigen Version, wie Driver Development Kit (DDK) ausreichen. Beim Erstellen einer Erweiterungs mithilfe des WDK verwenden Sie X 64 und X 86 freien Buildumgebungen.

Erstellen mit wenig Aufwand können Sie auch anpassen Meine Projekte im Windows SDK erstellen Umgebung oder Visual Studio.

Einen wichtigen Hinweis: das WDK nicht wie im Pfadnamen Leerzeichen. Stellen Sie sicher, dass Sie von einer ununterbrochenen Pfad kompilieren. Beispielsweise verwenden Sie etwa C:\Projects anstelle von C:\Users\Andrew Richards\Documents\Projects.

Unabhängig davon, wie Sie die Erweiterung erstellen müssen Sie die Dateien mit Nachrichtenheader und Bibliothek der Debugging Tools-SDK ist eine Komponente der Debugtools für Windows. Die Beispiele in diesem Artikel verwenden Meine X 86-Pfad (C:\debuggers_x86\sdk) beim Verweisen auf die Dateien mit Nachrichtenheader und Bibliothek. Wenn Sie den Debugger an anderer Stelle zu installieren, müssen Sie aktualisieren Sie den Pfad und Hinzufügen von Anführungszeichen bei Bedarf, um Leerzeichen in den Pfadnamen zu berücksichtigen.

Mithilfe der Debugtools

Das Debugging Tools for Windows-Debugger sind keine Informationen über die Architektur. Alle Zielarchitektur kann eine beliebige Edition des Debuggers gedebuggt werden. Ein typisches Beispiel ist der X 64-Debugger verwenden, um ein X 86-Anwendung zu debuggen. Der Debugger wird für X 86, x 64 (amd64) freigegeben und IA64, aber es kann X 86, x 64, IA64, ARM, EBC Debuggen und PowerPC (Xbox) Anwendungen. Sie können den Debugger Editionen Side-by-Side installiert.

Diese Flexibilität ist nicht universell, jedoch verstanden. Nicht alle Debugger-Erweiterungen Anpassung an die Zielarchitektur sowie die Debugger-Engine unterstützt. Einige Debugger-Erweiterungen wird davon ausgegangen, dass die Zeigergröße des Zieles identisch mit der Größe des Debuggers. Ebenso verwenden sie das falsche hartcodiert-Register (esp anstelle von Rsp, z. B.), anstatt ein Pseudo-register wie z. B. $Csp.

Wenn Sie ein Problem mit einem Debuggererweiterung haben, sollten Sie versuchen, den Debugger, entwickelt für die Architektur der Zielumgebung ausgeführt. Dies könnte die Annahmen für die schlecht überwinden, Erweiterung geschrieben.

Jede Anwendung Buildtyp und zugehörigen Prozessorarchitektur verfügt über einen eigenen Satz von Kopfschmerzen zu debuggen. Der Assembler für Debugbuilds generiert relativ linear ist, aber der Assembler für einen Releasebuild generiert ist optimiert und kann eine Schüssel Spaghetti aussehen. Klicken Sie auf X 86-Architekturen, Rahmenzeigerauslassung (FPO) Chaos mit Call Stack Wiederaufbau (der neueste Debugger behandelt diese Mulde) spielt. Auf X 64 sind Architekturen, Funktionsparameter und lokale Variablen in Registern gespeichert. Zum Zeitpunkt der Dump-Erfassung sie wurden auf dem Stapel abgelegt oder möglicherweise aufgrund von Register-Wiederverwendung nicht mehr vorhanden.

Erfahrung ist der Schlüssel. Um genau zu sein, eine Person-Erfahrung: der Schlüssel ist. Sie müssen lediglich ihre Know-how in eine Debuggererweiterung für den Rest von uns Flasche. Es dauert nur wenige Wiederholungen einer ähnlichen debugging-Sequenz, bevor ich es als eine Debuggererweiterung automatisieren. Ich habe einige meiner Erweiterungen so vielen, dass ich vergessen, wie ich das gleiche mit den zugrunde liegenden Debuggen-Befehlen verwendet.

Verwenden die Debug-APIs

Es gibt zwei debugger-APIs für Erweiterungen: die veraltete WdbgExts-API (wdbgexts.h) und die aktuelle DbgEng-API (dbgeng.h).

WdbgExts-Erweiterungen basieren auf einem globalen Aufruf, die bei der Initialisierung (WinDbgExtensionDllInit) konfiguriert ist:

WINDBG_EXTENSION_APIS ExtensionApis;

Die globale bietet die Funktionalität, die zum Ausführen von Funktionen wie dprintf("\n") und GetExpression("@$csp"), ohne Sie zu einem beliebigen Namespace erforderlich. Dieser Typ der Erweiterung ähnelt den Code, den Sie schreiben würden, bei der Win32-Programmierung.

DbgEng-Erweiterungen basieren auf Debugger-Schnittstellen. Die IDebugClient-Schnittstelle wird von Debugging-Modul als Parameter jeder Aufruf an Sie übergeben. Die Schnittstellen unterstützen die QueryInterface für den Zugriff auf den ständig wachsenden Bereich der Debugger-Schnittstellen. Dieser Typ der Erweiterung ähnelt den Code, den Sie schreiben würden, bei der COM-Programmierung.

Es ist möglich, eine Mischung aus den beiden Erweiterungstypen vorzunehmen. Sie die Erweiterung als DbgEng verfügbar zu machen, aber die Funktionalität der WdbgExts-API zur Laufzeit über den Aufruf von IDebugControl::GetWindbgExtensionApis64 hinzufügen. Als Beispiel die klassischen "Hello World" als Erweiterung des DbgEng in c geschrieben Auf Wunsch C++ finden Sie in der ExtException-Klasse in den Debugging Tools-SDK (. \inc\engextcpp.cpp).

Kompilieren Sie die Erweiterung als MyExt.dll ("TargetName" in die Datei Quellen in Abbildung 1). Außerdem stellt es einen Befehl namens! "HelloWorld". Die Erweiterung verknüpft dynamisch mit der Microsoft Visual C-Laufzeit (MSVCRT). Wenn Sie möchten, verwenden Sie statisch, ändern die USE_MSVCRT = 1-Anweisung, um USE_LIBCMT = 1 in der Quellen-Datei.

Abbildung 1 Quellen

TARGETNAME=MyExt
TARGETTYPE=DYNLINK
_NT_TARGET_VERSION=$(_NT_TARGET_VERSION_WINXP)
DLLENTRY=_DllMainCRTStartup
!if "$(DBGSDK_INC_PATH)" != ""
INCLUDES = $(DBGSDK_INC_PATH);$(INCLUDES)
!endif
!if "$(DBGSDK_LIB_PATH)" == ""
DBGSDK_LIB_PATH = $(SDK_LIB_PATH)
!else
DBGSDK_LIB_PATH = $(DBGSDK_LIB_PATH)\$(TARGET_DIRECTORY)
!endif
TARGETLIBS=$(SDK_LIB_PATH)\kernel32.lib \
           $(DBGSDK_LIB_PATH)\dbgeng.lib
USE_MSVCRT=1
UMTYPE=windows
MSC_WARNING_LEVEL = /W4 /WX
SOURCES= dbgexts.rc      \
         dbgexts.cpp     \
         myext.cpp

Die DebugExtensionInitialize-Funktion (siehe Abbildung 2) wird aufgerufen, wenn die Erweiterung geladen wird. Festlegen der Version-Parameter ist eine einfache Frage der mit dem DEBUG_EXTENSION_VERSION-Makro mit dem EXT_MAJOR_VER und EXT_MINOR_VER # defines habe ich die Header-Datei hinzugefügt:

/ / dbgexts.h

# include <windows.h># include <dbgeng.h>

# define EXT_MAJOR_VER 1 # define EXT_MINOR_VER 0

Abbildung 2 dbgexts.cpp

// dbgexts.cpp
#include "dbgexts.h"
extern "C" HRESULT CALLBACK
DebugExtensionInitialize(PULONG Version, PULONG Flags) {
  *Version = DEBUG_EXTENSION_VERSION(EXT_MAJOR_VER, EXT_MINOR_VER);
  *Flags = 0;  // Reserved for future use.
  return S_OK;
}
extern "C" void CALLBACK
DebugExtensionNotify(ULONG Notify, ULONG64 Argument) {
  UNREFERENCED_PARAMETER(Argument);
  switch (Notify) {
    // A debugging session is active. The session may not necessarily be suspended.
    case DEBUG_NOTIFY_SESSION_ACTIVE:
      break;
    // No debugging session is active.
    case DEBUG_NOTIFY_SESSION_INACTIVE:
      break;
    // The debugging session has suspended and is now accessible.
    case DEBUG_NOTIFY_SESSION_ACCESSIBLE:
      break;
    // The debugging session has started running and is now inaccessible.
    case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
      break;
  }
  return;
}
extern "C" void CALLBACK
DebugExtensionUninitialize(void) {
  return;
}

Der Version-Wert wird als die API-Version in der Debugger-Befehl .chain gemeldet. So ändern Sie die Dateiversion, Dateibeschreibung, Copyright und andere Werte müssen Sie die Datei dbgexts.rc bearbeiten:

myext.dll: image 6.1.7600.16385, API 1.0.0, built Wed Oct 13 20:25:10 2010
  [path: C:\Debuggers_x86\myext.dll]

Der Flags-Parameter ist reserviert und sollten auf NULL festgelegt werden. Die Funktion muss S_OK zurückgegeben.

Die DebugExtensionNotify-Funktion wird aufgerufen, wenn die Sitzung ändert sich der Status aktiven oder zugegriffen werden kann. Der Argument-Parameter wird mit dem UNREFERENCED_PARAMETER-Makro, um die nicht verwendeten Parameter Compilerwarnung zu beseitigen umbrochen.

Ich habe die Switch-Anweisung für die Notify-Parameter auf Vollständigkeit hinzugefügt, aber ich habe noch keine funktionalen Code hinzugefügt, in diesem Bereich. Die Switch-Anweisung verarbeitet vier Sitzungsstatus:

  • DEBUG_NOTIFY_SESSION_ACTIVE tritt auf, wenn Sie ein Ziel zuordnen.
  • DEBUG_NOTIFY_SESSION_INACTIVE tritt auf, wenn das Ziel (über .detach oder Qd) getrennt wird.
  • Wenn das Ziel unterbricht (Treffer einen Haltepunkt, z. B.), die Funktion übergeben DEBUG_NOTIFY_SESSION_ACCESSIBLE.
  • Wenn das Ziel Lebensläufe ausgeführt, die Funktion DEBUG_NOTIFY_SESSION_INACCESSIBLE übergeben.

Die DebugExtensionUninitialize-Funktion wird aufgerufen, wenn die Erweiterung entladen wird.

Jeder Befehl Extension offen gelegt werden als eine Funktion vom Typ PDEBUG_EXTENSION_CALL deklariert wird. Der Name der Funktion ist der Name des Befehls Erweiterung. Da ich mit "Hello World" schreibe, habe ich die Funktion "HelloWorld" genannt (siehe Abbildung 3).

Abbildung 3 MyExt.cpp

// MyExt.cpp
#include "dbgexts.h"
HRESULT CALLBACK 
helloworld(PDEBUG_CLIENT pDebugClient, PCSTR args) {
  UNREFERENCED_PARAMETER(args);
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl))) {
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "Hello World!\n");
    pDebugControl->Release();
  }
  return S_OK;
}

Beachten Sie, dass das Übereinkommen ist die Verwendung von Kleinbuchstaben Funktionsnamen. Da ich die WDK-Build-Umgebung verwendet wird, muss die myext.def-Datei auch geändert werden. Der Name des Befehls Erweiterung muss hinzugefügt werden, so dass es exportiert werden:

;-------------
;   MyExt.def
;-------------
EXPORTS
  helloworld
  DebugExtensionNotify
  DebugExtensionInitialize
  DebugExtensionUninitialize

Der Args-Parameter enthält eine Zeichenfolge der Argumente für den Befehl. Der Parameter wird als eine Null-terminierte ANSI-Zeichenfolge (CP_ACP) übergeben.

Der pDebugClient-Parameter ist der IDebugClient-Schnittstellenzeiger, der die Erweiterung für die Interaktion mit Debugmoduls ermöglicht. Obwohl der Schnittstellenzeiger es sich um ein COM-Schnittstellenzeiger ist scheint, kann nicht Sie gemarshallt und zu einem späteren Zeitpunkt zugegriffen wird. Es kann auch von jedem anderen Thread verwendet werden. Um in einem anderen Thread funktionieren, muss auf diesem Thread mithilfe der IDebugClient::CreateClient ein neuen Debugger-Client (eine neue Schnittstellenzeiger auf IDebugClient) erstellt werden. Dies ist die einzige Funktion, die in einem anderen Thread ausgeführt werden kann.

Die IDebugClient-Schnittstelle (z. B. alle Schnittstellen) wird von IUnknown abgeleitet. Verwenden Sie QueryInterface für den Zugriff auf die DbgEng-Schnittstellen werden sie spätere Versionen der IDebugClient-Schnittstelle (IDebugClient4) oder anderen Schnittstellen (IDebugControl, IDebugRegisters, IDebugSymbols, IDebugSystemObjects usw.). Ausgabe von Text an den Debugger, benötigen Sie die IDebugControl-Schnittstelle.

Ich habe zwei nicht-SDK-Dateien in den Ordner, die bei der Entwicklung helfen. Das make.cmd-Skript der WDK-Build-Umgebung der Debugger SDK inc und Lib-Pfade hinzugefügt und anschließend wird der entsprechende Buildbefehl ausgeführt:

@echo off
set DBGSDK_INC_PATH=C:\Debuggers_x86\sdk\inc
set DBGSDK_LIB_PATH=C:\Debuggers_x86\sdk\lib
set DBGLIB_LIB_PATH=C:\Debuggers_x86\sdk\lib
build -cZMg %1 %2

Beachten Sie, dass die WDK-Build-Umgebung selbst bestimmt, ob es sich bei einem X 86 oder X 64 Binärstruktur erstellt wird. Wenn Sie für mehrere Architekturen erstellen möchten, müssen Sie mehrere Aufforderungen zu öffnen, und führen Sie make.cmd in jedem. Das Gebäude kann gleichzeitig erfolgen.

Nach der Erstellung, verwende ich die (X 86) test.cmd ein Skript so kopieren Sie die kompilierten i386-Binärdateien in der X 86-Debugger-Ordner (c:\Debuggers_x86), und starten Sie eine Instanz des Editors, mit dem Debugger angefügt und die Erweiterung geladen:

@echo off
copy objfre_win7_x86\i386\myext.dll c:\Debuggers_x86
copy objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86
\Debuggers_x86\windbg.exe -a myext.dll -x notepad

Wenn alles wie geplant begeben hat, kann ich geben "!" HelloWorld "" in der Debugger-Eingabeaufforderung und Siehe Antwort "Hello World!":

0: 000 >! "HelloWorld" Hello World!

Symbol-Auflösung und Literatur

Die "Hello World"-Anwendung ist möglicherweise erstaunliche, jedoch können Sie besser. Ich werde jetzt diese Infrastruktur verwenden, um einen Befehl hinzuzufügen, der tatsächlich interagiert mit dem Ziel und würde Sie einige Analysen durchführen. Die einfache test01-Anwendung besteht aus einen globalen Zeiger, der ein Wert zugewiesen wird:

// test01.cpp
#include <windows.h>
void* g_ptr;
int main(int argc, char* argv[]) {
  g_ptr = "This is a global string";
  Sleep(10000);
  return 0;
}

Die neue! Gptr-Befehl in MyExt.cpp (finden Sie unter Abbildung 4) löst das test01! G_ptr global, lesen Sie den Zeiger, und geben Sie dann die Werte in das gleiche Format wie "X test01! G_ptr":

0: 000 >X-test01! G_ptr 012f3370 Test01! G_ptr = 0x012f20e4

0: 000 >! Gptr 012f3370 test01! G_ptr = 0x012f20e4 <string>

Abbildung 4 MyExt.cpp überarbeitet

HRESULT CALLBACK 
gptr(PDEBUG_CLIENT pDebugClient, PCSTR args) {
  UNREFERENCED_PARAMETER(args);
  IDebugSymbols* pDebugSymbols;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugSymbols), 
    (void **)&pDebugSymbols))) {  
    // Resolve the symbol.
    ULONG64 ulAddress = 0;
    if (SUCCEEDED(pDebugSymbols->GetOffsetByName("test01!g_ptr", &ulAddress))) {
      IDebugDataSpaces* pDebugDataSpaces;
      if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugDataSpaces),
        (void **)&pDebugDataSpaces))) {  
        // Read the value of the pointer from the target address space.
        ULONG64 ulPtr = 0;
        if (SUCCEEDED(pDebugDataSpaces->ReadPointersVirtual(1, ulAddress, &ulPtr))) {
          PDEBUG_CONTROL pDebugControl;
          if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
            (void **)&pDebugControl))) {  
            // Output the values.
            pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
              "%p test01!g_ptr = 0x%p\n", ulAddress, ulPtr);
            pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "%ma\n", ulPtr);
            pDebugControl->Release();
          }
        }
        pDebugDataSpaces->Release();
      }
      pDebugSymbols->Release();
    }
  }
  return S_OK;
}

Der erste Schritt ist die Position der test01 bestimmt! G_ptr-Zeiger. Der Zeiger werden in einen anderen Speicherort jedes Mal, die Anwendung ausgeführt wird, da Address Space Layout Randomization (ASLR) die Ladeadresse Modul geändert wird. Um den Speicherort zu erhalten, ich QueryInterface beim Abrufen der IDebugSymbols-Schnittstelle verwenden, und verwenden Sie GetOffsetByName. Die GetOffsetByName-Funktion akzeptiert einen Symbolnamen ein und gibt die Adresse als 64-Bit-Zeiger zurück. Die Debuggerfunktionen geben immer 64-Bit-Zeiger (ULONG64), sodass 64-Bit-Ziele mit einem 32-Bit-Debugger debuggt werden können.

Denken Sie daran, dies ist die Adresse des Zeigers in den Ziel-Adressraum nicht Ihren eigenen. Sie können nicht nur aus, um seinen Wert zu bestimmen lesen. Um den Wert des Zeigers zu erhalten, ich QueryInterface erneut beim Abrufen der IDebugDataSpaces-Schnittstelle verwenden, und verwenden Sie ReadPointersVirtual. Liest den Zeiger aus der Ziel-Adressraum. ReadPointersVirtual passt sich automatisch für die Zeigergröße und endian Unterschiede. Sie müssen nicht den zurückgegebene Zeiger zu manipulieren.

IDebugControl::Output hat dieselbe Formatzeichenfolge wie Printf, aber auch Formatierungsprogramme, mit denen Sie auf den Ziel-Adressraum zu verweisen. Ich verwende das % Ma-Format, um die ANSI-Zeichenfolge zu drucken, dass die Adresse der globalen Zeiger verweist auf das Ziel für Speicherplatz. %P-Format ist Zeiger-Größe-fähigen und sollte für die Ausgabe der Zeiger verwendet werden (Sie müssen ein ULONG64 übergeben).

Ich habe das Testskript, laden eine Speicherabbilddatei des X 86-Version von test01 anstelle von Notepad gestartet geändert:

@echo off
copy objfre_win7_x86\i386\myext.dll c:\Debuggers_x86
copy objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86
\Debuggers_x86\windbg.exe -a myext.dll -y "..\Test01\x86;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols" -z ..\Test01\x86\Test01.dmp

Ich habe die test01 x 86-Ordner und die öffentlichen Symbolserver von Microsoft auch den Symbolpfad festlegen, damit alles aufgelöst werden kann. Ich haben darüber hinaus eine X 64-Testskript vorgenommen, die die gleiche wie die X 86-Testskript, jedoch mit einer Speicherabbilddatei des X 64-Version der Testanwendung ist:

@echo off
copy objfre_win7_x86\i386\myext.dll c:\Debuggers_x86
copy objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86
\Debuggers_x64\windbg.exe -a myext.dll -y "..\Test01\x64;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols" -z ..\Test01\x64\Test01.dmp

Beim Ausführen der Skripts, X 86, Debugger gestartet wird, die entsprechenden Dumpdatei wird geöffnet, die X 86-Version der Erweiterung geladen wird und Symbole aufgelöst werden können.

Wenn alles, was sich begeben hat, um zu planen, ich kann Geben Sie "X test01! G_ptr" und! Gptr im Debugger Eingabeaufforderung und ähnliche Antworten angezeigt:

// x86 Target
0:000> x test01!g_ptr
012f3370 Test01!g_ptr = 0x012f20e4
0:000> !gptr
012f3370 test01!g_ptr = 0x012f20e4
This is a global string
// x64 Target
0:000> x test01!g_ptr
00000001`3fda35d0 Test01!g_ptr = 0x00000001`3fda21a0
0:000> !gptr
000000013fda35d0 test01!g_ptr = 0x000000013fda21a0
This is a global string

Wenn Sie den Test mit der X 64-Debugger, die amd64-kompilierte Version von Debuggererweiterung und die X 86 oder X 64-Dump-Dateien wiederholt werden, erhalten Sie die gleichen Ergebnisse. Die Erweiterung ist also keine Informationen über die Architektur.

Prozessortypen und Stapel

Ich werde jetzt diese Infrastruktur noch einmal zu erweitern. Fügen Sie einen Befehl, der die Dauer eines Sleep-Aufrufs an den aktuellen Threadstapel findet. Die! Müdes Befehl (siehe Abbildung 5) wird der Aufruf Symbole suchen für die Sleep-Funktion Stapeln und lesen Sie den DWORD-Wert, der die Millisekunden Verzögerung darstellt, und dann wird die Ausgabe des Verzögerungswert lösen (wenn gefunden).

Abbildung 5 Müdes

HRESULT CALLBACK 
sleepy(PDEBUG_CLIENT4 Client, PCSTR args) {
  UNREFERENCED_PARAMETER(args);
  BOOL bFound = FALSE;
  IDebugControl* pDebugControl;
  if (SUCCEEDED(Client->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl))) {
    IDebugSymbols* pDebugSymbols;
    if (SUCCEEDED(Client->QueryInterface(__uuidof(IDebugSymbols), 
      (void **)&pDebugSymbols))) {
      DEBUG_STACK_FRAME* pDebugStackFrame = 
        (DEBUG_STACK_FRAME*)malloc(
        sizeof(DEBUG_STACK_FRAME) * MAX_STACK_FRAMES);
      if (pDebugStackFrame != NULL) {  
        // Get the Stack Frames.
        memset(pDebugStackFrame, 0, (sizeof(DEBUG_STACK_FRAME) * 
          MAX_STACK_FRAMES));
        ULONG Frames = 0;
        if (SUCCEEDED(pDebugControl->GetStackTrace(0, 0, 0, 
          pDebugStackFrame, MAX_STACK_FRAMES, &Frames)) && 
          (Frames > 0)) {
          ULONG ProcessorType = 0;
          ULONG SymSize = 0;
          char SymName[4096];
          memset(SymName, 0, 4096);
          ULONG64 Displacement = 0;
          if (SUCCEEDED(pDebugControl->GetEffectiveProcessorType(
            &ProcessorType))) {
            for (ULONG n=0; n<Frames; n++) {  
              // Use the Effective Processor Type and the contents 
              // of the frame to determine existence
              if (SUCCEEDED(pDebugSymbols->GetNameByOffset(
                pDebugStackFrame[n].InstructionOffset, SymName, 4096, 
                &SymSize, &Displacement)) && (SymSize > 0)) {
                if ((ProcessorType == IMAGE_FILE_MACHINE_I386) && 
                  (_stricmp(SymName, "KERNELBASE!Sleep") == 0) && 
                  (Displacement == 0xF)) {  
                  // Win7 x86; KERNELBASE!Sleep+0xF is usually in frame 3.
                  IDebugDataSpaces* pDebugDataSpaces;
                  if (SUCCEEDED(Client->QueryInterface(
                    __uuidof(IDebugDataSpaces), 
                    (void **)&pDebugDataSpaces))) {  
                    // The value is pushed immediately prior to 
                    // KERNELBASE!Sleep+0xF
                    DWORD dwMilliseconds = 0;
                    if (SUCCEEDED(pDebugDataSpaces->ReadVirtual(
                      pDebugStackFrame[n].StackOffset, &dwMilliseconds, 
                      sizeof(dwMilliseconds), NULL))) {
                      pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
                        "Sleeping for %ld msec\n", dwMilliseconds);
                      bFound = TRUE;
                    }
                    pDebugDataSpaces->Release();
                  }
                  if (bFound) break;
                }
                else if ((ProcessorType == IMAGE_FILE_MACHINE_AMD64) && 
                  (_stricmp(SymName, "KERNELBASE!SleepEx") == 0) && 
                  (Displacement == 0xAB)) {  
                  // Win7 x64; KERNELBASE!SleepEx+0xAB is usually in frame 1.
                  IDebugRegisters* pDebugRegisters;
                  if (SUCCEEDED(Client->QueryInterface(
                    __uuidof(IDebugRegisters), 
                    (void **)&pDebugRegisters))) {  
                    // The value is in the 'rsi' register.
                    ULONG rsiIndex = 0;
                    if (SUCCEEDED(pDebugRegisters->GetIndexByName(
                      "rsi", &rsiIndex)))
                    {
                      DEBUG_VALUE debugValue;
                      if (SUCCEEDED(pDebugRegisters->GetValue(
                        rsiIndex, &debugValue)) && 
                        (debugValue.Type == DEBUG_VALUE_INT64)) {  
                        // Truncate to 32bits for display.
                        pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
                          "Sleeping for %ld msec\n", debugValue.I32);
                        bFound = TRUE;
                      }
                    }
                    pDebugRegisters->Release();
                  }
                  if (bFound) break;
                }
              }
            }
          }
        }
        free(pDebugStackFrame);
      }
      pDebugSymbols->Release();
    }
    if (!bFound)
      pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
        "Unable to determine if Sleep is present\n");
    pDebugControl->Release();
  }
  return S_OK;
}

Um den Befehl einige Komplexität hinzuzufügen, wird der Befehl X 86 und X 64-Versionen der Anwendung test01 unterstützen. Da die Aufrufkonvention für X 86 und X 64-Anwendungen unterscheidet, wird der Befehl berücksichtigen die Zielarchitektur im Verlauf aufweisen.

Die erste Schritt besteht darin, die Stack-Frames zu erhalten. Um die Rahmen zu erhalten, ich QueryInterface beim Abrufen der IDebugControl-Schnittstelle verwenden, und verwenden Sie GetStackTrace zum Abrufen von Informationen zu jedem Stapelrahmen. GetStackTrace akzeptiert ein Array von DEBUG_STACK_FRAME Strukturen. Ich reservieren immer das Array von DEBUG_STACK_FRAME Strukturen auf dem Heap, so dass ich ein Stapelüberlauf verursacht nicht. Wenn Sie retrieving ein Stack-Überlauf-Thread eines Zieles sind, werden Sie wahrscheinlich einen eigenen Stapel-Grenzwert erreicht, wenn das Array auf dem Stapel zugeordnet ist.

Wenn GetStackTrace erfolgreich ist, wird das Array mit Informationen für jeden Frame durchlaufen wurde aufgefüllt. Hier Erfolg bedeutet nicht notwendigerweise, dass die Frame-Informationen korrekt sind. Der Debugger versucht, den Stack-Frames zu durchlaufen, jedoch Fehler gemacht werden können, wenn die Symbole nicht korrekt (Wenn sie nicht vorhanden sind oder haben gezwungen wurden). Wenn Sie ".reload/f/i" um die Symbol-Last zu erzwingen, schlechte Symbol Ausrichtung wird wahrscheinlich auftreten.

Um den Inhalt der einzelnen DEBUG_STACK_FRAME-Strukturen effizient verwenden zu können, muss der Zieltyp effektive Prozessor kennen. Wie bereits erwähnt, kann die Zielarchitektur von Debugger-Erweiterungsarchitektur unterscheiden. Der effektive Prozessortyp (.effmach) ist die Architektur, die das Ziel derzeit verwendet wird.

Der Prozessortyp kann auch einen anderen Prozessortyp als in den Ziel-Host sein. Das am häufigsten verwendete Beispiel dafür ist, wenn das Ziel ist ein X 86-Anwendung, die über 32-Bit-Windows auf Windows-64-Bit (WOW64) läuft auf einer X 64-Edition von Windows. Der effektive Prozessortyp ist IMAGE_FILE_MACHINE_I386. Der tatsächliche Typ ist IMAGE_FILE_MACHINE_AMD64.

Das bedeutet, Sie sollten ein X 86-Anwendung ist ein X 86-Anwendung unabhängig davon, ob es auf einem X 86-Edition von Windows oder eine X 64-Edition von Windows ausgeführt wird. (Die einzige Ausnahme zu dieser Regel ist beim Debuggen darin, dass die WOW64, Surround-X 86-Prozess aufrufen.)

Um den effektive Prozessortyp zu erhalten, verwende ich die IDebugControl-Schnittstelle, dass ich bereits habe, und verwenden Sie GetEffectiveProcessorType.

Ist der effektive Prozessortyp i386, muss ich für die KERNELBASE zu sehen!Sleep + 0xf Funktion. Wenn die Symbole ordnungsgemäß aufgelöst werden, sollte die Funktion im Frame 3 aufweisen:

0:000> knL4
 # ChildEBP RetAddr  
00 001bf9dc 76fd48b4 ntdll!KiFastSystemCallRet
01 001bf9e0 752c1876 ntdll!NtDelayExecution+0xc
02 001bfa48 752c1818 KERNELBASE!SleepEx+0x65
03 001bfa58 012f1015 KERNELBASE!Sleep+0xf

Ist der effektive Prozessortyp AMD64, Suche ich für die KERNELBASE!SleepEx + 0xab-Funktion. Wenn die Symbole ordnungsgemäß aufgelöst werden, sollte die Funktion im Frame 1 aufweisen:

0:000> knL2
 # Child-SP          RetAddr           Call Site
00 00000000'001cfc08 000007fe'fd9b1203 ntdll!NtDelayExecution+0xa
01 00000000'001cfc10 00000001'3fda101d KERNELBASE!SleepEx+0xab

Jedoch basierend auf der Ebene des Symbols Auflösung zur Verfügung, das Funktionssymbol, das ich suche möglicherweise oder möglicherweise nicht im erwarteten Rahmen. Wenn Sie die test01 Öffnen X 86-dump-Datei und keinen Symbolpfad angeben, können Sie ein Beispiel dafür. Die KERNELBASE!Sleep-Aufruf werden in Frame 1 anstelle des Frames 3:

0:000> knL4
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 001bfa48 752c1818 ntdll!KiFastSystemCallRet
01 001bfa58 012f1015 KERNELBASE!Sleep+0xf
02 001bfaa4 75baf4e8 Test01+0x1015
03 001bfab0 76feaf77 kernel32!BaseThreadInitThunk+0x12

Der Debugger warnt Sie diese mögliche Fehler. Wenn Sie die Erweiterungs-Anpassung an dieser Art von Problemen haben möchten, sollten Sie wie ich anstelle des erwarteten Rahmens nur betrachten, die Frames durchlaufen.

Um das Vorhandensein der Sleep-Funktion zu bestimmen, muss ich das Symbol für jeden Frame nachschlagen. Wenn die effektive Prozessortyp und das Symbol eines gültigen Referenzpaars vornehmen, wurde die Funktion gefunden. Beachten Sie, dass diese Logik empfindlich ist und zur Vereinfachung des Beispiels verwendet wird. Das Symbol kann zwischen Plattformen und Builds ändern. Windows Server 2008 ist z. B. kernel32!Sleep + 0xf, aber Windows 7 ist KERNELBASE!Sleep + 0xf.

Um das Symbol zu erhalten, verwende ich die QueryInterface beim Abrufen der IDebugSymbol-Schnittstelle. Ich verwende dann GetNameByOffset, um das Symbol der Offset Anweisungsadresse zu erhalten.

Umfasst zwei Teile auf das Symbol: der Symbolname (KERNELBASE!Sleep) und die Verschiebung (0xf). Der Symbolname ist eine Verschmelzung von den Modulnamen und den Namen der Funktion (<module>! <function>). Die Verschiebung ist das Offset in Bytes vom Anfang der Funktion mit der Programmablauf zurückgegeben wird, nachdem der Aufruf zurückgegeben hat.

Wenn keine Symbole vorhanden sind, wird die Funktion als nur den Namen des Moduls mit einer großen Verschiebung gemeldet (Test01 + 0x1015).

Wenn ich den Frame gefunden haben, ist die nächste Schritt die Verzögerung zu extrahieren. Wenn das Ziel auf X 86-basiert ist, werden die Verzögerung in einen DWORD-Wert, der auf dem Stapel ab sofort vor, um den Funktionsaufruf (Hinweis) gelegt wurde, dass diese zerbrechliche Logik ist:

// @$csp is the pseudo-register of @esp
0:000> dps @$csp
<snip>
001bfa4c  752c1818 KERNELBASE!Sleep+0xf
001bfa50  00002710
<snip>

Tatsächlich zeigt der StackOffset-Member der DEBUG_STACK_FRAME-Struktur an diese Adresse bereits, die daher keine Zeigerarithmetik ist erforderlich. Um den Wert abzurufen, ich QueryInterface beim Abrufen der IDebugDataSpaces-Schnittstelle verwenden, und verwenden Sie ReadVirtual, um den DWORD-Wert aus der Ziel-Adressraum zu lesen.

Wenn das Ziel auf X 64-basierten ist, ist die Verzögerung im Stapel nicht – es ist im Register Rsi (Dies ist aufgrund ihrer Abhängigkeit von Frame-Kontext zerbrechliche Logik):

0: 000 >@ Rsi-Rsi R = 0000000000002710

Um den Wert zu erhalten, verwende ich die QueryInterface beim Abrufen der IDebugRegisters-Schnittstelle. Zunächst muss ich GetIndexByName verwenden, um den Index des Registers Rsi abzurufen. Ich verwende dann GetValue den Register-Wert aus der Ziel-Register lesen. Da Rsi ein 64-Bit-Register ist, wird der Wert als INT64 zurückgegeben. Da die DEBUG_VALUE-Struktur eine Union ist, können Sie einfach das I32-Element anstelle des Elements I64 um abgeschnittene Version abzurufen, die den DWORD-Wert übergeben, um den Ruhezustand zu darstellt, verweisen.

Wieder einmal verwenden in beiden Fällen kann ich die IDebugControl::Output-Funktion das Ergebnis ausgeben.

Break!

In diesem Artikel zerkratzt ich kaum die Oberfläche des was erreicht werden kann. Stacks, die Symbole, Register, e/A-Speicher und umweltbezogenen Informationen sind jedoch nur einige der vielen Dinge, können Sie Abfragen und Ändern von innerhalb einer Erweiterung.

In einem zukünftigen Artikel werde ich tiefer in die Beziehung vordringen, die mit dem Debugger eine Debuggererweiterung haben kann. Werde ich Debugger-Clients und Debugger Rückrufe, und ich werde diese verwenden, um die SOS-Debuggererweiterung kapseln, so dass Sie eine Erweiterung schreiben können, die verwaltete Anwendungen debuggen können, ohne dass Kenntnisse der zugrunde liegenden haben.NET-Strukturen.

Andrew Richards ist Microsoft senior Escalation Engineer für Exchange Server. Er hat eine Leidenschaft für Support-Tools und ist ständig Debugger erstellen, Erweiterungen und Anwendungen, die die Auftrag von Supporttechnikern zu vereinfachen.

Dank an den folgenden technischen Experten für die Überprüfung dieses Artikels: Drew Bliss, Jen-Lung Chiu, Mike Hendrickson, Ken Johnson, Brunda Nagalingaiah und Matt Weber