Este artículo proviene de un motor de traducción automática.

Sysinternals ProcDump v4.0

Desarrollo de un complemento para Sysinternals ProcDump v4.0

Andrew Richards

Descargar el ejemplo de código

Que has sido hasta toda la noche instalando la actualización más reciente de sus aplicaciones de misión crítica y todo ha ido perfectamente.Y, a continuación, pasa — la cuelga de aplicación, así como todo el mundo empieza a llegar al trabajo.En momentos como este, necesita reducir sus pérdidas, aceptar que la liberación es un fracaso, reunir pruebas pertinentes tan rápido como sea posible e inicie ese plan nunca importante reversión.

Capturar un volcado de memoria de una aplicación en ocasiones como ésta es una táctica común de resolución de problemas, ya sea por razones de colgar, accidente o rendimiento.Más herramientas de captura de volcado adoptan un enfoque de todo o nada: te regalan todo (volcados de lleno) o muy poco (mini vertederos).Los mini vertederos generalmente son tan pequeñas que análisis de depuración fructífera no es posible porque faltan los montones.Volcados de lleno siempre han preferido, pero rara vez son una opción más.Cada vez más memoria significa que un volcado completo puede tomar 15, 30 o incluso 60 minutos.Además, los archivos de volcado se están volviendo tan grandes que no puede fácilmente ser trasladados para análisis, incluso cuando se comprime.

El año pasado, Sysinternals ProcDump v3.0 introdujo el conmutador MiniPlus (-mp) para abordar la cuestión del tamaño para las aplicaciones nativas.Esto crea un vertedero que está en algún lugar entre un volcado de mini y un volcado completo en tamaño.Las decisiones de inclusión de memoria del conmutador MiniPlus se basan en una multitud de algoritmos heurísticos que considerar el tipo de memoria, protección de memoria, tamaño de la asignación, contenido de tamaño y pila de región.Según el diseño de la aplicación de destino, el archivo de volcado puede ser 50% y 95% menor que un volcado completo.Más importante, el vertedero es tan funcional como un volcado completo para la mayoría de las tareas de análisis.Cuando el conmutador MiniPlus se aplica para el almacén de Microsoft Exchange 2010 información ejecutando con 48 GB de memoria, el resultado es un archivo de volcado de 1GB–2GB (una reducción del 95 por ciento).

Mark Russinovich y yo hemos estado trabajando en una nueva versión de ProcDump que ahora le permite realizar la memoria de las decisiones de inclusión.Sysinternals ProcDump v4.0 expone el mismo API que MiniPlus utiliza internamente como externo basado en DLL complemento mediante el switch -d.

En este artículo, voy a diseccionar cómo expansión Sysinternals ProcDump v4.0 obras por la construcción de una serie de aplicaciones de ejemplo sobre la aplicación de los otros, más y más la funcionalidad de ProcDump.Por ahondar en cómo funciona el ProcDump bajo las cubiertas, a mostrarle cómo puede escribir un complemento que interactúa con el ProcDump y la API subyacente de DbgHelp.

La descarga de código contiene las aplicaciones de ejemplo y también una colección de aplicaciones que accidente de diversas maneras (por lo que puede probar el código).La muestra de MiniDump05 tiene todas las API implementadas como una aplicación independiente.La muestra MiniDump06 implementa la muestra de MiniDump05 como un plug-in de Sysinternals ProcDump v4.0.

Terminología

Es fácil de obtener todos los términos asociados con la recopilación de volcado confundido — el término "Mini" se utiliza mucho.Existe el formato de archivo de minivolcado, Mini y MiniPlus contenido volcado y las funciones MiniDumpWriteDump y MiniDumpCallback.

Windows admite el formato de archivo de minivolcado vía DbgHelp.dll.Un archivo de volcado de minivolcado (*.dmp) es un contenedor que soporta captura parcial o completa de la memoria en un destino de modo kernel o modo de usuario.El formato de archivo soporta el uso de "corrientes" para almacenar metadatos adicionales (comentarios, estadísticas de proceso etc.).Nombre del formato de archivo se deriva de la obligación de apoyar la captura de una cantidad mínima de datos.Las funciones DbgHelp API MiniDumpWriteDump y MiniDumpCallback llevan el prefijo minivolcado al coincidir con el formato de archivo que producen.

