Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Le API del debugger

Scrittura di uno strumento di debug per l'estensione Windows

Andrew Richards

Scaricare il codice di esempio

Risoluzione dei problemi di produzione può essere uno dei processi più frustranti che possono eseguire qualsiasi engineer. Inoltre può essere uno dei processi più gratificante. Utilizzo di supporto tecnico clienti Microsoft, sono affrontate con questo ogni giorno. Perché è stato arresto anomalo dell'applicazione? Perché è stato è bloccato? Motivo per cui presenta un problema di prestazioni?

Imparare a eseguire il debug può essere un'attività molto complessa e uno di tali competenze che richiede molte ore di regolare esercitazione per rimanere esperto. Ma è un'importante è la capacità per un sviluppatore a tutto tondo. Comunque, da imbottigliamento le competenze di alcuni esperti di debug, i tecnici del debugger di tutti i livelli possono eseguire logica debug estremamente complessa come comandi di esecuzione.

Numerose tecniche per la risoluzione dei problemi può essere utilizzato per ottenere la causa principale di un arresto anomalo del sistema, ma il massimo valore e più fecondi ai tecnici con competenze di debug, è un'immagine della memoria del processo. Processo dump contengono uno snapshot della memoria di un processo al momento dell'acquisizione. A seconda dello strumento di dumping, potrebbe trattarsi dello spazio di indirizzi o solo un sottoinsieme.

Quando un'applicazione genera un'eccezione non gestita, verrà creato automaticamente un minidump tramite segnalazione errori Windows (WER). Inoltre, è possibile creare manualmente un file dump tramite lo strumento Userdump. exe. (ProcDump, strumento di SysinternalsTechNet.microsoft.com/sysinternals/dd996900) sta diventando lo strumento preferito antidumping sul processo di supporto tecnico clienti Microsoft, perché in grado di acquisire un'immagine della memoria in base a una vasta gamma di trigger e può generare un dump di varie dimensioni. Ma quando si dispone di dati di dettaglio, cosa si può fare con esso per facilitare il debug?

Diverse versioni di supporto di Visual Studio aprire dump (dmp) del file, ma lo strumento migliore per l'utilizzo è un debugger da strumenti di debug per Windows. Questi strumenti sono basati su un singolo motore di debug che supporta l'estensione del debugger due API. In questo articolo illustrerò fornite le nozioni fondamentali della creazione di un'estensione di debugger personalizzato in modo da analizzare questi file di dump (e anche live sistemi) con facilità.

Configurare gli strumenti

Strumenti di debug per Windows (Microsoft.com/whdc/devtools/debugging) è un componente installabile e componente ridistribuibile di Windows SDK e WDK (Windows Driver Kit). Scrivo questo articolo, la versione corrente è 6. 12 ed è disponibile nella versione 7. 1 di Windows SDK o il kit WDK. Si consiglia di utilizzare la versione più recente, come il motore di debug ha numerose aggiunte utili, compresa l'analisi dello stack migliore.

Si supponga che le indicazioni di Debugging Tools for Windows che è necessario compilare le estensioni del debugger utilizzando l'ambiente di generazione WDK. Utilizzare la versione più recente di WDK in uso (build versione 7.1.0 7600.16385.1), ma qualsiasi versione del kit WDK o la sua evoluzione precedente come prodotti alimentari conservabili Driver Development Kit (DDK). Quando si crea un'estensione tramite il kit WDK, è possibile utilizzare ambienti x64 e x86 gratuito di generazione.

Con un po' di impegno che è anche possibile adattare i progetti personali per la creazione di Windows SDK build ambiente o Visual Studio.

Una nota di avviso: non WDK, quali gli spazi nei nomi di percorso. Assicurarsi di che compilare da un percorso ininterrotta. Ad esempio, utilizzare qualcosa di simile a C:\Projects invece di Richards\Documents\Projects di C:\Users\Andrew.

Indipendentemente dal modo in cui si genera l'estensione, è necessario che i file di intestazione e la libreria di debug strumenti SDK, un componente di Debugging Tools for Windows. Gli esempi in questo articolo utilizzano il percorso di 86 x (C:\debuggers_x86\sdk) quando si fa riferimento al file di intestazione e libreria. Se si sceglie di installare il debugger in un' posizione, è necessario aggiornare il percorso e aggiunta di virgolette quando è necessaria per soddisfare gli spazi nei nomi di percorso.

