Compartir a través de


WinDbg - Cronologías

Logotipo de WinDbg con una lupa inspeccionando bits.

La depuración de viaje en el tiempo (TTD) permite a los usuarios registrar rastreos, que son registros de la ejecución de un programa. Las cronologías son una representación visual de los eventos que suceden durante la ejecución. Estos eventos pueden ser localizaciones de: puntos de interrupción, lecturas/escrituras de memoria, llamadas y retornos de funciones y excepciones.

Cronología en el depurador que muestra excepciones, accesos a memoria, puntos de interrupción y llamadas a funciones.

Utilice la ventana de cronología para ver rápidamente los eventos importantes, comprender la posición relativa y saltar fácilmente a su ubicación en el archivo de rastreo de TTD. Utilice varias cronologías para explorar visualmente los eventos en el rastreo del viaje en el tiempo y detectar la correlación de eventos.

La ventana de cronología aparece al abrir un archivo de rastreo de TTD y muestra los eventos clave sin necesidad de crear manualmente consultas al modelo de datos. Al mismo tiempo, todos los objetos de viaje en el tiempo están disponibles para permitir consultas de datos más complejas.

Para obtener más información sobre cómo crear y trabajar con archivos de rastreo de viaje en el tiempo, consulte Depuración de viajes en el tiempo - Descripción general.

Tipos de cronologías

La ventana de cronologías puede mostrar los siguientes eventos:

  • Excepciones (puede filtrar aún más en un código de excepción específico)
  • Puntos de interrupción
  • Llamadas a funciones (búsqueda en forma de module!function)
  • Accesos a memoria (lectura / escritura / ejecución entre dos direcciones de memoria)

Pase el puntero por encima de cada evento para obtener más información. Al hacer clic en un evento, se ejecutará la consulta del evento y se mostrará más información. Al hacer doble clic en un evento, se saltará a esa ubicación en el archivo de rastreo de TTD.

Excepciones

Cuando cargue un archivo de rastreo y la cronología esté activa, mostrará automáticamente cualquier excepción en el registro.

Al pasar el ratón por encima de un punto de interrupción, se muestra información como el tipo de excepción y el código de excepción.

Cronología en el depurador que muestra excepciones con información sobre un código de excepción específico.

Puede filtrar aún más por un código de excepción específico utilizando el campo opcional de código de excepción.

Cuadro de diálogo de excepción del depurador de cronología con el tipo de cronología establecido en excepción y el código de excepción establecido en 0xC0000004.

También puede añadir una nueva cronología para un tipo de excepción específico.

Puntos de interrupción

Después de añadir un punto de interrupción, puede mostrar las posiciones de cuando se alcanza ese punto de interrupción en una cronología. Esto puede hacerse, por ejemplo, utilizando el comando bp Establecer punto de interrupción. Al pasar el ratón por encima de un punto de interrupción, se muestra la dirección y el puntero de instrucción asociados al punto de interrupción.

Cronología en el depurador que muestra aproximadamente 30 indicadores de puntos de interrupción.

Cuando se borra el punto de interrupción, la cronología del punto de interrupción asociado se elimina automáticamente.

Llamadas de función

Puede visualizar las posiciones de las llamadas a funciones en la cronología. Para ello, proporcione la búsqueda en forma de module!function, por ejemplo TimelineTestCode!multiplyTwo. También puede especificar caracteres comodín, por ejemplo TimelineTestCode!m*.

Añadir una cronología en el depurador con el nombre de llamada de función introducido.

Al pasar el ratón por encima de una llamada a función, se muestran el nombre de la función, los parámetros de entrada, sus valores y el valor de retorno. En este ejemplo se muestra el búfer y el tamaño, ya que son los parámetros para DisplayGreeting!GetCppConGreeting.

Cronología en el depurador que muestra las llamadas a funciones y la ventana de registros.

Acceso a memoria