Mini, MiniPlus y completo se utilizan para describir las diferentes cantidades de contenido en los archivos de volcado.Mini es el más pequeño (mínimo) e incluye el proceso bloque de entorno (PEB), bloques de medio ambiente de subproceso (TEBs), pilas de parciales, los módulos cargados y segmentos de datos.Mark y yo acuñado MiniPlus para describir el contenido de una captura de Sysinternals ProcDump -mp; incluye el contenido de un volcado de Mini, más memoria heurísticamente considera importante.Y un volcado completo (procdump.exe-ma) incluye el espacio de direcciones virtual completo del proceso, independientemente de si la memoria está paginada en RAM.

Función MiniDumpWriteDump

A un proceso en el formato de archivo de minivolcado a un archivo de captura, llame a la función DbgHelp MiniDumpWriteDump.La función requiere un identificador de proceso de destino (con acceso PROCESS_QUERY_INFORMATION y PROCESS_VM_READ), el PID del proceso de destino, un identificador de un archivo (con acceso FILE_GENERIC_WRITE), una máscara de bits de las banderas MINIDUMP_TYPE y tres parámetros opcionales: una estructura de información de la excepción (utilizada para incluir un registro de contexto de excepción); una estructura de información de la secuencia de usuario (comúnmente utilizada para incluir un comentario en el vertedero a través de los tipos de MINIDUMP_STREAM_TYPE de CommentStreamA/W); y una estructura de información de devolución de llamada (utilizado para modificar lo que es capturada durante la llamada):

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
);

La aplicación de ejemplo MiniDump01 (véase figura 1) se muestra cómo llamar a MiniDumpWriteDump para tomar un volcado de Mini sin que ninguno de los parámetros opcionales. Comienza marcando los argumentos de línea de comandos para un PID y llama OpenProcess para obtener un identificador de proceso de la meta. A continuación, llama a CreateFile para obtener un identificador de archivo. (Tenga en cuenta que MiniDumpWriteDump es compatible con cualquier destino de I/O). El archivo ha un ISO según fecha/tiempo el archivo de singularidad y clasificación cronológica: C:\dumps\minidump_YYYY-MM-DD_HH-MM-SS-MS.dmp. El directorio es codificados a C:\dumps para asegurar el acceso de escritura. Esto es necesario cuando se realiza la depuración post mortem porque la carpeta actual (por ejemplo, System32) podría no tener permisos de escritura.

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

El parámetro DumpType es una máscara basada en MINIDUMP_TYPE que provoca la inclusión o exclusión de determinados tipos de memoria. Las banderas MINIDUMP_TYPE son muy poderosas y permiten dirigir la captura de muchas regiones de memoria sin necesidad de codificación adicional a través de una devolución de llamada. Las opciones utilizadas por la muestra de MiniDump01 son las mismas que utilizan ProcDump. Crean un volcado (Mini) que puede utilizarse para resumir un proceso.

El DumpType siempre tiene el MiniDumpNormal bandera presente porque tiene un valor de 0 x 00000000. El DumpType utilizado incluye cada pila (MiniDumpNormal), toda la información Junta y TEB (MiniDumpWithProcessThreadData), la información del módulo cargado además cualquier globals (MiniDumpWithDataSegs), todos manejan de información (MiniDumpWithHandleData), toda información de región de memoria (MiniDumpWithFullMemoryInfo) y todos hilo tiempo y afinidad de información (MiniDumpWithThreadInfo). Con estos indicadores, el volcado creado es una rica versión de un volcado de Mini pero todavía muy pequeño (menos de 30 MB incluso para el más grande de FAC­cationes). Comandos del depurador ejemplo apoyados por estos indicadores MINIDUMP_TYPE aparecen en figura 2.

Figura 2 comandos del depurador

