# -*- coding: utf-8 -*-
# Copyright (c) 2002, 2003 Detlev Offenbach <detlev@die-offenbachs.de>
# Copyright (c) 2000 Phil Thompson <phil@river-bank.demon.co.uk>
#
"""
Module implementing a Qt free version of the debug client.
"""
import sys
import socket
import select
import codeop
import traceback
import bdb
import os
import types
import string
from DebugProtocol import *
import PyCoverage
import PyProfile
######################################################################
##
## Copy of the definition found in Config.py
##
######################################################################
ConfigVarTypeStrings = ['__', 'NoneType', 'type',\
'int', 'long', 'float', 'complex',\
'str', 'unicode', 'tuple', 'list',\
'dict', 'dict-proxy', 'file', 'xrange',\
'slice', 'buffer', 'class', 'instance',\
'instance method', 'property', 'generator',\
'function', 'builtin_function_or_method', 'code', 'module',\
'ellipsis', 'traceback', 'frame', 'other']
DebugClientInstance = None
def printerr(s):
"""
Module function used for debugging the debug client.
Arguments
s -- the data to be printed
"""
sys.__stderr__.write('%s\n' % str(s))
sys.__stderr__.flush()
def DebugClientRawInput(prompt):
"""
Replacement for the standard raw_input builtin.
This function works with the split debugger.
"""
if DebugClientInstance is None:
return DebugClientOrigRawInput(prompt)
return DebugClientInstance.raw_input(prompt)
# Use our own raw_input().
try:
DebugClientOrigRawInput = __builtins__.__dict__['raw_input']
__builtins__.__dict__['raw_input'] = DebugClientRawInput
except:
import __main__
DebugClientOrigRawInput = __main__.__builtins__.__dict__['raw_input']
__main__.__builtins__.__dict__['raw_input'] = DebugClientRawInput
#########################################################################
##
## Modified versions of the ones found in AsyncIO.py
##
#########################################################################
def AsyncPendingWrite(file):
"""
Module function to check for data to be written.
Arguments
file -- the file object to be checked (file)
Returns
flag indicating if there is data wating (int)
"""
try:
pending = file.pendingWrite()
except:
pending = 0
return pending
class AsyncFile:
"""
Class wrapping a socket object with a file interface.
"""
def __init__(self,sock,mode,name):
"""
Constructor
Arguments
sock -- the socket object being wrapped
mode -- mode of this file (string)
name -- name of this file (string)
"""
# Initialise the attributes.
self.closed = 0
self.sock = sock
self.mode = mode
self.name = name
self.softspace = 0
self.wpending = ''
def checkMode(self,mode):
"""
Private method to check the mode.
This method checks, if an operation is permitted according to
the mode of the file. If it is not, an IOError is raised.
Arguments
mode -- the mode to be checked (string)
"""
if mode != self.mode:
raise IOError, '[Errno 9] Bad file descriptor'
def nWrite(self,n):
"""
Private method to write a specific number of pending bytes.
Arguments
n -- the number of bytes to be written (int)
"""
if n:
sent = self.sock.send(self.wpending[:n])
self.wpending = self.wpending[sent:]
def pendingWrite(self):
"""
Public method that returns the number of bytes waiting to be written.
Returns
the number of bytes to be written (int)
"""
return self.wpending.rfind('\n') + 1
def close(self):
"""
Public method to close the file.
"""
if not self.closed:
self.flush()
self.sock.close()
self.closed = 1
def flush(self):
"""
Public method to write all pending bytes.
"""
self.nWrite(len(self.wpending))
def isatty(self):
"""
Public method to indicate whether a tty interface is supported.
Returns
always false
"""
return 0
def fileno(self):
"""
Public method returning the file number.
Returns
file number (int)
"""
return self.sock.fileno()
def read(self,size=-1):
"""
Public method to read bytes from this file.
Arguments
size -- maximum number of bytes to be read (int)
Returns
the bytes read (any)
"""
self.checkMode('r')
if size < 0:
size = 20000
return self.sock.recv(size)
def readline(self,size=-1):
"""
Public method to read a line from this file.
Arguments
size -- maximum number of bytes to be read (int)
Returns
one line of text up to size bytes (string)
Note
This method will not block and may return only a part of a
line if that is all that is available.
"""
self.checkMode('r')
if size < 0:
size = 20000
# The integration of the debugger client event loop and the connection
# to the debugger relies on the two lines of the debugger command being
# delivered as two separate events. Therefore we make sure we only
# read a line at a time.
line = self.sock.recv(size,socket.MSG_PEEK)
eol = line.find('\n')
if eol >= 0:
size = eol + 1
else:
size = len(line)
# Now we know how big the line is, read it for real.
return self.sock.recv(size)
def readlines(self,sizehint=-1):
"""
Public method to read all lines from this file.
Arguments
sizehint -- hint of the numbers of bytes to be read (int)
Returns
list of lines read (list of strings)
"""
lines = []
room = sizehint
line = self.readline(room)
linelen = len(line)
while linelen > 0:
lines.append(line)
if sizehint >= 0:
room = room - linelen
if room <= 0:
break
line = self.readline(room)
linelen = len(line)
return lines
def seek(self,offset,whence=0):
"""
Public method to move the filepointer.
This method is not supported and always raises an
IOError.
"""
raise IOError, '[Errno 29] Illegal seek'
def tell(self):
"""
Public method to get the filepointer position.
This method is not supported and always raises an
IOError.
"""
raise IOError, '[Errno 29] Illegal seek'
def truncate(self,size=-1):
"""
Public method to truncate the file.
This method is not supported and always raises an
IOError.
"""
raise IOError, '[Errno 29] Illegal seek'
def write(self,str):
"""
Public method to write a string to the file.
Arguments
str -- bytes to be written (string)
"""
self.checkMode('w')
self.wpending = self.wpending + str
self.nWrite(self.pendingWrite())
def writelines(self,list):
"""
Public method to write a list of strings to the file.
Arguments
list -- the list to be written (list of string)
"""
map(self.write,list)
class AsyncIO:
"""
Class implementing asynchronous reading and writing.
"""
def __init__(self,parent=None):
"""
Constructor
Arguments
parent -- the optional parent of this object (QObject) (ignored)
"""
# There is no connection yet.
self.disconnect()
def disconnect(self):
"""
Public method to disconnect any current connection.
"""
self.readfd = None
self.writefd = None
def setDescriptors(self,rfd,wfd):
"""
Public method called to set the descriptors for the connection.
Arguments
rfd -- file descriptor of the input file (int)
wfd -- file descriptor of the output file (int)
"""
self.rbuf = ''
self.readfd = rfd
self.wbuf = ''
self.writefd = wfd
def sessionClose(self):
"""
Method to be overwritten in subclasses to do something
usefull. It is called, if a zero length string is received on
self.readstream.
"""
pass
def handleLine(self, s):
"""
Method to be overwritten in subclasses. It is called when
a line of text is received on self.readstream.
"""
pass
def readReady(self,fd):
"""
Protected method called when there is data ready to be read.
Arguments
fd -- file descriptor of the file that has data to be read (int)
"""
try:
got = self.readfd.readline()
except:
return
if len(got) == 0:
self.sessionClose()
return
self.rbuf = self.rbuf + got
# Call handleLine for the line if it is complete.
eol = self.rbuf.find('\n')
while eol >= 0:
s = self.rbuf[:eol + 1]
self.rbuf = self.rbuf[eol + 1:]
self.handleLine(s)
eol = self.rbuf.find('\n')
def writeReady(self,fd):
"""
Protected method called when we are ready to write data.
Arguments
fd -- file descriptor of the file that has data to be written (int)
"""
self.writefd.write(self.wbuf)
self.writefd.flush()
self.wbuf = ''
def write(self,s):
"""
Public method to write a string.
Arguments
s -- the data to be written (string)
"""
self.wbuf = self.wbuf + s
class DebugClient(AsyncIO,bdb.Bdb):
"""
Class implementing the client side of the debugger.
It provides access to the Python interpeter from a debugger running in another
process whether or not the Qt event loop is running.
The protocol between the debugger and the client assumes that there will be
a single source of debugger commands and a single source of Python
statements. Commands and statement are always exactly one line and may be
interspersed.
The protocol is as follows. First the client opens a connection to the
debugger and then sends a series of one line commands. A command is either
>Load<, >Step<, >StepInto<, ... or a Python statement. See DebugProtocol.py
for a listing of valid protocol tokens.
A Python statement consists of the statement to execute, followed (in a
separate line) by >OK?<. If the statement was incomplete then the response
is >Continue<. If there was an exception then the response is >Exception<.
Otherwise the response is >OK<. The reason for the >OK?< part is to
provide a sentinal (ie. the responding >OK<) after any possible output as a
result of executing the command.
The client may send any other lines at any other time which should be
interpreted as program output.
If the debugger closes the session there is no response from the client.
The client may close the session at any time as a result of the script
being debugged closing or crashing.
"""
def __init__(self):
"""
Constructor
"""
AsyncIO.__init__(self)
bdb.Bdb.__init__(self)
# The context to run the debugged program in.
self.context = {'__name__' : '__main__'}
# The list of complete lines to execute.
self.buffer = ''
self.pendingResponse = ResponseOK
self.fncache = {}
self.dircache = []
self.inRawMode = 0
self.mainProcStr = None # used for the passive mode
self.passive = 0 # used to indicate the passive mode
self.running = None
self.readstream = None
self.writestream = None
self.errorstream = None
self.stepFrame = None
# So much for portability.
if sys.platform == 'win32':
self.skipdir = sys.prefix
else:
self.skipdir = os.path.join(sys.prefix,'lib/python' + sys.version[0:3])
def raw_input(self,prompt):
"""
Public method to implement raw_input() using the event loop.
Arguments
prompt -- the prompt to be shown (string)
Returns
the entered string
"""
self.write("%s%s\n" % (ResponseRaw, prompt))
self.inRawMode = 1
self.eventLoop()
return self.rawLine
def handleException(self):
"""
Private method called in the case of an exception
It ensures that the debug server is informed of the raised exception.
"""
self.pendingResponse = ResponseException
def dispatch_return(self, frame, arg):
"""
Reimplemented from bdb.py to handle passive mode cleanly.
"""
if self.stop_here(frame) or frame == self.returnframe:
self.user_return(frame, arg)
if self.quitting and not self.passive: raise bdb.BdbQuit
return self.trace_dispatch
def dispatch_exception(self, frame, arg):
"""
Reimplemented from bdb.py to always call user_exception.
"""
self.user_exception(frame, arg)
if self.quitting: raise bdb.BdbQuit
return self.trace_dispatch
def set_continue(self):
"""
Reimplemented from bdb.py to always get informed of exceptions.
"""
# Modified version of the one found in bdb.py
# Here we leave tracing switched on in order to get
# informed of exceptions
# Don't stop except at breakpoints or when finished
self.stopframe = self.botframe
self.returnframe = None
self.quitting = 0
def fix_frame_filename(self, frame):
"""
Protected method used to fixup the filename for a given frame.
The logic employed here is that if a module was loaded
from a .pyc file, then the correct .py to operate with
should be in the same path as the .pyc. The reason this
logic is needed is that when a .pyc file is generated, the
filename embedded and thus what is readable in the code object
of the frame object is the fully qualified filepath when the
pyc is generated. If files are moved from machine to machine
this can break debugging as the .pyc will refer to the .py
on the original machine. Another case might be sharing
code over a network... This logic deals with that.
Arguments
frame -- the frame object
"""
# get module name from __file__
if frame.f_globals.has_key('__file__'):
root, ext = os.path.splitext(frame.f_globals['__file__'])
if ext == '.pyc' or ext == '.py':
fixedName = root + '.py'
if os.path.exists(fixedName):
return fixedName
return frame.f_code.co_filename
def break_here(self, frame):
"""
Reimplemented from bdb.py to fix the filename from the frame.
See fix_frame_filename for more info.
Arguments
frame -- the frame object
Returns
flag indicating the break status (boolean)
"""
filename = self.canonic(self.fix_frame_filename(frame))
if not self.breaks.has_key(filename):
return 0
lineno = frame.f_lineno
if not lineno in self.breaks[filename]:
return 0
# flag says ok to delete temp. bp
(bp, flag) = bdb.effective(filename, lineno, frame)
if bp:
self.currentbp = bp.number
if (flag and bp.temporary):
self.do_clear(str(bp.number))
return 1
else:
return 0
def break_anywhere(self, frame):
"""
Reimplemented from bdb.py to fix the filename from the frame.
See fix_frame_filename for more info.
Arguments
frame -- the frame object
Returns
flag indicating the break status (boolean)
"""
return self.breaks.has_key(
self.canonic(self.fix_frame_filename(frame)))
def sessionClose(self):
"""
Private method to close the session with the debugger and terminate.
"""
try:
self.set_quit()
except:
pass
# clean up asyncio.
self.disconnect()
# make sure we close down our end of the socket
# might be overkill as normally stdin, stdout and stderr
# SHOULD be closed on exit, but it does not hurt to do it here
self.readstream.close()
self.writestream.close()
self.errorstream.close()
# Ok, go away.
sys.exit()
def handleLine(self,line):
"""
Private method to handle the receipt of a complete line.
It first looks for a valid protocol token at the start of the line. Thereafter
it trys to execute the lines accumulated so far.
Arguments
line -- the received line
"""
# Remove any newline.
if line[-1] == '\n':
line = line[:-1]
##~ printerr(line) ##debug
eoc = line.find('<')
if eoc >= 0 and line[0] == '>':
# Get the command part and any argument.
cmd = line[:eoc + 1]
arg = line[eoc + 1:]
if cmd == RequestVariables:
frmnr, scope, filter = eval(arg)
self.dumpVariables(int(frmnr), int(scope), filter)
return
if cmd == RequestStep:
self.stepFrame = self.currentFrame
self.currentFrame = None
self.set_step()
self.eventExit = 1
return
if cmd == RequestStepOver:
self.stepFrame = self.currentFrame
self.set_next(self.currentFrame)
self.eventExit = 1
return
if cmd == RequestStepOut:
self.stepFrame = self.currentFrame
self.set_return(self.currentFrame)
self.eventExit = 1
return
if cmd == RequestStepQuit:
if self.passive:
self.progTerminated(42)
else:
self.currentFrame = None
self.set_quit()
self.eventExit = 1
return
if cmd == RequestContinue:
self.currentFrame = None
self.set_continue()
self.eventExit = 1
return
if cmd == RequestOK:
self.write(self.pendingResponse + '\n')
self.pendingResponse = ResponseOK
return
if cmd == RequestLoad:
self.fncache = {}
self.dircache = []
sys.argv = []
wd, fn, args = arg.split('|')
sys.argv.append(fn)
sys.argv.extend(args.split())
sys.path[0] = os.path.dirname(sys.argv[0])
if wd == '':
os.chdir(sys.path[0])
else:
os.chdir(wd)
self.running = sys.argv[0]
self.firstFrame = None
self.currentFrame = None
self.inRawMode = 0
# set the system exception handling function to ensure, that
# we report on all unhandled exceptions
sys.excepthook = self.unhandled_exception
# This will eventually enter a local event loop.
# Note the use of backquotes to cause a repr of self.running. The
# need for this is on Windows os where backslash is the path separator.
# They will get inadvertantly stripped away during the eval causing IOErrors
# if self.running is passed as a normal str.
self.run('execfile(' + `self.running` + ')',self.context)
return
if cmd == RequestRun:
sys.argv = []
wd, fn, args = arg.split('|')
sys.argv.append(fn)
sys.argv.extend(args.split())
sys.path[0] = os.path.dirname(sys.argv[0])
if wd == '':
os.chdir(sys.path[0])
else:
os.chdir(wd)
# set the system exception handling function to ensure, that
# we report on all unhandled exceptions
sys.excepthook = self.unhandled_exception
execfile(sys.argv[0], self.context)
return
if cmd == RequestCoverage:
sys.argv = []
wd, fn, args, erase = arg.split('|')
sys.argv.append(fn)
sys.argv.extend(args.split())
sys.path[0] = os.path.dirname(sys.argv[0])
if wd == '':
os.chdir(sys.path[0])
else:
os.chdir(wd)
# set the system exception handling function to ensure, that
# we report on all unhandled exceptions
sys.excepthook = self.unhandled_exception
# generate a coverage object
self.cover = PyCoverage.coverage(sys.argv[0])
# register an exit handler to save the collected data
try:
import atexit
atexit.register(self.cover.save)
except ImportError:
sys.exitfunc = self.cover.save
if int(erase):
self.cover.erase()
self.cover.start()
execfile(sys.argv[0], self.context)
return
if cmd == RequestProfile:
sys.argv = []
wd, fn, args, erase = arg.split('|')
sys.argv.append(fn)
sys.argv.extend(args.split())
sys.path[0] = os.path.dirname(sys.argv[0])
if wd == '':
os.chdir(sys.path[0])
else:
os.chdir(wd)
# set the system exception handling function to ensure, that
# we report on all unhandled exceptions
sys.excepthook = self.unhandled_exception
# generate a profile object
self.prof = PyProfile.PyProfile(sys.argv[0])
if int(erase):
self.prof.erase()
self.prof.run('execfile(' + `sys.argv[0]` + ',' + `self.context` + ')')
return
if cmd == RequestShutdown:
self.sessionClose()
return
if cmd == RequestBreak:
fn, line, set, cond = arg.split(',')
line = int(line)
set = int(set)
if set:
if cond == 'None':
cond = None
self.set_break(fn,line, 0, cond)
else:
self.clear_break(fn,line)
return
if cmd == RequestEval:
try:
value = eval(arg, self.currentFrame.f_globals,
self.currentFrame.f_locals)
except:
# Report the exception and the traceback
try:
type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
tblist = traceback.extract_tb(tb)
del tblist[:1]
list = traceback.format_list(tblist)
if list:
list.insert(0, "Traceback (innermost last):\n")
list[len(list):] = traceback.format_exception_only(type, value)
finally:
tblist = tb = None
map(self.write,list)
self.write(ResponseException + '\n')
else:
self.write(str(value) + '\n')
self.write(ResponseOK + '\n')
return
if cmd == RequestExec:
globals = self.currentFrame.f_globals
locals = self.currentFrame.f_locals
try:
code = compile(arg + '\n', '<stdin>', 'single')
exec code in globals, locals
except:
# Report the exception and the traceback
try:
type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
tblist = traceback.extract_tb(tb)
del tblist[:1]
list = traceback.format_list(tblist)
if list:
list.insert(0, "Traceback (innermost last):\n")
list[len(list):] = traceback.format_exception_only(type, value)
finally:
tblist = tb = None
map(self.write,list)
self.write(ResponseException + '\n')
return
if cmd == RequestBanner:
self.write('%s%s\n' % (ResponseBanner,
str((sys.version, sys.platform, 'No Qt-Version'))))
return
# If we are handling raw mode input then reset the mode and break out
# of the current event loop.
if self.inRawMode:
self.inRawMode = 0
self.rawLine = line
self.eventExit = 1
return
if self.buffer:
self.buffer = self.buffer + '\n' + line
else:
self.buffer = line
try:
code = codeop.compile_command(self.buffer,self.readstream.name)
except (OverflowError, SyntaxError, ValueError):
# Report the exception
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
map(self.write,traceback.format_exception_only(sys.last_type,sys.last_value))
self.buffer = ''
self.handleException()
else:
if code is None:
self.pendingResponse = ResponseContinue
else:
self.buffer = ''
try:
if self.running is None:
exec code in self.context
else:
globals = self.currentFrame.f_globals
locals = self.currentFrame.f_locals
exec code in globals, locals
except SystemExit:
self.sessionClose()
except:
# Report the exception and the traceback
try:
type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
tblist = traceback.extract_tb(tb)
del tblist[:1]
list = traceback.format_list(tblist)
if list:
list.insert(0, "Traceback (innermost last):\n")
list[len(list):] = traceback.format_exception_only(type, value)
finally:
tblist = tb = None
map(self.write,list)
self.handleException()
def write(self,s):
"""
Private method to write data to the output stream.
Arguments
s -- data to be written (string)
"""
self.writestream.write(s)
self.writestream.flush()
def interact(self):
"""
Private method to Interact with the debugger.
Returns
Never
"""
global DebugClientInstance
self.setDescriptors(self.readstream,self.writestream)
DebugClientInstance = self
if not self.passive:
self.eventLoop()
def eventLoop(self):
"""
Private method implementing our event loop.
"""
self.eventExit = None
while self.eventExit is None:
wrdy = []
if AsyncPendingWrite(self.writestream):
wrdy.append(self.writestream)
if AsyncPendingWrite(self.errorstream):
wrdy.append(self.errorstream)
rrdy, wrdy, xrdy = select.select([self.readstream],wrdy,[])
if self.readstream in rrdy:
self.readReady(self.readstream.fileno())
if self.writestream in wrdy:
self.writeReady(self.writestream.fileno())
if self.errorstream in wrdy:
self.writeReady(self.errorstream.fileno())
self.eventExit = None
def connectDebugger(self,port,remoteAddress=None):
"""
Public method to establish a session with the debugger.
It opens a network connection to the debugger, connects it to stdin,
stdout and stderr and saves these file objects in case the application
being debugged redirects them itself.
Arguments
port -- the port number to connect to (int)
remoteAddress -- the network address of the debug server host (string)
"""
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
if remoteAddress is None:
sock.connect((DebugAddress,port))
else:
sock.connect((remoteAddress,port))
sock.setblocking(0)
sys.stdin = AsyncFile(sock,sys.stdin.mode,sys.stdin.name)
sys.stdout = AsyncFile(sock,sys.stdout.mode,sys.stdout.name)
sys.stderr = AsyncFile(sock,sys.stderr.mode,sys.stderr.name)
# save the stream in case a program redirects them
self.readstream = sys.stdin
self.writestream = sys.stdout
self.errorstream = sys.stderr
def user_line(self,frame):
"""
Reimplemented to handle the program about to execute a particular line.
Arguments
frame -- the frame object
"""
line = frame.f_lineno
# We never stop an line 0.
if line == 0:
return
fn = self.absPath(self.fix_frame_filename(frame))
# See if we are skipping at the start of a newly loaded program.
if self.firstFrame is None:
if fn != self.running:
return
self.firstFrame = frame
self.currentFrame = frame
fr = frame
stack = []
while fr is not None:
#
# Reset the trace function so we can be sure
# to trace all functions up the stack... This gets around
# problems where an exception/breakpoint has occurred
# but we had disabled tracing along the way via a None
# return from dispatch_call
fr.f_trace = self.trace_dispatch
fname = self.absPath(self.fix_frame_filename(fr))
fline = fr.f_lineno
ffunc = fr.f_code.co_name
if ffunc == '?':
ffunc = ''
stack.append((fname, fline, ffunc))
if fr == self.firstFrame:
fr = None
else:
fr = fr.f_back
self.write('%s%s\n' % (ResponseLine,str(stack)))
self.eventLoop()
def unhandled_exception(self, exctype, excval, exctb):
"""
Private method called to report an uncaught exception.
Arguments
exctype -- the type of the exception
excval -- data about the exception
exctb -- traceback for the exception
"""
self.user_exception(None, (exctype,excval,exctb), 1)
def user_exception(self,frame,(exctype,excval,exctb),unhandled=0):
"""
Reimplemented to report an exception to the debug server.
Arguments
frame -- the frame object
exctype -- the type of the exception
excval -- data about the exception
exctb -- traceback for the exception
unhandled -- flag indicating an uncaught exception
"""
if exctype == SystemExit:
self.progTerminated(excval)
elif exctype in [SyntaxError, IndentationError]:
try:
message, (filename, linenr, charnr, text) = excval
except:
exclist = []
else:
exclist = [message, (filename, linenr, charnr)]
self.write("%s%s\n" % (ResponseSyntax, str(exclist)))
else:
self.currentFrame = frame
if type(exctype) == types.ClassType:
exctype = exctype.__name__
if excval is None:
excval = ''
if unhandled:
exclist = ["unhandled %s" % str(exctype), str(excval)]
else:
exclist = [str(exctype), str(excval)]
frlist = self.extract_stack(exctb)
frlist.reverse()
for fr in frlist:
filename = self.absPath(self.fix_frame_filename(fr))
linenr = fr.f_lineno
exclist.append((filename, linenr))
self.write("%s%s\n" % (ResponseException, str(exclist)))
self.eventLoop()
def extract_stack(self, exctb):
"""
Protected member to return a list of stack frames.
Arguments
exctb -- exception traceback
Returns
list of stack frames
"""
tb = exctb
stack = []
while tb is not None:
stack.append(tb.tb_frame)
tb = tb.tb_next
tb = None
return stack
def user_return(self,frame,retval):
"""
Reimplemented to report program termination to the debug server.
Arguments
frame -- the frame object
retval -- the return value of the program
"""
# The program has finished if we have just left the first frame.
if frame == self.firstFrame:
self.progTerminated(retval)
elif frame is not self.stepFrame:
self.stepFrame = None
self.user_line(frame)
def stop_here(self,frame):
"""
Reimplemented to filter out debugger files.
Tracing is turned off for files that are part of the
debugger that are called from the application being debugged.
Arguments
frame -- the frame object
Returns
flag indicating whether the debugger should stop here
"""
fn = self.fix_frame_filename(frame)
# Eliminate things like <string> and <stdin>.
if fn[0] == '<':
return 0
#XXX - think of a better way to do this. It's only a convience for
#debugging the debugger - when the debugger code is in the current
#directory.
if os.path.basename(fn) in ['DebugClientNoQt.py']:
return 0
# Eliminate anything that is part of the Python installation.
#XXX - this should be a user option, or an extension of the meaning of
# 'step into', or a separate type of step altogether, or have a
#configurable set of ignored directories.
afn = self.absPath(fn)
if self.skipdir is not None and afn.find(self.skipdir) == 0:
return 0
return bdb.Bdb.stop_here(self,frame)
def absPath(self,fn):
"""
Private method to convert a filename to an absolute name.
sys.path is used as a set of possible prefixes. The name stays
relative if a file could not be found.
Arguments
fn -- filename (string)
Returns
the converted filename (string)
"""
if os.path.isabs(fn):
return fn
# Check the cache.
if self.fncache.has_key(fn):
return self.fncache[fn]
# Search sys.path.
for p in sys.path:
afn = os.path.abspath(os.path.join(p,fn))
if os.path.exists(afn):
self.fncache[fn] = afn
d = os.path.dirname(afn)
if (d not in sys.path) and (d not in self.dircache):
self.dircache.append(d)
return afn
# Search the additional directory cache
for p in self.dircache:
afn = os.path.abspath(os.path.join(p,fn))
if os.path.exists(afn):
self.fncache[fn] = afn
return afn
# Nothing found.
return fn
def progTerminated(self,status):
"""
Private method to tell the debugger that the program has terminated.
Arguments
status -- the return status
"""
if status is None:
status = 0
else:
try:
int(status)
except:
status = 1
self.set_quit()
self.running = None
self.write('%s%d\n' % (ResponseExit,status))
def dumpVariables(self, frmnr, scope, filter):
"""
Private method to return the variables of a frame to the debug server.
Arguments
frmnr -- distance of frame reported on. 0 is the current frame (int)
scope -- 1 to report global variables, 0 for local variables (int)
filter -- the indices of variable types to be filtered (list of int)
"""
f = self.currentFrame
while f is not None and frmnr > 0:
f = f.f_back
frmnr -= 1
if f is None:
return
if scope:
dict = f.f_globals
else:
dict = f.f_locals
if f.f_globals is f.f_locals:
scope = -1
varlist = [scope]
if scope != -1:
keylist = dict.keys()
(vlist, klist) = self.formatVariablesList(keylist, dict, filter)
varlist = varlist + vlist
if len(klist):
for key in klist:
cdict = None
try:
cdict = dict[key].__dict__
except:
try:
slv = dict[key].__slots__
cdict = {}
for v in slv:
exec 'cdict[v] = dict[key].%s' % v
except:
pass
if cdict is not None:
keylist = cdict.keys()
(vlist, klist) = \
self.formatVariablesList(keylist, cdict, filter, 1, key)
varlist = varlist + vlist
self.write('%s%s\n' % (ResponseVariables, str(varlist)))
def formatVariablesList(self, keylist, dict, filter = [], classdict = 0, prefix = ''):
"""
Private method to produce a formated variables list.
The dictionary passed in to it is scanned. If classdict is false,
it builds a list of all class instances in dict. If it is
true, we are formatting a class dictionary. In this case
we prepend prefix to the variable names. Variables are
only added to the list, if their type is not contained
in the filter list. The formated variables list (a list of
tuples of 3 values) and the list of class instances is returned.
Arguments
keylist -- keys of the dictionary
dict -- the dictionary to be scanned
filter -- the indices of variable types to be filtered. Variables are
only added to the list, if their type is not contained
in the filter list.
classdict -- boolean indicating the formating of a class or
module dictionary. If classdict is false,
it builds a list of all class instances in dict. If it is
true, we are formatting a class dictionary. In this case
we prepend prefix to the variable names.
prefix -- prefix to prepend to the variable names (string)
Returns
A tuple consisting of a list of formatted variables and a list of
class instances. Each variable entry is a tuple of three elements,
the variable name, its type and value.
"""
varlist = []
classes = []
for key in keylist:
# filter hidden attributes (filter #0)
if 0 in filter and str(key)[:2] == '__':
continue
# special handling for '__builtins__' (it's way too big)
if key == '__builtins__':
rvalue = '<module __builtin__ (built-in)>'
valtype = 'module'
else:
value = dict[key]
valtypestr = str(type(value))[1:-1]
if string.split(valtypestr,' ',1)[0] == 'class':
# handle new class type of python 2.2+
if ConfigVarTypeStrings.index('instance') in filter:
continue
valtype = valtypestr[7:-1]
classes.append(key)
else:
valtype = valtypestr[6:-1]
try:
if ConfigVarTypeStrings.index(valtype) in filter:
continue
if valtype in ['instance', 'module']:
classes.append(key)
except ValueError:
if ConfigVarTypeStrings.index('other') in filter:
continue
try:
rvalue = repr(value)
except:
rvalue = ''
if classdict:
key = prefix + '.' + key
varlist.append((key, valtype, rvalue))
return (varlist, classes)
def startDebugger(self, filename=None, host=None, port=None):
"""
Method used to start the remote debugger.
Arguments
filename -- the program to be debugged (string)
host -- hostname of the debug server (string)
port -- portnumber of the debug server (int)
"""
global debugClient
if host is None:
host = os.getenv('ERICHOST', 'localhost')
if port is None:
port = os.getenv('ERICPORT', 42424)
self.connectDebugger(port, socket.gethostbyname(host))
if filename is not None:
self.running = os.path.abspath(filename)
else:
try:
self.running = os.path.abspath(sys.argv[0])
except:
pass
self.passive = 1
self.write(PassiveStartup + '\n')
self.interact()
# setup the debugger variables
self.fncache = {}
self.dircache = []
self.firstFrame = None
self.currentFrame = None
self.inRawMode = 0
# set the system exception handling function to ensure, that
# we report on all unhandled exceptions
sys.excepthook = self.unhandled_exception
# now start debugging
self.set_trace()
# We are normally called by the debugger to execute directly.
if __name__ == '__main__':
try:
port = int(sys.argv[1])
except:
port = -1
try:
remoteAddress = sys.argv[2]
except:
remoteAddress = None
sys.argv = ['']
sys.path[0] = ''
debugClient = DebugClient()
if port >= 0:
debugClient.connectDebugger(port, remoteAddress)
debugClient.interact()
|