Il presente articolo è stato tradotto automaticamente.

Sysinternals ProcDump v4.0

Creazione di un plug-in per Sysinternals ProcDump v4.0

Andrew Richards

Scaricare il codice di esempio

Si hai stato up all night installando l'aggiornamento più recente dell'applicazione mission-critical e tutto è andato perfettamente.E poi succede — si blocca applicazione, proprio come tutti inizia ad arrivare sul luogo di lavoro.A volte come questo, è necessario tagliare le perdite, accettare che il rilascio è un fallimento, raccogliere prove pertinenti più rapidamente possibile e quindi avviare il piano di ripristino mai importante.

Catturare un dump della memoria di un'applicazione in momenti come questo è una tattica di risoluzione dei problemi comune, sia per ragioni di appendere, incidente o prestazioni.Maggior parte degli strumenti di cattura di discarica adottare un approccio imprigionato: ti danno tutto (dump completo) o molto poco (mini discariche).I dump mini sono di solito così piccoli che analisi fruttuosa debug non sono possibile perché i cumuli sono mancanti.Dump completo sono sempre stato preferito, ma sono raramente un'opzione più.Crescente memoria significa che un dump completo può prendere 15, 30 o anche 60 minuti.Inoltre, i file di dump stanno diventando così grande che essi non possono facilmente essere spostati per l'analisi, anche quando compresso.

L'anno scorso, Sysinternals ProcDump v 3.0 introdusse l'interruttore MiniPlus (-mp) per affrontare la questione di dimensione per le applicazioni native.Questo crea una discarica che è da qualche parte tra un dump mini e un dump completo in termini di dimensioni.Le decisioni di inclusione di memoria del commutatore MiniPlus si basano su una moltitudine di algoritmi euristici che considerare il tipo di memoria, protezione della memoria, dimensione di allocazione, contenuto di dimensioni e stack di regione.A seconda del layout dell'applicazione di destinazione, il file dump può essere il 50 per cento al 95 per cento più piccolo di un dump completo.Ancora più importante, il dump è funzionale come un dump completo per la maggior parte delle operazioni di analisi.Quando l'interruttore MiniPlus viene applicato per l'Archivio informazioni 2010 Microsoft Exchange in esecuzione con 48 GB di memoria, il risultato è un file di dump di 1GB–2GB (una riduzione del 95 per cento).

Mark Russinovich e io abbiamo lavorato su una nuova release di ProcDump che ora consente di rendere la memoria le decisioni di inclusione.Sysinternals ProcDump v 4.0 espone lo stesso API MiniPlus utilizza internamente come un esterno basati su DLL plug-in tramite lo switch -d.

In questo articolo, ho intenzione di sezionare come Sysinternals ProcDump v 4.0 opere di costruzione di una serie di applicazioni di esempio che espandersi su ogni altra implementazione più le funzionalità di ProcDump.Da approfondendo come ProcDump funziona sotto le coperte, ti mostrerò a com'è possibile scrivere un plug-in che interagisce con ProcDump e l'API di DbgHelp sottostante.

Download del codice contiene le applicazioni di esempio e anche una raccolta di applicazioni che cade in vari modi (così è possibile verificare il codice).Il campione di MiniDump05 ha tutte le API implementate come applicazione standalone.L'esempio di MiniDump06 implementa il campione di MiniDump05 come un plug-in per Sysinternals ProcDump v 4.0.

Terminologia

È facile da ottenere tutti i termini associati collezione dump confuso — il termine "Mini" è usato molto.C'è il formato di file MiniDump, MiniPlus e Mini discarica contenuto e le funzioni MiniDumpWriteDump e MiniDumpCallback.

Windows supporta il formato di file MiniDump via dbghelp.Un file di dump di MiniDump (*.dmp) è un contenitore che supporta l'acquisizione parziale o completa della memoria in una destinazione di modalità utente kernel o modalità.Il formato di file supporta l'utilizzo di "flussi" per memorizzare i metadati aggiuntivi (commenti, statistiche di processo e così via).Nome del formato di file è derivato dall'obbligo di sostenere la cattura di una minima quantità di dati.Le funzioni DbgHelp API MiniDumpWriteDump e MiniDumpCallback sono precedute da MiniDump per abbinare il formato di file che producono.

Mini, MiniPlus e Full vengono utilizzati per descrivere le diverse quantità di contenuti in file di dump.Mini è il più piccolo (minima) e comprende il processo blocco di ambiente (PEB), thread si blocca ambiente (TEBs), pile parziale, moduli caricati e segmenti di dati.Mark e ho coniato MiniPlus per descrivere il contenuto di un'acquisizione di Sysinternals ProcDump -mp; Esso comprende il contenuto di un dump di Mini, più memoria euristicamente ritenuto importante.E un dump completo (procdump.exe-ma) include lo spazio indirizzo virtuale intero del processo, indipendentemente dal fatto se la memoria è paging RAM.

MiniDumpWriteDump funzione

Per acquisire un processo nel formato di file MiniDump in un file, si chiama la funzione DbgHelp MiniDumpWriteDump.La funzione richiede un handle per il processo di destinazione (con accesso PROCESS_QUERY_INFORMATION e PROCESS_VM_READ), il PID del processo di destinazione, un handle per un file (con accesso FILE_GENERIC_WRITE), una maschera di bit di flag MINIDUMP_TYPE e tre parametri facoltativi: una struttura di informazioni sulle eccezioni (usata per includere un record di contesto di eccezione); una struttura di utente flusso informazioni (comunemente utilizzata per includere un commento nella discarica tramite i tipi CommentStreamA/W MINIDUMP_STREAM_TYPE); e una struttura di Callback informazioni (utilizzato per modificare ciò che viene catturato durante la chiamata):

BOOL WINAPI MiniDumpWriteDump(
  __in  HANDLE hProcess,
  __in  DWORD ProcessId,
  __in  HANDLE hFile,
  __in  MINIDUMP_TYPE DumpType,
  __in  PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
  __in  PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
  __in  PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);

