¿Cómo funciona la depuración inversa?

82

GDB tiene una nueva versión que admite la depuración inversa (consulte http://www.gnu.org/software/gdb/news/reversible.html ). Llegué a preguntarme cómo funciona eso.

Para que la depuración inversa funcione, me parece que debe almacenar todo el estado de la máquina, incluida la memoria para cada paso. Esto haría que el rendimiento fuera increíblemente lento, sin mencionar el uso de mucha memoria. ¿Cómo se resuelven estos problemas?

Nathan Fellman
fuente
4
Imagino que podría arreglárselas almacenando deltas de estado en lugar de todo el estado, pero aún parece que podría ser costoso.
gastador
1
pregunta relacionada stackoverflow.com/questions/522619/…
Brian Rasmussen
Guardar deltas puede funcionar muy bien, y es realmente necesario para una solución reversible de sistema completo eficiente.
jakobengblom2

Respuestas:

131

Soy un mantenedor de gdb y uno de los autores de la nueva depuración inversa. Me encantaría hablar sobre cómo funciona. Como varias personas han especulado, debe guardar suficiente estado de la máquina para poder restaurarlo más tarde. Hay varios esquemas, uno de los cuales es simplemente guardar los registros o ubicaciones de memoria que son modificados por cada instrucción de máquina. Luego, para "deshacer" esa instrucción, simplemente revierte los datos en esos registros o ubicaciones de memoria.

Sí, es caro, pero las CPU modernas son tan rápidas que, de todos modos, cuando eres interactivo (haciendo pasos o puntos de interrupción), no lo notas mucho.

Michael Snyder
fuente
4
Pero, ¿depuración inversa sólo le permite volver a rodillo nexty stepcomandos que ha escrito, o permite deshacer cualquier número de instrucciones? Por ejemplo, si establezco un punto de interrupción en una instrucción y dejo que se ejecute hasta entonces, ¿puedo volver a la instrucción anterior, aunque la haya omitido?
Nathan Fellman
10
> Pero, ¿la depuración inversa solo le permite retroceder los comandos siguientes y pasos que escribió o le permite deshacer cualquier número de instrucciones? Puede deshacer cualquier número de instrucciones. No está restringido, por ejemplo, a detenerse únicamente en los puntos en los que se detuvo cuando estaba avanzando. Puede establecer un nuevo punto de interrupción y ejecutarlo hacia atrás> Por ejemplo, si establezco un punto de interrupción en una instrucción y dejo que se ejecute hasta entonces, ¿puedo volver a la instrucción anterior, aunque la salté? Sí, siempre y cuando usted activó el modo de grabación antes de correr hacia el punto de interrupción
Michael Snyder
3
Perdón por el texto sin formato, no sé qué pasa con eso.
Michael Snyder
10
Me preocupa que la depuración inversa pueda deshacer el tiempo y hacernos volver a los años 60 o 70. No quiero ponerme pantalones de campana y dejarme crecer el pelo de nuevo.
The Tin Man
3
¿Y las llamadas al sistema que modifican el estado en el sistema operativo? ¿Eso simplemente no funciona correctamente? ¿Qué pasa cuando modifica un mango opaco?
Adrian
12

Tenga en cuenta que no debe olvidar el uso de simuladores, máquinas virtuales y grabadores de hardware para implementar la ejecución inversa.

Otra solución para implementarlo es rastrear la ejecución en hardware físico, como lo hacen GreenHills y Lauterbach en sus depuradores basados ​​en hardware. Con base en este seguimiento fijo de la acción de cada instrucción, puede moverse a cualquier punto del seguimiento eliminando los efectos de cada instrucción por turno. Tenga en cuenta que esto supone que puede rastrear todas las cosas que afectan el estado visible en el depurador.

Otra forma es usar un método de punto de control + re-ejecución, que es usado por VmWare Workstation 6.5 y Virtutech Simics 3.0 (y posterior), y que parece venir con Visual Studio 2010. Aquí, usa una máquina virtual o un simulador para obtener un nivel de indirección en la ejecución de un sistema. Regularmente vuelca todo el estado en el disco o la memoria, y luego confía en que el simulador pueda volver a ejecutar de manera determinista la misma ruta exacta del programa.

Simplificado, funciona así: digamos que se encuentra en el momento T en la ejecución de un sistema. Para ir al tiempo T-1, toma algún punto de control del punto t <T, y luego ejecuta ciclos (Tt-1) para terminar un ciclo antes de donde estaba. Se puede hacer que esto funcione muy bien y se aplique incluso a cargas de trabajo que realizan E / S de disco, constan de código a nivel de kernel y realizan el trabajo del controlador de dispositivo. La clave es tener un simulador que contenga todo el sistema de destino, con todos sus procesadores, dispositivos, memorias e IO. Consulte la lista de correo de gdb y la discusión que sigue en la lista de correo de gdb para obtener más detalles. Yo mismo utilizo este enfoque con bastante frecuencia para depurar códigos complicados, especialmente en los controladores de dispositivos y los primeros arranques del sistema operativo.

Otra fuente de información es un documento técnico de Virtutech sobre puntos de control (que escribí, en forma completa).

jakobengblom2
fuente
También vea jakob.engbloms.se/archives/1547 y sus dos publicaciones de blog siguientes para un recorrido más completo de las técnicas de depuración inversa.
jakobengblom2
¿Qué tal la capacidad de "establecer puntos de guardado" en lugar de implementar el paso inverso? Por lo tanto, depura, y en algún momento puede seleccionar el paso actual como "punto de guardado", y más adelante tiene la capacidad de volver a ese punto de guardado y avanzar nuevamente, editando sus variables si es necesario. Algo así como "instantáneas" para máquinas virtuales o "puntos de restauración" para sistemas operativos.
Rolf
9

Durante una sesión de EclipseCon, también les preguntamos cómo hacen esto con Chronon Debugger para Java. Ese no le permite dar un paso atrás, pero puede reproducir la ejecución de un programa grabado de tal manera que se siente como una depuración inversa. (La principal diferencia es que no puede cambiar el programa en ejecución en el depurador Chronon, mientras que puede hacerlo en la mayoría de los demás depuradores de Java).

Si lo entendí correctamente, manipula el código de bytes del programa en ejecución, de modo que se registra cada cambio de un estado interno del programa. Los estados externos no necesitan registrarse adicionalmente. Si influyen en su programa de alguna manera, entonces debe tener una variable interna que coincida con ese estado externo (y por lo tanto, esa variable interna es suficiente).

Durante el tiempo de reproducción, básicamente pueden recrear todos los estados del programa en ejecución a partir de los cambios de estado registrados.

Curiosamente, los cambios de estado son mucho más pequeños de lo que cabría esperar a primera vista. Entonces, si tiene una instrucción "if" condicional, pensaría que necesita al menos un bit para registrar si el programa tomó la instrucción then o la instrucción else. En muchos casos puede evitar incluso eso, como en el caso de que esas diferentes ramas contengan un valor de retorno. Entonces es suficiente registrar solo el valor de retorno (que sería necesario de todos modos) y volver a calcular la decisión sobre la rama ejecutada a partir del valor de retorno en sí.

Bananeweizen
fuente
8

Aunque esta pregunta es antigua, la mayoría de las respuestas también lo son, y como sigue siendo un tema interesante, estoy publicando una respuesta de 2015. Los capítulos 1 y 2 de mi tesis de maestría, Combinando la depuración inversa y la programación en vivo hacia el pensamiento visual en la programación de computadoras , cubre algunos de los enfoques históricos para la depuración inversa (especialmente enfocados en el enfoque de instantánea (o punto de control) y reproducción), y explica la diferencia entre él y la depuración omnisciente:

La computadora, habiendo ejecutado el programa hacia adelante hasta cierto punto, realmente debería poder proporcionarnos información al respecto. Tal mejora es posible y se encuentra en los llamados depuradores omniscientes. Por lo general, se clasifican como depuradores inversos, aunque podrían describirse con mayor precisión como depuradores de "registro histórico", ya que simplemente registran información durante la ejecución para verla o consultarla más tarde, en lugar de permitir que el programador retroceda en el tiempo en un programa en ejecución . "Omnisciente" proviene del hecho de que el historial de estado completo del programa, una vez registrado, está disponible para el depurador después de la ejecución. Entonces no hay necesidad de volver a ejecutar el programa ni de instrumentación de código manual.

La depuración omnisciente basada en software comenzó con el sistema EXDAMS de 1969, donde se le llamó "reproducción de historial en tiempo de depuración". El depurador GNU, GDB, ha admitido la depuración omnisciente desde 2009, con su función de "registro de procesos y reproducción". TotalView, UndoDB y Chronon parecen ser los mejores depuradores omniscientes disponibles actualmente, pero son sistemas comerciales. TOD, para Java, parece ser la mejor alternativa de código abierto, que hace uso de la reproducción determinista parcial, así como la captura de trazas parciales y una base de datos distribuida para permitir el registro de grandes volúmenes de información involucrada.

También existen depuradores que no solo permiten la navegación por una grabación, sino que también pueden retroceder en el tiempo de ejecución. Pueden describirse con mayor precisión como depuradores de retroceso en el tiempo, viajes en el tiempo, bidireccionales o inversos.

El primer sistema de este tipo fue el prototipo COPE de 1981 ...

Abrahán
fuente
4

mozilla rres una alternativa más sólida a la depuración inversa de GDB

https://github.com/mozilla/rr

El registro y la reproducción integrados de GDB tienen graves limitaciones, por ejemplo, no admite instrucciones AVX: la depuración inversa de gdb falla con "El registro del proceso no admite la instrucción 0xf0d en la dirección"

Ventajas de rr:

  • mucho más confiable actualmente. Lo he probado en ejecuciones relativamente largas de varios programas complejos.
  • también ofrece una interfaz GDB con el protocolo gdbserver, lo que lo convierte en un excelente reemplazo
  • pequeña caída de rendimiento para la mayoría de los programas, no lo he notado sin hacer mediciones
  • los rastros generados son pequeños en el disco porque solo se registran muy pocos eventos no deterministas, nunca he tenido que preocuparme por su tamaño hasta ahora

rr logra esto ejecutando primero el programa de una manera que registre lo que sucedió en cada evento no determinista, como un cambio de hilo.

Luego, durante la segunda ejecución de repetición, utiliza ese archivo de seguimiento, que es sorprendentemente pequeño, para reconstruir exactamente lo que sucedió en la ejecución no determinista original pero de una manera determinista, ya sea hacia adelante o hacia atrás.

rr fue desarrollado originalmente por Mozilla para ayudarlos a reproducir errores de sincronización que aparecieron en sus pruebas nocturnas al día siguiente. Pero el aspecto de la depuración inversa también es fundamental para cuando tiene un error que solo ocurre horas dentro de la ejecución, ya que a menudo desea dar un paso atrás para examinar qué estado anterior condujo al error posterior.

El siguiente ejemplo muestra algunas de sus características, en particular los reverse-next, reverse-stepy reverse-continuelos comandos.

Instalar en Ubuntu 18.04:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
# Overcome "rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is 3."
echo 'kernel.perf_event_paranoid=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Programa de prueba:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int f() {
    int i;
    i = 0;
    i = 1;
    i = 2;
    return i;
}

