devork

E pur si muove

How to bring a running python program under debugger control

Saturday, July 25, 2009

Of course pdb has already got functions to start a debugger in the middle of your program, most notably pdb.set_trace(). This however requires you to know where you want to start debugging, it also means you can't leave it in for production code.

But I've always been envious of what I can do with GDB: just interrupt a running program and start to poke around with a debugger. This can be handy in some situations, e.g. you're stuck in a loop and want to investigate. And today it suddenly occurred to me: just register a signal handler that sets the trace function! Here the 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()

Now I can send SIGUSR1 to the running application and get a debugger. Lovely!

I imagine you could spice this up by using Winpdb to allow remote debugging in case your application is no longer attached to a terminal. And the other problem the above code has is that it can't seem to resume the program after pdb got invoked, after exiting pdb you just get a traceback and are done (but since this is only bdb raising the bdb.BdbQuit exception I guess this could be solved in a few ways). The last immediate issue is running this on Windows, I don't know much about Windows but I know they don't have signals so I'm not sure how you could do this there.

4 comments:

Marius Gedminas said...

(I hate blogger. Every time I start commenting, it immediately reloads the page and loses the half-sentence of text I've already typed.)

Neat idea!

I long wished for gdb-like attachability for Python. This is very close to that ideal, but still requires a bit of foresight.

It should be possible to use gdb to attach to the Python interpreter and then invoke some API calls to effectively set a breakpoint after the next bytecode. I haven't seen anyone do that yet, though.

fumanchu said...

The Windows version might use the 'break' event and look something like this (although more Pythonic than this, since I had to move things around to get the point across in Blogger):

import pdb
import win32api
import win32con

def handle(event):
"""Handle console control events (like Ctrl-C)."""
if event != win32con.CTRL_BREAK_EVENT: return 0

pdb.set_trace()
# First handler to return True stops the calls to further handlers
return 1

result = win32api.SetConsoleCtrlHandler(handle, 1)
if result == 0: sys.exit('Could not SetConsoleCtrlHandler (error %r)' % win32api.GetLastError())

David Glick said...

I did something similar several months ago, for use with Zope 2, in http://pypi.python.org/pypi/mr.freeze

The essential difference is that I subclassed Pdb to make it possible to set a breakpoint on a particular file and line, rather than breaking immediately. The file and line are passed in via a file with a predefined name that you create before triggering the signal. With the help of a command in my editor that creates that file and then triggers the signal, debugging has become much more efficient!

(It does get a bit complicated because Zope is multi-threaded, and pdb can only set a trace in the current thread. So mr.freeze patches the main loop processing HTTP requests to set a trace in *each* thread once the signal has been received, then does some bookkeeping to make sure that it only actually breaks in the first thread where the breakpoint is hit.)

Anonymous said...

"And the other problem the above code has is that it can't seem to resume the program after pdb got invoked, after exiting pdb you just get a traceback and are done"

"c" in pdb will allow you to continue execution as normal without quitting.

New comments are not allowed.

Subscribe to: Post Comments (Atom)