Comment déboguer une impasse rare?

J’essaie de déboguer une implémentation de pool de threads personnalisée qui a rarement des blocages. Donc, je ne peux pas utiliser un débogueur comme gdb car j’ai cliqué comme “lancer” le débogueur 100 fois avant d’avoir un blocage

Actuellement, j’exécute le test de pool de threads dans une boucle infinie dans un script shell, mais cela signifie que je ne peux pas voir les variables, etc. J’essaie de std::cout data, mais cela ralentit le fil et réduit le risque d’impasses, ce qui signifie que je peux attendre environ une heure avec mon infini avant de recevoir des messages. Ensuite, je ne reçois pas l’erreur et j’ai besoin de plus de messages, ce qui signifie attendre une heure de plus …

Comment déboguer efficacement le programme pour qu’il redémarre encore et encore jusqu’à ce qu’il se bloque? (Ou peut-être devrais-je ouvrir une autre question avec tout le code pour obtenir de l’aide?)

Merci d’avance !

Question bonus: comment vérifier que tout se passe bien avec std::condition_variable ? Vous ne pouvez pas vraiment savoir quel thread est endormi ou si une condition de concurrence critique survient dans la condition d’ wait .

Il y a 2 manières de base:

  1. Automatiser l’exécution du programme sous le débogueur. L’utilisation du gdb program -ex 'run ' -ex 'quit' devrait exécuter le programme sous le débogueur, puis se fermer. Si le programme est toujours actif sous une forme ou une autre (erreur de segmentation, ou si vous l’avez interrompu manuellement), un message de confirmation vous sera demandé.
  2. Attachez le débogueur après avoir reproduit le blocage. Par exemple, gdb peut être exécuté en tant que gdb à attacher au programme en cours d’exécution – attendez simplement l’interblocage puis attachez. Ceci est particulièrement utile lorsque le débogueur attaché provoque un changement de minutage et que vous ne pouvez plus reproduire le bogue.

De cette façon, vous pouvez simplement le lancer en boucle et attendre le résultat pendant que vous buvez du café. BTW – Je trouve la deuxième option plus facile.

S’il s’agit d’une sorte de devoir – redémarrer encore et encore avec plus de débogage sera une approche raisonnable.

Si quelqu’un paie de l’argent pour chaque heure d’attente, il peut préférer investir dans un logiciel qui prend en charge le débogage basé sur la relecture , c’est-à-dire un logiciel qui enregistre tout ce qu’un programme fait, chaque instruction, et vous permet de le rejouer encore et encore. débogage en arrière. Ainsi, au lieu d’append plus de débogage, vous enregistrez une session au cours de laquelle un blocage se produit, puis démarrez le débogage juste avant l’arrêt. Vous pouvez vous déplacer aussi souvent que vous le souhaitez, jusqu’à ce que vous trouviez enfin le coupable.

Le logiciel mentionné dans le lien prend en charge Linux et le multithreading.

Vous pouvez exécuter votre gdb --eval-command=run --eval-command=quit --args ./a.out test sous GDB dans une boucle à l’aide de la commande indiquée dans https://stackoverflow.com/a/8657833/341065 : gdb --eval-command=run --eval-command=quit --args ./a.out .

J’ai moi-même utilisé ceci: (while gdb --eval-command=run --eval-command=quit --args ./thread_testU ; do echo . ; done) .

Une fois qu’il se bloque et ne quitte pas, vous pouvez simplement l’interrompre par CTRL + C pour entrer dans le débogueur.

Un débogage rapide et facile pour trouver des blocages consiste à définir certaines variables globales à l’endroit où vous souhaitez déboguer, puis à les imprimer dans un gestionnaire de signaux. Vous pouvez utiliser SIGINT (envoyé lorsque vous interrompez avec ctrl+c ) ou SIGTERM (envoyé lorsque vous supprimez le programme):

 int dbg; int multithreaded_function() { signal(SIGINT, dbg_sighandler); ... dbg = someVar; ... } void dbg_sighandler(int) { std::cout << dbg1 << std::endl; std::exit(EXIT_FAILURE); } 

Comme cela, vous voyez l’état de toutes vos variables de débogage lorsque vous interrompez le programme avec ctrl+c .

De plus, vous pouvez l'exécuter dans un shell en boucle:

 $> while [ $? -eq 0 ] do ./my_program done 

qui exécutera votre programme pour toujours jusqu'à ce qu'il échoue ( $? est le statut de sortie de votre programme et vous quittez avec EXIT_FAILURE dans votre gestionnaire de signaux).

Cela a bien fonctionné pour moi, en particulier pour savoir combien de threads ont passé avant et après quels verrous.

C'est assez rustique, mais vous n'avez besoin d'aucun outil supplémentaire et c'est rapide à mettre en œuvre.

Le débogage basé sur la relecture open source de Mozilla rr

https://github.com/mozilla/rr

Hans a mentionné le débogage basé sur la relecture , mais il existe une implémentation open source spécifique qu’il convient de mentionner: Mozilla rr .

Vous effectuez d’abord un cycle d’enregistrement, puis vous pouvez rejouer le même cycle autant de fois que vous le souhaitez et l’observer dans GDB, tout en conservant tout, y compris les entrées / sorties et l’ordre des threads.

Le site officiel mentionne:

La motivation initiale de rr était de faciliter le débogage des échecs intermittents

De plus, rr permet aux commandes de débogage inversé GDB telles que reverse-next de revenir à la ligne précédente, ce qui facilite beaucoup la recherche de la cause première du problème.

Voici un exemple minimal de rr en action: comment aller à la ligne précédente dans la firebase database?