MINIDUMP_TYPE Comandos del depurador
MiniDumpNormal knL99
MiniDumpWithProcessThreadData ! peb,! teb
MiniDumpWithDataSegs LM, dt <global>
MiniDumpWithHandleData ! manejar,! cs
MiniDumpWithFullMemoryInfo ! dirección
MiniDumpWithThreadInfo ! desbocada

Cuando utilizando MiniDumpWriteDump, el dump tomado coincidirá con la arquitectura del programa de captura, no el destino, utilice así una versión de 32 bits del programa captura al capturar un proceso de 32 bits y una versión de 64 bits del programa captura al capturar un proceso de 64 bits. Si necesita depurar "Windows 32 bits en Windows 64-bit" (WOW64), debe tomar un volcado de 64 bits de los proceso de 32 bits.

Si no coinciden la arquitectura (por accidente o a propósito), deberás cambiar la máquina eficaz (.effmach x 86) en el depurador para acceder a las pilas de 32 bits en un vertedero de 64 bits. Tenga en cuenta que muchas de las extensiones de depurador de fracasar en este escenario.

Registro de contexto de excepción

Ingenieros de soporte de Microsoft utilizan los términos "cuelgan volcado" y "volcado". Cuando piden un volcado, quieren un volcado con un registro de contexto de excepción. Cuando piden un volcado de bloqueo, (normalmente) significan una serie de depósitos sin uno. Un volcado de información de la excepción no es siempre desde el momento de un accidente, sin embargo; puede ser en cualquier momento. La información de excepción es sólo un medio para proporcionar datos adicionales en un vertedero. El usuario corriente infor­ción es similar a la información de excepción a este respecto.

Un registro de contexto de excepción es la combinación de una estructura de contexto (los registros de la CPU) y una estructura EXCEPTION_RECORD (el código de excepción, la instrucción de la dirección, etc.). Si incluye un registro de contexto de excepción en la descarga y ejecución .ecxr, el actual contexto de depurador (estado de subproceso y registro) se establece en la instrucción que produjo la excepción (véase figura 3).

Figura 3 cambio de contexto para el registro de contexto de excepción

Este archivo de volcado tiene una excepción de interés almacenados en ella.

La información de excepción almacenado puede accederse a través de .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=????????=????????

Para .ecxr, el MINIDUMP_EXCEPTION_ opcional­estructura de información que necesita pasar al MiniDumpWriteDump. Puede obtener información de excepción en tiempo de ejecución o post mortem.

Excepciones de tiempo de ejecución

Si implementa un bucle de suceso de depurador, información de excepción se pasa a usted cuando se produce la excepción. El bucle de eventos depurador recibirá una estructura EXCEPTION_DEBUG_EVENT de puntos de interrupción, excepciones primeras oportunidad y excepciones de segunda oportunidad.

La aplicación de ejemplo MiniDump02 muestra cómo llamar MiniDumpWriteDump desde dentro de un bucle de suceso del depurador para que registro de contexto la excepción de segunda oportunidad se incluirá en el volcado (equivalente a "procdump.exe -e"). Esta función se ejecuta cuando se utiliza el modificador -e. Porque el código es bastante largo, el pseudo código para la aplicación se muestra en figura 4. Consulte descarga de código de este artículo para el código fuente completo.

Figura 4 MiniDump02 Pseudo código

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

La aplicación se inicia marcando los argumentos de línea de comandos para un PID. A continuación llama OpenProcess para obtener un identificador de proceso de la meta, y luego llama CreateFile para obtener un identificador de archivo. Si falta el conmutador -e tarda un volcado de bloqueo como antes. Si el conmutador -e está presente, la aplicación adjunta al destino (como un depurador) utilizando DebugActiveProcess. En un tiempo bucle, espera una estructura de DEBUG_EVENT ser devuelto por WaitForDebugEvent. La sentencia switch utiliza al miembro dwDebugEventCode de la estructura DEBUG_EVENT. Después de que se ha tomado el volcado o ha finalizado el proceso, se llama DebugActiveProcessStop para soltar desde el destino.

