try : import readline # For readline input support except : pass import sys , os , traceback , signal , codeop , cStringIO , cPickle , tempfile def pipename ( pid ): """Return name of pipe to use""" return os . path . join ( tempfile . gettempdir (), 'debug- %d ' % pid ) class NamedPipe ( object ): def __init__ ( self , name , end = 0 , mode = 0666 ): """Open a pair of pipes, name.in and name.out for communication with another process. One process should pass 1 for end, and the other 0. Data is marshalled with pickle.""" self . in_name , self . out_name = name + '.in' , name + '.out' , try : os . mkfifo ( self . in_name , mode ) except OSError : pass try : os . mkfifo ( self . out_name , mode ) except OSError : pass # NOTE: The order the ends are opened in is important - both ends # of pipe 1 must be opened before the second pipe can be opened. if end : self . inp = open ( self . out_name , 'r' ) self . out = open ( self . in_name , 'w' ) else : self . out = open ( self . out_name , 'w' ) self . inp = open ( self . in_name , 'r' ) self . _open = True def is_open ( self ): return not ( self . inp . closed or self . out . closed ) def put ( self , msg ): if self . is_open (): data = cPickle . dumps ( msg , 1 ) self . out . write ( " %d

" % len ( data )) self . out . write ( data ) self . out . flush () else : raise Exception ( "Pipe closed" ) def get ( self ): txt = self . inp . readline () if not txt : self . inp . close () else : l = int ( txt ) data = self . inp . read ( l ) if len ( data ) < l : self . inp . close () return cPickle . loads ( data ) # Convert back to python object. def close ( self ): self . inp . close () self . out . close () try : os . remove ( self . in_name ) except OSError : pass try : os . remove ( self . out_name ) except OSError : pass def __del__ ( self ): self . close () def remote_debug ( sig , frame ): """Handler to allow process to be remotely debugged.""" def _raiseEx ( ex ): """Raise specified exception in the remote process""" _raiseEx . ex = ex _raiseEx . ex = None try : # Provide some useful functions. locs = { '_raiseEx' : _raiseEx } locs . update ( frame . f_locals ) # Unless shadowed. globs = frame . f_globals pid = os . getpid () # Use pipe name based on pid pipe = NamedPipe ( pipename ( pid )) old_stdout , old_stderr = sys . stdout , sys . stderr txt = '' pipe . put ( "Interrupting process at following point:

" + '' . join ( traceback . format_stack ( frame )) + ">>> " ) try : while pipe . is_open () and _raiseEx . ex is None : line = pipe . get () if line is None : continue # EOF txt += line try : code = codeop . compile_command ( txt ) if code : sys . stdout = cStringIO . StringIO () sys . stderr = sys . stdout exec code in globs , locs txt = '' pipe . put ( sys . stdout . getvalue () + '>>> ' ) else : pipe . put ( '... ' ) except : txt = '' # May be syntax err. sys . stdout = cStringIO . StringIO () sys . stderr = sys . stdout traceback . print_exc () pipe . put ( sys . stdout . getvalue () + '>>> ' ) finally : sys . stdout = old_stdout # Restore redirected output. sys . stderr = old_stderr pipe . close () except Exception : # Don't allow debug exceptions to propogate to real program. traceback . print_exc () if _raiseEx . ex is not None : raise _raiseEx . ex def debug_process ( pid ): """Interrupt a running process and debug it.""" os . kill ( pid , signal . SIGUSR1 ) # Signal process. pipe = NamedPipe ( pipename ( pid ), 1 ) try : while pipe . is_open (): txt = raw_input ( pipe . get ()) + '

' pipe . put ( txt ) except EOFError : pass # Exit. pipe . close () def listen (): signal . signal ( signal . SIGUSR1 , remote_debug ) # Register for remote debugging. if __name__ == '__main__' : if len ( sys . argv ) != 2 : print "Error: Must provide process id to debug" else : pid = int ( sys . argv [ 1 ]) debug_process ( pid )