Utilice la cronología de acceso a la memoria para visualizar cuándo se ha leído o escrito en un rango específico de memoria, o dónde ha tenido lugar la ejecución de código. Una dirección de inicio y otra de fin se utilizan para definir un intervalo entre dos direcciones de memoria.

Cuadro de diálogo Añadir acceso a la memoria de la cronología con el botón de escritura seleccionado.

Al pasar el ratón por encima de un elemento de acceso a la memoria, se muestran el valor y el puntero de la instrucción.

Cronología en el depurador que muestra eventos de acceso a memoria.

Trabajo con cronologías

Una línea gris vertical sigue al cursor cuando se sitúa sobre la cronología. La línea azul vertical indica la posición actual en el rastreo.

Haga clic en los iconos de lupa para acercar o alejar la cronología.

En el área de control superior de la cronología, utilice el rectángulo para desplazar la vista de la cronología. Arrastre los delimitadores exteriores del rectángulo para cambiar el tamaño de la vista de la cronología actual.

Cronología en el depurador que muestra el área superior que se utiliza para seleccionar la ventana activa.

Movimientos del ratón

Acercar y alejar el zoom con Ctrl + rueda de desplazamiento.

Desplazarse de lado a lado con Mayúsculas + rueda de desplazamiento.

Técnicas de depuración de la cronología

Para demostrar las técnicas de depuración de cronología, el Tutorial de depuración de viajes en el tiempo se reutiliza aquí. Esta demostración asume que usted ha completado los dos primeros pasos para crear el código de ejemplo y ha creado el registro TTD usando los dos primeros pasos descritos allí.

Sección 1: Compilar el código de ejemplo

Sección 2: Registrar un rastreo del ejemplo "DisplayGreeting"

En este escenario, el primer paso es buscar la excepción en el rastreo de del viaje en el tiempo. Para ello, haga doble +clic en la única excepción que aparece en la cronología.

En la ventana de comandos vemos que se emitió el siguiente comando cuando hicimos clic en la excepción.

(2dcc.6600): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: CC:0
@$curprocess.TTD.Events.Where(t => t.Type == "Exception")[0x0].Position.SeekTo()

Seleccione Ver>>Registros para visualizar los registros en este punto de la cronología y comenzar nuestra investigación.

Cronología en el depurador que muestra la excepción demolab y la ventana de registros.

En la salida del comando observe que la pila (esp) y el puntero base (ebp) apuntan a dos direcciones muy diferentes. Esto podría indicar que una corrupción de la pila, posiblemente una función regresó y luego corrompió la pila. Para validarlo, tenemos que retroceder hasta antes de que se corrompiera el estado de la CPU y ver si podemos determinar cuándo se produjo la corrupción de la pila.

Mientras lo hacemos, examinaremos los valores de las variables locales y de la pila.

Seleccione Ver>>Variables locales para mostrar los valores locales.

Seleccione Ver>>Pila para mostrar la pila de ejecución de código.

En el punto de fallo en el rastreo es común terminar unos pasos después de la verdadera causa en el código de manejo de errores. Con el viaje en el tiempo podemos retroceder una instrucción a la vez, para localizar la verdadera causa raíz.

Desde la cinta Inicio utilice el comando Volver atrás para retroceder tres instrucciones. Al hacerlo, continúe examinando la pila, las variables locales y las ventanas de registro.

La ventana de comandos mostrará la posición del viaje en el tiempo y los registros a medida que retrocede tres instrucciones.

0:000> t-
Time Travel Position: CB:41
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00540020 esp=003cf7d0 ebp=00520055 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00540020 ??              ???
0:000> t-
Time Travel Position: CB:40
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00061767 esp=003cf7cc ebp=00520055 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
DisplayGreeting!main+0x57:
00061767 c3              ret
0:000> t-
Time Travel Position: CB:3A
eax=0000004c ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=0006175f esp=003cf718 ebp=003cf7c8 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
DisplayGreeting!main+0x4f:
0006175f 33c0            xor     eax,eax