El EXCEPTION_DEBUG_EVENT dentro de la estructura DEBUG_EVENT contiene un registro de excepciones dentro de una excepción. Si el registro de excepciones es un punto de interrupción, es manejado localmente llamando al ContinueDebugEvent con DBG_CONTINUE. Si la excepción es una primera oportunidad, se no está manejado por lo que puede convertir una segunda oportunidad de excepción (si el destino no tiene un controlador). Para ello, se llama ContinueDebugEvent con DBG_EXCEPTION_NOT_HANDLED. El caso restante es una excepción de segunda oportunidad. Utilizando dwThreadId la estructura DEBUG_EVENT, OpenThread se llama para obtener un controlador para el hilo con la excepción. El identificador de subproceso se utiliza con GetThreadContext para llenar la estructura del contexto requerida. (Una palabra de precaución aquí: la estructura del contexto ha crecido en tamaño en los años registros adicionales se han añadido a los procesadores. Si un sistema operativo posterior aumenta el tamaño de la estructura del contexto, deberás volver a compilar este código.) La estructura del contexto obtenida y la EXCEPTION_RECORD de la DEBUG_EVENT se utilizan para rellenar una estructura EXCEPTION_POINTERS, y esto se utiliza para rellenar una estructura MINIDUMP_EXCEPTION_INFORMATION. Esta estructura se pasa a la función la aplicación WriteDump para uso con MiniDumpWriteDump.

La EXIT_PROCESS_DEBUG_EVENT se maneja específicamente para el escenario donde el destino termina antes de que se produzca una excepción. Se llama ContinueDebugEvent con DBG_CONTINUE para reconocer este evento y el tiempo se sale el bucle.

Los eventos CREATE_PROCESS_DEBUG_EVENT y LOAD_DLL_DEBUG_EVENT se tratan específicamente como un tirador debe cerrarse. Estas áreas llaman ContinueDebugEvent con DBG_CONTINUE.

El caso predeterminado controla todos los otros eventos llamando a continuar­DebugEvent con DBG_CONTINUE para continuar la ejecución y cierre el mango del pasado.

Post Mortem excepciones

Windows Vista introduce un tercer parámetro para la línea de comandos post mortem depurador para apoyar el paso de información de la excepción. Para recibir el tercer parámetro, debe tener un valor de depurador (en la clave AeDebug) que incluye sustituciones de tres % ld. Los tres valores son: proceso de ID, ID de evento y dirección de JIT. La dirección de JIT es la dirección de una estructura JIT_DEBUG_INFO en el espacio de dirección de destino. Windows Error Reporting (WER) asigna esta memoria en el espacio de direcciones de destino cuando se invoca WER como resultado de una excepción no controlada. Rellena la estructura JIT_DEBUG_INFO, invoca al depurador de post mortem (pasar la dirección de la asignación) y luego libera la memoria después de que termina el post mortem depurador.

Para determinar el registro de contexto de excepción, la autopsia aplicación lee la estructura JIT_DEBUG_INFO de espacio de direcciones de destino. La estructura tiene la dirección de una estructura de contexto y la estructura EXCEPTION_RECORD en el espacio de dirección de destino. En lugar de leer los contexto y EXCEPTION_RECORD estructuras del espacio de direcciones de destino, acaba de rellenar la estructura EXCEPTION_POINTERS con estas direcciones y, a continuación, establecer al miembro de ClientPointers en TRUE en la estructura MINIDUMP_EXCEPTION_INFORMATION. Esto hace que el depurador hacer todo el trabajo pesado. Leerá los datos de espacio de direcciones de destino (teniendo en cuenta diferencias de arquitectura por lo que es posible hacer un volcado de 64 bits de un proceso de 32 bits).

La aplicación de ejemplo MiniDump03 muestra cómo implementar el apoyo JIT_DEBUG_INFO (ver figura 5).

Figura 5 MiniDump03: controlador de 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;
}

