web-dev-qa-db-fra.com

Comment fonctionne le débogage inversé?

GDB a une nouvelle version qui prend en charge le débogage inverse (voir http://www.gnu.org/software/gdb/news/reversible.html ). Je me suis demandé comment cela fonctionne.

Pour que le débogage inverse fonctionne, il me semble que vous devez stocker l’état complet de la machine, y compris la mémoire pour chaque étape. Cela rendrait les performances incroyablement lentes, sans parler de l'utilisation de beaucoup de mémoire. Comment ces problèmes sont-ils résolus?

80
Nathan Fellman

Je suis un mainteneur gdb et l'un des auteurs du nouveau débogage inverse. Je serais ravi de vous expliquer comment cela fonctionne. Comme plusieurs personnes l'ont spéculé, vous devez enregistrer suffisamment l'état de la machine que vous pourrez restaurer plus tard. Il existe un certain nombre de schémas, dont l'un consiste simplement à enregistrer les registres ou emplacements de mémoire qui sont modifiés par chaque instruction machine. Ensuite, pour "annuler" cette instruction, il vous suffit de rétablir les données dans ces registres ou emplacements de mémoire.

Oui, c'est cher, mais les processeurs modernes sont si rapides que lorsque vous êtes de toute façon interactif (faisant des étapes ou des points d'arrêt), vous ne le remarquez pas vraiment.

123
Michael Snyder

Notez que vous ne devez pas oublier l'utilisation de simulateurs, de machines virtuelles et d'enregistreurs matériels pour implémenter l'exécution inverse.

Une autre solution pour l'implémenter consiste à suivre l'exécution sur le matériel physique, comme le font GreenHills et Lauterbach dans leurs débogueurs basés sur le matériel. Sur la base de cette trace fixe de l'action de chaque instruction, vous pouvez ensuite vous déplacer vers n'importe quel point de la trace en supprimant tour à tour les effets de chaque instruction. Notez que cela suppose que vous pouvez tracer tout ce qui affecte l'état visible dans le débogueur.

Une autre façon consiste à utiliser une méthode de point de reprise + de réexécution, qui est utilisée par VmWare Workstation 6.5 et Virtutech Simics 3.0 (et versions ultérieures), et qui semble venir avec Visual Studio 2010. Ici, vous utilisez une machine virtuelle ou un simulateur pour obtenir un niveau d'indirection sur l'exécution d'un système. Vous sauvegardez régulièrement l'intégralité de l'état sur le disque ou la mémoire, puis vous comptez sur le simulateur pour pouvoir réexécuter de manière déterministe exactement le même chemin de programme.

Simplifié, cela fonctionne comme ceci: disons que vous êtes au temps T dans l'exécution d'un système. Pour passer au temps T-1, vous prenez un point de contrôle du point t <T, puis exécutez des cycles (T-t-1) pour terminer un cycle avant l'endroit où vous vous trouviez. Cela peut fonctionner très bien et s'applique même aux charges de travail qui font des E/S sur disque, se composent de code au niveau du noyau et effectuent le travail du pilote de périphérique. La clé est d'avoir un simulateur qui contient tout le système cible, avec tous ses processeurs, périphériques, mémoires et E/S. Voir la liste de diffusion gdb et la discussion qui suit sur la liste de diffusion gdb pour plus de détails. J'utilise cette approche moi-même assez régulièrement pour déboguer du code délicat, en particulier dans les pilotes de périphérique et les premiers démarrages du système d'exploitation.

Une autre source d'information est un Livre blanc de Virtutech sur les points de contrôle (que j'ai écrit, en pleine divulgation).

11
jakobengblom2

