[Python-checkins] r46636 - sandbox/trunk/pdb/mpdb.py
matt.fleming
python-checkins at python.org
Sun Jun 4 03:33:33 CEST 2006
Author: matt.fleming
Date: Sun Jun 4 03:33:31 2006
New Revision: 46636
Modified:
sandbox/trunk/pdb/mpdb.py
Log:
Trying to follow gdb's command set as closely as possible.
Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py (original)
+++ sandbox/trunk/pdb/mpdb.py Sun Jun 4 03:33:31 2006
@@ -3,224 +3,273 @@
# This is a Google Summer of Code project
# Student: Matthew J. Fleming
# Mentor: Robert L. Bernstein
+"""
+This module provides improvements over the Python Debugger (Pdb).
+This module allows,
+
+- debugging of applications running in a separate process to the debugger
+- debugging of applications on a remote machine
+- debugging of threaded applications.
+
+"""
-import cmd
import os
from optparse import OptionParser
import pdb
import socket
import sys
import traceback
-import thread
# Need custom safe Repr just like pdb
_saferepr = pdb._repr.repr
line_prefix = '\n-> '
class MPdb(pdb.Pdb):
- def __init__(self):
- pdb.Pdb.__init__(self)
- self.fd = sys.stdout # The file descriptor we're writing output to
+ """ This class extends the command set and functionality of the
+ Python debugger and provides support for,
+
+ - debugging separate processes
+ - debugging applications on remote machines
+ - debugging threaded applications
+ """
+ def __init__(self, completekey='tab', stdin=None, stdout=None):
+ """ Instantiate a debugger.
+
+ The optional argument 'completekey' is the readline name of a
+ completion key; it defaults to the Tab key. If completekey is
+ not None and the readline module is available, command completion
+ is done automatically. The optional arguments stdin and stdout
+ specify alternate input and output file objects; if not specified,
+ sys.stdin and sys.stdout are used.
+ """
+ pdb.Pdb.__init__(self, completekey, stdin, stdout)
self.prompt = '(MPdb)'
- def read(self):
- """ Read a line from our input stream/socket. """
- try:
- line = raw_input(self.prompt)
- except EOFError:
- line = 'EOF'
- return line
-
-class DebuggerConsoleConnected(Exception): pass
-
-# Below are the classes that allow MPdb instances to be debugged locally and
-# remotely by a debugger running in a different process
-class MPdbServer(MPdb):
- def __init__(self):
- MPdb.__init__(self)
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.debugger_list = []
- self.attached = False
+ def _rebind_input(self, new_input):
+ """ This method rebinds the debugger's input to the object specified
+ by 'new_input'.
+ """
+ self.raw_input = 1
+ print >> sys.stderr, "From %s to %s" % (self.stdin, new_input)
+ self.stdin.flush()
+ self.stdin = new_input
+ self.stdin.flush()
+
+ def _rebind_output(self, new_output):
+ """ This method rebinds the debugger's output to the object specified
+ by 'new_output'.
+ """
+ self.stdout.flush()
+ self.stdout = new_output
+ self.stdout.flush()
+
+ # Debugger commands
+ def do_attach(self, addr):
+ """ Attach to a process or file outside of Pdb.
+This command attaches to another target, of the same type as your last
+"target" command. The command may take as argument a process id or a
+device file. For a process id, you must have permission to send the
+process a signal, and it must have the same effective uid as the debugger.
+When using "attach" with a process id, the debugger finds the
+program running in the process, looking first in the current working
+directory, or (if not found there) using the source file search path
+(see the "directory" command). You can also use the "file" command
+to specify the program, and to load its symbol table.
+"""
+
+ def do_target(self, args):
+ """ Connect to a target machine or process.
+The first argument is the type or protocol of the target machine
+(which can be the name of a class that is avaible either in the current
+working directory or in Python's PYTHONPATH environtment variable).
+Remaining arguments are interpreted by the target protocol. For more
+information on the arguments for a particular protocol, type
+`help target ' followed by the protocol name.
+
+List of target subcommands:
+
+target serial -- Use a remote computer via a serial line
+target tcp -- Use a remote computer via a TCP connection
+target udp -- Use a remote computer via a UDP connection
+target xml -- Use a remote computer via the xmlrpc lib
+"""
+ cls, addr = args.split(' ')
+ self.connection = eval(cls+'(addr)')
+ self.connection.setup()
+ # XXX currently this method doesn't do anything
+
+ def do_serve(self, args):
+ """ Allow a debugger to connect to this session.
+The first argument is the type or protocol that is used for this connection
+(which can be the name of a class that is avaible either in the current
+working directory or in Python's PYTHONPATH environtment variable).
+Remaining arguments are interpreted by the protocol. For more
+information on the arguments for a particular protocol, type
+`help target ' followed by the protocol name.
+"""
+ cls, addr = args.split(' ')
+ self.connection = eval(cls+'(addr)')
+ self.connection.setup()
+ self._rebind_output(self.connection.output)
+
+NotImplementedMessage = "This method must be overriden in a subclass"
+
+class MTargetInterface(object):
+ """ This is an abstract class that specifies the interface a debugging
+ target class must implement.
+ """
+ def accept(self, console, addr):
+ """ This method is called when a connection from a debugger
+ is accepted by this target.
+ """
+ raise NotImplementedError, NotImplementedMessage
+
+ def disconnect(self):
+ """ This method is called to disconnect any debuggers that
+ are connected and to stop accepting any more connections from
+ debuggers.
+ """
+ raise NotImplementedError, NotImplementedMessage
+
+ def listen(self):
+ """ This method is called when a target is initially
+ configured to listen for connections from debuggers.
+ """
+ raise NotImplementedError, NotImplementedMessage
+
+class MTargetConnectionTCP(MTargetInterface):
+ """ This is an implementation of a target class that uses the TCP
+ protocol as its means of communication. Debuggers wishing to connect
+ to this target must use this syntax for the target command,
- def listen(self, port):
- self.port = port
- self._sock.bind((self.host, self.port))
- self._sock.listen(1) # Only one debugger is allowed control
- self.thread_handle = thread.start_new_thread(self.accept, ())
+ `target tcp hostname:port
- def accept(self):
- c, a = self._sock.accept()
- print "\n\nDebugger attached..\n"
- self.debugger_list.append((c,a))
- self.fd = self.debugger_list[-1][0].makefile("w")
- self.attached = True
- # We've got a debugger console attached so we hijack stdout
- self.old_stdout = os.dup(1)
- os.close(1)
- sys.stdout = self.fd
- # XXX Need a way to enter the current 'waiting' on stdin for input
- # and start reading from the debugger console
+ """
+ def __init__(self, addr):
+ """ 'addr' specifies the hostname and port combination of
+ the target.
+ """
+ MTargetInterface.__init__(self)
+ h,p = addr.split(':')
+ self.host = h
+ self.port = int(p)
- def cmdloop(self):
- self.preloop()
- stop = None
- while not stop:
- if self.cmdqueue:
- line = self.cmdqueue.pop(0)
- else:
- line = self.read()
- if not len(line):
- line = 'EOF'
- line = self.precmd(line)
- stop = self.onecmd(line)
- stop = self.postcmd(stop, line)
- # Force stdout flush its contents
- sys.stdout.flush()
- self.postloop()
-
- def read(self):
- # We need to check whether we've actually been attached to yet
- if self.attached:
- line = self.debugger_list[-1][0].recv(1024)
- return line
- else:
- line = raw_input(self.prompt)
- return line
-
-class LMPdbServer(MPdbServer):
- """ A local debugee which can only communicate with a debugger running
- on the localhost.
- """
- def __init__(self):
- MPdbServer.__init__(self)
- self.host = 'localhost'
-
-class MPdbConsole(cmd.Cmd):
- def __init__(self):
- cmd.Cmd.__init__(self, stdin=None, stdout=None)
+
+ def setup(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.attached = False
- self.prompt = '(MPdb)'
- self.lastcmd = ""
+ self._sock.bind((self.host, self.port))
+ self.listen()
- def connect(self, host, port):
- self._sock.connect((host, port))
- self.th_r = thread.start_new_thread(self.recv, ())
-
- def do_attach(self, args):
- if self.attached:
- print "Already attached to a debugger.."
- return None
- h = args.split(" ")
- self.connect(h[0], int(h[1]))
- print "Attaching..\n"
- self.attached = True
-
- def help_attach(self):
- print """attach [host] [port]
- Attach to a debugging session at [host] on [port]."""
-
- def cmdloop(self):
- """ Repeatedly issue a prompt and accept input from a debugger
- console. All commands (apart from the 'attach' and 'help' commands)
- are sent to an attached debugee.
- """
- print self.prompt,
- stop = None
- while not stop:
- line = self.input()
- if not len(line):
- line = 'EOF'
- if self.attached:
- self.send(line)
- else:
- if 'attach' in line:
- line = line.split(' ')
- line = line[1:]
- line = line[0] + " " + line[1]
- self.do_attach(line)
- elif 'help' in line:
- self.help_attach()
- else:
- print "Not currently attached to a debugger..\n"
- continue
- self.postloop()
-
- def send(self, line):
- """ Send the line to the debugee server for parsing and execution. """
- self._sock.send(line)
+ def listen(self):
+ self._sock.listen(5)
+ debugger, addr = self._sock.accept()
+ self.accept(debugger, addr)
+
+ def accept(self, debugger, addr):
+ self.output = debugger.makefile('w')
+
+ def disconnect(self):
+ self.output.flush()
+ self.output.close()
+ self._sock.close()
+
+class MTargetConnectionSerial(MTargetInterface):
+ """ This is a target connection class that allows a debugger
+ to connect via a serial line. A serial device must be specified
+ (e.g. /dev/ttyS0, COM1, etc.).
+ """
+ def __init__(self, device):
+ self._dev
+ MTargetInterface.__init__(self)
+
+ def setup(self):
+ self.output = open(self._dev, 'w')
+ self.input = open(self._dev, 'r')
+
+ def listen(self):
+ pass
+
+ def accept(self, debugger, addr):
+ pass
+
+ def disconnect(self):
+ self.output.close()
+ self.input.close()
- def input(self):
- """ This method will be changed later. """
- line = raw_input()
- # Hitting enter repeatedly should execute the last command
- if line is not '':
- self.lastcmd = line
- elif line is '':
- if self.lastcmd is '':
- print "No previous command.. \n"
- line = 'EOF'
- else:
- line = self.lastcmd
- return line
-
- def recv(self):
- """ Receive all data being sent from the debugee. """
- while 1:
- data = self._sock.recv(1024)
- if data:
- print data,
- print self.prompt,
+class MDebuggerConnectionTCP(object):
+ """ A class that allows a connection to be made from a debugger
+ to a target via TCP. Specify the address of the target
+ (e.g. host:2020).
+ """
+ def __init__(self, addr):
+ """ Specify the address to connection to. """
+ h, p = addr.split(':')
+ self.host = h
+ self.port = int(p)
+ self.setup()
- def close(self):
- self._sock.close()
+ def setup(self):
+ """ Connect to the target. """
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.connect((self.host, self.port))
+
+class MDebuggerConnectionSerial(object):
+ """ A class that allows a connection to be made from a debugger
+ to a target via a serial line. Specify the serial device it is
+ connected to (e.g. /dev/ttya).
+ """
+ def __init__(self, device):
+ """ Specify the serial device. """
+ self._dev = device
+ self.input = None
+ self.output = None
+ self.setup()
+
+ def setup(self):
+ """ Create our fileobject by opening the serial device for
+ this connection.
+ """
+ self.output = open(self._dev, "w")
+
+# These are the default classes that are instantiated when connecting
+# to a target,
+#
+# `target tcp localhost:8080 '
+#
+# These can be changed at runtime.
+
+serial = MDebuggerConnectionSerial
+tcp = MDebuggerConnectionTCP
def main(options):
opts = options[0]
args = options[1]
- if not args:
- print 'usage: mpdb.py scriptfile [arg] ... '
- sys.exit(1)
- mainpyfile = args[0]
- if not os.path.exists(mainpyfile):
- print 'Error:', mainpyfile, 'does not exist'
- sys.exit(1)
- if opts.debugger:
- # We may be local or remote but we're still a debugger console
- # This console acts differently to everything else, for now, it's just
- # a glorified telnet client that works _properly_ with our server
- mpdb = MPdbConsole()
- # All we can actually do is issue commands and receieve the result
- mpdb.cmdloop()
- else:
- if opts.local_debugee:
- mpdb = LMPdbServer()
- mpdb.listen(7000)
- elif opts.remote_debugee:
- pass
- else:
- # Regular interactive debugger session
- mpdb = MPdb()
- while 1:
- try:
- mpdb._runscript(mainpyfile)
- if mpdb._user_requested_quit:
- break
- print "The program finished and will be restarted"
- except SystemExit:
- # In most cases SystemExit does not warrant a post-mortem session.
- print "The program exited via sys.exit(). " + \
- "Exit status:",sys.exc_info()[1]
- except:
- print traceback.format_exc()
- print "Uncaught exception. Entering post mortem debugging"
- print "Running 'cont' or 'step' will restart the program"
- t = sys.exc_info()[2]
- while t.tb_next is not None:
- t = t.tb_next
- mpdb.interaction(t.tb_frame,t)
- print "Post mortem debugger finished. The " + \
- mainpyfile + " will be restarted"
+ if args:
+ mainpyfile = args[0]
+ if not os.path.exists(mainpyfile):
+ print 'Error:', mainpyfile, 'does not exist'
+ sys.exit(1)
+ mpdb = MPdb()
+ while 1:
+ try:
+ mpdb._runscript(mainpyfile)
+ if mpdb._user_requested_quit:
+ break
+ print "The program finished and will be restarted"
+ except SystemExit:
+ # In most cases SystemExit does not warrant a post-mortem session.
+ print "The program exited via sys.exit(). " + \
+ "Exit status:",sys.exc_info()[1]
+ except:
+ print traceback.format_exc()
+ print "Uncaught exception. Entering post mortem debugging"
+ print "Running 'cont' or 'step' will restart the program"
+ t = sys.exc_info()[2]
+ while t.tb_next is not None:
+ t = t.tb_next
+ mpdb.interaction(t.tb_frame,t)
+ print "Post mortem debugger finished. The " + \
+ mainpyfile + " will be restarted"
# A utility function to parse the options from the command line
def parse_opts():
More information about the Python-checkins
mailing list