En este punto del rastreo nuestra pila y puntero base tienen valores que tienen más sentido, por lo que parece que nos hemos acercado al punto del código donde se produjo la corrupción.

esp=003cf718 ebp=003cf7c8

También es interesante que la ventana local contiene valores de nuestra aplicación de destino y la ventana de código fuente está resaltando la línea de código que está lista para ser ejecutada en nuestro código fuente en este punto del rastreo.

Para seguir investigando, podemos abrir una ventana de memoria para ver el contenido cerca de la dirección de memoria del puntero de pila (esp). En este ejemplo, tiene un valor de 003cf7c8. Seleccione Memoria>>Texto>>ASCII para visualizar el texto ASCII almacenado en esa dirección.

Depurador que muestra registros, pila y ventanas de memoria.

Cronología de acceso a memoria

Una vez identificada una posición de memoria de interés, añada una cronología de acceso a memoria utilizando ese valor. Haga clic en + Añadir cronología y rellene la dirección de inicio. Miraremos 4 bytes, así que sumando eso a la dirección de inicio de 003cf7c8, tenemos 003cf7cb. Por defecto se miran todas las escrituras en memoria, pero también se pueden mirar sólo las escrituras o la ejecución de código en esa dirección.

Cuadro de diálogo añadir un acceso a la memoria de la cronología con el botón de escritura seleccionado y un valor inicial de 003cf7c8.

Ahora podemos recorrer la cronología en sentido inverso para examinar en qué momento de este rastreo de viaje en el tiempo se escribió esta posición de memoria y ver qué podemos encontrar. Al hacer clic en esta posición en la cronología vemos que las variables locales valoran diferentes valores para la cadena que se está copiando. El valor de destino parece no estar completo, como si la longitud de nuestra cadena no fuera correcta.

Cronología de acceso a la memoria y ventana de valores locales que muestra los valores locales con diferentes valores de origen y destino.

Cronología del punto de interrupción

El uso de puntos de interrupción es un enfoque común para detener la ejecución de código en algún evento de interés. TTD le permite establecer un punto de interrupción y viajar hacia atrás en el tiempo hasta que ese punto de interrupción se alcanza después de registrar el rastreo. La capacidad de examinar el estado del proceso después de que se haya producido un problema, para determinar la mejor ubicación para un punto de interrupción, permite flujos de trabajo de depuración adicionales exclusivos de TTD.

Para explorar una técnica alternativa de depuración de la cronología, haga clic en la excepción de la cronología y, una vez más, retroceda tres pasos, utilizando el comando Volver atrás de la cinta Inicio.

En esta muestra tan pequeña sería bastante fácil simplemente mirar en el código, pero si hay cientos de líneas de código y docenas de subrutinas las técnicas descritas aquí pueden utilizarse para disminuir el tiempo necesario para localizar el problema.

Como se mencionó anteriormente, el puntero base (esp) en lugar de apuntar a una instrucción, está apuntando a nuestro texto de mensaje.

Utilice el comando ba para establecer un punto de interrupción en el acceso a la memoria. Estableceremos un punto de interrupción w - write para ver cuándo se escribe en esta área de memoria.

0:000> ba w4 003cf7c8

Aunque utilizaremos un simple punto de interrupción de acceso a memoria, los puntos de interrupción pueden construirse como sentencias condicionales más complejas. Para obtener más información, consulte bp, bu, bm (Establecer punto de interrupción).

En el menú Inicio, seleccione Volver para retroceder en el tiempo hasta alcanzar el punto de interrupción.

En este punto podemos examinar la pila del programa para ver qué código está activo.

Cronología en el depurador que muestra la cronología de acceso a memoria y las ventanas de pila.

Como es muy poco probable que la función wscpy_s() proporcionada por Microsoft tenga un error de código como este, miramos más allá en la pila. La pila muestra que Greeting!main llama a Greeting!GetCppConGreeting. En nuestro pequeño ejemplo de código, podríamos abrir el código en este punto y encontrar el error con bastante facilidad. Pero para ilustrar las técnicas que se pueden utilizar con programas más grandes y complejos, vamos a añadir una cronología de llamadas a funciones.