Au cours d'une session EclipseCon, nous avons également demandé comment ils le faisaient avec le Chronon Debugger pour Java. Celui-ci ne vous permet pas de en fait prendre du recul, mais peut lire l'exécution d'un programme enregistré de telle sorte qu'il se sent comme le débogage inverse. (La principale différence est que vous ne pouvez pas modifier le programme en cours d'exécution dans le débogueur Chronon, alors que vous pouvez le faire dans la plupart des autres débogueurs Java Java).)

Si je l'ai bien compris, il manipule le code d'octet du programme en cours d'exécution, de sorte que chaque changement d'un état interne du programme est enregistré. Les états externes n'ont pas besoin d'être enregistrés en plus. S'ils influencent votre programme d'une manière ou d'une autre, vous devez avoir une variable interne correspondant à cet état externe (et donc cette variable interne est suffisante).

Pendant le temps de lecture, ils peuvent ensuite recréer pratiquement chaque état du programme en cours à partir des changements d'état enregistrés.

Fait intéressant, les changements d'état sont beaucoup plus petits que ce à quoi on pourrait s'attendre à première vue. Donc, si vous avez une instruction conditionnelle "if", vous penseriez avoir besoin d'au moins un bit pour enregistrer si le programme a pris l'instruction then- ou else. Dans de nombreux cas, vous pouvez même éviter cela, comme dans le cas où ces différentes branches contiennent une valeur de retour. Ensuite, il suffit d'enregistrer uniquement la valeur de retour (qui serait de toute façon nécessaire) et de recalculer la décision concernant la branche exécutée à partir de la valeur de retour elle-même.

9
Bananeweizen

Bien que cette question soit ancienne, la plupart des réponses le sont aussi, et comme débogage inversé reste un sujet intéressant, je poste une réponse pour 2015. Chapitres 1 et 2 de ma thèse de maîtrise, Combinaison du débogage inverse et de la programmation en direct vers la pensée visuelle en programmation informatique , couvre certaines des approches historiques du débogage inverse (en particulier axé sur l'approche snapshot- (ou checkpoint) -et-replay), et explique la différence entre cela et le débogage omniscient:

L'ordinateur, après avoir exécuté le programme jusqu'à un certain point, devrait vraiment être en mesure de nous fournir des informations à ce sujet. Une telle amélioration est possible et se trouve dans ce que l'on appelle des débogueurs omniscients. Ils sont généralement classés en tant que débogueurs inversés, bien qu'ils puissent être plus précisément décrits comme des débogueurs de "journalisation de l'historique", car ils enregistrent simplement des informations pendant l'exécution pour les afficher ou les interroger plus tard, plutôt que de permettre au programmeur de remonter dans le temps dans un programme en cours d'exécution. . "Omniscient" vient du fait que l'historique complet de l'état du programme, après avoir été enregistré, est disponible pour le débogueur après exécution. Il n'est alors plus nécessaire de relancer le programme, ni d'instrumentation manuelle de code.

Le débogage omniscient basé sur le logiciel a commencé avec le système EXDAMS de 1969 où il était appelé "lecture d'historique de débogage". Le débogueur GNU, GDB, prend en charge le débogage omniscient depuis 2009, avec sa fonction "enregistrement de processus et relecture". TotalView, UndoDB et Chronon semblent être les meilleurs débogueurs omniscients actuellement disponibles, mais sont commerciaux TOD, pour Java, semble être la meilleure alternative open-source, qui utilise la relecture déterministe partielle, ainsi que la capture de trace partielle et une base de données distribuée pour permettre l'enregistrement des grands volumes d'informations impliqués.

Il existe également des débogueurs qui ne permettent pas simplement de naviguer dans un enregistrement, mais qui sont en fait capables de reculer dans le temps d'exécution. Ils peuvent être plus précisément décrits comme des débogueurs de retour dans le temps, de voyage dans le temps, bidirectionnels ou inversés.

Le premier de ces systèmes a été le prototype COPE de 1981 ...

8
Abraham

mozilla rr est une alternative plus robuste au débogage inverse GDB

https://github.com/mozilla/rr

L'enregistrement et la relecture intégrés de GDB ont de graves limitations, par exemple pas de support pour les instructions AVX: le débogage inverse gdb échoue avec "L'enregistrement de processus ne prend pas en charge l'instruction 0xf0d à l'adresse"

Avantages de rr:

  • beaucoup plus fiable actuellement. Je l'ai testé sur des exécutions relativement longues de plusieurs logiciels complexes.
  • offre également une interface GDB avec le protocole gdbserver, ce qui en fait un excellent remplacement
  • petite baisse de performance pour la plupart des programmes, je ne l'ai pas remarqué sans faire de mesures
  • les traces générées sont petites sur le disque car seuls très peu d'événements non déterministes sont enregistrés, je n'ai jamais eu à me soucier de leur taille jusqu'à présent

rr y parvient en exécutant d'abord le programme d'une manière qui enregistre ce qui s'est passé à chaque événement non déterministe tel qu'un commutateur de thread.

Ensuite, lors de la deuxième exécution de relecture, il utilise ce fichier de trace, qui est étonnamment petit, pour reconstruire exactement ce qui s'est passé lors de l'exécution non déterministe d'origine, mais de manière déterministe, en avant ou en arrière.

rr a été initialement développé par Mozilla pour les aider à reproduire les bugs de synchronisation qui sont apparus lors de leurs tests nocturnes le lendemain. Mais l'aspect de débogage inverse est également fondamental lorsque vous avez un bogue qui ne se produit que des heures à l'intérieur de l'exécution, car vous voulez souvent revenir en arrière pour examiner quel état précédent a conduit à l'échec ultérieur.

L'exemple suivant présente certaines de ses fonctionnalités, notamment le reverse-next, reverse-step et reverse-continue commandes.

Installer sur 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

Programme de test:

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

compiler et exécuter:

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

Vous êtes maintenant laissé dans une session GDB, et vous pouvez inverser correctement le débogage:

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

Lors du débogage d'un logiciel complexe, vous courrez probablement jusqu'à un point de plantage, puis tomberez dans un cadre profond. Dans ce cas, n'oubliez pas que pour reverse-next sur les images supérieures, vous devez d'abord:

reverse-finish

jusqu'à ce cadre, il ne suffit pas de faire le up habituel.

Les limitations les plus sérieuses de rr à mon avis sont:

UndoDB est une alternative commerciale à rr: https://undo.io Les deux sont basés sur la trace/la relecture, mais je ne sais pas comment ils se comparent en termes de fonctionnalités et de performances.

Nathan Fellman a écrit:

Mais le débogage inversé vous permet-il uniquement d'annuler les commandes suivantes et les étapes que vous avez tapées, ou vous permet-il d'annuler un certain nombre d'instructions?

Vous pouvez annuler un certain nombre d'instructions. Par exemple, vous n'êtes pas limité à ne vous arrêter qu'aux points où vous vous êtes arrêté lorsque vous avançiez. Vous pouvez définir un nouveau point d'arrêt et y revenir en arrière.

Par exemple, si je définis un point d'arrêt sur une instruction et que je la laisse s'exécuter jusque-là, puis-je revenir à l'instruction précédente, même si je l'ai sautée?

Oui. Tant que vous avez activé le mode d'enregistrement avant de courir jusqu'au point d'arrêt.

3
Michael Snyder

Ici est le fonctionnement d'un autre débogueur inversé appelé ODB. Extrait:

Le débogage omniscient est l'idée de collecter des "horodatages" à chaque "point d'intérêt" (définir une valeur, effectuer un appel de méthode, lever/intercepter une exception) dans un programme, puis permettre au programmeur d'utiliser ces horodatages pour explorer la historique de ce programme exécuté.

L'ODB ... insère du code dans les classes du programme lors de leur chargement et lorsque le programme s'exécute, les événements sont enregistrés.

Je suppose que le gdb fonctionne de la même manière.

2
demoncodemonkey

Le débogage inverse signifie que vous pouvez exécuter le programme en arrière, ce qui est très utile pour rechercher la cause d'un problème.

Vous n'avez pas besoin de stocker l'état complet de la machine pour chaque étape, uniquement les modifications. C'est probablement encore assez cher.

2
starblue