int main(void) {
    int i;

    i = 0;
    i = 1;
    i = 2;

    /* Local call. */
    f();

    printf("i = %d\n", i);

    /* Is randomness completely removed?
     * Recently fixed: https://github.com/mozilla/rr/issues/2088 */
    i = time(NULL);
    printf("time(NULL) = %d\n", i);

    return EXIT_SUCCESS;
}

compilar y ejecutar:

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay

Ahora está dentro de una sesión de GDB y puede revertir correctamente la depuración:

(rr) break main
Breakpoint 1 at 0x55da250e96b0: file a.c, line 16.
(rr) continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;
(rr) next
17          i = 1;
(rr) print i
$1 = 0
(rr) next
18          i = 2;
(rr) print i
$2 = 1
(rr) reverse-next
17          i = 1;
(rr) print i
$3 = 0
(rr) next
18          i = 2;
(rr) print i
$4 = 1
(rr) next
21          f();
(rr) step
f () at a.c:7
7           i = 0;
(rr) reverse-step
main () at a.c:21
21          f();
(rr) next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) reverse-next
23          printf("i = %d\n", i);
(rr) next
i = 2
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$5 = 1509245372
(rr) reverse-next
27          i = time(NULL);
(rr) next
28          printf("time(NULL) = %d\n", i);
(rr) print i
$6 = 1509245372
(rr) reverse-continue
Continuing.

