web-dev-qa-db-fra.com

Attacher un processus avec pdb

J'ai un script python que je soupçonne qu'il y a un blocage. J'essayais de déboguer avec pdb mais si je vais pas à pas, il n'obtient pas le blocage, et par la sortie renvoyée, je peux voir qu'il n'est pas suspendu à la même itération. Je voudrais attacher mon script à un débogueur uniquement lorsqu'il est verrouillé, est-ce possible? Je suis ouvert à utiliser d'autres débogueurs si nécessaire.

Pour le moment, pdb n'a pas la possibilité d'arrêter et de commencer le débogage sur un programme en cours d'exécution. Vous avez quelques autres options:

[~ # ~] gdb [~ # ~]

Vous pouvez utiliser GDB pour déboguer au niveau C. C'est un peu plus abstrait parce que vous fouillez le code source C de Python plutôt que votre script Python réel, mais cela peut être utile dans certains cas. Les instructions sont ici: https://wiki.python.org/moin/DebuggingWithGdb . Ils sont trop impliqués pour résumer ici.

Extensions et modules tiers

Le simple fait de googler pour "pdb attach process" révèle quelques projets pour donner à PDB cette capacité:
Pyringe: https://github.com/google/pyringe
Pycharm: https://blog.jetbrains.com/pycharm/2015/02/feature-spotlight-python-debugger-and-attach-to-process/
Cette page du wiki Python a plusieurs alternatives: https://wiki.python.org/moin/PythonDebuggingTools


Pour votre cas d'utilisation spécifique, j'ai quelques idées de solutions:

Signaux

Si vous êtes sous Unix, vous pouvez utiliser signaux comme dans ce billet de blog pour essayer de vous arrêter et de vous attacher à un script en cours d'exécution.

Ce bloc de devis est copié directement à partir de l'article de blog lié:

Bien sûr, pdb a déjà des fonctions pour démarrer un débogueur au milieu de votre programme, notamment pdb.set_trace (). Cela nécessite cependant que vous sachiez où vous souhaitez commencer le débogage, cela signifie également que vous ne pouvez pas le laisser pour le code de production.

Mais j'ai toujours été envieux de ce que je peux faire avec GDB: il suffit d'interrompre un programme en cours et de commencer à fouiner avec un débogueur. Cela peut être pratique dans certaines situations, par exemple vous êtes coincé dans une boucle et souhaitez enquêter. Et aujourd'hui, cela m'est soudain venu à l'esprit: enregistrez simplement un gestionnaire de signaux qui définit la fonction de trace! Voici le code de preuve de concept:

import os
import signal
import sys
import time    

def handle_pdb(sig, frame):
    import pdb
    pdb.Pdb().set_trace(frame)    

def loop():
    while True:
        x = 'foo'
        time.sleep(0.2)

if __name__ == '__main__':
    signal.signal(signal.SIGUSR1, handle_pdb)
    print(os.getpid())
    loop()

Maintenant, je peux envoyer SIGUSR1 à l'application en cours d'exécution et obtenir un débogueur. Charmant!

J'imagine que vous pourriez pimenter cela en utilisant Winpdb pour permettre le débogage à distance au cas où votre application ne serait plus attachée à un terminal. Et l'autre problème du code ci-dessus est qu'il ne semble pas pouvoir reprendre le programme après que pdb a été invoqué, après avoir quitté pdb, vous obtenez juste une traceback et vous avez terminé (mais comme ce n'est que bdb, lever l'exception bdb.BdbQuit je suppose cela pourrait être résolu de plusieurs manières). Le dernier problème immédiat est de faire fonctionner cela sur Windows, je ne sais pas grand-chose sur Windows mais je sais qu'ils n'ont pas de signaux, donc je ne sais pas comment vous pourriez y arriver.

Points d'arrêt et boucles conditionnels

Vous pouvez toujours utiliser PDB si vous n'avez pas de signaux disponibles, si vous encapsulez vos acquisitions de verrouillage ou de sémaphore dans une boucle qui incrémente un compteur, et ne vous arrêtez que lorsque le nombre a atteint un nombre ridiculement élevé. Par exemple, supposons que vous ayez un verrou que vous pensez faire partie de votre impasse:

lock.acquire() # some lock or semaphore from threading or multiprocessing

Réécrivez-le de cette façon:

count = 0
while not lock.acquire(False): # Start a loop that will be infinite if deadlocked
    count += 1

    continue # now set a conditional breakpoint here in PDB that will only trigger when
             # count is a ridiculously large number:
             # pdb> <filename:linenumber>, count=9999999999

Le point d'arrêt devrait se déclencher lorsque le nombre est très élevé, (espérons-le) indiquant qu'un blocage s'est produit à cet endroit. Si vous constatez qu'il se déclenche lorsque les objets de verrouillage ne semblent pas indiquer un blocage, vous devrez peut-être insérer un court délai dans la boucle afin qu'il n'incrémente pas assez rapidement. Vous devrez peut-être également jouer avec le seuil de déclenchement du point d'arrêt pour qu'il se déclenche au bon moment. Le nombre dans mon exemple était arbitraire.

Une autre variante serait de ne pas utiliser PDB et de lever intentionnellement une exception lorsque le compteur devient énorme, au lieu de déclencher un point d'arrêt. Si vous écrivez votre propre classe d'exception, vous pouvez l'utiliser pour regrouper tous les états de sémaphore/verrouillage locaux dans l'exception, puis l'attraper au niveau supérieur de votre script pour imprimer juste avant de quitter.

Indicateurs de fichier

Une autre façon d'utiliser votre boucle bloquée sans compter sur le bon choix des compteurs serait d'écrire dans des fichiers à la place:

import time

while not lock.acquire(False): # Start a loop that will be infinite if deadlocked
    with open('checkpoint_a.txt', 'a') as fo: # open a unique filename
        fo.write("\nHit") # write indicator to file
        time.sleep(3)     # pause for a moment so the file size doesn't explode

Maintenant, laissez votre programme fonctionner pendant une minute ou deux. Tuez le programme et parcourez ces fichiers "checkpoint". Si le blocage est responsable de votre programme bloqué, les fichiers qui contiennent le mot "hit" écrit plusieurs fois indiquent quelles acquisitions de verrouillage sont responsables de votre blocage.

Vous pouvez étendre l'utilité de cela en ayant les variables d'impression de boucle ou d'autres informations d'état au lieu d'une simple constante. Par exemple, vous avez dit que vous suspectez que le blocage se produit dans une boucle, mais ne savez pas sur quelle itération il s'agit. Demandez à cette boucle de verrouillage de vider les variables de contrôle de votre boucle ou d'autres informations d'état pour identifier l'itération sur laquelle le blocage s'est produit.

44
skrrgwasme

Il existe un clone de pdb, imaginativement appelé pdb-clone , qui peut se rattacher à un processus en cours .

Vous ajoutez simplement from pdb_clone import pdbhandler; pdbhandler.register() au code du processus principal, puis vous pouvez démarrer pdb avec pdb-attach --kill --pid PID.

11
eaglebrain