Adjuntar un proceso con pdb

9 minutos de lectura

Tengo una secuencia de comandos de Python que sospecho que hay un punto muerto. Estaba tratando de depurar con pdb pero si voy paso a paso, no se bloquea, y por el resultado devuelto puedo ver que no se cuelga en la misma iteración. Me gustaría adjuntar mi script a un depurador solo cuando se bloquee, ¿es posible? Estoy abierto a usar otros depuradores si es necesario.

  • Podría ayudar code.activestate.com/recipes/…

    – pavel_form

    14 de agosto de 2014 a las 15:03

  • Podría ayudar stackoverflow.com/a/2569696/2096752

    – shx2

    15 de agosto de 2014 a las 7:15

  • Como aceptaste mi respuesta, me hizo volver a mirarla. Se me ocurrieron un par de opciones más que he agregado a mi respuesta, si todavía estás interesado. Echa un vistazo a la Puntos de interrupción y bucles condicionales sección.

    – skrrgwasme

    10 de noviembre de 2015 a las 15:16

  • relacionado wiki.python.org/moin/DebuggingWithGdb

    – Trevor Boyd Smith

    10 de enero de 2019 a las 21:03

avatar de usuario de skrrgwasme
skrrgwasme

En este momento, pdb no tiene la capacidad de detener y comenzar la depuración en un programa en ejecución. Tienes algunas otras opciones:

BGF

Puede usar GDB para depurar en el nivel C. Esto es un poco más abstracto porque está hurgando en el código fuente C de Python en lugar de su script de Python real, pero puede ser útil en algunos casos. Las instrucciones están aquí: https://wiki.python.org/moin/DebuggingWithGdb. Están demasiado involucrados para resumirlos aquí.

Extensiones y módulos de terceros

Simplemente buscar en Google el “proceso de adjuntar pdb” revela un par de proyectos para darle a PDB esta capacidad:
Piringa: https://github.com/google/pyringe

Pycharm: https://blog.jetbrains.com/pycharm/2015/02/feature-spotlight-python-debugger-and-attach-to-process/

Esta página de la wiki de Python tiene varias alternativas: https://wiki.python.org/moin/PythonDebuggingTools


Para su caso de uso específico, tengo algunas ideas para soluciones alternativas:

Señales

Si estás en Unix, puedes usar señales como en esta entrada de blog para intentar detener y adjuntar a un script en ejecución.

Este bloque de citas se copia directamente de la publicación de blog vinculada:

Por supuesto, pdb ya tiene funciones para iniciar un depurador en medio de su programa, sobre todo pdb.set_trace(). Sin embargo, esto requiere que sepa dónde desea comenzar a depurar, también significa que no puede dejarlo para el código de producción.

Pero siempre he tenido envidia de lo que puedo hacer con GDB: simplemente interrumpir un programa en ejecución y comenzar a hurgar con un depurador. Esto puede ser útil en algunas situaciones, por ejemplo, si está atrapado en un bucle y desea investigar. Y hoy de repente se me ocurrió: ¡simplemente registre un controlador de señal que establezca la función de rastreo! Aquí el código de prueba de concepto:

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

Ahora puedo enviar SIGUSR1 a la aplicación en ejecución y obtener un depurador. ¡Hermoso!

Imagino que podría mejorar esto usando Winpdb para permitir la depuración remota en caso de que su aplicación ya no esté conectada a una terminal. Y el otro problema que tiene el código anterior es que parece que no puede reanudar el programa después de que se invocó pdb, después de salir de pdb solo obtiene un rastreo y listo (pero como esto es solo bdb generando la excepción bdb.BdbQuit, supongo) esto podría resolverse de varias maneras). El último problema inmediato es ejecutar esto en Windows, no sé mucho sobre Windows, pero sé que no tienen señales, así que no estoy seguro de cómo podría hacer esto allí.

Puntos de interrupción y bucles condicionales

Es posible que aún pueda usar PDB si no tiene señales disponibles, si envuelve sus adquisiciones de bloqueo o semáforo en un bucle que incrementa un contador, y solo se detiene cuando el conteo ha alcanzado un número ridículamente grande. Por ejemplo, supongamos que tiene un bloqueo que sospecha que es parte de su interbloqueo:

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

Reescríbelo de esta manera:

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

El punto de interrupción debe activarse cuando el recuento es muy grande, lo que (con suerte) indica que se ha producido un interbloqueo allí. Si encuentra que se activa cuando los objetos de bloqueo no parecen indicar un interbloqueo, es posible que deba insertar un retraso de tiempo corto en el ciclo para que no se incremente tan rápido. También es posible que tenga que jugar con el umbral de activación del punto de interrupción para que se active en el momento adecuado. El número en mi ejemplo fue arbitrario.

