Een proces koppelen met pdb

Ik heb een python-script waarvan ik vermoed dat er een impasse is. Ik probeerde te debuggen met pdbmaar als ik stap voor stap ga, wordt de impasse niet bereikt, en aan de geretourneerde uitvoer kan ik zien dat het niet in dezelfde iteratie wordt opgehangen. Ik wil mijn script alleen aan een debugger koppelen als het wordt vergrendeld, is dat mogelijk? Ik sta open voor het gebruik van andere debuggers indien nodig.


Antwoord 1, autoriteit 100%

Op dit moment heeft pdbniet de mogelijkheid om te stoppen en te beginnen met het debuggen van een actief programma. Je hebt nog een paar andere opties:

GDB

Je kunt GDB gebruiken om op C-niveau te debuggen. Dit is wat abstracter omdat je de C-broncode van Python doorzoekt in plaats van je eigenlijke Python-script, maar het kan in sommige gevallen nuttig zijn. De instructies zijn hier: https://wiki.python.org/moin/DebuggingWithGdb. Ze zijn te betrokken om hier samen te vatten.

Extensies van derden & Modules

Gewoon googelen naar “pdb attach process” onthult een aantal projecten om PDB deze mogelijkheid te geven:
Pyringe: https://github.com/google/pyringe
Pycharm: https:// blog.jetbrains.com/pycharm/2015/02/feature-spotlight-python-debugger-and-attach-to-process/
Deze pagina van de Python-wiki heeft verschillende alternatieven: https://wiki.python.org/moin/PythonDebuggingTools


Voor uw specifieke gebruik heb ik enkele ideeën voor tijdelijke oplossingen:

Signalen

Als je Unix gebruikt, kun je signalengebruiken zoals in deze blogpostom te proberen te stoppen en koppelen aan een draaiend script.

Dit citaatblok is rechtstreeks gekopieerd van de gelinkte blogpost:

Natuurlijk heeft pdb al functies om een ​​debugger in het midden van je programma te starten, met name pdb.set_trace(). Dit vereist echter dat je weet waar je wilt beginnen met debuggen, het betekent ook dat je het niet in de productiecode kunt laten staan.

Maar ik ben altijd jaloers geweest op wat ik met GDB kan doen: gewoon een draaiend programma onderbreken en rondneuzen met een debugger. Dit kan in sommige situaties handig zijn, b.v. je zit vast in een lus en wil het onderzoeken. En vandaag schoot me ineens te binnen: registreer gewoon een signaalbehandelaar die de traceerfunctie instelt! Hier de proof of concept-code:

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

Nu kan ik SIGUSR1 naar de actieve toepassing sturen en een debugger krijgen. Heerlijk!

Ik kan me voorstellen dat je dit kunt opfleuren door Winpdb te gebruiken om foutopsporing op afstand mogelijk te maken voor het geval je applicatie niet langer aan een terminal is gekoppeld. En het andere probleem dat de bovenstaande code heeft, is dat het niet lijkt alsof het programma kan worden hervat nadat pdb is aangeroepen, na het verlaten van pdb krijg je gewoon een traceback en ben je klaar (maar aangezien dit alleen bdb is die de bdb.BdbQuit-uitzondering verhoogt, denk ik dit kan op een aantal manieren worden opgelost). Het laatste directe probleem is om dit op Windows uit te voeren, ik weet niet veel over Windows, maar ik weet dat ze geen signalen hebben, dus ik weet niet zeker hoe je dit daar zou kunnen doen.

Voorwaardelijke breekpunten en lussen

Je kunt PDB misschien nog steeds gebruiken als je geen signalen beschikbaar hebt, als je je slot- of semafoor-acquisities in een lus plaatst die een teller verhoogt, en pas stopt wanneer de telling een belachelijk groot aantal heeft bereikt. Stel bijvoorbeeld dat u een slot heeft waarvan u vermoedt dat het deel uitmaakt van uw impasse:

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