Cuando se invoca una aplicación como depurador post mortem, es responsabilidad de la aplicación por la fuerza a terminar el proceso a través de una llamada de TerminateProcess. En el ejemplo de MiniDump03, TerminateProcess se llama después de que se ha tomado el volcado:

// 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);
}

Para reemplazar al post mortem depurador con su propia aplicación, elige el valor de depurador de las claves AeDebug el post mortem aplicaciones con la arquitectura adecuada. Mediante la aplicación coincidente, quita la necesidad de ajustar la máquina eficaz (.effmach) en el depurador.

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"

Función MiniDumpCallback

Hasta ahora, el dump tomado contiene memoria que indica el parámetro DumpType para incluir (y, opcionalmente, un registro de contexto de excepción). Mediante la implementación de un prototipo de función MiniDumpCallback, podemos añadir no sólo las regiones de memoria adicional pero también algún tratamiento de errores. Describiré más adelante cómo puede implementar exclusivamente el prototipo de función MiniDumpCallback para uso con Sysinternals ProcDump v4.0.

Hay actualmente 16 tipos de devolución de llamada que permiten controlar varios aspectos del dumping, tales como la memoria incluida para que módulos y subprocesos, memoria propia, la posibilidad de cancelar un vertedero que está en curso, el control de E/s de archivos de volcado y el manejo de errores.

La sentencia switch en mi código de plantilla (véase figura 6) incluye todos los tipos de devolución de llamada aproximadamente en el orden de llamada de su primer invo­catiónico. Algunas devoluciones de llamada pueden llamar más de una vez y posteriormente pueden ocurrir fuera de secuencia. Igualmente, no existe contrato para el orden y por lo tanto pueden cambiar en futuras versiones.

Figura 6 plantilla aplicación del prototipo de función 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;
}

La muestra de MiniDump04 contiene una devolución de llamada que hace dos cosas; incluye el contenido de la pila completa, y pasa por alto los errores de lectura. Este ejemplo utiliza ThreadCallback y MemoryCallback para incluir el stack y el ReadMemoryFailureCallback para omitir los errores de lectura.

Para invocar la devolución de llamada, el MINIDUMP_CALLBACK_ opcional­estructura de información se pasa a la función MiniDumpWriteDump. Miembro de CallbackRoutine de la estructura se utiliza para apuntar a la función MiniDumpCallback implementada (MiniDumpCallbackRoutine en mi plantilla y muestra). Los miembros de CallbackParam es un puntero VOID * que le permite retener el contexto entre llamadas de devolución de llamada. Mi función WriteDump de la Mini­ejemplo de Dump04 es en figura 7.

Figura 7 WriteDump del ejemplo 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 estructura he definido (MemoryInfoNode) para conservar el contexto entre llamadas es un nodo de la lista de vínculo que contiene una dirección y un tamaño, así:

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

Stack de

Cuando se utiliza el indicador MiniDumpWithProcessThreadData en el parámetro DumpType, el contenido de cada pila se incluyen desde la base de la pila para el puntero de pila actual. Mi función MiniDumpCallbackRoutine implementado en la muestra de MiniDump04 esto aumenta al incluir el resto de la pila. Incluyendo el stack, podría ser capaz de determinar el origen de una papelera de la pila.

Una papelera de pila es cuando se produce un desbordamiento de búfer en un búfer basado en pila. El desbordamiento de búfer escribe sobre la dirección de retorno de la pila y hace que la ejecución del código de operación "ret" pop el contenido del búfer como un puntero de instrucción, en lugar del puntero de instrucción PUSHed por el código de operación de "llamada". Esto resulta en la ejecución de una dirección de memoria no válida, o aún peor, la ejecución de una pieza aleatoria de código.

Cuando se produce una papelera de pila, la memoria de abajo (Recuerde, pilas de crecen hacia abajo) el puntero de pila actual todavía contendrá los datos de la pila sin modificar. Con estos datos, se puede determinar el contenido del búfer y, la mayoría del tiempo, las funciones que fueron llamadas para generar el contenido del búfer.

