[Python-checkins] r47266 - sandbox/trunk/pdb/README.txt sandbox/trunk/pdb/mpdb.py sandbox/trunk/pdb/mthread.py
matt.fleming
python-checkins at python.org
Thu Jul 6 12:11:03 CEST 2006
Author: matt.fleming
Date: Thu Jul 6 12:11:02 2006
New Revision: 47266
Modified:
sandbox/trunk/pdb/README.txt
sandbox/trunk/pdb/mpdb.py
sandbox/trunk/pdb/mthread.py
Log:
Some changes to the thread code.
Modified: sandbox/trunk/pdb/README.txt
==============================================================================
--- sandbox/trunk/pdb/README.txt (original)
+++ sandbox/trunk/pdb/README.txt Thu Jul 6 12:11:02 2006
@@ -53,4 +53,5 @@
* Run command does not pass arguments across a remote connection, i.e.
`run 2' does not pass the argument 2 to the script.
* Allow thread debugging to be turned on with a command line switch.
-
+* Allow reading commands from .mpdbrc file
+* Changed the name of the default history file from ~/.pydbhist to ~/.mpdbhist
\ No newline at end of file
Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py (original)
+++ sandbox/trunk/pdb/mpdb.py Thu Jul 6 12:11:02 2006
@@ -164,6 +164,9 @@
else:
pydb.Pdb.info_helper(self, cmd)
+ def help_mpdb(self, *arg):
+ help()
+
# Debugger commands
def do_attach(self, addr):
""" Attach to a process or file outside of Pdb.
Modified: sandbox/trunk/pdb/mthread.py
==============================================================================
--- sandbox/trunk/pdb/mthread.py (original)
+++ sandbox/trunk/pdb/mthread.py Thu Jul 6 12:11:02 2006
@@ -7,249 +7,38 @@
import sys
import threading
-class MTracer(object):
+from pydb.gdb import Gdb
+
+# Global variables
+tracers = [] # The MTracer objects that are tracing threads
+threads = [] # List of threads we're tracing
+
+# This global variable keeps track of the 'main' debugger which, when one
+# of the MTracer objects encounters a breakpoint in its thread, is used to
+# place a call to that main debugger asking it to stop and examine the frame
+# said thread stopped at.
+_main_debugger = None
+
+class MTracer(Gdb):
""" A class to trace a thread. Breakpoints can be passed from
a main debugger to this debugger through the constructor
which is useful, for instance, if a breakpoint occurs inside
a thread's run() method.
"""
- def __init__(self, msg, errmsg, breaks={}, filename=None):
+ def __init__(self):
+ Gdb.__init__(self)
self.thread = threading.currentThread()
- self.msg = msg
- self.errmsg = errmsg
# Each tracer instance must keep track of its own breakpoints
- self.breaks = breaks
self.fncache = {}
- self.filename = filename
self.lineno = 0
self.curframe = None
+ self.stopframe = None
+ self.botframe = None
+ self.quitting = False
- def canonic(self, filename):
- if filename == "<" + filename[1:-1] + ">":
- return filename
- canonic = self.fncache.get(filename)
- if not canonic:
- canonic = os.path.abspath(filename)
- canonic = os.path.normcase(canonic)
- self.fncache[filename] = canonic
- return canonic
-
- def info(self, arg):
- args = arg.split()
- if 'break'.startswith(args[0]):
- return self.breaks
-
- def get_break(self, filename, lineno):
- """ Return the breakpoint at [filename:]lineno. """
- filename = self.canonic(filename)
- return filename in self.breaks and \
- lineno in self.breaks[filename]
-
- def get_breaks(self, filename, lineno):
- """ Return all the breakpoints set in this thread. """
- filename = self.canonic(filename)
- return filename in self.breaks and \
- lineno in self.breaks[filename] and \
- Breakpoint.bplist[filename, lineno] or []
-
- def set_break(self, filename, lineno, temporary=0, cond = None,
- funcname=None):
- """ Set a breakpoint in this thread. """
-
- # Derived classes and clients can call the following methods
- # to manipulate breakpoints. These methods return an
- # error message is something went wrong, None if all is well.
- # Set_break prints out the breakpoint line and file:lineno.
- # Call self.get_*break*() to see the breakpoints or better
- # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
-
- filename = self.canonic(filename)
- import linecache # Import as late as possible
- line = linecache.getline(filename, lineno)
- if not line:
- return 'Line %s:%d does not exist' % (filename,
- lineno)
- if not filename in self.breaks:
- self.breaks[filename] = []
- list = self.breaks[filename]
- if not lineno in list:
- list.append(lineno)
- bp = Breakpoint(filename, lineno, temporary, cond, funcname)
-
- def do_break(self, arg=None):
- """ thread-specific breakpoint information. """
- # XXX For now we don't support temporary breakpoints in threads
- temporary = cond = False
- if arg is None:
- if self.lineno is None:
- lineno = max(1, inspect.lineno(self.curframe))
- else:
- lineno = self.lineno + 1
- filename = self.curframe.f_code.co_filename
- else:
- filename = None
- lineno = None
- comma = arg.find(',')
- if comma > 0:
- # parse stuff after comma: "condition"
- cond = arg[comma+1:].lstrip()
- arg = arg[:comma].rstrip()
- (funcname, filename, lineno) = self.__parse_filepos(arg)
- if lineno is None: return
-
- # FIXME This default setting doesn't match that used in
- # do_clear. Perhaps one is non-optimial.
- if not filename:
- filename = self.defaultFile()
-
- # Check for reasonable breakpoint
- line = self.checkline(filename, lineno)
- if line:
- # now set the break point
- # Python 2.3.5 takes 5 args rather than 6.
- # There is another way in configure to test for the version,
- # but this works too.
- try:
- err = self.set_break(filename, line, temporary, cond, funcname)
- except TypeError:
- err = self.set_break(filename, line, temporary, cond)
-
- if err: self.msg, err
- else:
- bp = self.get_breaks(filename, line)[-1]
- self.msg, "Breakpoint %d set in file %s, line %d." \
- % (bp.number, self.filename(bp.file), bp.line)
-
- def __parse_filepos(self, arg):
- """__parse_filepos(self,arg)->(fn, filename, lineno)
-
- Parse arg as [filename:]lineno | function
- Make sure it works for C:\foo\bar.py:12
- """
- colon = arg.rfind(':')
- if colon >= 0:
- filename = arg[:colon].rstrip()
- f = self.lookupmodule(filename)
- if not f:
- self.msg("%s not found from sys.path" %
- self._saferepr(filename))
- return (None, None, None)
- else:
- filename = f
- arg = arg[colon+1:].lstrip()
- try:
- lineno = int(arg)
- except ValueError, msg:
- self.msg('Bad lineno: %s' % str(arg))
- return (None, filename, None)
- return (None, filename, lineno)
- else:
- # no colon; can be lineno or function
- return self.__get_brkpt_lineno(arg)
-
- def __get_brkpt_lineno(self, arg):
- """__get_brkpt_lineno(self,arg)->(filename, file, lineno)
-
- See if arg is a line number or a function name. Return what
- we've found. None can be returned as a value in the triple."""
- funcname, filename = (None, None)
- try:
- # First try as an integer
- lineno = int(arg)
- filename = self.curframe.f_code.co_filename
- except ValueError:
- try:
- func = eval(arg, self.curframe.f_globals,
- self.curframe.f_locals)
- except:
- func = arg
- try:
- if hasattr(func, 'im_func'):
- func = func.im_func
- code = func.func_code
- #use co_name to identify the bkpt (function names
- #could be aliased, but co_name is invariant)
- funcname = code.co_name
- lineno = code.co_firstlineno
- filename = code.co_filename
- except:
- # last thing to try
- (ok, filename, ln) = self.lineinfo(arg)
- if not ok:
- self.msg('The specified object %s is not ' \
- ' a function, or not found' \
- ' along sys.path or no line given.' %
- str(repr(arg)))
-
- return (None, None, None)
- funcname = ok # ok contains a function name
- lineno = int(ln)
- return (funcname, filename, lineno)
-
- def lineinfo(self, identifier):
- failed = (None, None, None)
- # Input is identifier, may be in single quotes
- idstring = identifier.split("'")
- if len(idstring) == 1:
- # not in single quotes
- id = idstring[0].strip()
- elif len(idstring) == 3:
- # quoted
- id = idstring[1].strip()
- else:
- return failed
- if id == '': return failed
- parts = id.split('.')
- # Protection for derived debuggers
- if parts[0] == 'self':
- del parts[0]
- if len(parts) == 0:
- return failed
- # Best first guess at file to look at
- fname = self.defaultFile()
- if len(parts) == 1:
- item = parts[0]
- else:
- # More than one part.
- # First is module, second is method/class
- f = self.lookupmodule(parts[0])
- if f:
- fname = f
- item = parts[1]
- answer = find_function(item, fname)
- return answer or failed
-
- def defaultFile(self):
- """Produce a reasonable default."""
- filename = self.curframe.f_code.co_filename
- # Consider using is_exec_stmt(). I just don't understand
- # the conditions under which the below test is true.
- if filename == '<string>' and self.mainpyfile:
- filename = self.mainpyfile
- return filename
-
- def checkline(self, filename, lineno):
- """Check whether specified line seems to be executable.
-
- Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank
- line or EOF). Warning: testing is not comprehensive.
- """
- line = linecache.getline(filename, lineno)
- if not line:
- self.errmsg('End of file')
- return 0
- line = line.strip()
- # Don't allow setting breakpoint at a blank line
- if (not line or (line[0] == '#') or
- (line[:3] == '"""') or line[:3] == "'''"):
- self.errmsg('Blank or comment')
- return 0
- return lineno
-
def trace_dispatch(self, frame, event, arg):
- self.curframe = frame
+ self.currentframe = frame
if event == 'line':
- self.msg('%s *** line' % self.thread.getName())
return self.dispatch_line
if event == 'call':
self.msg('%s *** call' % self.thread.getName())
@@ -272,53 +61,66 @@
print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event)
return self.trace_dispatch
- def dispatch_line(self, frame, event, arg):
- print frame.f_code.co_filename, self.thread.getName()
+ def dispatch_line(self, frame, arg):
+ if self.stop_here(frame) or self.break_here(frame):
+ # Signal to the main debugger that we've hit a breakpoint
+ print 'bang'
+ _main_debugger.user_line(frame)
+ if self.quitting: raise BdbQuit
+ return self.trace_dispatch
+
+ def dispatch_call(self, frame, arg):
+ # XXX 'arg' is no longer used
+ if self.botframe is None:
+ # First call of dispatch since reset()
+ self.botframe = frame.f_back # (CT) Note that this may also be None!
+ return self.trace_dispatch
+ if not (self.stop_here(frame) or self.break_anywhere(frame)):
+ # No need to trace this function
+ return # None
+ _main_debugger.user_call(frame, arg)
+ if self.quitting: raise BdbQuit
+ return self.trace_dispatch
+
+ def dispatch_return(self, frame, arg):
+ if self.stop_here(frame) or frame == self.returnframe:
+ _main_debugger.user_return(frame, arg)
+ if self.quitting: raise BdbQuit
return self.trace_dispatch
-class ThreadDebug(object):
- def __init__(self, msg, errmsg):
- self.msg = msg
- self.errmsg = errmsg
- self.threads = []
- self.tracers = []
- self.current_thread = None
-
- def trace_dispatch_init(self, frame, event, arg):
- t = threading.currentThread()
- self.threads.append(t)
- m = MTracer(self.msg, self.errmsg)
- self.tracers.append(m)
-
- sys.settrace(m.trace_dispatch)
-
- def get_current_thread(self):
- self.msg('Current thread is %d (%s)' % \
- (self.threads.index(self.current_thread)+1,
- self.current_thread))
- return
-
- def set_current_thread(self, args):
- # XXX Switch to a different thread, although this doesn't
- # actually do anything yet.
- t_num = int(args)-1
- if t_num > len(self.threads):
- self.errmsg('Thread ID %d not known.' % t_num+1)
- return
- self.current_thread = self.threads[t_num]
- self.msg('Switching to thread %d (%s)' % (t_num+1, \
- self.current_thread))
- return
-
-def init(msg, errmsg):
- """ This method sets up thread debugging by creating a ThreadDebug
- object that creates MTracer objects for every thread that is created.
- 'msg' is a method to write standard output to, and 'errmsg' is a method
- to write error output to.
+ def dispatch_exception(self, frame, arg):
+ if self.stop_here(frame):
+ _main_debugger.user_exception(frame, arg)
+ if self.quitting: raise BdbQuit
+ return self.trace_dispatch
+
+def trace_dispatch_init(frame, event, arg):
+ """ This method is called by a sys.settrace when a thread is running
+ for the first time. Setup this thread with a tracer object and
+ set this thread's tracing function to that object's trace_dispatch
+ method.
"""
- t = ThreadDebug(msg, errmsg)
- threading.settrace(t.trace_dispatch_init)
- #sys.settrace(t.trace_dispatch_init)
+ global threads, tracers
+ threads.append(threading.currentThread())
+ t = MTracer()
+
+ tracers.append(t)
+ threading.settrace(t.trace_dispatch)
+ sys.settrace(t.trace_dispatch)
+
+
+def init(debugger):
+ """ This method intialises thread debugging. It sets up a tracing
+ method for newly created threads so that they call trace_dispatch_init,
+ which hooks them up with a MTracer object. The argument 'debugger' is
+ the debugger that is debugging the MainThread, i.e. the Python
+ interpreter.
+ """
+ global _main_debugger
+ _main_debugger = debugger
+ threading.settrace(trace_dispatch_init)
+
+
More information about the Python-checkins
mailing list