Otra variante de esto sería no usar PDB y generar intencionalmente una excepción cuando el contador se vuelve enorme, en lugar de activar un punto de interrupción. Si escribe su propia clase de excepción, puede usarla para agrupar todos los estados locales de semáforo/bloqueo en la excepción y luego capturarla en el nivel superior de su secuencia de comandos para imprimirla justo antes de salir.

Indicadores de archivo

Una forma diferente en la que puede usar su bucle interbloqueado sin depender de obtener contadores correctos sería escribir en archivos en su lugar:

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

Ahora deje que su programa se ejecute durante uno o dos minutos. Elimine el programa y revise esos archivos de “punto de control”. Si el interbloqueo es responsable de su programa estancado, los archivos que tienen la palabra “hit” escrita en ellos un montón de veces indican qué adquisiciones de bloqueo son responsables de su interbloqueo.

Puede ampliar la utilidad de esto haciendo que el bucle imprima variables u otra información de estado en lugar de solo una constante. Por ejemplo, dijo que sospecha que el interbloqueo está ocurriendo en un bucle pero no sabe en qué iteración está. Haga que este ciclo de bloqueo descargue las variables de control de su ciclo u otra información de estado para identificar la iteración en la que ocurrió el interbloqueo.

  • Esto requiere que reescriba mi aplicación. Pero acabo de buscar esto cuando uno de mis programas de python se está ejecutando y quiero depurarlo ahora mismo sin reiniciar.

    – Shiplu Mokaddim

    29/09/2016 a las 11:30

  • @shiplu.mokadd.im Bueno, sí… Necesitarás agregar algún código para usar las soluciones alternativas que he sugerido. Pero vea mi edición para algunas sugerencias de herramientas de terceros que puede usar en su lugar.

    – skrrgwasme

    29/09/2016 a las 18:17

  • ya tenía PyCharm instalado, solo quería cambiar el nivel de registro para Python logging módulo… tomó uno o dos minutos para que el proceso se mostrara como ‘en pausa’, pero luego vi algunas variables en el depurador… abrí el intérprete de código, pegué, ejecuté y bam… después de reiniciar el proceso ¡aparecía mi registro de depuración!

    – nmz787

    13 abr 2018 a las 21:05


  • fallar en Linux/Ubuntu

    – Ying

    4 de noviembre de 2019 a las 8:19

avatar de usuario de eaglebrain
cerebro de águila

Hay un clon de pdb, imaginativamente llamado pdb-clonque puede adjuntar a un proceso en ejecución.

simplemente agregas from pdb_clone import pdbhandler; pdbhandler.register() al código para el proceso principal, y luego puede iniciar pdb con pdb-attach --kill --pid PID.

  • ModuleNotFoundError: No module named 'readline' -> python -m pip install readline -> error: this module is not meant to work on Windows -> python -m pip install pyreadline -> AttributeError: module 'signal' has no attribute 'SIGUSR1'

    – andry

    5 dic 2019 a las 20:20


avatar de usuario de kmaork
kmaork

Puedes usar mi proyecto madbg. Es un depurador de python que le permite conectarse a un programa de python en ejecución y depurarlo en su terminal actual. Esto es similar a pyrasite y pyringepero admite python3, no requiere gdb y usa IPython para el depurador (lo que significa pdb con colores y autocompletar).

Por ejemplo, para ver dónde está atascado su script, puede ejecutar:

madbg attach <pid>

Y en el shell del depurador, ingrese:
bt

avatar de usuario de lumbric
lúmbrico

Usar pirasita:

>>> pyrasite 172483 dump_stacks.py

… donde 172483 es ​​el PID del proceso de Python en ejecución. El proceso de python luego imprimirá un seguimiento de pila para cada subproceso. Uno puede enviar código Python arbitrario para ejecutarlo o abrir un shell.

Esto es excelente para depurar bloqueos muertos. Incluso se puede instalar pirasita después de que se haya iniciado el proceso de colgado. Pero tenga en cuenta que debe instalarlo en el mismo entorno para que funcione.

Esta no es la única herramienta disponible, pero por alguna razón parece ser muy difícil tropezar con ella accidentalmente. Es antiguo, pero funciona de maravilla para Python 2 y 3.

Es posible que esta herramienta no admita win32 como la mayoría de los inyectores que usan encabezados de Unix para funciones C nativas, consulte, por ejemplo este problema abierto.

VSCode admite la depuración de un proceso de python que se ejecuta localmente.

Si no tiene un archivo launch.json, solo necesita comenzar a depurar (F5) y luego verá las siguientes opciones.

ingrese la descripción de la imagen aquí

Al seleccionar “Adjuntar usando ID de proceso”, se agregará lo siguiente a su launch.json

    { "versión": "0.2.0", "configuraciones": [
    {
      "name": "Python: Attach using Process Id",
      "type": "python",
      "request": "attach",
      "processId": "${command:pickProcess}",
    },
}

Now when you debug using that configuration you can select the local python process you want to debug.

enter image description here

¿Ha sido útil esta solución?