¿Si se compara la memoria por encima del límite de la pila en un vertedero con y sin la memoria de pila adicional, se verá que la memoria se muestra como falta (el? símbolo) en el vertedero normal, pero se incluye en el volcado con la devolución de llamada (véase figura 8).

Figura 8 comparación de contenidos de pila

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 primera parte del código "toda pila" es el manejo del tipo de devolución de llamada ThreadCallback (véase figura 9). Este tipo de devolución de llamada se llama una vez cada subproceso del proceso. La devolución de llamada se pasa una estructura MINIDUMP_THREAD_CALLBACK mediante el parámetro CallbackInput. La estructura incluye un miembro de StackBase que es la pila de la base de la rosca. El miembro StackEnd es el actual puntero de pila (esp/rsp para x 86 / x 64 respectivamente). La estructura no incluye el límite de la pila (parte del bloque de entorno Thread).

Figura 9 ThreadCallback se utiliza para recopilar la región de pila de cada subproceso

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;

El ejemplo toma un enfoque simplista y asume que la pila es 1 MB de tamaño, esto es el valor predeterminado para la mayoría de las aplicaciones. En caso de que esto se debajo del puntero de pila, el parámetro DumpType hará que la memoria que se incluirán. En el caso donde la pila es superior a 1 MB, se incluirán una pila parcial. Y en el caso donde la pila es menor de 1 MB, solo se incluirán datos adicionales. Tenga en cuenta que si el intervalo de memoria solicitado por la devolución de llamada abarca una región libre o se superpone otra inserción, ningún error.

El StackBase y el desplazamiento de 1 MB se registran en una nueva instancia de la estructura de MemoryInfoNode que he definido. La creación de nueva instancias se agrega al frente de la lista de enlaces que se pasa a la devolución de llamada mediante el argumento CallbackParam. Después de las múltiples invocaciones de ThreadCallback, la lista contiene múltiples nodos de memoria adicional para incluir.

La última parte del código "toda pila" es el manejo del tipo de devolución de llamada MemoryCallback (véase figura 10). MemoryCallback se llama continuamente mientras retorno TRUE de la devolución de llamada y proporciona un valor distinto de cero para los miembros de la estructura MINIDUMP_CALLBACK_OUTPUT MemoryBase y MemorySize.

Figura 10 MemoryCallback se llama continuamente mientras regresaba de una región de pila

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;

El código establece los valores del parámetro CallbackOutput y, a continuación, elimina el nodo de la lista enlazada. Después de varias invocaciones de MemoryCallback, la lista no contendrá más nodos y se devuelven los valores cero para poner fin a la invocación de MemoryCallback. Tenga en cuenta que los miembros de MemoryBase y MemorySize se ponen a cero cuando pasa; sólo debe devolver TRUE.

Puede utilizar el área de MemoryListStream de la salida del comando .dumpdebug para ver todas las regiones de memoria en el volcado (tenga en cuenta que pueden combinarse bloques adyacentes). Vea la Figura 11.

Figura 11 la salida del comando de .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

La última pieza de código es bastante simple (véase figura 12). Establece al miembro de estado de la estructura MINIDUMP_CALLBACK_OUTPUT en S_OK para indicar que es aceptar para omitir una región de memoria que no se puede leer durante la captura.

Figura 12 ReadMemoryFailureCallback se llama en errores de lectura

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;

En esta implementación simple, la devolución de llamada implementa la misma funcionalidad que la bandera de MiniDumpIgnoreInaccessibleMemory. La devolución de llamada ReadMemoryFailureCallback se pasa el desvío, número de bytes y el código de error a través de la estructura MINIDUMP_READ_MEMORY_FAILURE_CALLBACK en el parámetro CallbackInput. En una devolución de llamada más compleja, puede utilizar esta información para determinar si la memoria es fundamental para volcar el análisis, y si el vertedero debe ser anulado.

Memoria de disección