L'applicazione di esempio MiniDump01 (vedere Figura 1) mostra come si chiama MiniDumpWriteDump di prendere un dump di Mini senza i parametri facoltativi. Si inizia selezionando gli argomenti della riga di comando per un PID e quindi chiama OpenProcess per ottenere un handle di processo del bersaglio. Chiama quindi CreateFile per ottenere un handle di file. (Si noti che MiniDumpWriteDump supporta qualsiasi destinazione I/O). Il file ha un filename basati su data/ora ISO per l'unicità e l'ordinamento cronologico: C:\dumps\minidump_YYYY-mm-DD_HH-mm-ss-ms.dmp. La directory è codificato a C:\dumps per garantire l'accesso in scrittura. Questo è necessario quando fare il debug post mortem perché la cartella corrente (ad esempio, System32) possa non essere scrivibile.

Figura 1 MiniDump01.cpp

// MiniDump01.cpp : Capture a hang dump.
//
#include "stdafx.h"
#include <windows.h>
#include <dbghelp.h>
int WriteDump(HANDLE hProcess, DWORD dwProcessId, HANDLE hFile, MINIDUMP_TYPE miniDumpType);
int _tmain(int argc, TCHAR* argv[])
{
  int nResult = -1;
  HANDLE hProcess = INVALID_HANDLE_VALUE;
  DWORD dwProcessId = 0;
  HANDLE hFile = INVALID_HANDLE_VALUE;
  MINIDUMP_TYPE miniDumpType;
  // DbgHelp v5.2
  miniDumpType = (MINIDUMP_TYPE) (MiniDumpNormal | MiniDumpWithProcessThreadData |
    MiniDumpWithDataSegs | MiniDumpWithHandleData);
  // DbgHelp v6.3 - Passing unsupported flags to a lower version of DbgHelp
     does not cause any issues
  miniDumpType = (MINIDUMP_TYPE) (miniDumpType | MiniDumpWithFullMemoryInfo |
    MiniDumpWithThreadInfo);
  if ((argc == 2) && (_stscanf_s(argv[1], _T("%ld"), &dwProcessId) == 1))
  {
    // Generate the filename (ISO format)
    SYSTEMTIME systemTime;
    GetLocalTime(&systemTime);
    TCHAR szFilename[64];
    _stprintf_s(szFilename, 64, _T("c:\\dumps\\minidump_%04d-%02d-
      %02d_%02d-%02d-%02d-%03d.dmp"),
        systemTime.wYear, systemTime.wMonth, systemTime.wDay,
        systemTime.wHour, systemTime.wMinute, systemTime.wSecond,
        systemTime.wMilliseconds);
    // Create the folder and file
    CreateDirectory(_T("c:\\dumps"), NULL);
    if ((hFile = CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
      FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
    {
      _tprintf(_T("Unable to open '%s' for write (Error: %08x)\n"), szFilename,
        GetLastError());
      nResult = 2;
    }
    // Open the process
    else if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId)) == NULL)
    {
      _tprintf(_T("Unable to open process %ld (Error: %08x)\n"), dwProcessId,
        GetLastError());
      nResult = 3;
    }
    // Take a hang dump
    else
    {
      nResult = WriteDump(hProcess, dwProcessId, hFile, miniDumpType);
    }
    if (hFile) CloseHandle(hFile);
    if (hProcess) CloseHandle(hProcess);
    if (nResult == 0)
    {
      _tprintf(_T("Dump Created - '%s'\n"), szFilename);
    }
    else
    {
      DeleteFile(szFilename);
    }
  }
  else
  {
    _tprintf(_T("Usage: %s <pid>\n"), argv[0]);
    nResult = 1;
  }
  return 0;
}
int WriteDump(HANDLE hProcess, DWORD dwProcessId, HANDLE hFile, MINIDUMP_TYPE miniDumpType)
{
  if (!MiniDumpWriteDump(hProcess, dwProcessId, hFile, miniDumpType, NULL, NULL, NULL))
  {
    _tprintf(_T("Failed to create hang dump (Error: %08x)\n"), GetLastError());
    return 11;
  }
  return 0;
}

Il parametro DumpType è maschera di bit basata su MINIDUMP_TYPE che provoca l'inclusione (o esclusione) di particolari tipi di memoria. I flag MINIDUMP_TYPE sono abbastanza potenti e consentono di indirizzare la cattura di un sacco di regioni di memoria senza la necessità di ulteriori codifica mediante un callback. Le opzioni utilizzate per il campione di MiniDump01 sono le stesse utilizzate da ProcDump. Creano un dump (Mini) che può essere utilizzato per riassumere un processo.

