[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