Breakpoint 1, main () at a.c:16
16          i = 0;

Al depurar software complejo, es probable que corra hasta un punto de caída y luego caiga dentro de un marco profundo. En ese caso, no olvide que reverse-nexten marcos superiores, primero debe:

reverse-finish

hasta ese marco, hacer lo habitual upno es suficiente.

Las limitaciones más graves de rr en mi opinión son:

UndoDB es una alternativa comercial a rr: https://undo.io Ambos están basados ​​en rastreo / reproducción, pero no estoy seguro de cómo se comparan en términos de características y rendimiento.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
¿Sabes cómo puedo hacer esto con ddd? Gracias
spraff
@spraff No estoy seguro, pero es probable. Primero intente conectar ddd a gdbserver. Si eso funciona, también debería funcionar con rr.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@spraff sin embargo, no use ddd, use gdb dashboard ;-) stackoverflow.com/questions/10115540/gdb-split-view-with-code/… Esto definitivamente funcionará ya que es solo GDB normal.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3

Nathan Fellman escribió:

Pero, ¿la depuración inversa solo le permite revertir los comandos siguientes y pasos que escribió, o le permite deshacer cualquier cantidad de instrucciones?

Puede deshacer cualquier cantidad de instrucciones. No está restringido, por ejemplo, a detenerse solo en los puntos donde se detuvo cuando estaba avanzando. Puede establecer un nuevo punto de interrupción y ejecutarlo hacia atrás.