Il DumpType ha sempre MiniDumpNormal bandiera presenta perché ha un valore di 0x00000000. DumpType usata include ogni pila (MiniDumpNormal), tutte le informazioni di PEB e TEB (MiniDumpWithProcessThreadData), le informazioni del modulo caricato oltre qualsiasi globals (MiniDumpWithDataSegs), di gestire tutte le informazioni (MiniDumpWithHandleData), tutte le informazioni di regione di memoria (MiniDumpWithFullMemoryInfo) e tutti i thread tempo e affinità informaton (MiniDumpWithThreadInfo). Con questi flag, il dump creato è una versione ricca di un dump di Mini, ma è ancora abbastanza piccolo (meno di 30 MB, anche per il più grande dell'applicazione­cationi). I comandi del debugger esempio supportati da questi flag MINIDUMP_TYPE sono elencati in Figura 2.

Figura 2 comandi del Debugger

MINIDUMP_TYPE Comandi del debugger
MiniDumpNormal knL99
MiniDumpWithProcessThreadData ! peb,! teb
MiniDumpWithDataSegs LM, dt <global>
MiniDumpWithHandleData ! gestire,! cs
MiniDumpWithFullMemoryInfo ! indirizzo
MiniDumpWithThreadInfo ! fuggiasco

Quando usando MiniDumpWriteDump, il dump preso corrisponderà l'architettura del programma cattura, non il bersaglio, così utilizzare una versione a 32 bit del vostro programma di cattura quando catturare un processo a 32-bit e una versione a 64 bit del vostro programma di cattura quando catturare un processo a 64 bit. Se avete bisogno di eseguire il debug "Windows a 32-bit su Windows 64-bit" (WOW64), si dovrebbe prendere una discarica a 64-bit del processo a 32-bit.

Se voi non corrispondono all'architettura (accidentalmente o di proposito), dovrete cambiare la macchina efficacia (.effmach x 86) nel debugger per accedere ai Faraglioni a 32-bit in una discarica a 64-bit. Si noti che un sacco di estensioni debugger non riesce in questo scenario.

Record di contesto di eccezione

Ingegneri di supporto tecnico Microsoft utilizzano i termini "appendere discarica" e "discarica crash." Quando chiedono un dump di arresto anomalo, che vogliono un dump con un record di contesto di eccezione. Quando chiedono un dump di appendere, (di solito) significano una serie di discariche senza uno. Un dump con informazioni sull'eccezione non è sempre dal tempo di crash, però; può essere da qualsiasi momento. Le informazioni sull'eccezione è solo un mezzo per fornire dati aggiuntivi in una discarica. L'utente flusso infor­informazioni sono simile alle informazioni sull'eccezione a questo proposito.

Un record di contesto di eccezione è la combinazione di una struttura CONTEXT (i registri della CPU) e una struttura EXCEPTION_RECORD (il codice di eccezione, l'istruzione indirizzo e così via). Se si include un record di contesto di eccezione in discarica e .ecxr di esecuzione, il contesto corrente del debugger (stato di thread e registro) è impostato sull'istruzione che ha generato l'eccezione (vedere Figura 3).

Figura 3 cambiando il contesto per il Record di contesto di eccezione

Questo file di dump ha un'eccezione di interesse memorizzati in esso.

Le informazioni sull'eccezione memorizzati può essere acceduti via .ecxr.

(17cc.1968174c.6f8): Access violation - code c0000005 (first/second chance not available)
eax=00000000 ebx=001df788 ecx=75ba31e7 edx=00000000 esi=00000002 edi=00000000
eip=77c7014d esp=001df738 ebp=001df7d4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtWaitForMultipleObjects+0x15:
77c7014d 83c404          add     esp,4
0:000> .ecxr
eax=00000000 ebx=00000000 ecx=75ba31e7 edx=00000000 esi=00000001 edi=003b3374
eip=003b100d esp=001dfdbc ebp=001dfdfc iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
CrashAV_x86!wmain+0x140xd:
00000001`3f251014 45891b003b100d 8900            mov     dword ptr [r11],r11deax],eax  ds:002b:00000000`00000000=????????=????????

Per supportare il .ecxr, il MINIDUMP_EXCEPTION_ opzionale­struttura di informazioni deve essere passato a MiniDumpWriteDump. È possibile ottenere informazioni relative all'eccezione in fase di esecuzione o al post mortem.

Eccezioni Runtime

Se si implementa un ciclo di eventi del debugger, informazioni sull'eccezione viene passati a voi quando si verifica l'eccezione. Il ciclo degli eventi debugger riceverà una struttura EXCEPTION_DEBUG_EVENT per i punti di interruzione, eccezioni chance prime e seconda possibilità eccezioni.

L'applicazione di esempio MiniDump02 viene illustrato come chiamare MiniDumpWriteDump all'interno di un ciclo di eventi debugger in modo che il record di contesto dell'eccezione seconda possibilità è incluso nella discarica (equivalente a "procdump.exe -e"). Questa funzionalità viene eseguita quando si utilizza l'opzione -e. Perché il codice è abbastanza a lungo, il codice pseudo per l'applicazione viene mostrato Figura 4. Fare riferimento al download del codice di questo articolo per il codice sorgente completo.

Figura 4 MiniDump02 Pseudo codice

Function Main
Begin
  Check Command Line Arguments
  CreateFile(c:\dumps\minidump_YYYY-MM-DD_HH-MM-SS-MS.dmp)
  OpenProcess(PID)
  If "–e" Then
    DebugEventDump
    TerminateProcess(Process)
  Else
    WriteDump(NULL)
  CloseHandle(Process)
  CloseHandle(File)
End
Function WriteDump(Optional Exception Context Record)
Begin
  MiniDumpWriteDump(Optional Exception Context Record)
End
Function DebugEventDump
Begin
  DebugActiveProcess(PID)
  While (Not Done)
  Begin
    WaitForDebugEvent
    Switch (Debug Event Code)
    Begin
    Case EXCEPTION_DEBUG_EVENT
      If EXCEPTION_BREAKPOINT
        ContinueDebugEvent(DBG_CONTINUE)
      Else If "First Chance Exception"
        ContinueDebugEvent(DBG_EXCEPTION_NOT_HANDLED)
      Else "Second Chance Exception"
        OpenThread(Debug Event Thread ID)
        GetThreadContext
        WriteDump(Exception Context Record)
        CloseHandle(Thread)
        Done = True
    Case EXIT_PROCESS_DEBUG_EVENT
      ContinueDebugEvent(DBG_CONTINUE)
      Done = True
    Case CREATE_PROCESS_DEBUG_EVENT
      CloseHandle(CreateProcessInfo.hFile)
      ContinueDebugEvent(DBG_CONTINUE)
    Case LOAD_DLL_DEBUG_EVENT
      CloseHandle(LoadDll.hFile)
      ContinueDebugEvent(DBG_CONTINUE)
    Default
      ContinueDebugEvent(DBG_CONTINUE)
    End Switch
  End While
  DebugActiveProcessStop(PID)
End

L'applicazione viene avviata selezionando gli argomenti della riga di comando per un PID. Successivamente chiama OpenProcess per ottenere un handle di processo del bersaglio, e poi chiama CreateFile per ottenere un handle di file. Se manca l'opzione -e ci vuole un dump di appendere come prima. Se l'opzione -e è presente, l'applicazione si attacca alla destinazione (come un debugger) utilizzando DebugActiveProcess. Un po ' di tempo ciclo, in attesa di una struttura DEBUG_EVENT essere restituiti da WaitForDebugEvent. L'istruzione switch utilizza il membro dwDebugEventCode della struttura DEBUG_EVENT. Dopo il dump è stata presa, o si è concluso il processo, DebugActiveProcessStop viene chiamato per staccare dalla destinazione.

EXCEPTION_DEBUG_EVENT all'interno della struttura DEBUG_EVENT contiene un record di eccezione all'interno di un'eccezione. Se il record di eccezione è un punto di interruzione, viene gestita localmente chiamando ContinueDebugEvent con DBG_CONTINUE. Se l'eccezione è una prima occasione, esso non è gestita così che può trasformarsi a un'eccezione second chance (se il bersaglio non ha un gestore). A tale scopo, viene chiamato ContinueDebugEvent con DBG_EXCEPTION_NOT_HANDLED. Lo scenario rimanente è un'eccezione second-chance. Utilizzando dwThreadId della struttura DEBUG_EVENT, OpenThread viene chiamato per ottenere un handle per il thread con l'eccezione. L'handle di thread viene utilizzato con GetThreadContext per popolare la struttura di contesto richiesta. (Una parola di cautela qui: la struttura di contesto è cresciuta in dimensioni nel corso degli anni come registri supplementari sono state aggiunte a processori. Se un OS più tardi aumenta la dimensione della struttura CONTEXT, è necessario ricompilare il codice.) La struttura di contesto ottenuta ed EXCEPTION_RECORD dalla DEBUG_EVENT sono utilizzati per compilare una struttura EXCEPTION_POINTERS, e questo viene utilizzato per popolare una struttura MINIDUMP_EXCEPTION_INFORMATION. Questa struttura viene passata alla funzione di WriteDump dell'applicazione per l'uso con MiniDumpWriteDump.

Il EXIT_PROCESS_DEBUG_EVENT viene gestita specificamente per lo scenario dove l'obiettivo termina prima che si verifica un'eccezione. ContinueDebugEvent viene chiamato con DBG_CONTINUE a riconoscere questo evento e while loop è uscito.

Gli eventi CREATE_PROCESS_DEBUG_EVENT e LOAD_DLL_DEBUG_EVENT in particolare vengono gestiti come un HANDLE deve essere chiuso. Queste aree chiamano ContinueDebugEvent con DBG_CONTINUE.

Caso predefinito gestisce tutti gli altri eventi chiamando continua­DebugEvent con DBG_CONTINUE per continuare l'esecuzione e chiudere l'handle passato.

Post Mortem eccezioni

Windows Vista ha introdotto un terzo parametro alla riga di comando di post mortem debugger per supportare il passaggio di informazioni sull'eccezione. Per ricevere il terzo parametro, è necessario disporre di un valore di Debugger (nella chiave AeDebug) che include le sostituzioni di tre % ld. I tre valori sono: ID di processo, ID evento e indirizzo JIT. L'indirizzo di JIT è l'indirizzo di una struttura JIT_DEBUG_INFO nello spazio di indirizzi del bersaglio. Windows Error Reporting (WER) alloca questa memoria nello spazio di indirizzi di destinazione quando WER viene richiamato a causa di un'eccezione non gestita. Riempie la struttura JIT_DEBUG_INFO, richiama il post mortem debugger (passando l'indirizzo dell'allocazione) e quindi libera la memoria dopo il post mortem debugger finisce.

Per determinare il record di contesto di eccezione, post mortem applicazione legge la struttura JIT_DEBUG_INFO dallo spazio di indirizzi del bersaglio. La struttura ha l'indirizzo di una struttura di contesto ed EXCEPTION_RECORD nello spazio di indirizzi del bersaglio. Invece di leggere le strutture di contesto ed EXCEPTION_RECORD dallo spazio di indirizzi del bersaglio, appena riempito la struttura EXCEPTION_POINTERS con questi indirizzi e quindi impostare il membro ClientPointers su TRUE nella struttura MINIDUMP_EXCEPTION_INFORMATION. Questo rende il debugger di fare tutto il sollevamento di carichi pesanti. Si leggerà i dati dallo spazio di indirizzi del bersaglio (facendo indennità per differenze architettoniche in modo che è possibile prendere una discarica a 64-bit di un processo a 32-bit).

L'applicazione di esempio MiniDump03 viene illustrato come implementare il supporto JIT_DEBUG_INFO (vedere Figura 5).

Figura 5 MiniDump03 – gestore JIT_DEBUG_INFO

int JitInfoDump(HANDLE hProcess, DWORD dwProcessId, HANDLE hFile, MINIDUMP_TYPE miniDumpType, ULONG64 ulJitInfoAddr)
{
  int nResult = -1;
  JIT_DEBUG_INFO jitInfoTarget;
  SIZE_T numberOfBytesRead;
  if (ReadProcessMemory(hProcess, (void*)ulJitInfoAddr, &jitInfoTarget, sizeof(jitInfoTarget), &numberOfBytesRead) &&
    (numberOfBytesRead == sizeof(jitInfoTarget)))
  {
    EXCEPTION_POINTERS exceptionPointers = {0};
    exceptionPointers.ContextRecord = (PCONTEXT)jitInfoTarget.lpContextRecord;
    exceptionPointers.ExceptionRecord = (PEXCEPTION_RECORD)jitInfoTarget.lpExceptionRecord;
    MINIDUMP_EXCEPTION_INFORMATION    exceptionInfo = {0};
    exceptionInfo.ThreadId = jitInfoTarget.dwThreadID;
    exceptionInfo.ExceptionPointers = &exceptionPointers;
    exceptionInfo.ClientPointers = TRUE;
    nResult = WriteDump(hProcess, dwProcessId, hFile, miniDumpType, &exceptionInfo);
  }
  else
  {
    nResult = WriteDump(hProcess, dwProcessId, hFile, miniDumpType, NULL);
  }
  return nResult;
}

Quando un'applicazione viene richiamata come il post mortem debugger, è responsabilità dell'applicazione per terminare forzatamente il processo tramite una chiamata TerminateProcess. Nell'esempio MiniDump03, TerminateProcess viene chiamato dopo che è stata presa il dump:

// Post Mortem (AeDebug) dump - JIT_DEBUG_INFO - Vista+
else if ((argc == 4) && (_stscanf_s(argv[3], _T("%ld"), &ulJitInfoAddr) == 1))
{
  nResult = JitInfoDump(hProcess, dwProcessId, hFile, miniDumpType, ulJitInfoAddr);
  // Terminate the process
  TerminateProcess(hProcess, -1);
}

Per sostituire il post mortem debugger con la propria applicazione, si posiziona il valore Debugger dei tasti AeDebug post mortem applicazione con l'architettura appropriato. Utilizzando l'applicazione corrispondente, è rimuovere la necessità di adeguare la macchina efficacia (.effmach) nel debugger.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
Debugger (REG_SZ) = "C:\dumps\minidump03_x64.exe %ld %ld %ld"
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug
Debugger (REG_SZ) = "C:\dumps\minidump03_x86.exe %ld %ld %ld"

MiniDumpCallback funzione

Finora, il dump preso contiene memoria che il parametro DumpType indicato per includere (e, facoltativamente, un record di contesto di eccezione). Implementando un prototipo di funzione MiniDumpCallback, possiamo aggiungere non solo delle regioni di memoria aggiuntiva, ma anche alcuni gestione degli errori. Descriverò più tardi come implementare unicamente il prototipo di funzione MiniDumpCallback per l'utilizzo con Sysinternals ProcDump v 4.0.

Ci sono attualmente 16 tipi di callback che consentono di controllare i molteplici aspetti del dumping, come ad esempio la memoria inclusa per moduli e discussioni, memoria stessa, la possibilità di annullare una discarica che è in corso, il controllo del file dump i/O e la gestione di errori.

L'istruzione switch nel mio codice modello (vedere Figura 6) comprende tutti i tipi di richiamata all'incirca nell'ordine di chiamata del loro primo involucro­cazione. Alcuni callback può essere chiamato più di una volta e successivamente possono verificarsi fuori della sequenza. Ugualmente, non non c'è nessun contratto per l'ordine e quindi potrebbe cambiare in futuro comunicati.

Figura 6 Template implementazione del prototipo di funzione MiniDumpCallback

BOOL CALLBACK MiniDumpCallbackRoutine(
  __in     PVOID CallbackParam,
  __in     const PMINIDUMP_CALLBACK_INPUT CallbackInput,
  __inout  PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
)
{    // Callback supported in Windows 2003 SP1 unless indicated
  // Switch statement is in call order
  switch (CallbackInput->CallbackType)
  {
  case IoStartCallback:  //  Available in Vista/Win2008
    break;
  case SecondaryFlagsCallback:  //  Available in Vista/Win2008
    break;
  case CancelCallback:
    break;
  case IncludeThreadCallback:
    break;
  case IncludeModuleCallback:
    break;
  case ModuleCallback:
    break;
  case ThreadCallback:
    break;
  case ThreadExCallback:
    break;
  case MemoryCallback:
    break;
  case RemoveMemoryCallback:
    break;
  case WriteKernelMinidumpCallback:
    break;
  case KernelMinidumpStatusCallback:
    break;
  case IncludeVmRegionCallback:  //  Available in Vista/Win2008
    break;
  case IoWriteAllCallback:  //  Available in Vista/Win2008
    break;
  case IoFinishCallback:  //  Available in Vista/Win2008
    break;
  case ReadMemoryFailureCallback:  // Available in Vista/Win2008
    break;
  }
  return TRUE;
}

Il campione di MiniDump04 contiene un callback che fa due cose; Esso comprende il contenuto intero stack, e ignora gli errori di letti. Questo esempio utilizza ThreadCallback e MemoryCallback per includere l'intero stack e ReadMemoryFailureCallback per ignorare gli errori di letti.

Per richiamare il callback, MINIDUMP_CALLBACK_ opzionale­struttura di informazioni viene passato alla funzione MiniDumpWriteDump. Membro CallbackRoutine della struttura è usato per puntare alla funzione MiniDumpCallback implementata (MiniDumpCallbackRoutine nel mio modello e campione). Il membro CallbackParam è un puntatore VOID * che consente di mantenere il contesto tra le chiamate di richiamata. Mia funzione di WriteDump dal Mini­Dump04 campione è in Figura 7.

Figura 7 WriteDump dall'esempio MiniDump04

int WriteDump(HANDLE hProcess, DWORD dwProcessId, HANDLE hFile, MINIDUMP_TYPE miniDumpType, PMINIDUMP_EXCEPTION_INFORMATION pExceptionParam)
{
  MemoryInfoNode* pRootMemoryInfoNode = NULL;
  MINIDUMP_CALLBACK_INFORMATION callbackInfo;
  callbackInfo.CallbackParam = &pRootMemoryInfoNode;
  callbackInfo.CallbackRoutine = MiniDumpCallbackRoutine;
  if (!MiniDumpWriteDump(hProcess, dwProcessId, hFile, miniDumpType, pExceptionParam, NULL, &callbackInfo))
  {
    _tprintf(_T("Failed to create hang dump (Error: %08x)\n"), GetLastError());
    while (pRootMemoryInfoNode)
    {    // If there was an error, we'll need to cleanup here
      MemoryInfoNode* pNode = pRootMemoryInfoNode;
      pRootMemoryInfoNode = pNode->pNext;
      delete pNode;
    }
    return 11;
  }
  return 0;
}

La struttura che ho definito (MemoryInfoNode) per mantenere il contesto tra le chiamate è un nodo di elenco di link che contiene un indirizzo e una dimensione, in questo modo:

struct MemoryInfoNode
{
  MemoryInfoNode* pNext;
  ULONG64 ulBase;
  ULONG ulSize;
};

Intero Stack

Quando si utilizza il flag MiniDumpWithProcessThreadData nel parametro DumpType, il contenuto di ogni stack è incluso dalla base dello stack per il puntatore corrente dello stack. Il mio MiniDumpCallbackRoutine funzione implementata nel campione MiniDump04 potenzia questo compreso il resto dello stack. Includendo l'intero stack, potrebbe essere in grado di determinare l'origine di un cestino di pila.

Un cestino di pila è quando un buffer overflow si verifica a un buffer basato su stack. L'overflow del buffer scrive sopra l'indirizzo di ritorno dello stack e provoca l'esecuzione dell'opcode "ret" al POP il contenuto del buffer come un puntatore all'istruzione, anziché il puntatore all'istruzione PUSHed dalla "chiamata" opcode. Ciò comporta l'esecuzione di un indirizzo di memoria non validi, o anche peggio, l'esecuzione di un casuale pezzo di codice.

Quando si verifica un cestino di pila, la memoria qui sotto (ricordate, pile crescono verso il basso) il puntatore corrente dello stack ancora conterrà i dati dello stack non modificato. Con questi dati, è possibile determinare il contenuto del buffer e — la maggior parte del tempo — le funzioni che sono state chiamate per generare il contenuto del buffer.

Se si confronta la memoria di sopra del limite dello stack in una discarica preso con e senza la memoria dello stack aggiuntiva, si vedrà che la memoria venga visualizzata come mancanti (il? simbolo) nella discarica normale, ma è incluso nella discarica fatta con il callback (vedere Figura 8).

Figura 8 Stack contenuti confronto

0:003> !teb
TEB at 000007fffffd8000
  ExceptionList:        0000000000000000
  StackBase:            000000001b4b0000
  StackLimit:           000000001b4af000
  SubSystemTib:         0000000000000000
...
// No Callback
0:003> dp poi($teb+10) L6
00000000`1b4af000  ????????`????????
????????`????????
00000000`1b4af010  ????????`????????
????????`????????
00000000`1b4af020  ????????`????????
????????`????????
// Callback
0:003> dp poi($teb+10) L6
00000000`1b4af000  00000000`00000000 00000000`00000000
00000000`1b4af010  00000000`00000000 00000000`00000000
00000000`1b4af020  00000000`00000000 00000000`00000000

La prima parte del codice "intero stack" è la gestione del tipo ThreadCallback richiamata (vedere Figura 9). Questo tipo di callback viene chiamato una volta per ogni thread nel processo. Il callback viene passato una struttura MINIDUMP_THREAD_CALLBACK tramite il parametro CallbackInput. La struttura comprende un membro StackBase stack base del thread. Il membro StackEnd è il puntatore corrente dello stack (esp/rsp per x86 / x64 rispettivamente). La struttura non include il limite dello stack (parte del blocco di ambiente Thread).

Figura 9 ThreadCallback viene utilizzato per raccogliere la regione dello Stack di ciascun Thread

case ThreadCallback:
{    // We aren't passed the StackLimit so we use a 1MB offset from StackBase
  MemoryInfoNode** ppRoot = (MemoryInfoNode**)CallbackParam;
  if (ppRoot)
  {
    MemoryInfoNode* pNode = new MemoryInfoNode;
    pNode->ulBase = CallbackInput->Thread.StackBase - 0x100000;
    pNode->ulSize = 0x100000; // 1MB
    pNode->pNext = *ppRoot;
    *ppRoot = pNode;
  }
}
break;

L'esempio adotta un approccio semplicistico e si presuppone che lo stack è 1 MB in dimensioni — questa è l'impostazione predefinita per la maggior parte delle applicazioni. Nel caso in cui questo è sotto il puntatore dello stack, il parametro DumpType causerà la memoria essere incluso. Nel caso in cui lo stack è più grande di 1 MB, una pila di parziale sarà inclusa. E nel caso in cui lo stack è più piccolo di 1 MB, ulteriori dati appena saranno inclusi. Si noti che se l'intervallo di memoria richiesta dal callback si estende su un'area di libera o si sovrappone l'inclusione di un altro, nessun errore si verifica.

Il StackBase e l'offset di 1 MB sono registrati in una nuova creazione di un'istanza della struttura MemoryInfoNode che ho definito. La creazione di nuove istanze viene aggiunto a fronte di un elenco di link che viene passato al metodo di callback tramite l'argomento CallbackParam. Dopo il più chiamate a ThreadCallback, lista collegata contiene più nodi di memoria aggiuntiva per includere.

L'ultima parte del codice "intero stack" è la gestione del tipo MemoryCallback richiamata (vedere Figura 10). MemoryCallback è chiamato continuamente mentre si restituito TRUE dal callback e fornisce un valore diverso da zero per i membri di MemoryBase e MemorySize della struttura MINIDUMP_CALLBACK_OUTPUT.

Figura 10 MemoryCallback viene chiamato continuamente mentre tornava a una regione dello Stack

case MemoryCallback:
{    // Remove the root node and return its members in the callback
  MemoryInfoNode** ppRoot = (MemoryInfoNode**)CallbackParam;
  if (ppRoot && *ppRoot)
  {
    MemoryInfoNode* pNode = *ppRoot;
    *ppRoot = pNode->pNext;
    CallbackOutput->MemoryBase = pNode->ulBase;
    CallbackOutput->MemorySize = pNode->ulSize;
    delete pNode;
  }
}
break;

Il codice imposta i valori del parametro CallbackOutput e poi elimina il nodo dall'elenco collegato. Dopo più chiamate a MemoryCallback, nell'elenco collegato non conterrà nessun più nodi e valori zero vengono restituiti alla fine l'invocazione di MemoryCallback. Si noti che i membri di MemoryBase e MemorySize sono impostati a zero quando passato; è necessario restituire TRUE.

È possibile utilizzare l'area MemoryListStream dell'output del comando .dumpdebug per vedere tutte le regioni di memoria nella discarica (si noti che potrebbero essere unite blocchi adiacenti). Vedere Figura 11.

Figura 11 l'Output del comando .dumpdebug

0:000> .dumpdebug
----- User Mini Dump Analysis
...
Stream 3: type MemoryListStream (5), size 00000194, RVA 00000E86
  25 memory ranges
  range#    RVA      Address      Size
       0 0000101A    7efdb000   00005000
       1 0000601A    001d6000   00009734
       2 0000F74E    00010000   00021000
       3 0003074E    003b0f8d   00000100
       4 0003084E    003b3000   00001000
...
Read Memory Failure

L'ultimo pezzo di codice è abbastanza semplice (vedere Figura 12). Imposta il membro di Status della struttura MINIDUMP_CALLBACK_OUTPUT su S_OK per segnalare che è OK per omettere una regione di memoria che potrebbe non essere letto durante la cattura.

Figura 12 ReadMemoryFailureCallback viene chiamato su errori di lettura

case ReadMemoryFailureCallback:  // DbgHelp.dll v6.5; Available in Vista/Win2008
  {    //  There has been a failure to read memory.
Set Status to S_OK to ignore it.
CallbackOutput->Status = S_OK;
  }
  break;

In questa implementazione semplice, il callback implementa la stessa funzionalità come la bandiera di MiniDumpIgnoreInaccessibleMemory. Il callback ReadMemoryFailureCallback passato l'offset, numero di byte e il codice di errore tramite la struttura MINIDUMP_READ_MEMORY_FAILURE_CALLBACK nel parametro CallbackInput. In un callback più complesso, è possibile utilizzare queste informazioni per determinare se la memoria è fondamentale per il dump analisi, e se il dump dovrebbe essere interrotta.

Memoria di dissezione

Così come fai a sapere che cosa si può e non può permettersi di rimuovere da una discarica? Sysinternals VMMap è un ottimo modo per vedere che cosa memoria di un'applicazione assomiglia. Se si utilizza Sysinternals VMMap su un processo gestito, avrete notato che ci sono allocazioni connessi con la spazzatura raccolto heap (GC), e allocazioni associati con immagini dell'applicazione e file di mapping. Si tratta di heap del GC che avete bisogno in un dump di un processo gestito perché l'estensione del debugger figlio di Strike (SOS) richiede strutture di dati intatta all'interno di heap del GC per interpretare il dump.

Per determinare la posizione dell'heap GC, si potrebbe prendere un approccio esigente avviando una sessione di debug Engine (DbgEng) contro la destinazione utilizzando DebugCreate e IDebugClient::AttachProcess. Con questa sessione di debug, si potrebbe caricare l'estensione del debugger SOS ed eseguire comandi per restituire le informazioni dell'heap (questo è un esempio di utilizzo di conoscenze di dominio).

In alternativa, è possibile utilizzare euristiche. Si includono tutte le regioni che hanno un tipo di memoria di privato (MEMORY_PRIVATE) o una protezione di lettura/scrittura (PAGE_READWRITE o PAGE_EXECUTE_­READWRITE). Questo raccoglie più memoria rispetto assolutamente necessaria, ma fa ancora un significativo risparmio escludendo l'applicazione stessa. Il campione MiniDump05 prende questo approccio (vedere Figura 13), sostituendo il codice di stack thread del campione MiniDump04 con un ciclo di VirtualQueryEx solo una volta nel callback ThreadCallback (la nuova logica ancora provoca l'intero stack essere inclusi come prima). Viene quindi utilizzato lo stesso codice di MemoryCallback, utilizzato nell'esempio MiniDump04 per includere la memoria nella discarica.

Figura 13 MiniDump05 — ThreadCallback viene utilizzato una sola volta per raccogliere le regioni di memoria

case ThreadCallback:
{    // Collect all of the committed MEM_PRIVATE and R/W memory
  MemoryInfoNode** ppRoot = (MemoryInfoNode**)CallbackParam;
  if (ppRoot && !*ppRoot)    // Only do this once
  {
    MEMORY_BASIC_INFORMATION mbi;
    ULONG64 ulAddress = 0;
    SIZE_T dwSize = VirtualQueryEx(CallbackInput->ProcessHandle, (void*)ulAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
    while (dwSize == sizeof(MEMORY_BASIC_INFORMATION))
    {
      if ((mbi.State == MEM_COMMIT) &&
        ((mbi.Type == MEM_PRIVATE) || (mbi.Protect == PAGE_READWRITE) || (mbi.Protect == PAGE_EXECUTE_READWRITE)))
      {
        MemoryInfoNode* pNode = new MemoryInfoNode;
        pNode->ulBase = (ULONG64)mbi.BaseAddress;
        pNode->ulSize = (ULONG)mbi.RegionSize;
        pNode->pNext = *ppRoot;
        *ppRoot = pNode;
      }
      // Loop
      ulAddress = (ULONG64)mbi.BaseAddress + mbi.RegionSize;
      dwSize = VirtualQueryEx(CallbackInput->ProcessHandle, (void*)ulAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
    }
  }
}
break;

File di immagine di memoria mappata

Ci si potrebbe chiedere come poter eseguire il debug un dump con le regioni di immagine (MEM_IMAGE) mancanti. Ad esempio: Come sarebbe vedi il codice che è in esecuzione? La risposta è un po ' out-of-the-box. Quando il debugger deve accedere una regione immagine mancante in una discarica non completamente, ottiene i dati dal file immagine invece. Trova il file di immagine utilizzando il percorso di caricamento del modulo, il PDB originale percorso del file di immagine o usi i percorsi di ricerca.sympath/.exepath. Se si esegue lmvm <module>, vedrete una linea di "File di immagine della memoria mappato" che indica che il file è stato mappato discarica, in questo modo:

0:000> lmvm CrashAV_x86
start    end        module name
003b0000 003b6000   CrashAV_x86   (deferred)            
  Mapped memory image file: C:\dumps\CrashAV_x86.exe
  Image path: C:\dumps\CrashAV_x86.exe
  Image name: CrashAV_x86.exe
...

Basandosi sulla capacità del debugger "File di immagine della memoria mappato" è un approccio grande a mantenere le dimensioni del dump piccolo. Funziona particolarmente bene con applicazioni native, perché i binari compilati sono quelli utilizzati e sono quindi disponibili sul vostro server internal build (e appuntita dai PDB). Con applicazioni gestite, la compilazione JIT sul computer del cliente remoto complica questo. Se si desidera eseguire il debug di una discarica di applicazione gestita da un altro computer, è necessario copiare i file binari (come pure i dump) localmente. Questo è ancora un risparmio, perché più discariche possono essere prese rapidamente, e poi un insieme di file di immagine singola applicazione (grande) può essere fatto senza interruzione. Per semplificare la raccolta di file, è possibile utilizzare la ModuleCallback per scrivere uno script che raccoglie i moduli (file) viene fatto riferimenti nella discarica.

Me Plug-In!

Cambiando l'applicazione standalone per utilizzare Sysinternals ProcDump v 4.0 rende la vita molto più facile. È non è più necessario implementare tutto il codice associato a chiamata MiniDumpWriteDump e, più importante, tutto il codice per attivare il dump al momento giusto. Basta implementare una funzione di MiniDumpCallback ed esportarlo come MiniDumpCallbackRoutine in una DLL.

Il campione di MiniDump06 (vedere Figura 14) include il codice di richiamata da MiniDump05 con solo poche modifiche.

Figura 14 MiniDumpCallbackRoutine cambiato di utilizzare un globale anziché CallbackParam

MemoryInfoNode* g_pRootMemoryInfoNode = NULL;
...
case IncludeThreadCallback:
{
  while (g_pRootMemoryInfoNode)
  {    //Unexpected cleanup required
    MemoryInfoNode* pNode = g_pRootMemoryInfoNode;
    g_pRootMemoryInfoNode = pNode->pNext;
    delete pNode;
  }
}
break;
...
case ThreadCallback:
{    // Collect all of committed MEM_PRIVATE and R/W memory
  if (!g_pRootMemoryInfoNode)    // Only do this once
  {
...
pNode->pNext = g_pRootMemoryInfoNode;
    g_pRootMemoryInfoNode = pNode;
...
}
}
break;
...
case MemoryCallback:
{    // Remove the root node and return its members in the callback
  if (g_pRootMemoryInfoNode)
  {
    MemoryInfoNode* pNode = g_pRootMemoryInfoNode;
    g_pRootMemoryInfoNode = pNode->pNext;
    CallbackOutput->MemoryBase = pNode->ulBase;
    CallbackOutput->MemorySize = pNode->ulSize;
    delete pNode;
  }
}
break;

Il nuovo progetto MiniDump06 compila il codice di callback come DLL. Il progetto esporta il MiniDumpCallbackRoutine (maiuscole e minuscole) utilizzando un file DEF:

LIBRARY    "MiniDump06"
EXPORTS
  MiniDumpCallbackRoutine   @1

Poiché ProcDump passa un valore CallbackParam NULL, la funzione deve utilizzare una variabile globale invece per monitorare il progresso attraverso la mia struttura MemoryInfoNode. Su IncludeThreadCallback prima, c'è il nuovo codice per reimpostare (eliminare) if variabile globale impostato da un'acquisizione preventiva. Questo sostituisce il codice che è stato attuato dopo un fallito MiniDumpWriteDump chiamare nella mia funzione di WriteDump.

Per utilizzare la DLL con ProcDump, si specifica l'opzione -d seguita dal nome del vostro DLL che corrisponde all'architettura di cattura. L'opzione -d è disponibile quando prendendo Mini discariche (no interruttore) e completa (-ma) discariche; non è disponibile quando si scatta MiniPlus (-mp) dump:

procdump.exe -d MiniDump06_x64.dll notepad.exe
procdump.exe –ma -d MiniDump06_x64.dll notepad.exe

Si noti che il callback viene richiamato con tipi di richiamata diversi da quelli descritti nel mio campioni quando un dump completo (-ma) viene preso (consultare MSDN Library per la documentazione). La funzione MiniDumpWriteDump considera il dump come un dump completo quando il parametro DumpType contiene MiniDumpWithFullMemory.

Sysinternals ProcDump (procdump.exe) è un'applicazione a 32 bit che estrae da sé la versione a 64-bit (procdump64.exe) quando richiesto. Dopo procdump64.exe è stata estratta e lanciato da procdump.exe, procdump64.exe caricherà la DLL (64-bit). Come tale, 64-bit DLL di debug è abbastanza difficile perché l'applicazione lanciato non è la destinazione desiderata. La cosa più semplice da fare per sostenere la 64-bit DLL di debug è copiare procdump64.exe temporanea in un'altra cartella e quindi eseguire il debug utilizzando la copia. In questo modo, non si verificherà alcuna estrazione e la DLL verrà caricata nell'applicazione lanciate all'interno del debugger (Visual Studio, ad esempio).

Pausa!

Determinazione dell'origine di crash e si blocca non è facile, quando si può permettersi solo un dump di Mini. Facendo un file dump con ulteriori informazioni chiave risolve questo senza incorrere in spese di un dump completo.

Se siete interessati a implementare il proprio dump applicazione o DLL, suggerisco esaminerete l'efficacia dei programmi di utilità Sysinternals ProcDump, WER e AdPlus prima — non reinventare la ruota.

Quando si scrive un callback, assicurarsi che si spende il tempo di capire il layout esatto della memoria all'interno dell'applicazione. Prendere Sysinternals VMMap istantanee e utilizzare il dump dell'applicazione per approfondire i dettagli. Facendo sempre più piccole discariche è un approccio iterativo. Avviare compreso e escludendo aree ovvi e poi perfezionare il vostro algoritmo. Potrebbe essere necessario utilizzare sia euristico e dominio approcci di conoscenza arrivare il tuo obiettivo. Si può aiutare il tuo processo decisionale modificando l'applicazione di destinazione. Ad esempio, è possibile utilizzare dimensioni allocazione di ben noto (e unico) per ogni tipo di utilizzo della memoria. La cosa importante è quello di pensare in modo creativo quando decidere come determinare quale memoria è necessaria l'applicazione di destinazione sia l'applicazione di dumping.

Se sei uno sviluppatore del kernel e sono interessati a callback, c'è un meccanismo simile basato sul kernel; consultare la documentazione di BugCheckCallback Routine su msdn.com.

Andrew Richards è un Microsoft senior escalation engineer per Windows OEM. Ha una passione per gli strumenti di supporto e sta continuamente creando debugger estensioni e richiamate e applicazioni che semplificano il lavoro di supportano gli ingegneri. Egli può essere contattato al andrew.richards@microsoft.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Drew Bliss e Mark Russinovich