Entonces, ¿cómo sabes lo que puede y no puede permitirse quitar de un vertedero? Sysinternals VMMap es una excelente manera de ver el aspecto de memoria de una aplicación. Si usas Sysinternals VMMap en un proceso administrado, observará que hay asignaciones asociadas con la basura recogida montón (GC), y las asignaciones asociadas con imágenes de la aplicación y asignación archivos. Es el montón de GC que necesita en un basurero de un proceso administrado debido a la extensión de depurador de hijo de huelga (SOS) requiere estructuras de datos intactos desde dentro del montón de GC para interpretar el volcado.

Para determinar la ubicación del montón GC, podría adoptar un enfoque exigente iniciando una sesión de depuración motor (DbgEng) contra el destino mediante DebugCreate y IDebugClient::AttachProcess. Con esta sesión de depuración, pudo cargar la extensión de depurador SOS y ejecutar comandos para devolver la información de montón (este es un ejemplo del uso de conocimientos de dominio).

Alternativamente, puede utilizar heurística. Se incluyen todas las regiones que tienen un tipo de memoria de privados (MEMORY_PRIVATE) o una protección de lectura/escritura (PAGE_READWRITE o PAGE_EXECUTE_­READWRITE). Recopila más memoria que es absolutamente necesario, pero todavía hace un ahorro significativo mediante la exclusión de la propia aplicación. La muestra MiniDump05 adopta este enfoque (véase figura 13) reemplazando el código de pila de la muestra MiniDump04 hilo con un bucle de VirtualQueryEx sólo una vez en la devolución de llamada ThreadCallback (la nueva lógica aún provoca el stack para incluirse como antes). A continuación, utiliza el mismo código de MemoryCallback utilizado en este ejemplo de MiniDump04 para incluir la memoria en el vertedero.

Figura 13 MiniDump05 — ThreadCallback se utiliza una vez a recoger las regiones de 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;

Archivos de imagen de memoria asignada

Quizás se esté preguntando cómo puede depurar un volcado con regiones de imagen (MEM_IMAGE) falta. Por ejemplo: ¿cómo vería el código que está ejecutando? La respuesta es un poco listos. Cuando el depurador necesita tener acceso a una región de imagen falta en un vertedero no completo, obtiene los datos desde el archivo de imagen en su lugar. Encuentra el archivo de imagen mediante la ruta de carga del módulo, el PDB original ubicación de archivo de imagen o utiliza las rutas de búsqueda de.sympath/.exepath. Si ejecuta lmvm <module>, verá una línea de "Archivo de imagen de la memoria asignada" que indica que el archivo ha sido asignado al basurero, así:

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
...

Depender de la capacidad de "Archivo de imagen de la memoria asignada" del depurador es un gran enfoque para mantener pequeño el tamaño de los depósitos. Funciona especialmente bien con las aplicaciones nativas, porque los binarios compilados son utilizados y, por tanto, están disponibles en el servidor de generación interna (y señaló que por el PDB). Con las aplicaciones administradas, la compilación JIT en equipo del cliente remoto complica esto. Si desea depurar un volcado de aplicación administrada desde otro equipo, deberá copiar los archivos binarios (como los vertederos) localmente. Este es un ahorro porque varios vertederos pueden tomarse rápidamente y, a continuación, se puede hacer una colección de archivos de imagen única aplicación (grande) sin interrupción. Para simplificar la colección de archivos, puede utilizar el ModuleCallback para escribir un script que recopila los módulos (archivos) se hace referencia en el vertedero.

Enchufe Me!

Cambiar la aplicación independiente para utilizar Sysinternals ProcDump v4.0 hace la vida mucho más fácil. Ya no tienes que poner todo el código asociado a llamar a MiniDumpWriteDump y, más importante, todo el código para activar el volcado en el momento adecuado. Basta con aplicar una función de MiniDumpCallback y exportarlo como MiniDumpCallbackRoutine en un archivo DLL.

La muestra de MiniDump06 (ver figura 14) incluye el código de devolución de llamada de MiniDump05 con unas pocas modificaciones.

Figura 14 MiniDumpCallbackRoutine cambiado para usar Global en lugar de 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;

