Подтвердить что ты не робот

Присоединение процесса с помощью pdb

У меня есть python script, что я подозреваю, что есть тупик. Я пытался отлаживать с помощью pdb, но если я иду поэтапно, он не получает тупик, и по возвращенному результату я вижу, что он не был повешен на той же итерации. Я хотел бы прикрепить свой script к отладчику только тогда, когда он заблокирован, возможно ли это? Я открыт для использования других отладчиков, если это необходимо.

4b9b3361

Ответ 1

В настоящее время pdb не может останавливать и начинать отладку на запущенной программе. У вас есть еще несколько вариантов:

GDB

Вы можете использовать GDB для отладки на уровне C. Это немного более абстрактно, потому что вы пишете исходный код Python C, а не ваш реальный Python script, но это может быть полезно для некоторых случаев. Инструкции здесь: https://wiki.python.org/moin/DebuggingWithGdb. Они слишком вовлечены, чтобы обобщить здесь.

сторонние расширения и модули

Просто поиск в Google для процесса "pdb attach" показывает несколько проектов, чтобы дать PDB эту способность:
Pyringe: https://github.com/google/pyringe
Pycharm: https://blog.jetbrains.com/pycharm/2015/02/feature-spotlight-python-debugger-and-attach-to-process/
Эта страница вики Python имеет несколько альтернатив: https://wiki.python.org/moin/PythonDebuggingTools


Для вашего конкретного случая использования у меня есть некоторые идеи для обходных решений:

Сигналы

Если вы используете unix, вы можете использовать сигналы, как в это сообщение в блоге, чтобы попытаться остановить и прикрепить к запущенному script.

Этот блок цитат скопирован непосредственно из связанного сообщения в блоге:

Конечно, pdb уже имеет функции для запуска отладчика в середине вашей программы, в первую очередь pdb.set_trace(). Это, однако, требует от вас знать, где вы хотите начать отладку, это также означает, что вы не можете оставить его для производственного кода.

Но я всегда завидовал тому, что я могу сделать с GDB: просто прервите запущенную программу и начните соваться с помощью отладчика. Это может быть удобно в некоторых ситуациях, например. вы застряли в петле и хотите исследовать. И сегодня мне вдруг пришло в голову: просто зарегистрируйте обработчик сигнала, который устанавливает функцию трассировки! Здесь доказательство кода концепции:

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

Теперь я могу отправить SIGUSR1 в запущенное приложение и получить отладчик. Прекрасный!

Я предполагаю, что вы могли бы оживить это, используя Winpdb, чтобы позволить удаленную отладку, если ваше приложение больше не привязано к терминалу. И другая проблема, указанная выше, заключается в том, что она не может возобновить выполнение программы после вызова pdb, после выхода из pdb вы просто получите трассировку и выполняете (но поскольку это только bdb, поднимающее исключение bdb.BdbQuit, я думаю это можно решить несколькими способами). Последняя проблема связана с Windows, я мало что знаю о Windows, но я знаю, что у них нет сигналов, поэтому я не уверен, как вы могли это сделать.

Условные точки останова и циклы

Вы все еще можете использовать PDB, если у вас нет доступных сигналов, если вы обмениваете блокировки или семафорные покупки в цикле, который увеличивает счетчик, и останавливается только тогда, когда счетчик достигает смехотворно большого количества. Например, предположим, что у вас есть блокировка, которую вы подозреваете, является частью вашего тупика:

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

Перепишите это следующим образом:

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

Точка останова должна срабатывать, когда счетчик очень велик (надеюсь), указывая на то, что там произошел тупик. Если вы обнаружите, что это срабатывает, когда объекты блокировки, похоже, не указывают на тупик, тогда вам может понадобиться вставить короткую задержку в цикле, чтобы она не увеличивалась достаточно быстро. Возможно, вам также придется поиграть с порогом срабатывания точки останова, чтобы заставить его запускаться в нужное время. Число в моем примере было произвольным.

Другим вариантом этого было бы не использовать PDB и преднамеренно создавать исключение, когда счетчик становится огромным, вместо запуска точки останова. Если вы пишете свой собственный класс исключений, вы можете использовать его для объединения всех локальных состояний семафора/блокировки в исключение, а затем перехватить его на верхнем уровне вашего script, чтобы распечатать прямо перед выходом.

Индикаторы файлов

Другой способ, которым вы можете использовать свой тупиковый цикл, не полагаясь на правильность получения счетчиков, - это вместо этого записать в файлы:

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

Теперь пусть ваша программа заработает минуту или две. Убейте программу и пройдите через эти файлы "контрольной точки". Если тупик отвечает за вашу застопоренную программу, файлы, в которых есть слово "hit", написанное в них несколько раз, указывают, какие блокировки несут ответственность за ваш тупик.

Вы можете расширить полезность этого, указав переменные печати цикла или другую информацию состояния, а не только константу. Например, вы сказали, что подозреваете, что тупик происходит в цикле, но не знаю, на что итерация. Пусть этот цикл блокировки дампирует ваш цикл, управляющий переменными или другой информацией о состоянии, чтобы идентифицировать итерацию, на которой произошел тупик.

Ответ 2

Существует клон pdb, образно называемый pdb-clone, который может прикрепляется к запущенному процессу.

Просто добавьте from pdb_clone import pdbhandler; pdbhandler.register() в код основного процесса, а затем вы можете запустить pdb с помощью pdb-attach --kill --pid PID.