Herschrijf het op deze manier:

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

Het breekpunt zou moeten worden geactiveerd wanneer het aantal erg groot is, (hopelijk) wat aangeeft dat daar een impasse is opgetreden. Als u merkt dat het wordt geactiveerd wanneer de vergrendelende objecten geen deadlock lijken aan te geven, moet u mogelijk een korte vertraging in de lus invoegen, zodat deze niet zo snel toeneemt. Mogelijk moet u ook spelen met de activeringsdrempel van het breekpunt om het op het juiste moment te activeren. Het nummer in mijn voorbeeld was willekeurig.

Een andere variant hierop zou zijn om geen PDB te gebruiken en opzettelijk een uitzondering te maken wanneer de teller enorm wordt, in plaats van een breekpunt te activeren. Als u uw eigen uitzonderingsklasse schrijft, kunt u deze gebruiken om alle lokale semafoor-/vergrendelingsstatussen in de uitzondering te bundelen, en deze vervolgens op het hoogste niveau van uw script af te drukken voordat u afsluit.

Bestandsindicatoren

Een andere manier waarop u uw vastgelopen lus kunt gebruiken zonder te vertrouwen op de juiste tellers, is door in plaats daarvan naar bestanden te schrijven:

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

Laat je programma nu een minuut of twee draaien. Dood het programma en ga door die “checkpoint”-bestanden. Als deadlock verantwoordelijk is voor je vastgelopen programma, geven de bestanden met het woord “hit” een aantal keren aan welke lock-acquisities verantwoordelijk zijn voor je deadlock.

U kunt de bruikbaarheid hiervan vergroten door de lus-afdrukvariabelen of andere statusinformatie te hebben in plaats van alleen een constante. U zei bijvoorbeeld dat u vermoedt dat de impasse zich in een lus voordoet, maar niet weet in welke iteratie het is. Laat deze lock-lus de controlerende variabelen van uw lus of andere statusinformatie dumpen om de iteratie te identificeren waarop de deadlock plaatsvond.


Antwoord 2, autoriteit 22%

Er is een kloon van pdb, fantasierijk genaamd pdb-clone, die toevoegen aan een lopend proces.

Je voegt eenvoudig from pdb_clone import pdbhandler; pdbhandler.register()toe aan de code voor het hoofdproces, en dan kun je pdb starten met pdb-attach --kill --pid PID.


Antwoord 3, autoriteit 9%

Je kunt mijn project madbggebruiken. Het is een python-debugger waarmee je een lopend python-programma kunt koppelen en in je huidige terminal kunt debuggen. Het is vergelijkbaar met pyrasiteen pyringe, maar ondersteunt python3, vereist geen gdb en gebruikt IPythonvoor de debugger (wat pdb betekent met kleuren en automatisch aanvullen).

Als u bijvoorbeeld wilt zien waar uw script vastloopt, kunt u het volgende uitvoeren:

madbg attach <pid>

En voer in de debugger-shell het volgende in:
bt


Antwoord 4, autoriteit 5%

Gebruik pyrasite:

>>> pyrasite 172483 dump_stacks.py

…waar 172483 de PID is van het draaiende Python-proces. Het python-proces drukt vervolgens een stapeltracering af voor elke thread. Men kan willekeurige Python-code sturen om uit te voeren of een shell openen.

Dit is geweldig voor het debuggen van deadlocks. Men kan zelfs pyrasiet installeren nadat het ophangproces is gestart. Maar houd er rekening mee dat u het in dezelfde omgeving moet installeren om het te laten werken.

Dit is niet de enige tool die beschikbaar is, maar om de een of andere reden lijkt het erg moeilijk te zijn om er per ongeluk over te struikelen. Het is oud, maar werkt als een tierelier voor Python 2 en 3.

Deze tool ondersteunt mogelijk geen win32 zoals de meeste injectoren die Unix-headers gebruiken voor native C-functies, zie b.v. dit openstaande probleem.

Other episodes