El nuevo proyecto MiniDump06 compila el código de devolución de llamada como una DLL. El proyecto exporta el MiniDumpCallbackRoutine (minúsculas) utilizando un archivo de definición:

LIBRARY    "MiniDump06"
EXPORTS
  MiniDumpCallbackRoutine   @1

Como ProcDump pasa CallbackParam el valor NULL, la función debe utilizar una variable global en lugar de realizar un seguimiento de su progreso a través de mi estructura de MemoryInfoNode. En el primer IncludeThreadCallback, hay un nuevo código para restablecer (eliminar) if variable global establecido de una captura anterior. Esto reemplaza el código que se implementó después de llamar a un MiniDumpWriteDump error en mi función de WriteDump.

Para utilizar el archivo DLL con ProcDump, especifique el modificador -d seguida del nombre del archivo DLL que coincida con la arquitectura de la captura. El modificador -d está disponible cuando teniendo Mini vuelca (ningún conmutador) y total (-ma) vuelca; no está disponible cuando se toma MiniPlus (-mp) vuelca:

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

Tenga en cuenta que la devolución de llamada se invoca con tipos diferentes de devolución de llamada que las descritas en mis muestras cuando un volcado completo (-ma) se toman (consulte la documentación de MSDN Library). La función MiniDumpWriteDump trata el volcado como un volcado completo cuando el parámetro DumpType contiene MiniDumpWithFullMemory.

Sysinternals ProcDump (procdump.exe) es una aplicación de 32 bits que extrae de sí misma la versión de 64 bits (procdump64.exe) cuando sea necesario. Después de haber extraído y lanzado por procdump.exe procdump64.exe, procdump64.exe se cargará la DLL (64 bits). Como tal, la depuración de la DLL de 64 bits es bastante complicada porque la aplicación iniciada no es el destino deseado. Lo más fácil que hacer para apoyar la depuración de la DLL de 64 bits es copiar el procdump64.exe temporal a otra carpeta y, a continuación, depurar utilizando la copia. De esta manera, no se producirá extracción y se cargará el archivo DLL de la aplicación se inicia desde dentro el depurador (Visual Studio, por ejemplo).

Romper!

Determinación del origen de los bloqueos y se cuelga no es fácil cuando puede permitirse sólo un volcado de Mini. Haciendo un archivo de volcado con información clave adicional, esto resuelve sin incurrir en el gasto de un volcado completo.

Si está interesado en implementar su propia aplicación de volcado o DLL, sugiero que investiga la eficacia de las utilidades de Sysinternals ProcDump, WER y AdPlus primero — no reinventar la rueda.

Al escribir una devolución de llamada, asegúrese de que pasa el tiempo para comprender la composición exacta de memoria dentro de la aplicación. Tomar Sysinternals VMMap instantáneas y volcados de la aplicación para ahondar en los detalles. Volcados de menores es un enfoque iterativo. Inicie incluyendo y excluyendo áreas obvias y luego refinar su algoritmo. Puede que necesite utilizar heurística y dominio de los enfoques de conocimientos para ayudarle a su objetivo. Puede ayudar a la toma de decisiones mediante la modificación de la aplicación de destino. Por ejemplo, podría utilizar tamaños de asignación conocidos (y único) para cada tipo de uso de memoria. Lo importante es pensar creativamente al decidir cómo determinar qué memoria es necesaria en la aplicación de destino y la aplicación de dumping.

Si eres un desarrollador del núcleo y están interesados en las devoluciones de llamada, existe un mecanismo similar basado en el núcleo; Consulte la documentación de la rutina de BugCheckCallback en msdn.com.

Andrew Richards es ingeniero senior de escalada de Microsoft para Windows OEM. Él tiene una pasión por las herramientas de soporte y continuamente está creando a depurador extensiones y devoluciones de llamada y aplicaciones que simplifican el trabajo de ingenieros de soporte. Él puede ser contactado en andrew.richards@microsoft.com.

Gracias a los siguientes expertos técnicos para revisar este artículo: Drew Bliss y Mark Russinovich