Por ejemplo, si establezco un punto de interrupción en una instrucción y la dejo ejecutar hasta entonces, ¿puedo volver a la instrucción anterior, aunque la haya omitido?

Si. Siempre que haya activado el modo de grabación antes de llegar al punto de interrupción.

Michael Snyder
fuente
2
Una parte crucial de cualquier solución inversa es que la enciende en algún momento y solo puede revertir hasta ese punto. No hay magia que pueda hacer funcionar una máquina al revés verdadero y descubrir lo que sucedió anteriormente sin algún tipo de registro de lo que sucedió.
jakobengblom2
2

Aquí es cómo otro inversa depurador llamado ODB funciona. Extraer:

La depuración omnisciente es la idea de recopilar "marcas de tiempo" en cada "punto de interés" (establecer un valor, hacer una llamada a un método, lanzar / capturar una excepción) en un programa y luego permitir que el programador use esas marcas de tiempo para explorar el historial de ese programa ejecutado.

El ODB ... inserta código en las clases del programa a medida que se cargan y cuando el programa se ejecuta, los eventos se registran.

Supongo que el gdb uno funciona de la misma manera.

demonio codigo
fuente
Entonces, ¿esto requeriría directivas en el código para decirle al compilador y al depurador dónde están esos puntos interesantes?
Nathan Fellman
No. Hay una demostración de Java Web Start en www.LambdaCS.com/debugger/debugger.html que le muestra cómo funciona. Parece un programa normal. Eso es ODB de todos modos, no sé nada de gdb. Aunque es muy bueno :)
demoncodemonkey
Tenga en cuenta que la solución gdb NO cambia el programa de destino de ninguna manera. Si tiene que instrumentar un programa para depurarlo, tiene una buena probabilidad de que el problema desaparezca debido a la diferencia de tiempo y otras alteraciones. Todas las herramientas comerciales de revexec se basan en algún tipo de registro externo que no cambia el código del programa en sí.
jakobengblom2
@ jakobengblom2: Creo que está poniendo demasiado énfasis en la diferencia entre cambiar el objetivo escribiendo en su memoria, emulando la ejecución o simplemente agregando puntos de interrupción de hardware. Todos cambian el tiempo. De hecho, la instrumentación de destino probablemente sea la que menos cambie el tiempo.
Ben Voigt
2

La depuración inversa significa que puede ejecutar el programa hacia atrás, lo cual es muy útil para rastrear la causa de un problema.

No es necesario almacenar el estado completo de la máquina para cada paso, solo los cambios. Probablemente todavía sea bastante caro.

estrella azul
fuente
Ya veo, pero aún necesita interrumpir la ejecución en cada cambio para guardar los cambios.
Nathan Fellman
Sí, eso es correcto, pero las máquinas son bastante rápidas ahora, y en términos humanos no creo que la desaceleración sea intolerable. Es comparable a valgrind, quizás no tan lento como valgrind.
Michael Snyder