Cronología de llamada a función

Haga clic en + Añadir cronología y rellene el DisplayGreeting!GetCppConGreeting para la cadena de búsqueda de funciones.

Las casillas de verificación de ubicación inicial y final indican que el inicio y el final de una llamada de función en el rastreo.

Podemos utilizar el comando dx para visualizar el objeto de llamada a función y ver los campos TimeStart y TimeEnd asociados que corresponden a ubicación inicial y ubicación final de la llamada a función.

dx @$cursession.TTD.Calls("DisplayGreeting!GetCppConGreeting")[0x0]
    EventType        : 0x0
    ThreadId         : 0x6600
    UniqueThreadId   : 0x2
    TimeStart        : 6D:BD [Time Travel]
    SystemTimeStart  : Thursday, October 31, 2019 23:36:05
    TimeEnd          : 6D:742 [Time Travel]
    SystemTimeEnd    : Thursday, October 31, 2019 23:36:05
    Function         : DisplayGreeting!GetCppConGreeting
    FunctionAddress  : 0x615a0
    ReturnAddress    : 0x61746
    Parameters  

Las casillas Inicio o Fin, o ambas, deben estar marcadas.

Cuadro de diálogo Añadir nueva cronología que muestra la adición de una cronología de llamada a función con una cadena de búsqueda de función de DisplayGreeting!GetCppConGreeting.

Como nuestro código no es ni recursivo ni reentrante, es bastante fácil localizar en la cronología cuándo se llama al método GetCppConGreeting. La llamada a GetCppConGreeting también se produce al mismo tiempo que nuestro punto de interrupción, así como el evento de acceso a memoria que hemos definido. Así que parece que hemos reducido en un área de código para mirar con cuidado para la causa raíz del bloqueo de nuestra aplicación.

Cronología en el depurador que muestra la cronología de acceso a memoria y la ventana local con mensaje y búfer que contiene diferentes valores de cadena.

Exploración de la ejecución de código mediante la visualización de varias cronologías

Aunque nuestra muestra de código es pequeña, la técnica de utilizar varias cronologías permite explorar visualmente el rastreo de un viaje en el tiempo. Puede buscar en el archivo de rastreo para hacer preguntas, como "¿cuándo se accedió a un área de memoria antes de que se alcanzara un punto de interrupción?".

Cronología en el depurador que muestra la cronología de acceso a memoria y las ventanas de variables locales.

La posibilidad de ver correlaciones adicionales y encontrar cosas que quizá no esperaba diferencia a la herramienta de cronología de la interacción con el rastreo del viaje en el tiempo mediante comandos de línea de comandos.

Marcadores de cronología

Marca las posiciones importantes del viaje en el tiempo en WinDbg en lugar de copiar y pegar manualmente la posición en el bloc de notas. Los marcadores facilitan la visualización de un vistazo de las distintas posiciones en el rastreo en relación con otros eventos, así como su anotación.

Puede dar un nombre descriptivo a los marcadores.

Cuadro de diálogo Nuevo marcador que muestra un nombre de ejemplo para la primera llamada a la API en la aplicación Display Greeting.

Acceda a los marcadores a través de la ventana Cronología disponible en Ver > Cronología. Cuando pase el puntero sobre un marcador, mostrará el nombre del marcador.

Cronología que muestra tres marcadores con el cursor sobre uno, revelando el nombre del marcador.

Puede hacer clic con el botón derecho del ratón en el marcador para desplazarse a esa posición, cambiarle el nombre o eliminarlo.

Menú emergente de clic derecho en marcador que muestra opciones para desplazarse a la posición, editar y eliminar.

Nota:

En la versión 1.2402.24001.0 del depurador, la función de marcadores no está disponible.

Consulte también

Características de WinDbg

Tutorial de depuración de viajes en el tiempo