Utilizzando gli strumenti di debug

Il debugger di Debugging Tools for Windows sono indipendente dall'architettura. Tutte le edizioni del debugger possono eseguire il debug di qualsiasi architettura di destinazione. Un esempio comune è mediante il debugger 64 x per eseguire il debug di un'applicazione di 86 x. Il debugger è stato rilasciato per x86, x64 (amd64) e IA64, ma è possibile eseguire il debug di x86, x64, IA64, ARM, da visita e applicazioni PowerPC (Xbox). È possibile installare tutti i debugger edizioni side-by-side.

Questa flessibilità non è universalmente riconosciuto, però. Non tutte le estensioni di debugger adattano anche come il motore del debugger per l'architettura di destinazione. Alcune estensioni di debugger presuppongono che le dimensioni del puntatore della destinazione sarà identico le dimensioni del puntatore del debugger. Allo stesso modo, utilizzano il registro errato hardcoded (esp al posto di rsp, ad esempio) invece di un pseudo-register, ad esempio $csp.

Se si riscontra un problema con un'estensione del debugger, è consigliabile che esegue il debugger progettato per la stessa architettura dell'ambiente di destinazione. Per risolvere le ipotesi il male questo potrebbe essere scritto di estensione.

Ogni tipo di generazione dell'applicazione e l'architettura del processore associato è dotato di una serie di problemi di debug. L'assembler generato per una build di debug è abbastanza lineare, ma l'assembler generato per una build di rilascio ottimizzata e può essere simile a una cavità di spaghetti. In x x86 architetture, omissione dei puntatori ai Frame) FPO (riproduce seri problemi alle ricostruzione dello stack di chiamate (il più recente debugger gestisce il pozzetto). In x64 architetture, i parametri di funzione e le variabili locali vengono memorizzate nei registri. Al momento dell'acquisizione di dump, che può essere inseriti nello stack o non esiste più a causa di riutilizzo del registro.

L'esperienza è la chiave qui. Per essere precisi, l'esperienza dell'utente è la chiave qui. È sufficiente Imbottigliare il know-how in un'estensione del debugger per il resto di noi. Accetta solo poche ripetizioni di una sequenza simile debug prima di automatizzare l'estensione di un debugger. Ho utilizzato alcune estensioni my così tanta dimenticare come ho fatto la stessa operazione utilizzando i comandi di debug sottostanti.

Utilizzando le API del Debugger

Esistono due API dell'estensione del debugger: l'API WdbgExts obsoleto (wdbgexts.h) e l'API DbgEng corrente (dbgeng.h).

Le estensioni di WdbgExts si basano su una chiamata globale che è configurata in fase di inizializzazione (WinDbgExtensionDllInit):

ExtensionApis WINDBG_EXTENSION_APIS;

Globale fornisce la funzionalità necessaria per eseguire funzioni quali dprintf("\n") e GetExpression("@$csp") senza alcuno spazio dei nomi. Questo tipo di estensione è simile al codice necessario quando si esegue la programmazione Win32.

Estensioni DbgEng si basano sulle interfacce del debugger. L'interfaccia IDebugClient viene passato come parametro della chiamata all'utente dal motore di debug. Le interfacce supportano QueryInterface per l'accesso all'intervallo crescente di interfacce di debugger. Questo tipo di estensione è simile al codice necessario quando si esegue la programmazione COM.

È possibile creare una combinazione di tipi di due estensione. Si espone l'estensione come DbgEng, ma aggiunta la funzionalità dell'API WdbgExts in fase di esecuzione mediante una chiamata a IDebugControl::GetWindbgExtensionApis64. Ad esempio, ho scritto il classico "Hello World" come estensione DbgEng c. Se si preferisce C++, fare riferimento alla classe ExtException nel SDK di strumenti di debug (. \inc\engextcpp.cpp).

Compilare l'estensione come MyExt.dll (TARGETNAME nel file di origine indicato nella nella figura 1). Espone un comando denominato! helloworld. L'estensione viene collegata in modo dinamico per il runtime di Microsoft Visual C (MSVCRT). Se si desidera utilizzare static, modificare il USE_MSVCRT = 1 istruzione a USE_LIBCMT = 1 nel file di origine.

Figura 1 origini

TARGETNAME = TARGETTYPE MyExt = DLL del programma

_NT_TARGET_VERSION=$(_NT_TARGET_VERSION_WINXP)

DLLENTRY = _ DllMainCRTStartup

! Se "$ (DBGSDK_INC_PATH)"!= ""INCLUDE = $(DBGSDK_INC_PATH);$(INCLUDES)! endif! se "$ (DBGSDK_LIB_PATH)"== ""DBGSDK_LIB_PATH = $(SDK_LIB_PATH)! DBGSDK_LIB_PATH altro = $(DBGSDK_LIB_PATH)\$(TARGET_DIRECTORY)! endif

TARGETLIBS = \kernel32.lib $(SDK_LIB_PATH) \ $(DBGSDK_LIB_PATH)\dbgeng.lib

USE_MSVCRT = 1

UMTYPE = windows

MSC_WARNING_LEVEL = /W4 /WX

ORIGINI = dbgexts.rc \ dbgexts.cpp \ myext.cpp

La funzione DebugExtensionInitialize (vedere nella figura 2) viene chiamato quando viene caricata l'estensione. Impostazione della versione di parametro è sufficiente utilizzare la macro DEBUG_EXTENSION_VERSION con EXT_MAJOR_VER ed EXT_MINOR_VER # definisce ho aggiunto al file di intestazione:

/ / dbgexts.h

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

# define EXT_MAJOR_VER 1 # define EXT_MINOR_VER 0

Figura 2 dbgexts.cpp

/ / dbgexts.cpp

# include \"dbgexts.h\"

extern "C"HRESULT CALLBACK DebugExtensionInitialize (versione PULONG, PULONG, flag) {* versione = DEBUG_EXTENSION_VERSION (EXT_MAJOR_VER, EXT_MINOR_VER);* Flag = 0;/ / Riservato per utilizzi futuri.
Restituisce S_OK;}

extern "C"void CALLBACK DebugExtensionNotify(ULONG Notify, ULONG64 Argument) {UNREFERENCED_PARAMETER(Argument);switch (notifica) {/ / è attiva una sessione di debug.
La sessione non può necessariamente essere sospesa.
Case DEBUG_NOTIFY_SESSION_ACTIVE: break;/ / Nessuna sessione di debug è attiva.
Case DEBUG_NOTIFY_SESSION_INACTIVE: break;/ / La sessione di debug è sospeso ed è ora possibile accedere.
Case DEBUG_NOTIFY_SESSION_ACCESSIBLE: break;/ / La sessione di debug ha avviato l'esecuzione e ora non è accessibile.
Case DEBUG_NOTIFY_SESSION_INACCESSIBLE: break;} restituire;}

extern "C"void CALLBACK DebugExtensionUninitialize(void) {ritorno;}

Il valore della versione viene segnalato come la versione API nel comando debugger .chain. Per modificare la versione del File, File di descrizione, Copyright e altri valori, è necessario modificare il file dbgexts.rc:

myext.dll: 6.1.7600.16385, API 1.0.0, di immagine generata mer ott 13 20:25:10 2010 [percorso: C:\Debuggers_x86\myext.dll]

Il parametro Flags è riservato e deve essere impostato su zero. La funzione deve restituire S_OK.

La funzione DebugExtensionNotify viene chiamata quando la sessione di modifica lo stato attivo o accessibile. Il parametro argomento viene eseguito il wrapping con la macro UNREFERENCED_PARAMETER per eliminare l'avviso del compilatore di parametro non utilizzato.

Ho aggiunto l'istruzione switch per il parametro di notifica per completezza, ma non ho aggiunto codice funzionale in quest'area. L'istruzione switch elabora le modifiche di stato sessione quattro:

  • DEBUG_NOTIFY_SESSION_ACTIVE si verifica quando si collega a una destinazione.
  • DEBUG_NOTIFY_SESSION_INACTIVE si verifica quando la destinazione diventa scollegata (tramite .detach o qd).
  • Se la destinazione sospende (riscontri un punto di interruzione, ad esempio), la funzione verrà passata DEBUG_NOTIFY_SESSION_ACCESSIBLE.
  • Se viene passato il curriculum di destinazione in esecuzione, la funzione DEBUG_NOTIFY_SESSION_INACCESSIBLE.

La funzione DebugExtensionUninitialize viene chiamata quando l'estensione viene scaricato.

Ogni comando deve essere esposta per l'estensione viene dichiarata come una funzione di tipo PDEBUG_EXTENSION_CALL. Il nome della funzione è il nome del comando di estensione. Poiché sto scrivendo "Hello World", ho denominato helloworld la funzione (vedere nella figura 3).

Figura 3 MyExt.cpp

/ / MyExt.cpp

# include \"dbgexts.h\"

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

  IDebugControl * pDebugControl;Se (SUCCEEDED (pDebugClient - > QueryInterface(__uuidof(IDebugControl), (void **) & pDebugControl))) {pDebugControl - > Output (DEBUG_OUTPUT_NORMAL, "Hello World!
\n");pDebugControl - > Release ();} restituiscono S_OK;}

Si noti che la convenzione consiste nell'utilizzare i nomi delle funzioni di minuscoli. Poiché si utilizza l'ambiente di generazione WDK, il file myext.def deve anche essere modificato. È necessario aggiungere in modo che viene esportato il nome del comando estensione:

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

Il parametro args contiene una stringa di argomenti per il comando. Il parametro viene passato come stringa ANSI con terminazione null (CP_ACP).

Il parametro pDebugClient è il puntatore all'interfaccia IDebugClient che consente l'estensione interagire con il motore di debug. Non anche se il puntatore a interfaccia probabilmente non è un puntatore a interfaccia COM, può essere eseguito il marshalling, né accedere a un momento successivo. Non può essere utilizzato da qualsiasi altro thread. Per lavorare su un thread alternativi, è necessario creare un nuovo client debugger (nuovo puntatore a interfaccia a IDebugClient) su quel thread utilizzando IDebugClient::CreateClient. Questo è l'unica funzione che può essere eseguito su un thread alternativo.

L'interfaccia IDebugClient (ad esempio, tutte le interfacce) è derivata da IUnknown. È possibile utilizzare QueryInterface per l'accesso alle interfacce di DbgEng, siano essi le versioni successive dell'interfaccia IDebugClient (IDebugClient4) o interfacce diverse (IDebugControl, IDebugRegisters, IDebugSymbols, IDebugSystemObjects e così via). Per eseguire l'output di testo per il debug, è necessario che l'interfaccia IDebugControl.

Due file SDK non contiene la cartella di supporto allo sviluppo. Lo script make.cmd aggiunge i percorsi di Debugger SDK inc e lib all'ambiente di generazione WDK, quindi esegue il comando generazione appropriato:

@ 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 generazione - cZMg % 1 %2

Si noti che l'ambiente di generazione WDK stesso determina se verrà generato un x86 o x64 binario. Se si desidera generare per più architetture, è necessario aprire più prompt ed eseguire make.cmd in ognuna. L'edificio può essere eseguito contemporaneamente.

Una volta creato, utilizzare il (x86) test.cmd di script per copiare i file binari compilati i386 nella cartella x 86 debugger (c:\Debuggers_x86), quindi avviare un'istanza del blocco note con il debugger e l'estensione caricato:

@ echo off copia objfre_win7_x86\i386\myext.dll c:\Debuggers_x86 copia objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86 \Debuggers_x86\windbg.exe-myext.dll - x il blocco note

Se tutto ciò che si è recato come previsto, è possibile digitare "! helloworld" nel prompt dei comandi del debugger e vedere una risposta "Hello World!":

0: 000 >! helloworld Hello World!

La lettura e la risoluzione di simbolo

L'applicazione "Hello World" potrebbe essere sorprendente, ma è possibile effettuare una migliore. Illustrerò ora utilizzano questa infrastruttura per aggiungere un comando che effettivamente interagisce con la destinazione e consente di effettuare alcune analisi. L'applicazione test01 semplice è un puntatore globale in cui è assegnato un valore:

/ / test01.cpp

# include <windows.h>

void * g_ptr;int main (int argc, char * argv[]) {g_ptr = "È una stringa globale";Sleep(10000);return 0;}

Il nuovo! comando gptr in MyExt.cpp (vedere nella figura 4) consente di risolvere il test01! g_ptr globali, leggere il puntatore del mouse e quindi i valori trovati nello stesso formato di output "x test01! g_ptr":

0: 000 >x test01! g_ptr 012f3370 Test01! g_ptr = 0x012f20e4

0: 000 >! gptr 012f3370 test01! g_ptr = 0x012f20e4 <string>

Figura 4 revisione MyExt.cpp

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

  IDebugSymbols * pDebugSymbols;Se (SUCCEEDED (pDebugClient - > QueryInterface(__uuidof(IDebugSymbols), (void **) & pDebugSymbols))) {/ / risolvere il simbolo.
ULONG64 ulAddress = 0;Se (SUCCEEDED (pDebugSymbols - > GetOffsetByName ("test01! g_ptr", & ulAddress))) {IDebugDataSpaces * pDebugDataSpaces;Se (SUCCEEDED (pDebugClient - > QueryInterface(__uuidof(IDebugDataSpaces), (void **) & pDebugDataSpaces))) {/ / Leggi il valore del puntatore dallo spazio degli indirizzi di destinazione.
ULONG64 ulPtr = 0;Se (SUCCEEDED (pDebugDataSpaces - > ReadPointersVirtual (1, ulAddress & ulPtr))) {PDEBUG_CONTROL pDebugControl;Se (SUCCEEDED (pDebugClient - > QueryInterface(__uuidof(IDebugControl), (void **) & pDebugControl))) {/ / i valori di Output.
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 ();}}} restituiscono S_OK;}

Il primo passaggio consiste nel determinare la posizione del test01! g_ptr puntatore. Il puntatore del mouse è in una posizione diversa ogni volta che l'applicazione viene eseguita perché l'indirizzo di spazio di Layout Randomization (ASLR) verrà modificato l'indirizzo di caricamento del modulo. Per ottenere la posizione, si utilizza QueryInterface per ottenere l'interfaccia IDebugSymbols e quindi GetOffsetByName. La funzione GetOffsetByName accetta un nome di simbolo e restituisce l'indirizzo come un puntatore a 64 bit. Il debugger opera sempre restituisce puntatori a 64 bit (ULONG64) possono eseguire il debug di destinazioni a 64 bit con un debugger a 32 bit.

Tenere presente che questo non è l'indirizzo del puntatore nello spazio degli indirizzi di destinazione personalizzato. Semplicemente non è possibile leggere da essa per determinarne il valore. Per ottenere il valore del puntatore, si utilizza QueryInterface nuovamente per ottenere l'interfaccia IDebugDataSpaces e quindi ReadPointersVirtual. Il puntatore del mouse viene letto dallo spazio degli indirizzi di destinazione. ReadPointersVirtual viene regolata automaticamente per la dimensione del puntatore e le differenze little-endian. Non è necessario modificare il puntatore restituito.

IDebugControl::Output ha la stessa stringa di formato printf, ma dispone anche di formattatori che consentono di fare riferimento lo spazio degli indirizzi di destinazione. Utilizzare il formato di AG % per stampare una copia della stringa ANSI che i punti puntatore globale per la destinazione dello spazio di indirizzi. Puntatore dimensione riconosce il formato %p e deve essere utilizzato per l'output di puntatore (è necessario passare un ULONG64).

Ho modificato lo script di test per caricare un file di dump della versione x86 di test01 anziché avviare il blocco note:

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

Ho impostato anche il percorso di simboli per test01 x cartella x86 e il Server di simboli pubblico Microsoft, in modo che tutti gli elementi possono essere risolti. Inoltre, che ho preso uno script di test 64 x esegue la stessa come script di prova 86 x, ma con un file di dettagli della versione x64 di applicazione di test:

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

Quando si eseguono script, x86 debugger viene avviato, viene aperto il file dump appropriato, x86 versione dell'estensione viene caricato e simboli sono risolvibili.

Ancora una volta, tutto ciò che si è recato per pianificare, è possibile digitare "x test01! g_ptr" e! gptr nel debugger prompt dei comandi e vedere le risposte simili:

/ / x 86 destinazione 0: 000 >x test01! g_ptr 012f3370 Test01! g_ptr = 0x012f20e4

0: 000 >! gptr 012f3370 test01! g_ptr = 0x012f20e4 è una stringa globale

/ / x 64 destinazione 0: 000 >x test01! g_ptr 00000001'3fda35d0 Test01! g_ptr = 0x00000001'3fda21a0

0: 000 >! gptr 000000013fda35d0 test01! g_ptr = 0x000000013fda21a0 è una stringa globale

Se si ripete il test utilizzando il debugger 64 x, la versione compilata amd64 l'estensione del debugger e i file di dump x86 o x64, si otterrà lo stesso risultato. Vale a dire l'estensione è indipendente dall'architettura.

Gli stack e tipi di processore

Ora desidero estendere questa infrastruttura ancora una volta. È possibile aggiungere un comando che consente di trovare la durata di una chiamata di sospensione dello stack del thread corrente. Il! sleepy comando (vedere nella figura 5) risolverà la chiamata impilare i simboli, cercare la funzione di sospensione e leggere il valore DWORD che rappresenta i millisecondi di ritardo e quindi verrà generato il valore di ritardo (se disponibile).

Figura 5 Sleepy

HRESULT CALLBACK sleepy (PDEBUG_CLIENT4 Client, PCSTR args) {UNREFERENCED_PARAMETER(args);BOOL bFound = FALSE;

  IDebugControl * pDebugControl;

  Se (SUCCEEDED (Client - > QueryInterface(__uuidof(IDebugControl), (void **) & pDebugControl))) {IDebugSymbols * pDebugSymbols;

    Se (SUCCEEDED (Client - > QueryInterface(__uuidof(IDebugSymbols), (void **) & pDebugSymbols))) {DEBUG_STACK_FRAME * pDebugStackFrame = malloc (DEBUG_STACK_FRAME *) (sizeof(DEBUG_STACK_FRAME) * MAX_STACK_FRAMES);

      Se (pDebugStackFrame! = NULL) {/ / Get Stack frame.
memset (pDebugStackFrame, 0, (sizeof(DEBUG_STACK_FRAME) * MAX_STACK_FRAMES));ULONG fotogrammi = 0;

        Se (SUCCEEDED (pDebugControl - > GetStackTrace (0, 0, 0, pDebugStackFrame, MAX_STACK_FRAMES e cornici)) & &(Frame >0)) {ULONG ProcessorType = 0;ULONG SymSize = 0;char SymName [4096];memset (SymName, 0, 4096);ULONG64 Spostamento = 0;

          Se (SUCCEEDED (pDebugControl - > GetEffectiveProcessorType (& ProcessorType))) {per (ULONG n = 0;n < frame;n++) {  

              / / Utilizza il tipo di processore efficace e il contenuto / / del frame per determinare l'esistenza se (SUCCEEDED (pDebugSymbols - > GetNameByOffset (pDebugStackFrame [n].InstructionOffset, SymName, 4096, & SymSize, & spostamento)) & &(SymSize >0)) {

                Se ((ProcessorType == IMAGE_FILE_MACHINE_I386) & &_ (stricmp (SymName, "KERNELBASE!Sleep") = = 0) & &(Displacement == 0xF)) {/ / Win7 x 86;KERNELBASE!Sospensione + 0xF è in genere nel frame 3.
IDebugDataSpaces * pDebugDataSpaces;

                  Se (SUCCEEDED (Client - > QueryInterface (__uuidof(IDebugDataSpaces), (void **) & pDebugDataSpaces))) {/ / il valore viene inserito immediatamente prima di / / KERNELBASE!Sospensione + 0xF dwMilliseconds DWORD = 0;

                    Se (SUCCEEDED (pDebugDataSpaces - > ReadVirtual (pDebugStackFrame [n].StackOffset & dwMilliseconds, sizeof(dwMilliseconds), NULL))) {pDebugControl - > Output (DEBUG_OUTPUT_NORMAL, "Pelo % ld msec\n", dwMilliseconds);bFound = TRUE;} pDebugDataSpaces - > Release ();} Se interruzione (bFound);}

                altrimenti ((ProcessorType == IMAGE_FILE_MACHINE_AMD64) & &_ (stricmp (SymName, "KERNELBASE!SleepEx") = = 0) & &(Displacement == 0xAB)) {/ / Win7 64;KERNELBASE!SleepEx + 0xAB è in genere nel fotogramma 1.
IDebugRegisters * pDebugRegisters;

                  Se (SUCCEEDED (Client - > QueryInterface (__uuidof(IDebugRegisters), (void **) & pDebugRegisters))) {/ / è il valore di 'rsi'effettuare la registrazione.
ULONG rsiIndex = 0;Se (SUCCEEDED (pDebugRegisters - > GetIndexByName ("rsi" & rsiIndex))) {DEBUG_VALUE debugValue;Se (SUCCEEDED (pDebugRegisters - > GetValue (rsiIndex & debugValue)) & &(debugValue.Type == DEBUG_VALUE_INT64)) {/ / Troncamento a 32bits per la visualizzazione.
pDebugControl - > Output (DEBUG_OUTPUT_NORMAL, "Pelo % ld msec\n", debugValue.I32);bFound = TRUE;pDebugRegisters}}} - > Release ();}

                  Se l'interruzione di (bFound);free(pDebugStackFrame)}}};} pDebugSymbols - > Release ();} Se (! bFound) pDebugControl - > Output (DEBUG_OUTPUT_NORMAL, "Impossibile determinare se la sospensione è present\n");pDebugControl - > Release ();} restituiscono S_OK;}

Per aggiungere alcune complessità al comando, il comando supporta le versioni x86 e x64 dell'applicazione test01. Poiché la convenzione di chiamata è diversa per le applicazioni x86 e x64, il comando dovrà essere a conoscenza dell'architettura di destinazione durante l'avanzamento.

Il primo passaggio consiste nel recuperare gli stack frame. Per ottenere i frame, si utilizza QueryInterface per ottenere l'interfaccia IDebugControl e quindi GetStackTrace per recuperare informazioni su ogni stack frame. GetStackTrace accetta una matrice di strutture DEBUG_STACK_FRAME. La matrice di strutture di DEBUG_STACK_FRAME nell'heap sempre allocare in modo che non causare un overflow dello stack. Se si è retrieving un thread di overflow dello stack di una destinazione, verrà probabilmente raggiunto un limite dello stack, se la matrice viene allocata nello stack.

Se GetStackTrace ha esito positivo, la matrice verrà popolata con le informazioni per ciascun fotogramma che è stato descritto. Successo non significa necessariamente che le informazioni di frame siano corrette. Il debugger nel modo migliore per esaminare gli stack frame, ma gli errori possono essere eseguiti quando i simboli non sono corretti (quando essi cosa mancante o è stata imposta). Se è stato utilizzato ". Reload /f /i" per forzare il caricamento di simboli, allineamento simbolo scarsa verificherà probabilmente.

Per utilizzare efficacemente il contenuto di ciascuna delle strutture DEBUG_STACK_FRAME, è necessario conoscere il tipo di processore efficace della destinazione. Come accennato in precedenza, l'architettura di destinazione può essere completamente diverso dall'architettura di estensione del debugger. Il tipo di processore efficace (.effmach) è l'architettura che la destinazione è attualmente in uso.

Il tipo di processore può anche essere un tipo di processore diverso da quello utilizzato dall'host di destinazione. L'esempio più comune è quando la destinazione è un'applicazione di 86 x viene eseguito tramite Windows a 32 bit su Windows a 64 bit (WOW64) in un'edizione x64 di Windows. Il tipo di processore efficace è IMAGE_FILE_MACHINE_I386. Il tipo effettivo è IMAGE_FILE_MACHINE_AMD64.

Ciò significa che è opportuno considerare un'applicazione di 86 x può essere un'applicazione di 86 x indipendentemente se è in esecuzione su una versione di Windows a x 86 o un'edizione x64 di Windows. (L'unica eccezione a questa regola è quando esegue il debug che WOW64 chiama processo surround x86).

Per ottenere il tipo di processore efficace, utilizzare l'interfaccia IDebugControl, che già disponibile e quindi utilizzare GetEffectiveProcessorType.

Se il tipo di processore efficace è i386, è necessario cercare di KERNELBASE!Sospensione + 0xf funzione. Se tutti i simboli sono stati risolti correttamente, la funzione deve essere nel frame 3:

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

Se il tipo di processore efficace AMD64, cercare di KERNELBASE!SleepEx + 0xab funzione. Se tutti i simboli sono stati risolti correttamente, la funzione deve essere nel frame 1:

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

Tuttavia, in base al livello di risoluzione dei simboli disponibili, può o potrebbe non essere nel frame previsto il simbolo di funzione che si sta cercando. Se si apre il test01 x86 file di dettagli e non viene specificato un percorso di simboli, è possibile visualizzare un esempio. KERNELBASE!Chiamata di sospensione sarà nel fotogramma 1 invece di frame 3:

0: 000 >AVVISO di RetAddr # ChildEBP knL4: informazioni non disponibili di rimozione dello Stack.
I frame seguenti potrebbero essere errati.
001bfa48 00 c 752 1818 ntdll!01 KiFastSystemCallRet 001bfa58 012f1015 KERNELBASE!Sospensione + 0xf 02 001bfaa4 75baf4e8 Test01 + 0x1015 03 001bfab0 76feaf77 chiamata a kernel32!BaseThreadInitThunk + 0x12

Il debugger avverte dell'errore possibile. Se si desidera che abbia l'estensione di adattarsi a questi tipi di problemi, è necessario scorrere i fotogrammi altrettanto, anziché semplicemente esaminando il frame previsto.

Per determinare l'esistenza della funzione sospensione, è necessario cercare il codice per ciascun fotogramma. Se il tipo di processore efficace e il simbolo di una coppia valida, la funzione è stata trovata. Si noti che questa logica è fragile e viene utilizzata per semplificare l'esempio. Il simbolo può variare tra generazioni e piattaforme. Ad esempio, Windows Server 2008 è kernel32!Sospensione + 0xf, ma Windows 7 è KERNELBASE!Sospensione + 0xf.

Per ottenere il simbolo, utilizzare QueryInterface per ottenere l'interfaccia IDebugSymbol. Quindi possibile utilizzare GetNameByOffset per ottenere il simbolo dell'indirizzo di offset di istruzione.

Ci sono due parti al simbolo: il nome del simbolo (KERNELBASE!Sospensione) e lo spostamento (0xf). Il nome del simbolo è un misto tra il nome del modulo e il nome della funzione (<module>! <function>). Lo spostamento è l'offset di byte dall'inizio della funzione a cui flusso di programma tornerà a dopo la restituzione della chiamata.

Se non vi sono simboli, la funzione viene segnalata come solo il nome del modulo con uno spostamento di grandi dimensioni (Test01 + 0x1015).

Una volta che ho trovato la cornice, il passaggio successivo consiste nell'estrarre il ritardo. Quando la destinazione è basate su x86, il ritardo può trovarsi in un DWORD che è stato inserito nello stack immediatamente prima della funzione chiamata a (si noti che si tratta di logica fragile):

/ / $ @ csp è pseudo-register di esp @ 0: 000 >DPS @$ csp <snip>001bfa4c c 752 1818 KERNELBASE!Sospensione + 0xf 001bfa50 00002710 <snip>

Il membro StackOffset della struttura DEBUG_STACK_FRAME effettivamente punta a questo indirizzo, in modo che nessun aritmetica dei puntatori. Per ottenere il valore, si utilizza QueryInterface per ottenere l'interfaccia IDebugDataSpaces e quindi ReadVirtual per leggere il valore DWORD dallo spazio degli indirizzi di destinazione.

Se la destinazione è basato su architettura x64, il ritardo non è nell'elenco, è nel registro rsi (che è anche logica precaria a causa di sua dipendenza del contesto di frame):

0: 000 >r @ rsi rsi = 0000000000002710

Per ottenere il valore, utilizzare QueryInterface per ottenere l'interfaccia IDebugRegisters. È necessario prima utilizzare GetIndexByName per ottenere l'indice del registro rsi. Utilizzare quindi GetValue per leggere il valore di registro dai registri di destinazione. Poiché rsi è un registro a 64 bit, viene restituito il valore come INT64. Poiché la struttura DEBUG_VALUE è un'unione, è sufficiente fare riferimento al membro I32 anziché il membro I64 per ottenere la versione troncata che rappresenta il valore DWORD passato alla sospensione.

Ancora una volta, in entrambi i casi, utilizzare la funzione IDebugControl::Output per il risultato di output.

Break!

In questo articolo, descrizione accenna solamente la superficie di quella ottenibile. Gli stack, i simboli, registri, memoria dei / O e informazioni ambientali non sono altro che alcuni dei numerosi elementi che è possibile interrogare e modificare da all'interno di un'estensione.

In un altro articolo viene approfondito più a fondo la relazione che con il debugger può avere un'estensione del debugger. Si parlerà client debugger e callback del debugger e verranno utilizzate per incapsulare l'estensione del debugger SOS, in modo che è possibile scrivere un'estensione che è possibile eseguire il debug delle applicazioni gestite senza richiedere alcuna conoscenza dello strumento sottostante.NET interne.

Andrew Richards è un Microsoft senior escalation engineer per Exchange Server. Egli ha una spiccata passione per gli strumenti di supporto e continuamente creazione debugger estensioni e le applicazioni che consentono di semplificare il processo di tecnici di supporto.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Possibilità di avere Drew, Polmonare Jen Chiu, Mike hendrickson, Ken Johnson, brunda nagalingaiah e Matt Weber