#!/usr/bin/env python
# Copyright (c) 2003-2006 ActiveState Software Inc.
#
# The MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished
# to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#
# Authors:
# Shane Caraveo <ShaneC@ActiveState.com>
# Trent Mick <TrentM@ActiveState.com>
import sys
if sys.hexversion < 0x020000F0:
exe = sys.executable
ver = '.'.join(map(str, sys.version_info[:3]))
sys.stderr.write("The dbgpClient must be run with Python "
"version 2.0 or greater. Your current "
"python, '%s', is version '%s'\n"
% (exe, ver))
sys.exit(1)
import socket, copy
import thread, threading
# command line host driver
import getopt, os, types, StringIO, Queue
import traceback, re
import base64, urlparse, urllib
import dbgp.listcmd as listcmd
from dbgp.common import *
import dbgp.common
dbgp.common.__builtins__['DBGPHideChildren'] = 0
try:
import logging
except ImportError:
from dbgp import _logging as logging
log = logging.getLogger("dbgp.client")
# Import the client support module (implemented in C for some Python
# versions for speed).
if sys.hexversion < 0x02020000:
# We don't compile _clientXY for pre-2.2 Python.
from dbgp._pyclient import *
else:
major = sys.hexversion >> 24
minor = (sys.hexversion & 0x00ff0000) >> 16
modname = "_client%s%s" % (major, minor)
try:
# Try importing our fast C module.
import imp
import dbgp
info = imp.find_module(modname, dbgp.__path__)
_client = imp.load_module(modname, *info)
sys.modules["_client"] = _client
from _client import *
del sys.modules["_client"], info, _client
except ImportError, ex:
#configureLogging(log, logging.INFO)
#log.exception(ex)
# Fallback to pure Python support module.
from dbgp._pyclient import *
del major, minor, modname
# prevent internal DBGP threads from getting debugged. Any thread started
# from DBGP for debugger purposes should use _nonDebugThread
# to start threads with.
_nonDebugThread = thread.start_new_thread
# base types do not have children
BaseTypes = [types.StringType,
types.UnicodeType,
types.IntType,
types.LongType,
types.FloatType]
if sys.hexversion >= 0x020300F0:
BaseTypes.append(type(True))
StringTypes = [types.StringType,
types.UnicodeType]
HiddenTypes = [types.BuiltinMethodType,
types.BuiltinFunctionType,
types.FunctionType,
types.UnboundMethodType,
types.TypeType,
types.ClassType,
types.ModuleType]
if hasattr('', '__add__'):
HiddenTypes.append(type(''.__add__))
# these are the names of the different contexts available from python, which show up
# in Komodo as variable tabs in the debugger pane. contexts after the first are
# based the global namespace, and show specificly the variable types listed in
# the hiddenContextTypes list. The first context is always the local context.
# the local context behaves differently than the global contexts. It will show the
# types in the list IN ADDITION to regular variables. The global contexts will exclude
# any type not in the list.
contextNames = ['Locals', 'Globals', 'Code Objects']
hiddenContextTypes = [[], [], HiddenTypes]
startupDir = os.getcwd()
if os.name == 'java': # for jython
# Check if we are running a recent enough version of jython
# Currently we have problems with v<2.1
if not hasattr(sys, 'version_info') or int(''.join(map(str, sys.version_info[0:3]))) < 210:
print 'Jython debugging is not support for Jython versions below 2.1'
sys.exit(1)
# Check if we are running on windows
import java.lang.System
isWindows = java.lang.System.getProperty("os.name").lower()[0:3] == 'win'
else:
isWindows = sys.platform[0:3].lower() == 'win'
def url2pathname( url ):
decomposedURL = urlparse.urlparse( url, 'file:///' )
path = decomposedURL[2]
# The path will begin with a superfluous '/' in this case
if ( url.strip().lower()[0:8] == 'file:///' and isWindows):
path = path[1:]
if os.name == 'java' and isWindows:
# because jython's url2path does not correctly detect the platform,
# we need to import nturl2path explicitly
import nturl2path
return nturl2path.url2pathname(path)
else:
return urllib.url2pathname( path )
def pathname2url( pathname ):
if os.name == 'java' and isWindows:
# because jython's url2path does not correctly detect the platform,
# we need to import nturl2path explicitly
import nturl2path
return 'file:' + nturl2path.pathname2url( os.path.abspath( pathname ) )
else:
return 'file:' + urllib.pathname2url( os.path.abspath( pathname ) )
# Only used by jython
def findOpenPort(start, retries):
"""findOpenPort(9000) => 9002
Return the first open port greater or equal to the specified one."""
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
for i in range(retries):
port = start+i
try:
test_socket.bind(('',port))
return port
except socket.error:
pass
raise "Could not find open port from %d to %d." % (start, start + retries)
# duplicated from xml.sax.saxutils to avoid sax requirement
def escape(data, entities={}):
"""Escape &, <, and > in a string of data.
You can escape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
data = data.replace("&", "&")
data = data.replace("<", "<")
data = data.replace(">", ">")
for chars, entity in entities.items():
data = data.replace(chars, entity)
return data
# duplicated from xml.sax.saxutils to avoid sax requirement
def quoteattr(data, entities={}):
"""Escape and quote an attribute value.
Escape &, <, and > in a string of data, then quote it for use as
an attribute value. The \" character will be escaped as well, if
necessary.
You can escape other strings of data by passing a dictionary as
the optional entities parameter. The keys and values must all be
strings; each key will be replaced with its corresponding value.
"""
if type(data) not in StringTypes:
return '"%s"' % str(data)
data = escape(data, entities)
if '"' in data:
if "'" in data:
data = '"%s"' % data.replace('"', """)
else:
data = "'%s'" % data
else:
data = '"%s"' % data
return data
def _getAttrStr(attrs):
"""Construct an XML-safe attribute string from the given attributes
"attrs" is a dictionary of attributes
The returned attribute string includes a leading space, if necessary,
so it is safe to use the string right after a tag name.
"""
s = ''
for attr, value in attrs.items():
s += ' %s=%s' % (attr, quoteattr(value))
return s
def _get_stack_data(item):
frame, lineno = item
where = None
if frame.f_code.co_name == '?':
if frame.f_locals.has_key('__name__'):
where = frame.f_locals['__name__']
elif frame.f_globals.has_key('__name__'):
where = frame.f_globals['__name__']
if not where:
where = frame.f_code.co_name or '?'
codeFileName = frame.f_code.co_filename
if codeFileName.startswith('<'):
filename = codeFileName
lineno = 0
elif os.path.isfile(codeFileName):
filename = codeFileName
else:
# OK, what probably happened is that the .py[c,o] file
# was relocated and no longer points to
# a correct source file. We will try to find the source
# file in the same directory as the
# .py[c,o] file.
if not frame.f_globals.has_key('__file__'):
# This actually seems possible based on import.c
filename = codeFileName
else:
moduleFileName = frame.f_globals['__file__']
if os.path.isabs(moduleFileName):
filename = os.path.join(
os.path.dirname(moduleFileName),
os.path.basename(codeFileName))
else:
# Assume that all module imports were relative
# the the current directory at kdb startup.
# This should work most of the time. Heck, it
# might not even be necessary because, due
# to the compile, all paths seem to be absolute.
filename = os.path.join(startupDir,
os.path.dirname(moduleFileName),
os.path.basename(codeFileName))
return (filename, lineno, where)
def _stack_repr(stack):
level = 0;
for item in stack:
level = level + 1
(filename, lineno, where) = _get_stack_data(item)
print "%d %s:%d:%s" % (level,filename, lineno, where)
def _hide_stack(f):
return DBGPDebugDebugger == 0 and f.f_globals.has_key('DBGPHide')
def _print_tb(tb, limit=None, file=None):
"""Print up to 'limit' stack trace entries from the traceback 'tb'.
If 'limit' is omitted or None, all entries are printed. If 'file'
is omitted or None, the output goes to sys.stderr; otherwise
'file' should be an open file or file-like object with a write()
method.
"""
import linecache
if not file:
file = sys.stderr
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
orig_tb = tb
while tb is not None and (limit is None or n < limit):
if DBGPFullTraceback or not _hide_stack(tb.tb_frame):
(filename, lineno, name) = _get_stack_data((tb.tb_frame, tb.tb_lineno))
file.write(' File "%s", line %d, in %s\n' % (filename,lineno,name))
line = linecache.getline(filename, lineno)
if line: file.write(' %s\n' % line.strip())
n = n+1
tb = tb.tb_next
if n < 1:
# exception happened in dbgp, and there are no stacks for the user, lets just
# do a regular print_tb
traceback.print_tb(orig_tb, limit, file)
def _print_exception(etype, value, tb, limit=None, file=None):
"""Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
This differs from print_tb() in the following ways: (1) if
traceback is not None, it prints a header "Traceback (most recent
call last):"; (2) it prints the exception type and value after the
stack trace; (3) if type is SyntaxError and value has the
appropriate format, it prints the line where the syntax error
occurred with a caret on the next line indicating the approximate
position of the error.
"""
if not file:
file = sys.stderr
if tb:
file.write('Traceback (most recent call last):\n')
_print_tb(tb, limit, file)
lines = traceback.format_exception_only(etype, value)
for line in lines[:-1]:
file.write(line+' ')
file.write(lines[-1])
def _print_exc(limit=None, file=None):
"""Shorthand for 'print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)'.
(In fact, it uses sys.exc_info() to retrieve the same information
in a thread-safe way.)"""
if not file:
file = sys.stderr
try:
etype, value, tb = sys.exc_info()
_print_exception(etype, value, tb, limit, file)
finally:
etype = value = tb = None
def _code_repr(code,name=None):
if code:
if name is None: name = code.co_name
if code.co_filename:
fname = os.path.split(code.co_filename)[1]
else:
fname = "??"
v = Property('code','code',code)
v.encoding = None
return v.toxml()
return "Code %s: args: %r %s,%d (%r)" % (name,
code.co_varnames[:code.co_argcount],
fname,
code.co_firstlineno,
code)
else:
return "Code %s: None" % (name,)
def _frame_repr(frame,name=None):
if name is None: name = ""
if frame:
if frame.f_code and frame.f_code.co_filename:
fname = os.path.split(frame.f_code.co_filename)[1]
else:
fname = "??"
v = Property('frame','frame',frame)
v.encoding = None
return v.toxml()
else:
return "Frame %s: None" % (name,)
def _get_object_type_string(value):
try:
return type(value).__name__
except AttributeError:
return str(type(value))
def _format_exception_only():
try:
# Assume exception can be formatted as a list of with single element, with a trailing \n.
return traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0][:-1]
except IndexError:
return str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1])
def _frame_name_dump(frame):
if frame.f_globals.has_key('__name__'):
return "%s at %s:%d" % (frame.f_globals['__name__'],
frame.f_code.co_filename,
frame.f_lineno)
else:
return "?? at %s:%d" % (frame.f_code.co_filename,
frame.f_lineno)
def _safe_apply(what_failed, func, *args):
try:
return apply(func, args)
except:
return "%s failed - %s: %s" % (what_failed, sys.exc_info()[0], sys.exc_info()[1])
def _safe_index(what_failed, ob, index):
try:
return ob[index]
except:
return "%s[%s] failed - %s: %s" % (what_failed, index, sys.exc_info()[0], sys.exc_info()[1])
classre = re.compile('<(?P<fullname>.*?\.(?P<name>.*?))\s')
def _class_names(ob):
# returns a dictionary like:
# {'fullname': 'exceptions.Exception', 'name': 'Exception'}
try:
return classre.match(repr(ob)).groupdict()
except:
return None
# fix getopt. we don't care if extra options are passed in, lets
# be as lenient as possible
def short_has_arg(opt, shortopts):
for i in range(len(shortopts)):
if opt == shortopts[i] != ':':
return shortopts.startswith(':', i+1)
return 0
getopt.short_has_arg = short_has_arg
# we have to execute our debugging scripts under a __main__ module.
# Unfortunately, we're already using it. So we create a fake __main__ with imp,
# and use ihooks to prevent a script for accessing the 'real' __main__. However,
# if the script imported dbgpClient.brk, then it controls main, and we don't
# want to do ihooks, so all the h_e* functions will work without our magic main
# module.
try:
import ihooks
Hooks = ihooks.Hooks
_verbose = ihooks.VERBOSE
ihooks.DBGPHide = 1
except ImportError:
# Jython doesn't have ihooks. We put in a placeholder
# for now. This will limit what jython debugging can do.
_verbose = 0
class Hooks:
def __init__(self, verbose = _verbose):
self.verbose = verbose
def modules_dict(self): return sys.modules
pass
import imp
class h_main:
"""
h_main creates a module that can be used for exec'ing scripts with their own
__main__ module. This should be created seperately, and provided as a
parameter to the h_exec* functions below
"""
def __init__(self, main_name='__main__'):
import __main__
self.name = main_name
self.module = imp.new_module(main_name)
if hasattr(__main__, '__builtins__'): # not Jython
self.module.__builtins__ = __main__.__builtins__
self.locals = self.globals = self.module.__dict__
class h_base(Hooks):
def setModule(self, module):
d = self.modules_dict()
if d.has_key(module.name):
self.old_main = d[module.name]
d[module.name] = module.module
def resetModule(self, module):
d = self.modules_dict()
if self.old_main:
d[module.name] = self.old_main
def setSysInfo(self, mysys, file, args):
mysys.argv = args
if os.path.dirname(file) not in mysys.path:
if mysys.path[0] == '':
mysys.path.insert(1, os.path.dirname(file))
else:
mysys.path.insert(0, os.path.dirname(file))
class h_exec(h_base):
"""
h_exec(code, globals, locals, module)
This class should be called like a function and no reference to it should be
maintained. The module parameter is a h_main instance. It emulates exec
using a special __main__ module so that scripts depending on the use of
__main__ will operate correctly.
"""
def __init__(self, code, globals=None, locals=None, module=None, verbose = _verbose, tracer=None):
h_base.__init__(self, verbose)
if not isinstance(code, types.CodeType):
code = code+'\n'
if module:
h_base.__init__(self, verbose)
if not globals:
globals = module.globals
if not locals:
locals = module.locals
self.setModule(module)
else:
if not globals:
globals = {}
if not locals:
locals = globals
try:
if tracer:
tracer.starttrace()
exec code in globals, locals
finally:
if tracer:
tracer.stoptrace()
if module:
self.resetModule(module)
class h_eval(h_base):
"""
h_eval(code, globals, locals, module)
This class should be called like a function and no reference to it should be
maintained. TThe module parameter is a h_main instance. It emulates eval
using a special __main__ module so that scripts depending on the use of
__main__ will operate correctly.
"""
def __init__(self, code, globals=None, locals=None, module=None, verbose = _verbose, tracer=None):
h_base.__init__(self, verbose)
if not isinstance(code, types.CodeType):
code = code+'\n'
if module:
h_base.__init__(self, verbose)
if not globals:
globals = module.globals
if not locals:
locals = module.locals
self.setModule(module)
else:
if not globals:
globals = {}
if not locals:
locals = globals
try:
if tracer:
tracer.starttrace()
return eval(code, globals, locals)
finally:
if tracer:
tracer.stoptrace()
if module:
self.resetModule(module)
class h_execfile(h_base):
"""
h_execfile(filename, args, module)
This class should be called like a function and no reference to it should be
maintained. The module parameter is a h_main instance. It emulates execfile
using a special __main__ module so that scripts depending on the use of
__main__ will operate correctly.
"""
def __init__(self, file, args, globals=None, locals=None, module=None, verbose = _verbose, tracer=None):
if module:
h_base.__init__(self, verbose)
d = self.modules_dict()
self.setSysInfo(d['sys'], file, args)
# add __file__, bug 43703
if module.name == '__main__' and not hasattr(module.module,'__file__'):
module.module.__file__ = file
if not globals:
globals = module.globals
if not locals:
locals = module.locals
self.setModule(module)
else:
if not globals:
globals = {}
if not locals:
locals = globals
self.setSysInfo(sys, file, args)
try:
exec "import site\n\n" in globals, locals
del globals['site']
if tracer:
tracer.starttrace()
execfile(file, globals, locals)
finally:
if tracer:
tracer.stoptrace()
if module:
self.resetModule(module)
class StreamOut:
# this class is used for copying/redirecting the
# stdout and stderr streams
# XXX this may not be necessary:
# we use cStringIO to provide the typical file functions, then on any
# write we clear the buffer and send the entire buffer.
def __init__(self, name, origStream, client, redirect = 0):
self.__dict__['_name'] = name
self.__dict__['_client'] = client
self.__dict__['_redirect'] = redirect
self.__dict__['_origStream'] = origStream
def stop(self):
return self._origStream
def write(self, s):
global DBGPHideChildren
origDBGPHideChildren = DBGPHideChildren
DBGPHideChildren = DBGPDebugDebugger is not 2
try:
if type(s)==types.UnicodeType:
s = s.encode('UTF-8')
if not self._redirect:
self._origStream.write(s)
self._client.send_stream(self._name, s)
finally:
DBGPHideChildren = origDBGPHideChildren
def writelines(self, lines):
text = ''.join(lines)
self.write(text)
def __getattr__(self, attr):
if self.__dict__.has_key(attr):
return getattr(self,attr)
return getattr(self._origStream, attr)
class StreamStdin:
# this class is used for doing a notification of stdin reads
def __init__(self, origStream, client):
self.__dict__['_client'] = client
self.__dict__['_origStream'] = origStream
def stop(self):
return self._origStream
def read(self, size=-1):
self._client.notify('stdin')
return self._origStream.read(size)
def readline(self, size=-1):
self._client.notify('stdin')
return self._origStream.readline(size)
def readlines(self, size=-1):
self._client.notify('stdin')
return self._origStream.readlines(size)
def __getattr__(self, attr):
if self.__dict__.has_key(attr):
return getattr(self,attr)
return getattr(self._origStream, attr)
class StreamIn(StringIO.StringIO):
def __init__(self, origStream, client):
self.__dict__['_origStream'] = origStream
StringIO.StringIO.__init__(self, '')
self.__dict__['_read_cv'] = threading.Condition()
self.__dict__['_client'] = client
def __getattr__(self, attr):
if self.__dict__.has_key(attr):
return getattr(self,attr)
return getattr(self._origStream, attr)
def stop(self):
if not self.closed:
self.write('')
StringIO.StringIO.close(self)
return self._origStream
def write(self, s):
if self.closed:
raise ValueError, "I/O operation on closed file"
self._read_cv.acquire()
try:
# always write to the end
StringIO.StringIO.seek(self, 0, 2)
StringIO.StringIO.write(self, s)
self._read_cv.notify()
finally:
self._read_cv.release()
def read(self, size = -1):
if self.closed:
raise ValueError, "I/O operation on closed file"
self._read_cv.acquire()
try:
if not self.buf:
# no buffer available, block until we have something
self._client.notify('stdin')
self._read_cv.wait()
# always read from the start
if self.closed:
return None
StringIO.StringIO.seek(self, 0)
x = StringIO.StringIO.read(self, size)
# remove what we read from the stream
# this ensures that reading from the beginning
# will always give us new data, we're not in the
# business of data storage here.
self.buf = self.buf[self.pos:]
finally:
self._read_cv.release()
return x
def readline(self, size = None):
if self.closed:
raise ValueError, "I/O operation on closed file"
self._read_cv.acquire()
try:
if not self.buf:
# no buffer available, block until we have something
self._client.notify('stdin')
self._read_cv.wait()
# always read from the start
if self.closed:
return None
StringIO.StringIO.seek(self, 0)
x = StringIO.StringIO.readline(self, size)
# remove what we read from the stream
# this ensures that reading from the beginning
# will always give us new data, we're not in the
# business of data storage here.
self.buf = self.buf[self.pos:]
finally:
self._read_cv.release()
return x
if sys.hexversion < 0x020300F0:
# we only replace the original queue class if we're
# running less than Python 2.3. This allows us to
# have a timeout so that the debugger threads do not
# lock up on us if we attempt to kill the session
# XXX for some reason, the Queue.Queue.get function from
# 2.3 sources is a DOG under 2.2. This code essentially
# does the same thing but works great under 2.2.
class TimedQueue(Queue.Queue):
"""TimedQueue enhanced Queue with wait timeouts
The regular Queue only provides blocking or non-blocking
access. We need blocking with a timeout.
"""
def __init__(self, maxsize=0):
Queue.Queue.__init__(self, maxsize)
self._waitCondition = threading.Condition()
def put(self, item, block=1):
# all we want to do here is notify blocking get
# calls that we've put something in. This releases
# the condition faster. We don't need a timeout
# in the put, only in get.
self._waitCondition.acquire()
try:
Queue.Queue.put(self, item, block)
self._waitCondition.notify()
finally:
self._waitCondition.release()
def get(self, block=1, timeout=None):
"""Remove and return an item from the queue with blocking.
If we timeout, then toss an exception
"""
# block is never used here, we rely on the
# condition variables for blocking
try:
self._waitCondition.acquire()
try:
# try non blocking first, then wait
return Queue.Queue.get(self, 0)
except Queue.Empty, e:
if timeout is None and not block:
raise
# ok, so we wait now
self._waitCondition.wait(timeout)
return Queue.Queue.get(self, 0)
finally:
self._waitCondition.release()
else:
TimedQueue = Queue.Queue
class CommandError(Exception):
"""A simple exception that knows how to serialize itself to xml
to facilitate sending a DBGP response for an error.
"""
_cmd_error = '<response xmlns="urn:debugger_protocol_v1" command="%s" transaction_id="%s"><error code="%d"><message><![CDATA[%s]]></message></error></response>'
def __init__(self, name, tid, errid, msg):
self.name = name
self.tid = tid
self.errid = errid
self.msg = msg
def __str__(self):
return self._cmd_error % (self.name,self.tid,self.errid,self.msg)
# validation functions used by the backend class
def _validateBreakpointType(client, bptype):
if bptype not in ['line', 'call', 'return',
'exception', 'conditional', 'watch']:
raise CommandError('','',ERROR_BREAKPOINT_TYPE,'invalid value for type')
def _validateBreakpointState(client, bpstate):
if bpstate not in ['enabled','disabled','temporary','deleted']:
raise CommandError('','',ERROR_BREAKPOINT_STATE,'invalid value for state')
def _validateStackDepth(client, depth):
stack_depth = len(client.dbg.stack)
if depth >= stack_depth:
raise CommandError('','',ERROR_STACK_DEPTH,'Invalid stack depth %d requested, stack depth is %d' % (depth, stack_depth))
def _validateContextId(client, context_id):
if context_id < 0 or context_id >= len(contextNames):
raise CommandError('','',ERROR_CONTEXT_INVALID,'Invalid context id %d requested' % context_id)
# we need some stuff the regular breakpoint doesn't support, this
# provides it. Basicaly we track the type and enabled status of
# the breakpoint, and a way to serialize the breakpoint to xml
breakpointsByNumber = [None]
class Breakpoint:
"""Breakpoint class
Implements temporary breakpoints, ignore counts, disabling and
(re)-enabling, and conditionals.
Breakpoints are indexed by number through bpbynumber and by
the file,line tuple using bplist. The former points to a
single instance of class Breakpoint. The latter points to a
list of such instances since there may be more than one
breakpoint per line.
The Breakpoint class also handles serializing breakpoints
to xml for transport to the IDE.
"""
def __init__(self, type, file = '', line = 0, enabled = 1, temporary=0,
cond = None, hitValue = None, hitCondition = None):
self.type = type
self.hitValue = hitValue
if hitValue and not hitCondition:
# this is the default value for hitCondition
self.hitCondition = ">="
else:
self.hitCondition = hitCondition
self.file = file # This better be in canonical form!
self.line = line
self.temporary = temporary
# depending on type, self.cond contains an expression,
# function name or exception name
self.cond = cond
# if we're a watch bp, then we need to keep the
# last known value here, so that we can see if
# it changes
self.lastValue = None
self.enabled = enabled
self.hits = 0
self.number = -1
# Build the two lists
self.insert()
def insert(self):
if self.number < 0 or len(breakpointsByNumber) < self.number:
breakpointsByNumber.append(self)
self.number = len(breakpointsByNumber) - 1
else:
breakpointsByNumber[self.number] = self
if breakpointList.has_key((self.file, self.line)):
breakpointList[self.file, self.line].append(self)
else:
breakpointList[self.file, self.line] = [self]
if breakpointsByFile.has_key(self.file):
breakpointsByFile[self.file].append(self)
else:
breakpointsByFile[self.file] = [self]
def deleteMe(self):
index = (self.file, self.line)
breakpointsByNumber[self.number] = None # No longer in list
breakpointList[index].remove(self)
if not breakpointList[index]:
# No more bp for this f:l combo
del breakpointList[index]
breakpointsByFile[self.file].remove(self)
if not breakpointsByFile[self.file]:
# No more bp for this f:l combo
del breakpointsByFile[self.file]
def enable(self):
self.enabled = 1
def disable(self):
self.enabled = 0
def toxml(self):
"""Return an XML representation of the breakpoint."""
#XXX We are not yet handling the following attributes:
# function, exception
tExpression = None
tFunction = None
tException = None
if self.cond:
if type in ['call', 'return']:
tFunction = ' function="%s"' % self.cond
elif type == 'exception':
tException = ' exception="%s"' % self.cond
elif type in ['conditional', 'watch']:
tExpression = '<expression><![CDATA[%s]]></expression>' % self.cond
bp = '<breakpoint id="%s" type="%s"' % (self.number, self.type)
bp += ' filename="%s" lineno="%s"' % (pathname2url(self.file), self.line)
bp += ' state="%s"' % (self.enabled and 'enabled' or 'disabled')
bp += ' temporary="%s"' % self.temporary
if self.hitValue is not None:
bp += ' hitValue="%s"' % self.hitValue
if self.hitCondition is not None:
bp += ' hitCondition="%s"' % self.hitCondition
if tFunction:
bp += tFunction
if tException:
bp += tException
bp += '>'
if tExpression:
bp += tExpression
bp += '</breakpoint>'
return bp
def canonic(fname):
canonic = canonicCache.get(fname)
if canonic:
return canonic
if fname.startswith("<") and fname.endswith(">"):
canonicCache[fname] = fname
return fname
canonic = os.path.abspath(fname)
canonic = os.path.normcase(canonic)
# Work around a problem where some filenames
# in Python code objects are relative.
if not os.path.exists(canonic):
fullname = None
# Try looking through the module search path
for dirname in sys.path:
testname = os.path.join(dirname, fname)
if os.path.exists(testname):
canonic = os.path.abspath(testname)
canonic = os.path.normcase(canonic)
break
canonicCache[fname] = canonic
return canonic
# Determines if there is an effective (active) breakpoint at this
# line of code. Returns breakpoint number or 0 if none
def effective(frame, arg, type):
"""Determine which breakpoint for this file:line is to be acted upon.
Returns breakpoint that was triggered and a flag
that indicates if it is ok to delete a temporary bp.
"""
file = canonic(frame.f_code.co_filename)
if not breakpointsByFile.has_key(file):
return (None, None)
line = frame.f_lineno
possibles = []
if breakpointList.has_key((file, line)):
possibles += breakpointList[file, line]
if breakpointList.has_key((file, 0)):
possibles += breakpointList[file, 0]
if breakpointList.has_key(('', 0)):
possibles += breakpointList['', 0]
# reduce list by type if we have a type
if type:
possibles = [bp for bp in possibles if bp.type == type]
if not possibles:
return (None, None)
exNames = None
for i in range(0, len(possibles)):
b = possibles[i]
if b.enabled == 0:
continue
val = None
if b.cond:
if b.type == 'conditional':
# Conditional bp.
# hits and hitValue applies only to those bp
# hits where the condition evaluates to true.
try:
val = eval(b.cond, frame.f_globals,
frame.f_locals)
except:
# if eval fails, most conservative
# thing is to stop on breakpoint
# regardless of ignore count.
# Don't delete temporary,
# as another hint to user.
return (b, 0)
if not val:
# conditionals are only a hit if they
# eval to true, so continue to the
# next breakpoint
continue
elif b.type == 'watch':
# for watched breaks, we eval the condition, and if it
# is not the same as the last value, then it has changed
# and we must break.
# this does not exactly match a watch where you want to
# break when a value changes, but is as close an
# aproximation as we can get until python supports this
# internally.
try:
value = eval(b.cond, frame.f_globals, frame.f_locals)
except:
# we don't care about any exceptions here, we just
# want a value if it exists
continue
if value == b.lastValue:
continue
b.lastValue = value
elif type:
# we'll only do these if a type was defined
if b.type in ['call', 'return']:
# arg is none for call
# arg is return type for return
if frame.f_code.co_name != b.cond:
continue
elif b.type == 'exception':
# arg = (exception, value, traceback)
if not exNames:
# if we cannot get the exception names, then
# return no breakpoint
exNames = _class_names(arg[0])
if not exNames:
return (None, None)
if b.cond not in exNames.values():
continue
else:
continue
else:
continue
# Count every hit when bp is enabled
b.hits = b.hits + 1
# handle hitValue/hitConditions
if b.hitValue:
if b.hitCondition == '>=':
if b.hits < b.hitValue:
continue
elif b.hitCondition == '==':
if b.hits != b.hitValue:
continue
elif b.hitCondition == '%':
import operator
if operator.mod(b.hits, b.hitValue):
continue
else:
# invalid hitCondition, we'll just ignore it
# since we might not support all the condition
# types that an IDE might send us
pass
# breakpoint and marker that's ok
# to delete if temporary
return (b, 1)
return (None, None)
class Property:
"""DBGP Python Property class.
a class to serialize python data types into xml
handles children of objects/arrays/etc.
"""
def __init__(self, name, fullname, value, encoding = 'base64',
include_private=0, include_hiddenTypes=None):
# protoct against new types
self.name = name
self.fullname = fullname
self.value = value
self._children = None
self._numchildren = None
self.encoding = encoding
self.include_private = include_private # to show __X__ names also
# if examining a module, then let us see the children. This should actually
# be a type from the types module
self.include_hiddenTypes = include_hiddenTypes
def _get_encodedData(self, data):
try:
vType = type(data)
if vType in types.StringTypes and vType != types.UnicodeType:
data = unicode(data)
data = data.encode('utf-8')
except:
pass
if self.encoding == 'base64':
return base64.encodestring(data), self.encoding
return escape(data), None
def get_encodedName(self):
return self._get_encodedData(self.name)
def get_encodedFullname(self):
return self._get_encodedData(self.fullname)
def get_encodedValue(self, maxdata = 0):
# maxdata constraint, so we cannot use _get_encodedData
data = self.value
if maxdata:
data = self.value[:maxdata]
return self._get_encodedData(data)
def get_type(self):
typ = type(self.value)
if typ == 'instance':
cn = _class_names(self.value)
typ = cn['fullname']
return typ
def get_typeString(self):
try:
return self.get_type().__name__
except AttributeError:
return str(self.get_type())
def get_dataSize(self):
try:
if '__len__' not in dir(self.value):
return 0
return len(self.value)
except:
return 0
def get_valueString(self):
try:
return repr(self.value)
except:
# XXX raise a CommandError exception?
return "Looking at object failed - %s: %s" % (sys.exc_info()[0], sys.exc_info()[1])
def get_numchildren(self):
if self._numchildren is not None:
return self._numchildren
self._numchildren = 0
# checking for properties should catch the vast majority of cases!
if self.get_type() not in BaseTypes:
# count the children and set into numchildren
self.get_children(1)
return self._numchildren
def get_children(self, countOnly=0):
if self._children is not None:
return self._children
if type(self.value) in HiddenTypes and \
type(self.value) not in self.include_hiddenTypes and \
not self.include_private:
return []
self._numchildren = 0
children = []
_val_dir = dir(self.value)
for childStr in _val_dir:
if ((not self.include_private) and \
(childStr[:1] == '_')) or \
childStr == "__builtins__":
continue
try:
# bug 83896 -- treat properties as non-eval-able objects.
# We want to display them, but make sure the debugger
# doesn't call their getters
propObj = getattr(self.value.__class__, childStr)
if type(propObj) == property:
self._numchildren += 1
if not countOnly:
c = Property(childStr,
"%s.%s" % (self.name, childStr),
propObj,
self.encoding,
self.include_private,
self.include_hiddenTypes)
# Clear the children count to prevent the debugger from
# invoking the getter when the user tries to expand this variable.
# properties don't have interesting children anyway - they hide them.
c._numchildren = 0
children.append(c)
continue
except (AttributeError, NameError):
pass
except:
log.exception("Unexpected error trying to get type of getattr(%s, %s)",
self.value.__class__, childStr)
try:
child = getattr(self.value, childStr)
except:
self._numchildren += 1
if countOnly: continue
c = Property(childStr,
"%s.%s" % (self.name,childStr),
None, self.encoding,
self.include_private,
self.include_hiddenTypes)
children.append(c)
else:
cvalue = _safe_apply("Getting attribute failed",
getattr, self.value, childStr)
if self.include_hiddenTypes or type(cvalue) not in HiddenTypes:
self._numchildren += 1
if countOnly: continue
c = Property(childStr,
"%s.%s" % (self.name,childStr),
cvalue, self.encoding,
self.include_private,
self.include_hiddenTypes)
children.append(c)
# handle tuples, dicts and lists
# we always show a value for these types, even if it is in the hidden types list
if '__getitem__' in _val_dir:
#log.debug("value has __getitem__")
if 'keys' in _val_dir:
#log.debug("value has keys")
try:
# repr introduces quotes if the object is already a string
# (but the object may be one being debugged, and its repr
# may fail! This wouldn't be good, as it looks like the
# debugger failed!
# XXX Shouldn't return all of really big mappings but how
# do we know what part the user is interested in?
child_keys = self.value.keys()
self._numchildren += len(child_keys)
if not countOnly:
for child in child_keys:
cvalue = _safe_index("child", self.value, child)
if type(child) not in types.StringTypes:
cname = _safe_apply("repr", repr, child)
else:
cname = "'%s'" % child.replace("'","\'")
c = Property(cname,
"%s[%s]" % (self.name,cname),
cvalue, self.encoding,
self.include_private,
self.include_hiddenTypes)
children.append(c)
except:
# The users sequence object returned by keys is raising
# exceptions, so let's just give up
pass
# value doesn't seem to be a mappying-like thing, let's see
# if it is a sequence-like thing
elif '__len__' in _val_dir:
#log.debug("value has __len__")
try:
vlen = len(self.value)
self._numchildren += vlen
if not countOnly:
for child in range(vlen):
cvalue = _safe_index("object", self.value, child)
c = Property("[%d]" % child,
"%s[%d]" % (self.name,child),
cvalue, self.encoding,
self.include_private,
self.include_hiddenTypes)
children.append(c)
except:
pass
if countOnly: return None
self._children = children
return children
def get_hasChildren(self):
return int(self.get_numchildren() > 0)
def toxml(self, depth = MAX_DEPTH, maxchildren = MAX_CHILDREN, maxdata = MAX_DATA, page = 0):
childprops = []
numchildren = self.get_numchildren()
haschildren = self.get_hasChildren()
if depth > 0 and numchildren > 0:
start = page * maxchildren
end = start + maxchildren
if end >= numchildren:
end = numchildren
#print "numchildren %s" % (numchildren)
#print "max children %s" % (maxchildren)
#print "page %s" % (page)
#print "getting %d children" % (end-start)
childprops = [child.toxml(depth-1, maxchildren, maxdata, 0)
for child in self.get_children()[start:end]]
vType = type(self.value)
if not self.include_private and \
vType not in self.include_hiddenTypes and \
numchildren > 0 and not childprops and depth > 0:
numchildren = 0
haschildren = 0
value = ''
if vType not in StringTypes:
if numchildren == 0 or vType in HiddenTypes or vType == types.InstanceType:
value = '<value><![CDATA[%s]]></value>' % (self.get_valueString()[:maxdata])
else:
data, encoding = self.get_encodedValue(maxdata)
value = '<value encoding="%s"><![CDATA[%s]]></value>' % (encoding, data)
name, encoding = self.get_encodedName()
childprops.append('<name encoding="%s"><![CDATA[%s]]></name>' % (encoding, name))
fullname, encoding = self.get_encodedFullname()
childprops.append('<fullname encoding="%s"><![CDATA[%s]]></fullname>' % (encoding, fullname))
value = value + ''.join(childprops)
attrs = {'type': self.get_typeString(),
'children': haschildren,
'size': self.get_dataSize()}
if numchildren > 0:
attrs['page'] = page
attrs['pagesize'] = maxchildren
attrs['numchildren'] = numchildren
return '<property %s>%s</property>' % (_getAttrStr(attrs), value)
class dbgpClient(clientBase):
"""DBGP Python debugger base class.
This class takes care of details of the trace facility;
"""
fncache = {}
def __init__(self, requester, ignoreList=[], module=None):
ignoreModules = ignoreList
clientBase.__init__(self)
self._interactiveDebugger = None
self.requester = requester
self._threadStart = 0
self.main_module = module
self.forget()
def reset(self):
self.botframe = None
self.stopframe = None
self.returnframe = None
self.quitting = 0
# Override the dbd set_* functions to manage our bottom frame fix.
def set_step(self):
self.botframeBehaviour = BOTFRAME_STEP
self.stopframe = None
self.returnframe = None
self.quitting = 0
def set_next(self, frame):
# A "step over" operation - if we are at the bottom frame,
# then we need to be in step mode.
if frame is self.botframe:
self.botframeBehaviour = BOTFRAME_STEP
self.stopframe = frame
self.returnframe = None
self.quitting = 0
def set_return(self, frame):
# A "step out" operation - if the frame we are returning to is
# the bottom frame, then we need to be in step mode.
if frame.f_back is self.botframe:
self.botframeBehaviour = BOTFRAME_STEP
elif frame is self.botframe:
# At the bot-frame and stepping out - like a "run"
self.botframeBehaviour = BOTFRAME_CONTINUE
self.stopframe = frame.f_back
self.returnframe = frame
self.quitting = 0
def set_trace(self):
"""Start debugging from here."""
frame = sys._getframe().f_back
self.reset()
while frame:
if not frame.f_globals.has_key('DBGPHide'):
if hasattr(self,'trace_dispatch'):
frame.f_trace = self.trace_dispatch
else:
frame.f_trace = self
self.botframe = frame
frame = frame.f_back
self.set_step()
self.starttrace()
def set_continue(self):
# Don't stop except at breakpoints or when finished
self.botframeBehaviour = BOTFRAME_CONTINUE
self.stopframe = None
self.returnframe = None
self.quitting = 0
def set_quit(self):
self.botframeBehaviour = BOTFRAME_CONTINUE
self.stopframe = self.botframe
self.returnframe = None
self.quitting = 1
self.stoptrace()
def set_break(self, type, filename, lineno, enabled, temporary=0,
cond=None, hitValue=None, hitCondition=None):
import linecache # Import as late as possible
if filename:
filename = canonic(filename)
# some breakpoint types do not have line numbers
if lineno:
line = linecache.getline(filename, lineno)
if not line:
raise DBGPError('Line %s:%d does not exist' % (filename, lineno))
return Breakpoint(type, filename, lineno, enabled, temporary, cond,
hitValue, hitCondition)
def get_stack(self, f, t):
stack = []
if t and t.tb_frame is f:
t = t.tb_next
while f is not None:
if f.f_builtins.has_key('DBGPHideChildren') and \
f.f_builtins['DBGPHideChildren']:
# clear the stack of the children
stack = []
if _hide_stack(f) or f.f_lineno == 0:
f = f.f_back
continue
stack.append((f, f.f_lineno))
f = f.f_back
stack.reverse()
i = max(0, len(stack) - 1)
while t is not None:
if not _hide_stack(t.tb_frame):
stack.append((t.tb_frame, t.tb_lineno))
t = t.tb_next
# when using interactive shell, we dont have a stack frame, we use a fake
# frame instead, which maintains our globals and locals across interactions
if not stack and self._interactiveDebugger:
stack.append((self._interactiveDebugger.frame, 0))
return stack, i
def setup(self, f, t):
self.forget()
self.stack, self.curindex = self.get_stack(f, t)
self.stack.reverse()
def forget(self):
self.stack = []
if self._interactiveDebugger:
self.stack.append((self._interactiveDebugger.frame, 0))
self.curindex = 0
def interaction(self, frame, tb = None, async = 0):
self.interrupt = 0
self.setup(frame, tb)
try:
return self.requester.cmdloop(async)
finally:
self.forget()
return RESUME_STOP
def dispatch_interaction(self, frame, arg = None):
if os.name == 'java':
f = sys._getframe()
while f.f_back:
f = f.f_back
# if the very top item on the stack is __del__
# then we must be in a Jython finalization thread
# ignore it!
if f.f_code.co_name == "__del__":
return
rc = self.interaction(frame)
if rc == RESUME_STOP:
self.set_quit()
sys.exit(-1)
elif rc == RESUME_STEP_IN:
self.set_step()
elif rc == RESUME_STEP_OVER:
self.set_next(frame)
elif rc == RESUME_STEP_OUT:
self.set_return(frame)
elif rc == RESUME_GO:
self.set_continue()
else:
raise Exception("Unknown resume state %r" % rc)
# The following two methods can be called by clients to use
# a debugger to debug a statement, given as a string.
def run(self, cmd, globals=None, locals=None, module=None):
if module is None:
module = self.main_module
self.reset()
try:
try:
h_exec(cmd, globals=globals, locals=locals, module=module, tracer=self)
except DBGPQuit:
pass
finally:
self.quitting = 1
def runfile(self, file, args, main=None):
if main is None:
main = self.main_module
self.reset()
try:
try:
h_execfile(file, args, module=main, tracer=self)
except DBGPQuit:
pass
finally:
self.quitting = 1
def runeval(self, expr, globals=None, locals=None, module=None):
if module is None:
module = self.main_module
self.reset()
try:
try:
return h_eval(expr, globals=globals, locals=locals, module=module, tracer=self)
except DBGPQuit:
pass
finally:
self.quitting = 1
# This method is more useful to debug a single function call.
def runcall(self, func, args, kwargs):
self._threadStart = 1
self.reset()
self.starttrace()
res = None
try:
try:
res = apply(func, args, kwargs)
except DBGPQuit:
pass
finally:
self.stoptrace()
self.quitting = 1
return res
# this allows debuging code without having exceptions set
# the quiting flag
def runcode(self, code, globals=None, locals=None, module=None):
if module is None:
module = self.main_module
# we do not debug interactive code, as that has not been
# tested and dealt with correctly. It's also much faster if we dont
self.stoptrace()
try:
try:
h_exec(code, globals=globals, locals=locals, module=module)
except DBGPQuit:
self.quitting = 1
finally:
if not self.quitting:
self.starttrace()
def getInteractiveDebugger(self, main=None):
if not main:
main = self.main_module
if not self._interactiveDebugger:
# we always pick the top level stack and use the globals for
# everything. Interacting does not happen at different points
# in the stack
frame = None
try:
frame, lineno = self.stack[0]
except IndexError, e:
pass
if not frame:
# looks like we were started in interactive mode
frame = FakeFrame(main.globals, main.locals)
self._interactiveDebugger = InteractiveDebugger(self, frame)
return self._interactiveDebugger
def releaseInteractiveDebugger(self):
self._interactiveDebugger = None
_clientInstances = {}
def registerClient(client):
global _clientInstances
_clientInstances[client.thread_id] = client
def deregisterClient(client):
#print "deregister ",client.thread_id
global _clientInstances
if _clientInstances.has_key(client.thread_id):
del _clientInstances[client.thread_id]
def warnMainThreadEnding():
global _clientInstances
if len(_clientInstances) > 0:
log.error("""
The main thread of this application is exiting while there are still threads
alive. When the main thread exits, it is system defined whether the other
threads survive.
See Caveats at http://docs.python.org/lib/module-thread.html
""")
def getClientForThread():
# this is tricky. we have to dig back through the frames to find our
# clientInstances variable, then use the client for this thread if there
# is one available.
frame = sys._getframe().f_back
# find a dbgp stack frame
while frame and frame.f_back:
if '_clientInstances' in frame.f_globals:
ci = frame.f_globals['_clientInstances']
tid = thread.get_ident()
if tid in ci:
return ci[tid]
frame = frame.f_back
return None
class dbgpSocket:
socket_type = None
hostname = None
port = 0
orig_stdin = None
notify_ok = 0
def __init__(self, mainThread, hostname = '', port = 9000, socket_type=socket.AF_INET):
self.queue = TimedQueue()
self.mainThread = mainThread
# override stdin so we can send notifications
if not dbgpSocket.orig_stdin:
dbgpSocket.orig_stdin = sys.stdin
sys.stdin = StreamStdin(dbgpSocket.orig_stdin, self)
if dbgpSocket.hostname is None:
dbgpSocket.hostname = hostname
dbgpSocket.port = port
dbgpSocket.socket_type = socket_type
def connect(self):
# connect to the host, then wait for the first header
# sent from the host and process it
try:
self._socket = socket.socket(self.socket_type, socket.SOCK_STREAM)
self._socket.connect((self.hostname,self.port))
except socket.error, e:
sys.stderr.write("dbgpSocket: error: unable to connect to remote host at %s:%d\n\n"% (self.hostname,self.port))
raise
self._stop = 0
self._startCommandThread()
def stop(self):
self._stop = 1
self.queue.put(None)
if self._socket:
self._socket.close()
self._socket = None
def _getIncomingDataPacket(self):
# _getIncomingDataPacket needs to be in a thread that
# stuffs incoming packets into a queue.
# _getIncomingDataPacket should also dispatch
# calls for stdin so we can debug raw_input
log.debug("_getIncomingDataPacket starting...")
# gets the next chunk of data until a null byte is
# encountered
data = ''
while not self._stop:
log.debug("_getIncomingDataPacket getting data...")
try:
data = data + self._socket.recv(1024)
except socket.error, e:
# socket was closed on us, quit now
log.debug("_getIncomingDataPacket socket closed")
self.queue.put(None)
break
if not data:
# protocol error, we should never receive an empty
# data set
log.debug("_getIncomingDataPacket socket closed")
self.queue.put(None)
break
log.debug(" %d[%r]" , len(data), data)
while data:
eop = data.find('\0')
if eop < 0:
break
cmddata = data[:eop]
data = data[eop+1:] # skip \0
argv = listcmd.line2argv(cmddata)
log.debug(" put data in queue [%r]", cmddata)
# stdin blocks the executing thread, so we have to do the async
# stdin call here. This only happens with remote debugging when
# stdin is redirected.
if argv[0] in ['stdin', 'stop']:
self.mainThread.onecmd(argv, 'do_async_')
else:
self.mainThread.dbg.interrupt = 1
self.queue.put(argv)
log.debug("_getIncomingDataPacket exiting...")
def _startCommandThread(self):
_nonDebugThread(self._getIncomingDataPacket, ())
def send_response(self, response):
if self._stop:
return
header = u'<?xml version="1.0" encoding="utf-8"?>\n'
response = (header+response)
try:
response = response.encode('utf-8')
except (UnicodeEncodeError,UnicodeDecodeError), e:
pass
l = len(response)
#log.debug('sending [%r]', response)
try:
self._socket.send('%d\0%s\0' % (l, response))
except socket.error, e:
self.stop()
def notify(self, name, data=None):
if not self.notify_ok: return
out = '<notify xmlns="urn:debugger_protocol_v1" name="%s"' % name
if data:
out += '>%s</notify>' % data
else:
out += '/>'
self.send_response(out)
class backend(listcmd.ListCmd):
# this is the base communications class. It handles reading
# and writing data to the sockets, dispatching of commands,
# communication, etc. Command implementations are in
# the backendCmd subclass below
ide_key = ''
destination_id = 0
source_id = 0
appid = None
parent_appid = ''
_stop = 0
# application wide configuration
_encoding = 'utf-8'
_data_encoding = 'base64'
def __init__(self, idekey = 0, preloadScript = None, ignoreModules = [], module=None):
listcmd.ListCmd.__init__(self)
self.stdin_enabled = 1
self._preloadScript = preloadScript
self._stdin = None
self._stdout = None
self._stderr = None
self._continue = RESUME_STOP
self._continueTransactionId = None
self._continuationCommand = None
self._break_reason = REASON_OK
self._break_status = STATUS_STOPPED
self._lastErrorMessage = ''
self._detach = 0
self.thread_id = thread.get_ident()
if not backend.appid:
if hasattr(os, 'getpid'):
backend.appid = os.getpid()
else:
# Jython doesn't provide getpid(), so we
# have to have _something_
backend.appid = 1
backend.parent_appid = getenv("DEBUGGER_APPID", '')
if hasattr(os, 'putenv'):
os.putenv('DEBUGGER_APPID',str(backend.appid))
self.filename = ''
if idekey:
backend.ide_key = idekey
# interactive debugger support
self._interactiveBuffer = []
self._isInteractiveShell = 0
registerClient(self)
self.dbg = dbgpClient(self, ignoreModules, module=module)
def emptyline(self):
pass
def connect(self, hostname = '', port = 9000, name = 'unknown',
args = ['interactive'], socket_type=socket.AF_INET):
self.socket = dbgpSocket(self, hostname, port, socket_type)
self.socket.connect()
self.sendInit(name, args[0])
def notify(self, name, data=None):
self.socket.notify(name, data)
def stopNow(self):
self._stop = self._detach = 1
self.dbg.stoptrace()
self.socket.stop()
def detachNow(self):
self._detach = 1
set_thread_support(None)
self.dbg.stoptrace()
self.socket.stop()
def close(self):
self.stopNow()
def atexit(self):
# signal the IDE that we're stopping
self._break_status = STATUS_STOPPING
self.send_continuationResult(self._continue, STATUS_STOPPING, REASON_OK)
if not self._stop:
# we want to stop now until the IDE chooses to end the session
while not self._detach:
self.cmdloop()
if self._stdin:
sys.stdin = self._stdin.stop()
self._stop = self._detach = 1
self.dbg.stoptrace()
self.dbg = None
def breakNow(self):
self._break_status = STATUS_BREAK
frame = sys._getframe().f_back
self.dbg.setup(frame, None)
self.dbg.set_trace()
def preloadScript(self, locals):
if not self._preloadScript:
return
# Ready to roll - do it.
code = open(self._preloadScript).read()
# python expects lines to end with '\n' only so kill the '\r's
code = code.replace('\r\n', '\n')
code = code.replace('\r', '\n')
# there's also an issue with files that don't end with newlines, so tag on a few
code = code + '\n\n\n'
# let this throw an exception if it fails
exec code in locals, locals
def runExceptHook(self, type, value, traceback):
self._isInteractiveShell = 1 # exit when shell is stopped
self._break_status = STATUS_INTERACTIVE
self.dbg.setup(None, traceback)
# do a command loop until we're stopped
while not self._detach and not self._stop:
self.cmdloop()
self.dbg = None
def runInteractive(self):
self._isInteractiveShell = 1
self._break_status = STATUS_INTERACTIVE
# force the interactive debugger to be created so we have a frame available
self.dbg.getInteractiveDebugger()
self.dbg.reset()
self.dbg.forget() # reset the stack so we have our fake stack available
self.dbg.set_continue()
self.dbg.starttrace()
# do a command loop until we're stopped
while not self._detach and not self._stop:
self.cmdloop()
self.dbg = None
def runThread(self, target, args, kargs):
# threads start in run mode
self._break_status = STATUS_STARTING
res = None
try:
res = self.dbg.runcall(target, args, kargs)
self.send_continuationResult(self._continue, STATUS_STOPPING, REASON_OK)
except SystemExit, e: # if someone does a sys.exit(), it's not really an exception.
self.send_continuationResult(self._continue, STATUS_STOPPED, REASON_ABORTED)
raise
except:
#traceback.print_exc()
_print_exc()
self.send_continuationResult(self._continue, STATUS_STOPPING, REASON_EXCEPTION)
if not self._stop:
# we want to stop now until the IDE chooses to end the session
while not self._detach:
self.cmdloop()
self.dbg = None
deregisterClient(self)
return res
def runMain(self, debug_args, interactive=0):
if interactive:
end_status = STATUS_INTERACTIVE
else:
end_status = STATUS_STOPPING
self._break_status = STATUS_STARTING
try:
self.preloadScript(locals)
self.dbg.runfile(debug_args[0], debug_args)
if not (self._stop or self._detach):
self.send_continuationResult(self._continue, end_status, REASON_OK)
except SyntaxError, e:
_print_exc()
if not (self._stop or self._detach):
self.send_continuationResult(self._continue, end_status, REASON_EXCEPTION)
self._stop = self._detach = 1
self.dbg.stoptrace()
except SystemExit, e: # if someone does a sys.exit(), it's not really an exception.
if not (self._stop or self._detach):
self.send_continuationResult(self._continue, end_status, REASON_ABORTED)
raise
except:
# uncaught exception, enter interactive mode
#traceback.print_exc()
_print_exc()
tb_info = sys.exc_info()
end_status = STATUS_INTERACTIVE
self.dbg.setup(None, tb_info[2])
self.dbg.getInteractiveDebugger()
interactive = 0 # we don't want that logic
self._isInteractiveShell = 1 # stopping the shell stops debugger
if not (self._stop or self._detach):
self.send_continuationResult(self._continue, end_status, REASON_EXCEPTION)
if interactive:
self._isInteractiveShell = 1 # stopping the shell stops debugger
self._break_status = end_status
self.dbg.getInteractiveDebugger()
self.dbg.set_continue()
self.dbg.starttrace()
deregisterClient(self)
warnMainThreadEnding()
if not self._stop:
# we want to stop now until the IDE chooses to end the session
while not self._detach:
self.cmdloop()
if self._stdin:
sys.stdin = self._stdin.stop()
self.dbg = None
def getHostName(self):
# if we're running under a webserver, get the webserver hostname
# otherwise return the hostname normally
if os.environ.has_key("HTTP_HOST"):
return os.environ["HTTP_HOST"]
return socket.gethostname()
def sendInit(self, name, filename):
attrs = {'xmlns': 'urn:debugger_protocol_v1',
'hostname': self.getHostName(),
'appid': self.appid,
'idekey': self.ide_key,
'thread': "%s %d" % (name, self.thread_id),
'parent': self.parent_appid,
'session': getenv('DBGP_COOKIE',''),
'language': 'python',
'protocol_version': '1.0'}
if filename == 'interactive':
self._break_status = STATUS_BREAK
attrs['interactive'] = '>>> '
else:
filename = pathname2url(filename)
attrs['fileuri'] = filename
self.socket.send_response('<init %s/>' % _getAttrStr(attrs))
def poll(self):
return self.supports_async and \
self._continue != RESUME_STOP and \
not self.socket.queue.empty()
def cmdloop(self, async=0):
# loop reading commands until no more commands are avialable
# if this is async, only do one command
#log.debug("cmdloop async=%d", async)
if async and not self.poll():
return self._continue
self._continue = RESUME_STOP
while not self._continue and not self._detach:
if not async and self._continueTransactionId is not None:
self.send_continuationResult(self._continuationCommand, STATUS_BREAK, REASON_OK)
#log.debug("retreiving current command packet")
data = None
while not data and not self._detach:
# timeout in half second intervals. Ignore the exception
# and see if we've detached from debugging. If so, we'll
# fall out of here rather than blocking (and locking up)
try:
data = self.socket.queue.get(1)
if (self.socket.queue.empty()):
self.dbg.interrupt = 0
except:
pass
#log.debug("cmdloop %r", data)
if data:
if async:
self.onecmd(data, 'do_async_')
else:
self.onecmd(data)
if async:
break
return self._continue
def send_stream(self, cmd, data=None):
if not data:
return
_template = '<stream type="%s" encoding="%s">%s</stream>'
if self._data_encoding == 'base64':
data = base64.encodestring(data).rstrip()
else:
data = escape(data)
self.socket.send_response(_template % (cmd,self._data_encoding,data))
def send_continuationResult(self, command, status, reason):
if self._continueTransactionId is None:
log.debug("no continuation transaction id")
return
_template = '<response xmlns="urn:debugger_protocol_v1" command="%s" status="%s" reason="%s" transaction_id="%s"%s/>'
self._break_reason = reason
self._break_status = status
name = resume_command_names[command]
reason = reason_names[reason]
status = status_names[status]
interactive = ""
if self._break_status == STATUS_INTERACTIVE:
interactive = ' prompt=">>> " more="0"'
self.socket.send_response(_template % (name, status, reason, self._continueTransactionId, interactive))
self._continueTransactionId = None
def _getopts(self, args, options, cmd="unknown"):
# options is an array of option arrays:
# [['i','transaction_id',int,1,-1, func],...]
# [short_arg, long_arg, type_handler, required, default, validat_func]
# returns values for all requested options
origargs = args
short = ''
longopt = []
shortopt = []
found = []
for opt in options:
short = short + opt[0]+':'
shortopt.append('-'+opt[0])
longopt.append(opt[1])
try:
opts, args = getopt.getopt(args, short, longopt)
except getopt.GetoptError, e:
tid = self._getTransactionId(origargs)
raise CommandError(cmd,tid,ERROR_INVALID_ARGS,'invalid argument supplied: %s' % (str(e)))
# get the default values
result = [o[4] for o in options]
# override defaults with provided arg values
for o, a in opts:
found.append(o)
if o in shortopt:
i = shortopt.index(o)
result[i] = options[i][2](a.strip())
if options[i][5]:
options[i][5](self, result[i])
elif o in longopt:
i = longopt.index(o)
result[i] = options[i][2](a.strip())
if options[i][5]:
options[i][5](self, result[i])
# check for required arguments
for o in options:
i = options.index(o)
if o[3] and not (shortopt[i] in found or longopt[i] in found):
tid = self._getTransactionId(origargs)
raise CommandError(cmd,tid,ERROR_INVALID_ARGS,'required argument [%s:%s] missing' % (o[0],o[1]))
data = ' '.join(args)
result.append(data)
return result
def _getTransactionId(self, args):
# try to find the transaction id
if '-i' in args:
tid = args[args.index("-i")+1]
elif "--transaction-id" in args:
tid = args[args.index("--transaction-id")+1]
else:
tid = "-1"
return tid
def onecmd(self, argv, fbegin = 'do_'):
try:
self.lastcmd = argv
cmd = argv[0]
if cmd == '':
return self.default(argv)
else:
try:
func = getattr(self, fbegin + cmd)
except AttributeError:
return self.default(argv)
return func(argv[1:])
except CommandError, e:
if not e.tid:
try:
e.tid = self._getTransactionId(argv[1:])
except Exception, e:
pass
if cmd and e.name != cmd:
e.name = cmd
self.socket.send_response(str(e))
return None
except SystemExit, e:
# we dont want to send anything on an exit, just reraise
raise
except Exception, e:
try:
tid = self._getTransactionId(argv[1:])
except:
tid = 0
ex = CommandError(cmd, tid, ERROR_EXCEPTION, str(e))
self.socket.send_response(str(ex))
raise
return None
def default(self, argv):
try:
tid = self._getTransactionId(argv[1:])
except Exception, e:
log.exception(e)
tid = '-1'
self._continue = RESUME_STOP
raise CommandError(argv[0], tid, ERROR_COMMAND_NOT_SUPPORTED,
'command not supported')
class backendCmd(backend):
"""DBGP Command Handler
This class contains all the command handlers for DBGP
"""
# application wide debugger configuration vars
debug_threads = 0 # must turn on to debug threads
supports_threads = 1
language_name = 'Python'
language_version = sys.version
supports_async = 1
_max_children = MAX_CHILDREN
_max_data = MAX_DATA
_max_depth = MAX_DEPTH
_show_hidden = SHOW_HIDDEN
def get_feature_language_supports_threads(self):
return self.supports_threads
def get_feature_language_name(self):
return self.language_name
def get_feature_language_version(self):
return self.language_version
def get_feature_encoding(self):
return self._encoding
def set_feature_encoding(self, value):
import codecs
try:
codec = codecs.lookup(value)
self._encoding = value
return 1
except LookupError, e:
return 0
def get_feature_data_encoding(self):
return self._data_encoding
def set_feature_data_encoding(self, value):
if value == 'base64' or value == 'none':
self._data_encoding = value
return 1
return 0
def get_feature_protocol_version(self):
return DBGP_VERSION
def get_feature_supports_async(self):
return self.supports_async
def get_feature_multiple_sessions(self):
return self.debug_threads
def set_feature_multiple_sessions(self, value):
self.debug_threads = long(value)
set_thread_support(self.debug_threads)
return 1
def get_feature_max_children(self):
return self._max_children
def set_feature_max_children(self, value):
self._max_children = long(value)
return 1
def get_feature_max_data(self):
return self._max_data
def set_feature_max_data(self, value):
self._max_data = long(value)
return 1
def get_feature_max_depth(self):
return self._max_depth
def set_feature_max_depth(self, value):
self._max_depth = long(value)
return 1
def get_feature_show_hidden(self):
return self._show_hidden
def set_feature_show_hidden(self, value):
self._show_hidden = long(value)
return 1
def set_feature_notify_ok(self, value):
dbgpSocket.notify_ok = long(value)
return 1
def get_feature_supports_postmotem(self):
return 1
def do_help(self, cmdargs, *args):
_template = '<response xmlns="urn:debugger_protocol_v1" command="help" transaction_id="%s"><![CDATA[%s]]></response>'
tid = self._getTransactionId(cmdargs)
stdout = sys.stdout
sys.stdout = StringIO.StringIO()
listcmd.ListCmd.do_help(self, args)
out = sys.stdout.getvalue()
self.socket.send_response(_template % (tid, out))
sys.stdout = stdout
def do_async_status(self, cmdargs, *args):
self.do_status(cmdargs, args)
def do_status(self, cmdargs, *args):
_template = '<response xmlns="urn:debugger_protocol_v1" command="status" status="%s" reason="%s" transaction_id="%s">%s</response>'
tid = self._getTransactionId(cmdargs)
self.socket.send_response(_template %
(status_names[self._break_status],
reason_names[self._break_reason],
tid,
self._lastErrorMessage))
_stop_optlist = [['i', 'transaction_id', int, 1, -1, None],
['e', 'exit_code', int, 0, 0, None]]
def do_async_stop(self, cmdargs, *args):
self.do_stop(cmdargs, args)
def do_stop(self, cmdargs, *args):
(tid, exit, data,) = self._getopts(cmdargs, self._stop_optlist, "stop")
_template = '<response xmlns="urn:debugger_protocol_v1" command="stop" status="stopped" reason="ok" transaction_id="%s">%s</response>'
self.socket.send_response(_template % (tid,'application terminated'))
self._continueTransactionId = tid
backend._stop = 1
self.stopNow()
self._continue = RESUME_STOP
if self._stdin:
sys.stdin = self._stdin.stop()
sys.exit(exit)
def do_async_break(self, cmdargs, *args):
tid = self._getTransactionId(cmdargs)
frame = sys._getframe().f_back
self.dbg.setup(frame, None)
self.dbg.set_step()
self.dbg.starttrace()
# we dont know if this will successfully break or not :(
_template = '<response xmlns="urn:debugger_protocol_v1" command="break" transaction_id="%s" success="1"/>'
self.socket.send_response(_template % (tid))
self.send_continuationResult(self._continuationCommand, STATUS_BREAK, REASON_OK)
def do_break(self, cmdargs, *args):
tid = self._getTransactionId(cmdargs)
raise CommandError('break', tid, ERROR_COMMAND_NOT_AVAILABLE,
'break is only available while running')
def do_async_detach(self, cmdargs, *args):
self.do_detach(cmdargs, args)
def do_detach(self, cmdargs, *args):
_template = '<response xmlns="urn:debugger_protocol_v1" command="detach" status="stopped" reason="ok" transaction_id="%s">%s</response>'
self._continueTransactionId = self._getTransactionId(cmdargs)
self._continue = RESUME_GO
self.socket.send_response(_template % (self._continueTransactionId,''))
log.debug('do_detach send response')
if self._stdin:
sys.stdin = self._stdin.stop()
self.detachNow()
def _do_continue(self, cmd, cmdargs, state, *args):
tid = self._getTransactionId(cmdargs)
if self._break_status not in [STATUS_BREAK, STATUS_STARTING]:
raise CommandError(cmd, tid, ERROR_COMMAND_NOT_AVAILABLE,
'%s is only available in break state' % cmd)
self._break_status = STATUS_RUNNING
self._continueTransactionId = tid
self._continuationCommand = self._continue = state
def do_run(self, cmdargs, *args):
self._do_continue('run', cmdargs, RESUME_GO, *args)
def do_step_into(self, cmdargs, *args):
self._do_continue('step_into', cmdargs, RESUME_STEP_IN, *args)
def do_step_over(self, cmdargs, *args):
self._do_continue('step_over', cmdargs, RESUME_STEP_OVER, *args)
def do_step_out(self, cmdargs, *args):
self._do_continue('step_out', cmdargs, RESUME_STEP_OUT, *args)
_feature_get_optlist = [['i', 'transaction_id', int, 1, -1, None],
['n', 'feature_name', str, 1, None, None]]
def do_feature_get(self, cmdargs, *args):
feature_value = ''
feature_success = 0
(tid, name, data,) = self._getopts(cmdargs, self._feature_get_optlist, "feature_get")
if hasattr(self, 'get_feature_'+name):
func = getattr(self, 'get_feature_'+name)
feature_value = func()
if feature_value:
feature_value = '<![CDATA[%s]]>' % feature_value
feature_success = 1
elif hasattr(self, name+'_enabled'):
feature_success = getattr(self, name+'_enabled')
elif hasattr(self, 'do_'+name):
feature_success = 1
else:
feature_success = 0
_template = '<response xmlns="urn:debugger_protocol_v1" command="feature_get" feature_name="%s" supported="%d" transaction_id="%s">%s</response>'
self.socket.send_response(_template % (name, feature_success, tid, feature_value))
_feature_set_optlist = [['i', 'transaction_id', int, 1, -1, None],
['n', 'feature_name', str, 1, None, None],
['v', 'feature_value', str, 1, None, None]]
def do_feature_set(self, cmdargs, *args):
(tid, name, feature_value, data,) = \
self._getopts(cmdargs, self._feature_set_optlist, "feature_set")
feature_success = 0
try:
func = getattr(self, 'set_feature_'+name)
feature_success = func(feature_value)
except AttributeError:
feature_success = 0
_template = '<response xmlns="urn:debugger_protocol_v1" command="feature_set" feature_name="%s" success="%d" transaction_id="%s"/>'
self.socket.send_response(_template % (name, feature_success, tid))
def do_stack_depth(self, cmdargs, *args):
tid = self._getTransactionId(cmdargs)
depth = len(self.dbg.stack)
_template = '<response xmlns="urn:debugger_protocol_v1" command="stack_depth" depth="%d" transaction_id="%s"/>'
self.socket.send_response(_template % (depth, tid))
stack_re = re.compile('.*?<(.*?)>.*')
_stack_get_optlist = [['i', 'transaction_id', int, 1, -1, None],
['d', 'depth', int, 0, 0, _validateStackDepth]]
def do_stack_get(self, cmdargs, *args):
(tid, depth, data,) = self._getopts(cmdargs, self._stack_get_optlist, "stack_get")
_frame_template = '<stack level="%d" type="%s" filename="%s" lineno="%d" where="%s"/>'
ret = []
level = -1
try:
for item in self.dbg.stack:
level = level + 1
(filename, lineno, where) = _get_stack_data(item)
type = 'file'
if filename.startswith('<'):
type = filename[1:-1]
# XXX if we figure out how to get the source from an exec
# statement, we can do something like this: (see do_source)
#filename = "dbgp:///"+self.stack_re.match(repr(item)).group(1)
filename = "dbgp:///"+type
elif not filename.startswith('dbgp:'):
filename = pathname2url(filename)
ret.append( _frame_template % (level, type,
escape(filename),
lineno, escape(where)) )
except Exception, e:
tb = escape(''.join(traceback.format_list(traceback.extract_tb(sys.exc_info()[2]))))
raise CommandError('stack_get', tid, ERROR_EXCEPTION,
'Unknown exception %s\n%s' % (str(e), tb))
_template = '<response xmlns="urn:debugger_protocol_v1" command="stack_get" depth="%d" transaction_id="%s">%s</response>'
stack_depth = len(self.dbg.stack)
if depth > 0:
self.socket.send_response(_template % (stack_depth, tid, ret[depth]))
else:
self.socket.send_response(_template % (stack_depth, tid, ''.join(ret)))
def do_context_names(self, cmdargs, *args):
tid = self._getTransactionId(cmdargs)
_template = '<response xmlns="urn:debugger_protocol_v1" command="context_names" transaction_id="%s">'
for name in contextNames:
_template += '<context name="%s" id="%d"/>' % (name, contextNames.index(name))
_template += '</response>'
self.socket.send_response(_template % tid)
_context_get_optlist = [['i','transaction_id', int, 1, -1, None],
['d','depth', int, 0, 0, _validateStackDepth],
['c','context_id', int, 0, 0, _validateContextId]]
def do_context_get(self, cmdargs, *args):
(tid, depth, context_id, data,) = \
self._getopts(cmdargs, self._context_get_optlist, "context_get")
contextTypes = hiddenContextTypes[context_id]
try:
frame, lineno = self.dbg.stack[depth]
if context_id == 0: # Locals
items = frame.f_locals
elif context_id < len(contextNames): # Globals
items = frame.f_globals
else:
raise CommandError('context_get', tid, ERROR_CONTEXT_INVALID,
'Invalid context id [%d] requested' % context_id)
ret = []
names = items.keys()
def mycmp(i,j): return cmp(i.lower(), j.lower())
names.sort(mycmp)
# Remove __builtins__ everywhere!
if "__builtins__" in names:
names.remove('__builtins__')
for name in names:
if (not self._show_hidden) and (name[:2] == name[-2:] == '__'):
continue
item = items[name]
typ = type(item)
if contextTypes:
if typ not in contextTypes:
continue
elif typ in HiddenTypes and name not in frame.f_code.co_varnames:
continue
itemProperty = Property(name, name, item, self._data_encoding,
self._show_hidden, contextTypes)
ret.append( itemProperty.toxml(0, self._max_children, self._max_data) )
except Exception, e:
tb = escape(''.join(traceback.format_list(traceback.extract_tb(sys.exc_info()[2]))))
raise CommandError('context_get', tid, ERROR_EXCEPTION,
'Unknown exception %s\n%s' % (str(e),tb))
_template = '<response xmlns="urn:debugger_protocol_v1" command="context_get" context="%d" transaction_id="%s">%s</response>'
data = ''.join(ret)
self.socket.send_response(_template % (context_id, tid, data))
def _getObject(self, frame_index, expr):
frame, lineno = self.dbg.stack[frame_index]
try:
value = eval(expr, frame.f_globals, frame.f_locals)
typ = _get_object_type_string(value)
except:
value = _format_exception_only()
typ = "Error"
return value, typ
def _getContextObject(self, context_id, frame_index, expr ):
frame, lineno = self.dbg.stack[frame_index]
if context_id == 0: # Locals
bindings = frame.f_locals
elif context_id == 1: # Globals
bindings = frame.f_globals
try:
value = eval(expr, bindings)
typ = _get_object_type_string(value)
except Exception, e:
#print str(e)
value = _format_exception_only()
typ = "Error"
return value, typ
_property_get_optlist = [['i', 'transaction_id', int, 1, -1, None],
['d', 'depth', int, 0, 0, _validateStackDepth],
['c', 'context_id', int, 0, 0, _validateContextId],
['n', 'fullname', str, 1, None, None],
['m', 'maxdata', int, 0, 0, None],
['t', 'datatype', str, 0, None, None],
['p', 'datapage', int, 0, 0, None]]
def do_property_get(self, cmdargs, *args):
(tid, depth, context_id, fullname,
maxdata, datatype, datapage, data,) = \
self._getopts(cmdargs, self._property_get_optlist, "property_get")
value, typ = self._getContextObject(context_id, depth, fullname)
if typ == 'Error':
value, typ = self._getObject(depth, fullname)
if typ == 'Error':
raise CommandError('property_get', tid,
ERROR_PROPERTY_DOES_NOT_EXIST, value)
if not maxdata:
maxdata = self._max_data
prop = Property(fullname, fullname, value, self._data_encoding,
self._show_hidden, hiddenContextTypes[context_id])
_template = '<response xmlns="urn:debugger_protocol_v1" command="property_get" context="%d" transaction_id="%s">%s</response>'
self.socket.send_response(_template %
(context_id, tid,
prop.toxml(self._max_depth,
self._max_children,
maxdata,
datapage)))
_property_value_optlist = [['i', 'transaction_id', int, 1, -1, None],
['d', 'depth', int, 0, 0, _validateStackDepth],
['c', 'context_id', int, 0, 0, _validateContextId],
['n', 'fullname', str, 1, None, None]]
def do_property_value(self, cmdargs, *args):
(tid, depth, context_id, fullname, data,) = \
self._getopts(cmdargs, self._property_value_optlist, "property_value")
value, typ = self._getContextObject(context_id, depth, fullname)
if typ == 'Error':
raise CommandError('property_value', tid,
ERROR_PROPERTY_DOES_NOT_EXIST, value)
prop = Property(fullname, fullname, value, self._data_encoding,
self._show_hidden, hiddenContextTypes[context_id])
_template = '<response xmlns="urn:debugger_protocol_v1" command="property_value" size="%d" encoding="%s" transaction_id="%s">%s</response>'
vType = prop.get_type()
encoding=''
value=''
size=0
if vType not in StringTypes:
#if prop.numchildren == 0 or vType in HiddenTypes or vType == types.InstanceType:
value = '<![CDATA[%s]]>' % prop.get_valueString()
else:
value = '<![CDATA[%s]]>' % prop.get_encodedValue()[0]
encoding = prop.encoding
if value:
size = prop.get_dataSize()
self.socket.send_response(_template % (size, encoding, tid, value))
_property_set_optlist = [['i', 'transaction_id', int, 1, -1, None],
['d', 'depth', int, 0, 0, _validateStackDepth],
['c', 'context_id', int, 0, 0, _validateContextId],
['n', 'fullname', str, 1, None, None],
['t', 'type', str, 0, None, None],
['l','length', int, 0, 0, None]]
def do_property_set(self, cmdargs, *args):
(tid, depth, context_id, fullname, data_type, data_length, data,) = \
self._getopts(cmdargs, self._property_set_optlist, "property_set")
frame, lineno = self.dbg.stack[depth]
if self._data_encoding == 'base64':
try:
data = base64.decodestring(data)
except:
pass
# fixup multiline strings
if data_type:
if data_type in ['str','unicode']:
if data_type == 'str':
# no unicode data, try to downgrade the data type
try:
data = str(data)
except:
pass
elif data_type == "unicode":
try:
data = data.decode('utf-8')
except (UnicodeEncodeError, UnicodeDecodeError), e:
try:
data = data.decode()
except (UnicodeEncodeError, UnicodeDecodeError), e:
pass
data = repr(data)
# now we can eval the string data
# if no data type is provided, we assume that the data is an expression!!!
# eval only evaluates the expression in the local scope, but
# using the global scope to help with that evaluation. We want
# to eval the expression, but then modify the variable in the
# correct context. If the variable does not exist, then we
# must use the exec statement to get it created correctly, and
# the fetch it by the variable name.
try:
if frame.f_locals.has_key(fullname) \
or frame.f_globals.has_key(fullname):
# Eval the rhs first.
try:
#print 'eval %r in %r, %r' % (data, frame.f_globals, frame.f_locals)
value = eval(data, frame.f_globals, frame.f_locals)
except:
raise CommandError('property_set', tid,
ERROR_EVAL_FAILED,
'Invalid variable expression eval\'d for [%s]' % fullname)
if context_id == 1 or \
(not frame.f_locals.has_key(fullname) \
and frame.f_globals.has_key(fullname)):
frame.f_globals[fullname] = value
elif self._isInteractiveShell:
# we can set into locals if we're in the shell
frame.f_locals[fullname] = value
else:
setlocal(frame, fullname, value)
else:
# Hack an assignment together.
# XXX This doesn't always work because of LOAD_FAST.
# Either we need to work around or change this code to
# generate an error message if the assignment fails.
expr = fullname + " = " + data
expr = str(expr)
#print 'exec %s in %s, %s' % (expr, frame.f_globals, frame.f_locals)
try:
exec expr in frame.f_globals, frame.f_locals
#print "frame.f_globals:", frame.f_globals
#print "frame.f_locals:", frame.f_locals
except:
raise CommandError('property_set', tid,
ERROR_INVALID_EXPRESSION,
'Invalid variable expression exec\'d [%s]' % expr)
value, typ = self._getContextObject(context_id, depth, fullname)
if typ == 'Error':
raise CommandError('property_set', tid,
ERROR_INVALID_EXPRESSION, value)
except Exception ,e:
raise CommandError('property_set', tid,
ERROR_INVALID_EXPRESSION,
'Invalid variable expression exec\'d [%s]' % e)
_template = '<response xmlns="urn:debugger_protocol_v1" command="property_set" success="1" transaction_id="%s"/>'
self.socket.send_response(_template % (tid))
def do_async_breakpoint_set(self, cmdargs, *args):
self.do_breakpoint_set(cmdargs, args)
_breakpoint_set_optlist = [['i', 'transaction_id', int, 1, -1, None],
['t','type', str, 1, None, _validateBreakpointType],
['s','state', str, 0, 'enabled', _validateBreakpointState],
['n','lineno', int, 0, 0, None],
['f','filename', str, 0, '', None],
['m','function', str, 0, None, None],
['x','exception', str, 0, None, None],
['c','expression', str, 0, None, None],
['h','hit_value', int, 0, None, None],
['o','hit_condition', str, 0, None, None],
['r','temporary', int, 0, 0, None]]
def do_breakpoint_set(self, cmdargs, *args):
(tid, type, state, lineno,
filename, function, exception,
expression, hitValue, hitCondition, temporary, data,) = self._getopts(cmdargs, self._breakpoint_set_optlist, "breakpoint_set")
if filename:
filename = url2pathname(filename)
condition = None
if type == 'line':
if not filename:
(filename, ln, where) = _get_stack_data(self.dbg.stack[0])
filename = url2pathname( filename )
elif type in ['conditional', 'watch']:
if data:
condition = base64.decodestring(data)
else:
raise CommandError('breakpoint_set', tid,
ERROR_BREAKPOINT_INVALID,
'Condition or Watch breakpoint without expression!')
elif type in ['call', 'return']:
condition = function
elif type == 'exception':
condition = exception
enabled = state == 'enabled'
try:
bp = self.dbg.set_break(type, filename, lineno, enabled,
temporary, condition, hitValue,
hitCondition)
except Exception, e:
raise CommandError('breakpoint_set', tid,
ERROR_BREAKPOINT_INVALID, str(e))
if bp.enabled:
state = 'enabled'
else:
state = 'disabled'
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_set" transaction_id="%s" ' +\
'id="%d" state="%s"/>'
self.socket.send_response(_template % (tid, bp.number, state))
def do_async_breakpoint_update(self, cmdargs, *args):
self.do_breakpoint_update(cmdargs, args)
_breakpoint_update_optlist = [['i', 'transaction_id', int, 1, -1, None],
['d','id', int, 1, 0, None],
['s','state', str, 0, None, _validateBreakpointState],
['n','lineno', int, 0, 0, None],
['h','hit_value', int, 0, None, None],
['o','hit_condition', str, 0, None, None],
['r','temporary', int, 0, -1, None]]
def do_breakpoint_update(self, cmdargs, *args):
(tid, bpid, state, lineno,
hitValue, hitCondition, temporary, data,) = self._getopts(cmdargs, self._breakpoint_update_optlist, "breakpoint_update")
try:
bp = breakpointsByNumber[bpid]
except IndexError:
bp = None
if not bp:
raise CommandError('breakpoint_update', tid,
ERROR_BREAKPOINT_DOES_NOT_EXIST,
'Breakpoint number (%d) out of range' % bpid)
if not lineno:
# we have to remove the old breakpoint and add it again,
# since the breakpoints are indexed by line
lineno = bp.line
if not state:
enabled = bp.enabled
else:
enabled = state == 'enabled'
if temporary < 0:
temporary = bp.temporary
try:
bpnew = self.dbg.set_break(bp.type, bp.file, lineno, enabled,
temporary, bp.cond, hitValue,
hitCondition)
# XXX TODO make this part of our breakpoint class
# remove the old, reset the id
bp.deleteMe()
bp = copy.copy(bpnew)
bp.number = bpid
bpnew.deleteMe()
bp.insert()
except Exception, e:
raise CommandError('breakpoint_update', tid,
ERROR_BREAKPOINT_INVALID, str(e))
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_update" transaction_id="%s"/>'
self.socket.send_response(_template % (tid))
def do_async_breakpoint_get(self, cmdargs, *args):
self.do_breakpoint_get(cmdargs, args)
_breakpoint_info_optlist = \
[['i','transaction_id', int, 1, -1, None],
['d','id', int, 1, 0, None]]
def do_breakpoint_get(self, cmdargs, *args):
(tid, bpid, data,) = self._getopts(cmdargs, self._breakpoint_info_optlist, "breakpoint_get")
try:
bp = breakpointsByNumber[bpid]
except IndexError:
bp = None
if not bp:
raise CommandError('breakpoint_get', tid,
ERROR_BREAKPOINT_DOES_NOT_EXIST,
'Breakpoint number (%d) out of range' % bpid)
bpinfo = bp.toxml()
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_get" transaction_id="%s">%s</response>'
self.socket.send_response(_template % (tid, bpinfo))
def do_async_breakpoint_enable(self, cmdargs, *args):
self.do_breakpoint_enable(cmdargs, args)
def do_breakpoint_enable(self, cmdargs, *args):
(tid, bpid, data,) = self._getopts(cmdargs, self._breakpoint_info_optlist, "breakpoint_enable")
try:
bp = breakpointsByNumber[bpid]
except IndexError:
bp = None
if not bp:
raise CommandError('breakpoint_set', tid,
ERROR_BREAKPOINT_DOES_NOT_EXIST,
'Breakpoint number (%d) out of range' % bpid)
bp.enable()
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_enable" transaction_id="%s" ' +\
'id="%d" state="enabled"/>'
self.socket.send_response(_template % (tid, bpid))
def do_async_breakpoint_disable(self, cmdargs, *args):
self.do_breakpoint_disable(cmdargs, args)
def do_breakpoint_disable(self, cmdargs, *args):
(tid, bpid, data,) = self._getopts(cmdargs, self._breakpoint_info_optlist, "breakpoint_disable")
try:
bp = breakpointsByNumber[bpid]
except IndexError:
bp = None
if not bp:
raise CommandError('breakpoint_disable', tid,
ERROR_BREAKPOINT_DOES_NOT_EXIST,
'Breakpoint number (%d) out of range' % bpid)
bp.disable()
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_disable" transaction_id="%s" ' +\
'id="%d" state="disabled"/>'
self.socket.send_response(_template % (tid, bpid))
def do_async_breakpoint_remove(self, cmdargs, *args):
self.do_breakpoint_remove(cmdargs, args)
def do_breakpoint_remove(self, cmdargs, *args):
(tid, bpid, data,) = self._getopts(cmdargs, self._breakpoint_info_optlist, "breakpoint_remove")
try:
bp = breakpointsByNumber[bpid]
except IndexError:
bp = None
if not bp:
raise CommandError('breakpoint_remove', tid,
ERROR_BREAKPOINT_DOES_NOT_EXIST,
'Breakpoint number (%d) out of range' % bpid)
bp.deleteMe()
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_remove" transaction_id="%s"/>'
self.socket.send_response(_template % tid)
def do_async_breakpoint_list(self, cmdargs, *args):
self.do_breakpoint_list(cmdargs, args)
def do_breakpoint_list(self, cmdargs, *args):
tid = self._getTransactionId(cmdargs)
bpinfo = []
for bp in breakpointsByNumber:
# seems bpbynumber always has a single item == None
if not bp:
continue
bpinfo.append(bp.toxml())
bpinfo = ''.join(bpinfo)
_template = '<response xmlns="urn:debugger_protocol_v1" command="breakpoint_list" transaction_id="%s">%s</response>'
self.socket.send_response(_template % (tid, bpinfo))
_eval_optlist = [['i','transaction_id', int, 1, -1, None],
['l','length', int, 1, 0, None]]
def do_eval(self, cmdargs, *args):
(tid, data_length, data,) = self._getopts(cmdargs, self._eval_optlist, "eval")
# read data_length from the socket
if self._data_encoding == 'base64':
try:
data = base64.decodestring(data)
except:
pass
data = data + "\n"
frame, lineno = self.dbg.stack[0]
try:
value = eval(data, frame.f_globals, frame.f_locals)
except Exception, e:
raise CommandError('eval', tid, ERROR_EVAL_FAILED,
'eval of expression failed: '+str(e))
prop = Property(None, None, value, self._data_encoding,
self._show_hidden, hiddenContextTypes[context_id])
_template = '<response xmlns="urn:debugger_protocol_v1" command="eval" transaction_id="%s">%s</response>'
self.socket.send_response(_template %
(tid,
prop.toxml(self._max_depth,
self._max_children,
self._max_data,
0)))
_source_optlist = [['i', 'transaction_id', int, 1, -1, None],
['f','filename', str, 0, None, None],
['b','startline', int, 0, 0, None],
['e','endline', int, 0, -1, None]]
def do_source(self, cmdargs, *args):
(tid, filename, startline, endline, data,) = \
self._getopts(cmdargs, self._source_optlist, "source")
source = None
if not filename:
(filename, lineno, where) = _get_stack_data(self.dbg.stack[0])
try:
filepath = url2pathname( filename )
fullpath = pathname2url( filepath )
f = open(filepath)
source = f.readlines()
f.close()
except IOError, e:
if filename.startswith("dbgp:"):
source = ["Source for exec/eval is unavailable"]
# XXX if we figure out how to get the source from an exec
# statement, we can do something like this:
#filename = filename[8:]
#for item in self.dbg.stack:
# m = self.stack_re.match(repr(item))
# if m and m.group(1) == filename:
# source = ["Source Unavailable"]
# break
#if not source:
# raise CommandError('source', tid,
# ERROR_FILE_ACCESS,
# 'source not available %s\n%s' % (filename,str(e)))
else:
raise CommandError('source', tid,
ERROR_FILE_ACCESS,
'invalid file uri %s\n%s' % (filename,str(e)))
if startline > 0 or endline > -1:
source = ''.join(source[startline:endline])
else:
source = ''.join(source)
if self._data_encoding == 'base64':
source = base64.encodestring(source)
else:
source = escape(source)
_template = '<response xmlns="urn:debugger_protocol_v1" command="source" transaction_id="%s" ' +\
'filename="%s" startline="%d" endline="%d" ' +\
'encoding="%s"><![CDATA[%s]]></response>'
self.socket.send_response(_template % (tid, fullpath, startline,
endline, self._data_encoding,
source))
def do_async_stdin(self, cmdargs, *args):
self.do_stdin(cmdargs, args)
_stdin_optlist = [['i','transaction_id', int, 1, -1, None],
['c','type', int, 0, None, None]]
def do_stdin(self, cmdargs, *args):
(tid, copy, data,) = \
self._getopts(cmdargs, self._stdin_optlist, "stdin")
if copy is not None:
if copy and not self._stdin:
sys.stdin = self._stdin = StreamIn(sys.stdin, self)
elif not copy and self._stdin:
sys.stdin = self._stdin.stop()
sys.stdin.close()
self._stdin = None
else:
raise CommandError('stdin', tid,
ERROR_STREAM_REDIRECT_FAILED,
'setting stdin redirection failed')
elif self._stdin:
# read data_length from the socket
try:
data = base64.decodestring(data)
except:
pass
if data:
sys.stdin.write(data)
else:
sys.stdin = self._stdin.stop()
sys.stdin.close()
self._stdin = None
else:
# we got stdin data without a request to redirect stdin
raise CommandError('stdin', tid,
ERROR_STREAM_REDIRECT_FAILED,
'invalid stdin access, redirect first')
# send a response
_template = '<response xmlns="urn:debugger_protocol_v1" command="stdin" success="1" transaction_id="%s"/>'
self.socket.send_response(_template % tid)
_redirect_optlist = [['i','transaction_id', int, 1, -1, None],
['c','type', int, 0, 1, None]]
def do_stdout(self, cmdargs, *args):
(tid, copy, data,) = \
self._getopts(cmdargs, self._redirect_optlist, "stdout")
if self._stdout:
if copy:
raise CommandError('stdout', tid,
ERROR_STREAM_REDIRECT_FAILED,
'Already redirected')
sys.stdout = self._stdout.stop()
self._stdout = None
else:
sys.stdout = self._stdout = StreamOut('stdout', sys.stdout, self, copy==2)
_template = '<response xmlns="urn:debugger_protocol_v1" command="stdout" success="1" transaction_id="%s"/>'
self.socket.send_response(_template % tid)
def do_stderr(self, cmdargs, *args):
(tid, copy, data,) = \
self._getopts(cmdargs, self._redirect_optlist, "stderr")
if self._stderr:
if copy:
raise CommandError('stderr', tid,
ERROR_STREAM_REDIRECT_FAILED,
'Already redirected')
sys.stderr = self._stderr.stop()
self._stderr = None
else:
sys.stderr = self._stderr = StreamOut('stderr', sys.stderr, self, copy==2)
_template = '<response xmlns="urn:debugger_protocol_v1" command="stderr" success="1" transaction_id="%s"/>'
self.socket.send_response(_template % tid)
def do_typemap_get(self, cmdargs, *args):
tid = self._getTransactionId(cmdargs)
_template = '<response xmlns="urn:debugger_protocol_v1" command="typemap_get" transaction_id="%s" ' + \
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' + \
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' + \
'>%s</response>'
_map = '<map type="%s" name="%s"%s/>'
commonTypes = {
types.IntType: ['int',' xsi:type="xsd:int"'],
types.LongType: ['int',' xsi:type="xsd:long"'],
types.FloatType: ['float',' xsi:type="xsd:float"'],
types.StringType: ['string',' xsi:type="xsd:string"'],
types.NoneType: ['null',''],
types.ListType: ['array',''],
types.TupleType: ['array',''],
types.DictType: ['hash',''],
types.InstanceType: ['object',''],
}
if hasattr(types,'BooleanType'):
commonTypes[types.BooleanType] = ['bool',' xsi:type="xsd:boolean"']
if hasattr(types,'UnicodeType'):
commonTypes[types.UnicodeType] = ['string',' xsi:type="xsd:string"']
# a better way to do this?
# make a list of all the types defined above, then put all the
# other types into the 'resource' type
map = []
usedTypes = []
for pyType in commonTypes.keys():
commonTypeName = commonTypes[pyType][0]
schemaType = commonTypes[pyType][1]
map.append(_map % (commonTypeName,
pyType.__name__,
schemaType))
usedTypes.append(pyType.__name__)
for pyTypeAttr in dir(types):
pyType = getattr(types,pyTypeAttr)
if type(pyType) == types.TypeType and \
pyType.__name__ not in usedTypes:
map.append(_map % ('resource',pyType.__name__,''))
usedTypes.append(pyType.__name__)
maps = ''.join(map)
self.socket.send_response(_template % (tid,maps))
_interact_optlist = [['i','transaction_id', int, 1, -1, None],
['m','mode', int, 0, 1, None]]
def do_interact(self, cmdargs, *args):
(tid, mode, data,) = \
self._getopts(cmdargs, self._interact_optlist, "interact")
lastStatus = self._break_status
self._break_status = STATUS_INTERACTIVE
prompt = ">>> "
more = 0
status = "interactive"
if not mode:
# we're stopping the interactive session
self._interactiveBuffer = []
prompt = ""
status = "break"
self._break_status = STATUS_BREAK
self.dbg.releaseInteractiveDebugger()
if self._isInteractiveShell:
# if we quit interacting, then quit the shell too
self.stopNow()
self._break_status = STATUS_STOPPED
status = "stopped"
elif data or self._interactiveBuffer:
try:
data = base64.decodestring(data)
except:
pass
if data.strip() == "" and len(self._interactiveBuffer) and \
self._interactiveBuffer[-1].strip() == "":
# dedent on a second blank line
data = ""
data = data.split('\n')
for line in data:
self._interactiveBuffer.append(line)
source = '\n'.join(self._interactiveBuffer)
more = self.dbg.getInteractiveDebugger().runsource(source, '<console>')
if more:
prompt = "... "
else:
self._interactiveBuffer = []
self._break_status = lastStatus
_template = '<response xmlns="urn:debugger_protocol_v1" command="interact" status="%s" more="%d" prompt="%s" transaction_id="%s"/>'
self.socket.send_response(_template % (status, more, prompt, tid))
import code
# code module rudely doesn't export this
def softspace(file, newvalue):
oldvalue = 0
try:
oldvalue = file.softspace
except AttributeError:
pass
try:
file.softspace = newvalue
except (AttributeError, TypeError):
# "attribute-less object" or "read-only attributes"
pass
return oldvalue
class FakeCode:
def __init__(self, filename="<console>", co_name="__console__"):
# number of arguments (not including * or ** args)
self.co_argcount = 0
# string of raw compiled bytecode
self.co_code = ""
# tuple of constants used in the bytecode
self.co_consts = ()
# name of file in which this code object was created
self.co_filename = filename
# number of first line in Python source code
self.co_firstlineno = 0
# bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg
self.co_flags = 2
# encoded mapping of line numbers to bytecode indices
self.co_lnotab = {}
# name with which this code object was defined
self.co_name = co_name
# tuple of names of local variables
self.co_names = ()
# number of local variables
self.co_nlocals = 0
# virtual machine stack space required
self.co_stacksize = 0
# tuple of names of arguments and local variables
self.co_varnames = ()
class FakeFrame:
def __init__(self, globals=None, locals=None):
m = None
if not globals:
m = h_main()
m.importSite()
globals = m.globals
globals["__name__"]= "__console__"
globals["__doc__"]= None
globals["__file__"]= "<console>"
if not locals:
locals = globals
exec "import site\n\n" in globals, locals
del globals['site']
#next outer frame object (this frame's caller)
self.f_back = None
# built-in namespace seen by this frame
self.f_builtins = None
# code object being executed in this frame
self.f_code = FakeCode()
# traceback if raised in this frame, or None
self.f_exc_traceback = None
# exception type if raised in this frame, or None
self.f_exc_type = None
# exception value if raised in this frame, or None
self.f_exc_value = None
# global namespace seen by this frame
self.f_globals = globals
# index of last attempted instruction in bytecode
self.f_lasti = 0
# current line number in Python source code
self.f_lineno = 0
# local namespace seen by this frame
self.f_locals = locals
# 0 or 1 if frame is in restricted execution mode
self.f_restricted = 0
# tracing function for this frame, or None
self.f_trace = None
class InteractiveDebugger(code.InteractiveInterpreter):
def __init__(self, debugger, frame=None):
"""Constructor.
The optional 'locals' argument specifies the dictionary in
which code will be executed; it defaults to a newly created
dictionary with key "__name__" set to "__console__" and key
"__doc__" set to None.
"""
self.debugger = debugger
if not frame:
frame = FakeFrame()
self.frame = frame
code.InteractiveInterpreter.__init__(self, frame.f_locals)
def runcode(self, code):
try:
self.debugger.runcode(code, globals = self.frame.f_globals,
locals = self.frame.f_locals)
except SystemExit:
raise
except:
_print_exc()
else:
if softspace(sys.stdout, 0):
print
def _dbgp_start_new_thread(function, args=(), kwargs={}):
# this is called after a REAL thread has been started. Now we must
# actually run the code that was requested for this thread.
# try to get a name for the function that is being executed. First, if
# there is a class with a getName method (ie. trheading.Thread) call that,
# second, if func_name exists, use that, otherwise repr the function
_f_dict = dir(function)
if 'im_self' in _f_dict and function.im_self and \
'getName' in dir(function.im_self):
name = function.im_self.getName()
elif 'func_name' in _f_dict:
name = function.func_name
else:
name = repr(function)
client = backendCmd()
## the values for host and port do not matter here, they are retrieved
## appropriately from the class variables
client.connect('', 9000, name)
client.runThread(function, args, kwargs)
def _thread_start_new_thread(function, args=(), kwargs={}):
# this is called prior to a REAL thread being started. We interrupt and
# redirect the new thread to our own function so we can connect to the
# IDE, then run the thread under the debugger. We call the original
# thread.start_new_thread function here, as that is what actually creates
# the new thread.
return thread._thread_start_new_thread(_dbgp_start_new_thread, (function, args), kwargs)
def set_thread_support(debug_threads):
import thread, threading
if debug_threads:
if not hasattr(thread, '_thread_start_new_thread'):
thread._thread_start_new_thread = thread.start_new_thread
thread.start_new_thread = _thread_start_new_thread
threading._start_new_thread = _thread_start_new_thread
else:
if hasattr(thread, '_thread_start_new_thread'):
thread.start_new_thread = thread._thread_start_new_thread
threading._start_new_thread = thread.start_new_thread
del thread._thread_start_new_thread
def stopDBGP(client):
log.debug("stopDBGP: atexit has been called")
# prevent stepping into functions we call
global DBGPHideChildren
origDBGPHideChildren = DBGPHideChildren
DBGPHideChildren = DBGPDebugDebugger is not 2
try:
client.atexit()
finally:
DBGPHideChildren = origDBGPHideChildren
_connectionData = None
def brk(host = '127.0.0.1', port = 9000, idekey = '',
preloadScript = None, logLevel = logging.WARN):
global _connectionData
# prevent stepping into functions we call
global DBGPHideChildren
origDBGPHideChildren = DBGPHideChildren
DBGPHideChildren = DBGPDebugDebugger is not 2
try:
client = getClientForThread()
if client:
# we're already connected on this thread, so just break
client.breakNow()
return
if _connectionData:
host = _connectionData['host']
port = _connectionData['port']
idekey = _connectionData['idekey']
preloadScript = _connectionData['preloadScript']
logLevel = _connectionData['logLevel']
module = _connectionData['module']
else:
if not idekey:
idekey = getenv('USER', getenv('USERNAME',''))
module = None
# set connectionData now
_connectionData = {'host':host, 'port':port, 'idekey':idekey,
'preloadScript': preloadScript, 'logLevel': logLevel,
'module': module}
# we're not connected, so lets connect now. Also catch exit so we can end
# the connection later
configureLogging(log, logLevel)
frame = sys._getframe().f_back
# get the filename we were called from
scriptArgs = [frame.f_code.co_filename]
# Note: __name__ is not always set, such as when "exec()" was used:
# http://bugs.python.org/issue2903
name = frame.f_globals.get('__name__', '<unknown>')
# set the thread debugging support now
set_thread_support(backendCmd.debug_threads)
# start debugging now
client = backendCmd(idekey, preloadScript,module=module)
client.connect(host, port, name, scriptArgs)
import atexit
atexit.register(stopDBGP, client)
client.breakNow()
finally:
DBGPHideChildren = origDBGPHideChildren
_orig_excepthook = None
def excepthook(type, value, tb):
# prevent stepping into functions we call
global _connectionData
global _orig_excepthook
sys.excepthook = _orig_excepthook
global DBGPHideChildren
origDBGPHideChildren = DBGPHideChildren
DBGPHideChildren = DBGPDebugDebugger is not 2
try:
# we print the traceback to the regular stdout, since we don't know if or
# when the IDE will redirect stdin so it can receive the traceback
_print_exception(type, value, tb)
client = getClientForThread()
if client:
# we're already connected on this thread, so let it go
return
# we're not connected, so lets connect now. Also catch exit so we can end
# the connection later
configureLogging(log, _connectionData['logLevel'])
frame = tb.tb_frame
# get the filename we were called from
scriptArgs = [frame.f_code.co_filename]
name = frame.f_globals['__name__']
# start debugging now
client = backendCmd(_connectionData['idekey'],
_connectionData['preloadScript'],
module=_connectionData['module'])
client.connect(_connectionData['host'], _connectionData['port'], name, scriptArgs)
client.runExceptHook(type, value, tb)
finally:
DBGPHideChildren = origDBGPHideChildren
sys.exit(1)
def brkOnExcept(host = '127.0.0.1', port = 9000, idekey = '',
preloadScript = None, logLevel = logging.WARN,
module=None):
global _connectionData
global _orig_excepthook
if not _connectionData:
if not idekey:
idekey = getenv('USER', getenv('USERNAME',''))
_connectionData = {'host':host, 'port':port, 'idekey':idekey,
'preloadScript': preloadScript, 'logLevel': logLevel,
'module': module}
_orig_excepthook = sys.excepthook
sys.excepthook = excepthook
def runWithoutDebug(debug_args, interactive, host = '127.0.0.1', port = 9000,
idekey = '', logLevel = logging.WARN):
global _connectionData
# execute the script
main = h_main()
if not idekey:
idekey = getenv('USER', getenv('USERNAME',''))
_connectionData = {'host':host, 'port':port, 'idekey':idekey,
'preloadScript': None, 'logLevel': logLevel,
'module': main}
try:
# setup the exception handler
brkOnExcept(host, port, idekey, None, logLevel, module=main)
#exec code_ob in globals, locals
h_execfile(debug_args[0], debug_args, module=main)
except SystemExit, e:
# if someone does a sys.exit(), it's not really an exception.
pass
# turn off exception catch
sys.excepthook = _orig_excepthook
if interactive:
set_thread_support(backendCmd.debug_threads)
client = backendCmd(idekey, module=main)
client.connect(host, port, '__main__', debug_args)
# wait until exit
try:
client.runInteractive()
except SystemExit, e:
# if someone does a sys.exit(), it's not really an exception.
pass
except:
client.close()
traceback.print_exc()