Files
qt-creator/share/qtcreator/debugger/pdbbridge.py
Friedemann Kleint 332a742e0b pdb: Fix command line arguments
pdbbridge uses exec() on the main file which causes the inferior to
use its sys.argv. Inferiors using argparse would then fail since they
cannot make use of the working directory passed.

To fix this, append the inferior arguments separated by "--" and
change sys.argv accordingly.

Change-Id: I35caf4b3ec19c5259c0d4235787c03a3e592768a
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
2023-12-06 12:20:48 +00:00

1734 lines
60 KiB
Python

# Copyright (C) 2016 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import os
import re
import sys
import code
import base64
import linecache
import signal
import string
import inspect
import traceback
import fnmatch
import platform
class QuitException(Exception):
pass
class QtcInternalBreakpoint():
"""Breakpoint class.
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.
"""
next = 1 # Next bp to be assigned
bplist = {} # indexed by (file, lineno) tuple
bpbynumber = [None] # Each entry is None or an instance of Bpt
# index 0 is unused, except for marking an
# effective break .... see effective()
def __init__(self, filepath, line, temporary=False, cond=None, funcname=None):
self.funcname = funcname
# Needed if funcname is not None.
self.func_first_executable_line = None
self.file = filepath # This better be in canonical form!
self.line = line
self.temporary = temporary
self.cond = cond
self.enabled = True
self.ignore = 0
self.hits = 0
self.number = QtcInternalBreakpoint.next
QtcInternalBreakpoint.next += 1
# Build the two lists
self.bpbynumber.append(self)
if (filepath, line) in self.bplist:
self.bplist[filepath, line].append(self)
else:
self.bplist[filepath, line] = [self]
def deleteMe(self):
index = (self.file, self.line)
self.bpbynumber[self.number] = None # No longer in list
self.bplist[index].remove(self)
if not self.bplist[index]:
# No more bp for this f:l combo
del self.bplist[index]
def enable(self):
self.enabled = True
def disable(self):
self.enabled = False
def __str__(self):
return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)
def checkfuncname(b, frame):
"""Check whether we should break here because of `b.funcname`."""
if not b.funcname:
# Breakpoint was set via line number.
if b.line != frame.f_lineno:
# Breakpoint was set at a line with a def statement and the function
# defined is called: don't break.
return False
return True
# Breakpoint set via function name.
if frame.f_code.co_name != b.funcname:
# It's not a function call, but rather execution of def statement.
return False
# We are in the right frame.
if not b.func_first_executable_line:
# The function is entered for the 1st time.
b.func_first_executable_line = frame.f_lineno
if b.func_first_executable_line != frame.f_lineno:
# But we are not at the first line number: don't break.
return False
return True
# Determines if there is an effective (active) breakpoint at this
# line of code. Returns breakpoint number or 0 if none
def effective(filename, line, frame):
"""Determine which breakpoint for this file:line is to be acted upon.
Called only if we know there is a bpt at this
location. Returns breakpoint that was triggered and a flag
that indicates if it is ok to delete a temporary bp.
"""
possibles = QtcInternalBreakpoint.bplist[filename, line]
for b in possibles:
if not b.enabled:
continue
if not checkfuncname(b, frame):
continue
# Count every hit when bp is enabled
b.hits += 1
if not b.cond:
# If unconditional, and ignoring go on to next, else break
if b.ignore > 0:
b.ignore -= 1
continue
else:
# breakpoint and marker that it's ok to delete if temporary
return (b, True)
else:
# Conditional bp.
# Ignore count applies only to those bpt hits where the
# condition evaluates to true.
try:
val = eval(b.cond, frame.f_globals, frame.f_locals)
if val:
if b.ignore > 0:
b.ignore -= 1
# continue
else:
return (b, True)
# else:
# continue
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, False)
return (None, None)
# __all__ = ['QtcInternalDumper']
def find_function(funcname, filename):
cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname))
try:
fp = open(filename)
except OSError:
return None
# consumer of this info expects the first line to be 1
with fp:
for lineno, line in enumerate(fp, start=1):
if cre.match(line):
return funcname, filename, lineno
return None
class _rstr(str):
"""String that doesn't quote its repr."""
def __repr__(self):
return self
class QtcInternalDumper():
identchars = string.ascii_letters + string.digits + '_'
lastcmd = ''
use_rawinput = 1
def __init__(self, stdin=None, stdout=None):
self.skip = []
self.breaks = {}
self.fncache = {}
self.frame_returning = None
nosigint = False
if stdin is not None:
self.stdin = stdin
else:
self.stdin = sys.stdin
if stdout is not None:
self.stdout = stdout
else:
self.stdout = sys.stdout
self.cmdqueue = []
if stdout:
self.use_rawinput = 0
self.aliases = {}
self.mainpyfile = ''
self._wait_for_mainpyfile = False
# Try to load readline if it exists
try:
import readline
# remove some common file name delimiters
readline.set_completer_delims(' \t\n`@#$%^&*()=+[{]}\\|;:\'",<>?')
except ImportError:
pass
self.allow_kbdint = False
self.nosigint = nosigint
self.commands = {} # associates a command list to breakpoint numbers
self.botframe = None
self.currentbp = -1
self.stopframe = None
self.returnframe = None
self.quitting = False
self.stoplineno = 0
self.mainpyfile = ''
self._user_requested_quit = False
self.stack = []
self.curindex = 0
self.curframe = None
self.curframe_locals = {}
self.typeformats = {}
self.formats = {}
self.output = ''
self.expandedINames = []
# Hex decoding operating on str, return str.
@staticmethod
def hexdecode(s):
if sys.version_info[0] == 2:
return s.decode('hex')
return bytes.fromhex(s).decode('utf8')
# Hex encoding operating on str or bytes, return str.
@staticmethod
def hexencode(s):
if sys.version_info[0] == 2:
return s.encode('hex')
if isinstance(s, __builtins__.str):
s = s.encode('utf8')
return base64.b16encode(s).decode('utf8')
@staticmethod
def cleanAddress(addr):
if addr is None:
return '<no address>'
h = hex(addr)
return '0x%x' % (int(h, 16) if sys.version_info[0] >= 3 else long(h, 16))
def canonic(self, filename):
if filename == '<' + filename[1:-1] + '>':
return filename
canonic = self.fncache.get(filename)
if not canonic:
canonic = os.path.abspath(filename)
canonic = os.path.normcase(canonic)
if platform.system() in ('Microsoft', 'Windows'):
canonic = canonic.replace('\\', '/')
self.fncache[filename] = canonic
return canonic
def reset(self):
linecache.checkcache()
self.botframe = None
self._set_stopinfo(None, None)
self.forget()
def trace_dispatch(self, frame, event, arg):
if self.quitting:
return None
if event == 'line':
return self.dispatch_line(frame)
if event == 'call':
return self.dispatch_call(frame)
if event == 'return':
return self.dispatch_return(frame, arg)
if event == 'exception':
return self.dispatch_exception(frame, arg)
if event == 'c_call':
return self.trace_dispatch
if event == 'c_exception':
return self.trace_dispatch
if event == 'c_return':
return self.trace_dispatch
print('Bdb.dispatch: unknown debugging event:', repr(event))
return self.trace_dispatch
def dispatch_line(self, frame):
if self.stop_here(frame) or self.break_here(frame):
self.user_line(frame)
if self.quitting:
raise QuitException
return self.trace_dispatch
def dispatch_call(self, frame):
if self.botframe is None:
# First call of dispatch since reset()
self.botframe = frame.f_back # (CT) Note that this may also be None!
return self.trace_dispatch
if not (self.stop_here(frame) or self.break_anywhere(frame)):
# No need to trace this function
return None
# Ignore call events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & inspect.CO_GENERATOR:
return self.trace_dispatch
self.user_call(frame)
if self.quitting:
raise QuitException
return self.trace_dispatch
def dispatch_return(self, frame, arg):
if self.stop_here(frame) or frame == self.returnframe:
# Ignore return events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & inspect.CO_GENERATOR:
return self.trace_dispatch
try:
self.frame_returning = frame
self.user_return(frame, arg)
finally:
self.frame_returning = None
if self.quitting:
raise QuitException
# The user issued a 'next' or 'until' command.
if self.stopframe is frame and self.stoplineno != -1:
self._set_stopinfo(None, None)
return self.trace_dispatch
def dispatch_exception(self, frame, arg):
if self.stop_here(frame):
# When stepping with next/until/return in a generator frame, skip
# the internal StopIteration exception (with no traceback)
# triggered by a subiterator run with the 'yield from' statement.
if not (frame.f_code.co_flags & inspect.CO_GENERATOR
and arg[0] is StopIteration and arg[2] is None):
self.user_exception(frame, arg)
if self.quitting:
raise QuitException
# Stop at the StopIteration or GeneratorExit exception when the user
# has set stopframe in a generator by issuing a return command, or a
# next/until command at the last statement in the generator before the
# exception.
elif (self.stopframe and frame is not self.stopframe
and self.stopframe.f_code.co_flags & inspect.CO_GENERATOR
and arg[0] in (StopIteration, GeneratorExit)):
self.user_exception(frame, arg)
if self.quitting:
raise QuitException
return self.trace_dispatch
# Normally derived classes don't override the following
# methods, but they may if they want to redefine the
# definition of stopping and breakpoints.
def is_skipped_module(self, module_name):
for pattern in self.skip:
if fnmatch.fnmatch(module_name, pattern):
return True
return False
def stop_here(self, frame):
# (CT) stopframe may now also be None, see dispatch_call.
# (CT) the former test for None is therefore removed from here.
if self.skip and \
self.is_skipped_module(frame.f_globals.get('__name__')):
return False
if frame is self.stopframe:
if self.stoplineno == -1:
return False
return frame.f_lineno >= self.stoplineno
if not self.stopframe:
return True
return False
def break_here(self, frame):
filename = self.canonic(frame.f_code.co_filename)
if filename not in self.breaks:
return False
lineno = frame.f_lineno
if lineno not in self.breaks[filename]:
# The line itself has no breakpoint, but maybe the line is the
# first line of a function with breakpoint set by function name.
lineno = frame.f_code.co_firstlineno
if lineno not in self.breaks[filename]:
return False
# flag says ok to delete temp. bp
(bp, flag) = effective(filename, lineno, frame)
if bp:
self.currentbp = bp.number
if (flag and bp.temporary):
self.do_clear(__builtins__.str(bp.number))
return True
else:
return False
def break_anywhere(self, frame):
return self.canonic(frame.f_code.co_filename) in self.breaks
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
self.stopframe = stopframe
self.returnframe = returnframe
self.quitting = False
# stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all
self.stoplineno = stoplineno
# Derived classes and clients can call the following methods
# to affect the stepping state.
def set_step(self):
"""Stop after one line of code."""
# Issue #13183: pdb skips frames after hitting a breakpoint and running
# step commands.
# Restore the trace function in the caller (that may not have been set
# for performance reasons) when returning from the current frame.
if self.frame_returning:
caller_frame = self.frame_returning.f_back
if caller_frame and not caller_frame.f_trace:
caller_frame.f_trace = self.trace_dispatch
self._set_stopinfo(None, None)
def set_trace(self, frame=None):
"""Start debugging from `frame`.
If frame is not specified, debugging starts from caller's frame.
"""
if frame is None:
frame = sys._getframe().f_back
self.reset()
while frame:
frame.f_trace = self.trace_dispatch
self.botframe = frame
frame = frame.f_back
self.set_step()
sys.settrace(self.trace_dispatch)
def set_continue(self):
# Don't stop except at breakpoints or when finished
self._set_stopinfo(self.botframe, None, -1)
if not self.breaks:
# no breakpoints; run without debugger overhead
sys.settrace(None)
frame = sys._getframe().f_back
while frame and frame is not self.botframe:
del frame.f_trace
frame = frame.f_back
def set_quit(self):
self.stopframe = self.botframe
self.returnframe = None
self.quitting = True
sys.settrace(None)
# Derived classes and clients can call the following methods
# to manipulate breakpoints. These methods return an
# error message if something went wrong, None if all is well.
# Set_break prints out the breakpoint line and file:lineno.
# Call self.get_*break*() to see the breakpoints or better
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
def set_break(self, filename, lineno, temporary=False, cond=None,
funcname=None):
filename = self.canonic(filename)
line = linecache.getline(filename, lineno)
if not line:
return 'Line %s:%d does not exist' % (filename, lineno)
lines = self.breaks.setdefault(filename, [])
if lineno not in lines:
lines.append(lineno)
QtcInternalBreakpoint(filename, lineno, temporary, cond, funcname)
return None
def _prune_breaks(self, filename, lineno):
if (filename, lineno) not in QtcInternalBreakpoint.bplist:
self.breaks[filename].remove(lineno)
if not self.breaks[filename]:
del self.breaks[filename]
def clear_break(self, filename, lineno):
filename = self.canonic(filename)
if filename not in self.breaks:
return 'There are no breakpoints in %s' % filename
if lineno not in self.breaks[filename]:
return 'There is no breakpoint at %s:%d' % (filename, lineno)
# If there's only one bp in the list for that file,line
# pair, then remove the breaks entry
for bp in QtcInternalBreakpoint.bplist[filename, lineno][:]:
bp.deleteMe()
self._prune_breaks(filename, lineno)
return None
def clear_bpbynumber(self, arg):
try:
bp = self.get_bpbynumber(arg)
except ValueError as err:
return __builtins__.str(err)
bp.deleteMe()
self._prune_breaks(bp.file, bp.line)
return None
def clear_all_file_breaks(self, filename):
filename = self.canonic(filename)
if filename not in self.breaks:
return
for line in self.breaks[filename]:
blist = QtcInternalBreakpoint.bplist[filename, line]
for bp in blist:
bp.deleteMe()
del self.breaks[filename]
def clear_all_breaks(self):
if not self.breaks:
return
for bp in QtcInternalBreakpoint.bpbynumber:
if bp:
bp.deleteMe()
self.breaks = {}
@staticmethod
def get_bpbynumber(arg):
if not arg:
raise ValueError('Breakpoint number expected')
try:
number = int(arg)
except ValueError:
raise ValueError('Non-numeric breakpoint number %s' % arg)
try:
bp = QtcInternalBreakpoint.bpbynumber[number]
except IndexError:
raise ValueError('Breakpoint number %d out of range' % number)
if bp is None:
raise ValueError('Breakpoint %d already deleted' % number)
return bp
def get_break(self, filename, lineno):
filename = self.canonic(filename)
return filename in self.breaks and \
lineno in self.breaks[filename]
def get_breaks(self, filename, lineno):
filename = self.canonic(filename)
return QtcInternalBreakpoint.bplist.get((filename, lineno), [])
def get_file_breaks(self, filename):
filename = self.canonic(filename)
return self.breaks.get(filename, [])
def get_all_breaks(self):
return self.breaks
# Derived classes and clients can call the following method
# to get a data structure representing a stack trace.
def get_stack(self, frame, tb):
stack = []
if tb and tb.tb_frame is frame:
tb = tb.tb_next
while frame is not None:
stack.append((frame, frame.f_lineno))
if frame is self.botframe:
break
frame = frame.f_back
stack.reverse()
i = max(0, __builtins__.len(stack) - 1)
while tb is not None:
stack.append((tb.tb_frame, tb.tb_lineno))
tb = tb.tb_next
if frame is None:
i = max(0, __builtins__.len(stack) - 1)
return stack, i
# The following methods can be called by clients to use
# a debugger to debug a statement or an expression.
# Both can be given as a string, or a code object.
def run(self, cmd, pyGlobals=None, pyLocals=None):
if pyGlobals is None:
import __main__
pyGlobals = __main__.__dict__
if pyLocals is None:
pyLocals = pyGlobals
self.reset()
if isinstance(cmd, __builtins__.str):
cmd = compile(cmd, '<string>', 'exec')
sys.settrace(self.trace_dispatch)
try:
exec(cmd, pyGlobals, pyLocals)
except QuitException:
pass
finally:
self.quitting = True
sys.settrace(None)
def runeval(self, expr, pyGlobals=None, pyLocals=None):
if pyGlobals is None:
import __main__
pyGlobals = __main__.__dict__
if pyLocals is None:
pyLocals = pyGlobals
self.reset()
sys.settrace(self.trace_dispatch)
try:
return eval(expr, pyGlobals, pyLocals)
except QuitException:
pass
finally:
self.quitting = True
sys.settrace(None)
# This method is more useful to debug a single function call.
def runcall(self, func, *args, **kwds):
self.reset()
sys.settrace(self.trace_dispatch)
res = None
try:
res = func(*args, **kwds)
except QuitException:
pass
finally:
self.quitting = True
sys.settrace(None)
return res
def cmdloop(self):
"""Repeatedly accept input, parse an initial prefix
off the received input, and dispatch to action methods, passing them
the remainder of the line as argument.
"""
try:
stop = None
while not stop:
if self.cmdqueue:
line = self.cmdqueue.pop(0)
else:
if self.use_rawinput:
try:
if sys.version_info[0] == 2:
line = raw_input('')
else:
line = input('')
except EOFError:
line = 'EOF'
else:
self.stdout.write('')
self.stdout.flush()
line = self.stdin.readline()
if not line:
line = 'EOF'
else:
line = line.rstrip('\r\n')
print('LINE: %s' % line)
stop = self.onecmd(line)
finally:
pass
def parseline(self, line):
"""Parse the line into a command name and a string containing
the arguments. Returns a tuple containing (command, args, line).
'command' and 'args' may be None if the line couldn't be parsed.
"""
line = line.strip()
if not line:
return None, None, line
elif line[0] == '?':
line = 'help ' + line[1:]
elif line[0] == '!':
if hasattr(self, 'do_shell'):
line = 'shell ' + line[1:]
else:
return None, None, line
i, length = 0, __builtins__.len(line)
while i < length and line[i] in self.identchars:
i = i + 1
cmd, arg = line[:i], line[i:].strip()
return cmd, arg, line
def onecmd(self, line):
"""Interpret the argument as though it had been typed in response
to the prompt.
The return value is a flag indicating whether interpretation of
commands by the interpreter should stop.
"""
line = __builtins__.str(line)
print('LINE 0: %s' % line)
cmd, arg, line = self.parseline(line)
print('LINE 1: %s' % line)
if cmd is None:
return self.default(line)
self.lastcmd = line
if line == 'EOF':
self.lastcmd = ''
if cmd == '':
return self.default(line)
else:
func = getattr(self, 'do_' + cmd, None)
if func:
return func(arg)
return self.default(line)
def runit(self):
print('DIR: %s' % dir())
print('ARGV: %s' % sys.argv)
if sys.argv[0] == '-c':
sys.argv = sys.argv[2:]
else:
sys.argv = sys.argv[1:]
mainpyfile = sys.argv[0] # Get script filename
sys.path.append(os.path.dirname(mainpyfile))
# Delete arguments superfluous to the inferior
try:
args_pos = sys.argv.index("--")
sys.argv = [sys.argv[0]] + sys.argv[args_pos + 1:]
except ValueError:
pass
print('INFERIOR ARGV: %s' % sys.argv)
print('MAIN: %s' % mainpyfile)
while True:
try:
# The script has to run in __main__ namespace (or imports from
# __main__ will break).
#
# So we clear up the __main__ and set several special variables
# (this gets rid of pdb's globals and cleans old variables on restarts).
import __main__
# __main__.__dict__.clear()
__main__.__dict__.update({'__name__': '__main__',
'__file__': mainpyfile,
#'__builtins__': __builtins__,
})
# When bdb sets tracing, a number of call and line events happens
# BEFORE debugger even reaches user's code (and the exact sequence of
# events depends on python version). So we take special measures to
# avoid stopping before we reach the main script (see user_line and
# user_call for details).
self._wait_for_mainpyfile = True
self.mainpyfile = self.canonic(mainpyfile)
self._user_requested_quit = False
with open(mainpyfile, 'rb') as fp:
statement = "exec(compile(%r, %r, 'exec'))" % \
(fp.read(), self.mainpyfile)
self.run(statement)
if self._user_requested_quit:
break
print('The program finished')
sys.exit(0)
except SystemExit:
# In most cases SystemExit does not warrant a post-mortem session.
print('The program exited via sys.exit(). Exit status:')
print(sys.exc_info()[1])
t = sys.exc_info()[2]
print('Post-mortem debugging is finished - ending debug session.')
sys.exit(0)
except:
traceback.print_exc()
print('Uncaught exception. Entering post mortem debugging')
t = sys.exc_info()[2]
self.curframe_locals['__exception__'] = t
print('Post mortem debugger finished - ending debug session.')
sys.exit(0)
def sigint_handler(self, signum, frame):
if self.allow_kbdint:
raise KeyboardInterrupt
self.report('state="stopped"')
self.set_step()
self.set_trace(frame)
# restore previous signal handler
signal.signal(signal.SIGINT, self._previous_sigint_handler)
def forget(self):
self.stack = []
self.curindex = 0
self.curframe = None
def setup(self, frame, tb):
self.forget()
self.stack, self.curindex = self.get_stack(frame, tb)
self.curframe = self.stack[self.curindex][0]
# The f_locals dictionary is updated from the actual frame
# locals whenever the .f_locals accessor is called, so we
# cache it here to ensure that modifications are not overwritten.
self.curframe_locals = self.curframe.f_locals
def user_call(self, frame):
"""This method is called when there is the remote possibility
that we ever need to stop in this function."""
if self._wait_for_mainpyfile:
return
if self.stop_here(frame):
self.message('--Call--')
self.interaction(frame, None)
def user_line(self, frame):
"""This function is called when we stop or break at this line."""
if self._wait_for_mainpyfile:
if (self.mainpyfile != self.canonic(frame.f_code.co_filename)
or frame.f_lineno <= 0):
return
self._wait_for_mainpyfile = False
if self.bp_commands(frame):
self.interaction(frame, None)
def bp_commands(self, frame):
"""Call every command that was set for the current active breakpoint
(if there is one).
Returns True if the normal interaction function must be called,
False otherwise."""
# self.currentbp is set in break_here if a breakpoint was hit
if getattr(self, 'currentbp', False) and self.currentbp in self.commands:
currentbp = self.currentbp
self.currentbp = 0
lastcmd_back = self.lastcmd
self.setup(frame, None)
for line in self.commands[currentbp]:
self.onecmd(line)
self.lastcmd = lastcmd_back
self.forget()
return False
return True
def user_return(self, frame, return_value):
"""This function is called when a return trap is set here."""
if self._wait_for_mainpyfile:
return
frame.f_locals['__return__'] = return_value
self.message('--Return--')
self.interaction(frame, None)
def user_exception(self, frame, exc_info):
"""This function is called if an exception occurs,
but only if we are to stop at or just below this level."""
if self._wait_for_mainpyfile:
return
exc_type, exc_value, exc_traceback = exc_info
frame.f_locals['__exception__'] = exc_type, exc_value
# An 'Internal StopIteration' exception is an exception debug event
# issued by the interpreter when handling a subgenerator run with
# 'yield from' or a generator controled by a for loop. No exception has
# actually occurred in this case. The debugger uses this debug event to
# stop when the debuggee is returning from such generators.
prefix = 'Internal ' if (not exc_traceback
and exc_type is StopIteration) else ''
self.message('%s%s' % (prefix,
traceback.format_exception_only(exc_type, exc_value)[-1].strip()))
self.interaction(frame, exc_traceback)
def interaction(self, frame, tb):
if self.setup(frame, tb):
# no interaction desired at this time (happens if .pdbrc contains
# a command like 'continue')
self.forget()
return
frame, lineNumber = self.stack[self.curindex]
fileName = self.canonic(frame.f_code.co_filename)
self.report('location={file="%s",line="%s"}' % (fileName, lineNumber))
while True:
try:
# keyboard interrupts allow for an easy way to cancel
# the current command, so allow them during interactive input
self.allow_kbdint = True
self.cmdloop()
self.allow_kbdint = False
break
except KeyboardInterrupt:
self.message('--KeyboardInterrupt--')
self.forget()
def displayhook(self, obj):
"""Custom displayhook for the exec in default(), which prevents
assignment of the _ variable in the builtins.
"""
# reproduce the behavior of the standard displayhook, not printing None
if obj is not None:
self.message(repr(obj))
def default(self, line):
if line[:1] == '!':
line = line[1:]
pyLocals = self.curframe_locals
pyGlobals = self.curframe.f_globals
try:
code = compile(line + '\n', '<stdin>', 'single')
save_stdout = sys.stdout
save_stdin = sys.stdin
save_displayhook = sys.displayhook
try:
sys.stdin = self.stdin
sys.stdout = self.stdout
sys.displayhook = self.displayhook
exec(code, pyGlobals, pyLocals)
finally:
sys.stdout = save_stdout
sys.stdin = save_stdin
sys.displayhook = save_displayhook
except:
exc_info = sys.exc_info()[:2]
self.error(traceback.format_exception_only(*exc_info)[-1].strip())
@staticmethod
def message(msg):
print(msg)
@staticmethod
def error(msg):
# print('***'+ msg)
pass
def do_break(self, arg, temporary=0):
"""b(reak) [ ([filename:]lineno | function) [, condition] ]
Without argument, list all breaks.
With a line number argument, set a break at this line in the
current file. With a function name, set a break at the first
executable line of that function. If a second argument is
present, it is a string specifying an expression which must
evaluate to true before the breakpoint is honored.
The line number may be prefixed with a filename and a colon,
to specify a breakpoint in another file (probably one that
hasn't been loaded yet). The file is searched for on
sys.path; the .py suffix may be omitted.
"""
if not arg:
if self.breaks: # There's at least one
self.message('Num Type Disp Enb Where')
for bp in QtcInternalBreakpoint.bpbynumber:
if bp:
self.message(bp.bpformat())
return
# parse arguments; comma has lowest precedence
# and cannot occur in filename
filename = None
lineno = None
cond = None
comma = arg.find(',')
if comma > 0:
# parse stuff after comma: 'condition'
cond = arg[comma + 1:].lstrip()
arg = arg[:comma].rstrip()
# parse stuff before comma: [filename:]lineno | function
colon = arg.rfind(':')
funcname = None
if colon >= 0:
filename = arg[:colon].rstrip()
f = self.lookupmodule(filename)
if not f:
self.error('%r not found from sys.path' % filename)
return
else:
filename = f
arg = arg[colon + 1:].lstrip()
try:
lineno = int(arg)
except ValueError:
self.error('Bad lineno: %s' % arg)
return
else:
# no colon; can be lineno or function
try:
lineno = int(arg)
except ValueError:
try:
func = eval(arg,
self.curframe.f_globals,
self.curframe_locals)
except:
func = arg
try:
if hasattr(func, '__func__'):
func = func.__func__
code = func.__code__
# use co_name to identify the bkpt (function names
# could be aliased, but co_name is invariant)
funcname = code.co_name
lineno = code.co_firstlineno
filename = code.co_filename
except:
# last thing to try
(ok, filename, ln) = self.lineinfo(arg)
if not ok:
self.error('The specified object %r is not a function '
'or was not found along sys.path.' % arg)
return
funcname = ok # ok contains a function name
lineno = int(ln)
if not filename:
filename = self.defaultFile()
# Check for reasonable breakpoint
line = self.checkline(filename, lineno)
if line:
# now set the break point
err = self.set_break(filename, line, temporary, cond, funcname)
if err:
self.error(err)
else:
bp = self.get_breaks(filename, line)[-1]
self.message('Breakpoint %d at %s:%d' %
(bp.number, bp.file, bp.line))
# To be overridden in derived debuggers
def defaultFile(self):
"""Produce a reasonable default."""
filename = self.curframe.f_code.co_filename
if filename == '<string>' and self.mainpyfile:
filename = self.mainpyfile
return filename
def do_tbreak(self, arg):
"""tbreak [ ([filename:]lineno | function) [, condition] ]
Same arguments as break, but sets a temporary breakpoint: it
is automatically deleted when first hit.
"""
self.do_break(arg, 1)
def lineinfo(self, identifier):
failed = (None, None, None)
# Input is identifier, may be in single quotes
idstring = identifier.split("'")
if __builtins__.len(idstring) == 1:
# not in single quotes
tmp_id = idstring[0].strip()
elif __builtins__.len(idstring) == 3:
# quoted
tmp_id = idstring[1].strip()
else:
return failed
if tmp_id == '':
return failed
parts = tmp_id.split('.')
# Protection for derived debuggers
if parts[0] == 'self':
del parts[0]
if not parts:
return failed
# Best first guess at file to look at
fname = self.defaultFile()
if __builtins__.len(parts) == 1:
item = parts[0]
else:
# More than one part.
# First is module, second is method/class
f = self.lookupmodule(parts[0])
if f:
fname = f
item = parts[1]
answer = find_function(item, fname)
return answer or failed
def checkline(self, filename, lineno):
"""Check whether specified line seems to be executable.
Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank
line or EOF). Warning: testing is not comprehensive.
"""
# this method should be callable before starting debugging, so default
# to "no globals" if there is no current frame
globs = self.curframe.f_globals if hasattr(self, 'curframe') else None
line = linecache.getline(filename, lineno, globs)
if not line:
self.message('End of file')
return 0
line = line.strip()
# Don't allow setting breakpoint at a blank line
if (not line or (line[0] == '#') or
(line[:3] == '"""') or line[:3] == "'''"):
self.error('Blank or comment')
return 0
return lineno
def do_enable(self, arg):
"""enable bpnumber [bpnumber ...]
Enables the breakpoints given as a space separated list of
breakpoint numbers.
"""
args = arg.split()
for i in args:
try:
bp = self.get_bpbynumber(i)
except ValueError as err:
self.error(err)
else:
bp.enable()
self.message('Enabled %s' % bp)
def do_disable(self, arg):
"""disable bpnumber [bpnumber ...]
Disables the breakpoints given as a space separated list of
breakpoint numbers. Disabling a breakpoint means it cannot
cause the program to stop execution, but unlike clearing a
breakpoint, it remains in the list of breakpoints and can be
(re-)enabled.
"""
args = arg.split()
for i in args:
try:
bp = self.get_bpbynumber(i)
except ValueError as err:
self.error(err)
else:
bp.disable()
self.message('Disabled %s' % bp)
def do_condition(self, arg):
"""condition bpnumber [condition]
Set a new condition for the breakpoint, an expression which
must evaluate to true before the breakpoint is honored. If
condition is absent, any existing condition is removed; i.e.,
the breakpoint is made unconditional.
"""
args = arg.split(' ', 1)
try:
cond = args[1]
except IndexError:
cond = None
try:
bp = self.get_bpbynumber(args[0].strip())
except IndexError:
self.error('Breakpoint number expected')
except ValueError as err:
self.error(err)
else:
bp.cond = cond
if not cond:
self.message('Breakpoint %d is now unconditional.' % bp.number)
else:
self.message('New condition set for breakpoint %d.' % bp.number)
def do_ignore(self, arg):
"""ignore bpnumber [count]
Set the ignore count for the given breakpoint number. If
count is omitted, the ignore count is set to 0. A breakpoint
becomes active when the ignore count is zero. When non-zero,
the count is decremented each time the breakpoint is reached
and the breakpoint is not disabled and any associated
condition evaluates to true.
"""
args = arg.split()
try:
count = int(args[1].strip())
except:
count = 0
try:
bp = self.get_bpbynumber(args[0].strip())
except IndexError:
self.error('Breakpoint number expected')
except ValueError as err:
self.error(err)
else:
bp.ignore = count
if count > 0:
if count > 1:
countstr = '%d crossings' % count
else:
countstr = '1 crossing'
self.message('Will ignore next %s of breakpoint %d.' %
(countstr, bp.number))
else:
self.message('Will stop next time breakpoint %d is reached.'
% bp.number)
def do_clear(self, arg):
"""cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
With a space separated list of breakpoint numbers, clear
those breakpoints. Without argument, clear all breaks (but
first ask confirmation). With a filename:lineno argument,
clear all breaks at that line in that file.
"""
if not arg:
try:
reply = input('Clear all breaks? ')
except EOFError:
reply = 'no'
reply = reply.strip().lower()
if reply in ('y', 'yes'):
bplist = [bp for bp in QtcInternalBreakpoint.bpbynumber if bp]
self.clear_all_breaks()
for bp in bplist:
self.message('Deleted %s' % bp)
return
if ':' in arg:
# Make sure it works for 'clear C:\foo\bar.py:12'
i = arg.rfind(':')
filename = arg[:i]
arg = arg[i + 1:]
try:
lineno = int(arg)
except ValueError:
err = 'Invalid line number (%s)' % arg
else:
bplist = self.get_breaks(filename, lineno)
err = self.clear_break(filename, lineno)
if err:
self.error(err)
else:
for bp in bplist:
self.message('Deleted %s' % bp)
return
numberlist = arg.split()
for i in numberlist:
try:
bp = self.get_bpbynumber(i)
except ValueError as err:
self.error(err)
else:
self.clear_bpbynumber(i)
self.message('Deleted %s' % bp)
def do_until(self, arg):
"""until [lineno]
Without argument, continue execution until the line with a
number greater than the current one is reached. With a line
number, continue execution until a line with a number greater
or equal to that is reached. In both cases, also stop when
the current frame returns.
"""
if arg:
try:
lineno = int(arg)
except ValueError:
self.error('Error in argument: %r' % arg)
return 0
if lineno <= self.curframe.f_lineno:
self.error('"until" line number is smaller than current '
'line number')
return 0
else:
lineno = None
lineno = self.curframe.f_lineno + 1
self._set_stopinfo(self.curframe, self.curframe, lineno)
return 1
def do_step(self, arg):
self.set_step()
return 1
def do_next(self, arg):
self._set_stopinfo(self.curframe, None)
return 1
def do_return(self, arg):
if self.curframe.f_code.co_flags & inspect.CO_GENERATOR:
self._set_stopinfo(self.curframe, None, -1)
else:
self._set_stopinfo(self.curframe.f_back, self.curframe)
return 1
def do_continue(self, arg):
"""continue
Continue execution, only stop when a breakpoint is encountered.
"""
if not self.nosigint:
try:
self._previous_sigint_handler = \
signal.signal(signal.SIGINT, self.sigint_handler)
except ValueError:
# ValueError happens when do_continue() is invoked from
# a non-main thread in which case we just continue without
# SIGINT set. Would printing a message here (once) make
# sense?
pass
self.set_continue()
return 1
def do_jump(self, arg):
"""jump lineno
Set the next line that will be executed. Only available in
the bottom-most frame. This lets you jump back and execute
code again, or jump forward to skip code that you don't want
to run.
It should be noted that not all jumps are allowed -- for
instance it is not possible to jump into the middle of a
for loop or out of a finally clause.
"""
if self.curindex + 1 != __builtins__.len(self.stack):
self.error('You can only jump within the bottom frame')
return
try:
arg = int(arg)
except ValueError:
self.error("The 'jump' command requires a line number")
else:
try:
# Do the jump, fix up our copy of the stack, and display the
# new position
self.curframe.f_lineno = arg
self.stack[self.curindex] = self.stack[self.curindex][0], arg
except ValueError as e:
self.error('Jump failed: %s' % e)
def do_debug(self, arg):
"""debug code
Enter a recursive debugger that steps through the code
argument (which is an arbitrary expression or statement to be
executed in the current environment).
"""
sys.settrace(None)
pyGlobals = self.curframe.f_globals
pyLocals = self.curframe_locals
p = QtcInternalDumper(self.stdin, self.stdout)
self.message('ENTERING RECURSIVE DEBUGGER')
sys.call_tracing(p.run, (arg, pyGlobals, pyLocals))
self.message('LEAVING RECURSIVE DEBUGGER')
sys.settrace(self.trace_dispatch)
self.lastcmd = p.lastcmd
def do_quit(self, arg):
"""q(uit)\nexit
Quit from the debugger. The program being executed is aborted.
"""
self._user_requested_quit = True
self.set_quit()
return 1
def do_EOF(self, arg):
"""EOF
Handles the receipt of EOF as a command.
"""
self.message('')
self._user_requested_quit = True
self.set_quit()
return 1
def do_args(self, arg):
"""a(rgs)
Print the argument list of the current function.
"""
co = self.curframe.f_code
loc = self.curframe_locals
n = co.co_argcount
if co.co_flags & 4:
n = n + 1
if co.co_flags & 8:
n = n + 1
for i in range(n):
name = co.co_varnames[i]
if name in loc:
self.message('%s = %r' % (name, loc[name]))
else:
self.message('%s = *** undefined ***' % (name,))
def do_retval(self, arg):
"""retval
Print the return value for the last return of a function.
"""
if '__return__' in self.curframe_locals:
self.message(repr(self.curframe_locals['__return__']))
else:
self.error('Not yet returned!')
def _getval(self, arg):
try:
return eval(arg, self.curframe.f_globals, self.curframe_locals)
except:
exc_info = sys.exc_info()[:2]
self.error(traceback.format_exception_only(*exc_info)[-1].strip())
raise
def _getval_except(self, arg, frame=None):
try:
if frame is None:
return eval(arg, self.curframe.f_globals, self.curframe_locals)
return eval(arg, frame.f_globals, frame.f_locals)
except:
exc_info = sys.exc_info()[:2]
err = traceback.format_exception_only(*exc_info)[-1].strip()
return _rstr('** raised %s **' % err)
def do_whatis(self, arg):
"""whatis arg
Print the type of the argument.
"""
try:
value = self._getval(arg)
except:
# _getval() already printed the error
return
code = None
# Is it a function?
try:
code = value.__code__
except Exception:
pass
if code:
self.message('Function %s' % code.co_name)
return
# Is it an instance method?
try:
code = value.__func__.__code__
except Exception:
pass
if code:
self.message('Method %s' % code.co_name)
return
# Is it a class?
if value.__class__ is type:
self.message('Class %s.%s' % (value.__module__, value.__name__))
return
# None of the above...
self.message(__builtins__.type(value))
def do_interact(self, arg):
"""interact
Start an interactive interpreter whose global namespace
contains all the (global and local) names found in the current scope.
"""
ns = self.curframe.f_globals.copy()
ns.update(self.curframe_locals)
code.interact('*interactive*', local=ns)
def lookupmodule(self, filename):
"""Helper function for break/clear parsing -- may be overridden.
lookupmodule() translates (possibly incomplete) file or module name
into an absolute file name.
"""
if os.path.isabs(filename) and os.path.exists(filename):
return filename
f = os.path.join(sys.path[0], filename)
if os.path.exists(f) and self.canonic(f) == self.mainpyfile:
return f
ext = os.path.splitext(filename)[1]
if ext == '':
filename = filename + '.py'
if os.path.isabs(filename):
return filename
for dirname in sys.path:
while os.path.islink(dirname):
dirname = os.readlink(dirname)
fullname = os.path.join(dirname, filename)
if os.path.exists(fullname):
return fullname
return None
def evaluateTooltip(self, args):
self.updateData(args)
def updateData(self, args):
self.expandedINames = args.get('expanded', {})
self.typeformats = args.get('typeformats', {})
self.formats = args.get('formats', {})
self.output = ''
frameNr = args.get('frame', 0)
if frameNr == -1:
frameNr = 0
frame_lineno = self.stack[-1 - frameNr]
frame = frame_lineno[0]
self.output += 'data={'
for var in frame.f_locals.keys():
if var in ('__file__', '__name__', '__package__', '__spec__',
'__doc__', '__loader__', '__cached__', '__the_dumper__',
'__annotations__', 'QtcInternalBreakpoint', 'QtcInternalDumper'):
continue
value = frame.f_locals[var]
# this applies only for anonymous arguments
# e.g. def dummy(var, (width, height), var2) would create an anonymous local var
# named '.1' for (width, height) as this is the second argument
if var.startswith('.'):
var = '@arg' + var[1:]
self.dumpValue(value, var, 'local.%s' % var)
for watcher in args.get('watchers', []):
iname = watcher['iname']
exp = self.hexdecode(watcher['exp'])
exp = __builtins__.str(exp).strip()
escapedExp = self.hexencode(exp)
self.put('{')
self.putField('iname', iname)
self.putField('wname', escapedExp)
try:
res = eval(exp, {}, frame.f_locals)
self.putValue(res)
except:
self.putValue('<unavailable>')
self.put('}')
# self.dumpValue(eval(value), escapedExp, iname)
self.output += '}'
self.output += '{frame="%s"}' % frameNr
self.flushOutput()
def flushOutput(self):
sys.stdout.write('@\n' + self.output + '@\n')
sys.stdout.flush()
self.output = ''
def put(self, value):
# sys.stdout.write(value)
self.output += value
def putField(self, name, value):
self.put('%s="%s",' % (name, value))
def putItemCount(self, count):
self.put('value="<%s items>",numchild="%s",' % (count, count))
@staticmethod
def cleanType(typename):
t = __builtins__.str(typename)
if t.startswith("<type '") and t.endswith("'>"):
t = t[7:-2]
if t.startswith("<class '") and t.endswith("'>"):
t = t[8:-2]
return t
def putType(self, typeName, priority=0):
self.putField('type', QtcInternalDumper.cleanType(typeName))
def putNumChild(self, numchild):
self.put('numchild="%s",' % numchild)
def putValue(self, value, encoding=None, priority=0):
self.putField('value', value)
def putName(self, name):
self.put('name="%s",' % name)
def isExpanded(self, iname):
# DumperBase.warn('IS EXPANDED: %s in %s' % (iname, self.expandedINames))
if iname.startswith('None'):
raise "Illegal iname '%s'" % iname
# DumperBase.warn(' --> %s' % (iname in self.expandedINames))
return iname in self.expandedINames
def isExpandedIName(self, iname):
return iname in self.expandedINames
def itemFormat(self, item):
form = self.formats.get(__builtins__.str(QtcInternalDumper.cleanAddress(item.value.address)))
if form is None:
form = self.typeformats.get(__builtins__.str(item.value.type))
return form
def dumpValue(self, value, name, iname):
t = __builtins__.type(value)
tt = QtcInternalDumper.cleanType(t)
valueStr = __builtins__.str(value)
if tt == 'module' or tt == 'function':
return
if valueStr.startswith("<class '"):
return
# FIXME: Should we?
if valueStr.startswith('<enum-item '):
return
self.put('{')
self.putField('iname', iname)
self.putName(name)
if tt == 'NoneType':
self.putType(tt)
self.putValue('None')
self.putNumChild(0)
elif tt == 'list' or tt == 'tuple':
self.putType(tt)
self.putItemCount(__builtins__.len(value))
# self.putValue(value)
self.put('children=[')
for i, val in enumerate(value):
self.dumpValue(val, __builtins__.str(i), '%s.%d' % (iname, i))
self.put(']')
elif tt == 'str':
v = value
self.putType(tt)
self.putValue(self.hexencode(v))
self.putField('valueencoded', 'utf8')
self.putNumChild(0)
elif tt == 'unicode':
v = value
self.putType(tt)
self.putValue(self.hexencode(v))
self.putField('valueencoded', 'utf8')
self.putNumChild(0)
elif tt == 'buffer':
v = valueStr
self.putType(tt)
self.putValue(self.hexencode(v))
self.putField('valueencoded', 'latin1')
self.putNumChild(0)
elif tt == 'xrange':
b = iter(value).next()
e = b + __builtins__.len(value)
self.putType(tt)
self.putValue('(%d, %d)' % (b, e))
self.putNumChild(0)
elif tt == 'dict':
self.putType(tt)
self.putItemCount(__builtins__.len(value))
self.putField('childnumchild', 2)
self.put('children=[')
i = 0
if sys.version_info[0] >= 3:
vals = value.items()
else:
vals = value.iteritems()
for (k, v) in vals:
self.put('{')
self.putType(' ')
self.putValue('%s: %s' % (k, v))
if self.isExpanded(iname):
self.put('children=[')
self.dumpValue(k, 'key', '%s.%d.k' % (iname, i))
self.dumpValue(v, 'value', '%s.%d.v' % (iname, i))
self.put(']')
self.put('},')
i += 1
self.put(']')
elif tt == 'class':
pass
elif tt == 'module':
pass
elif tt == 'function':
pass
elif valueStr.startswith('<enum-item '):
# FIXME: Having enums always shown like this is not nice.
self.putType(tt)
self.putValue(valueStr[11:-1])
self.putNumChild(0)
else:
v = valueStr
p = v.find(' object at ')
if p > 1:
self.putValue('@' + v[p + 11:-1])
self.putType(v[1:p])
else:
p = v.find(' instance at ')
if p > 1:
self.putValue('@' + v[p + 13:-1])
self.putType(v[1:p])
else:
self.putType(tt)
self.putValue(v)
if self.isExpanded(iname):
self.put('children=[')
for child in dir(value):
if child in ('__dict__', '__doc__', '__module__'):
continue
attr = getattr(value, child)
if callable(attr):
continue
try:
self.dumpValue(attr, child, '%s.%s' % (iname, child))
except:
pass
self.put('],')
self.put('},')
def warn(self, msg):
self.putField('warning', msg)
def listModules(self, args):
self.put('modules=[')
for name in sys.modules:
self.put('{')
self.putName(name)
self.putValue(sys.modules[name])
self.put('},')
self.put(']')
self.flushOutput()
def listSymbols(self, args):
moduleName = args['module']
module = sys.modules.get(moduleName, None)
self.put("symbols={module='%s',symbols='%s'}"
% (module, dir(module) if module else []))
self.flushOutput()
def assignValue(self, args):
exp = args['expression']
value = args['value']
cmd = '%s=%s' % (exp, value)
eval(cmd, {})
self.put('CMD: "%s"' % cmd)
self.flushOutput()
def stackListFrames(self, args):
# result = 'stack={current-thread="%s"' % thread.GetThreadID()
result = 'stack={current-thread="%s"' % 1
result += ',frames=['
try:
level = 0
frames = __builtins__.list(reversed(self.stack))
frames = frames[:-2] # Drop "pdbbridge" and "<string>" levels
for frame_lineno in frames:
frame, lineno = frame_lineno
filename = self.canonic(frame.f_code.co_filename)
level += 1
result += '{'
result += 'file="%s",' % filename
result += 'line="%s",' % lineno
result += 'level="%s",' % level
result += '}'
except KeyboardInterrupt:
pass
result += ']'
# result += ',hasmore="%d"' % isLimited
# result += ',limit="%d"' % limit
result += '}'
self.report(result)
@staticmethod
def report(stuff):
sys.stdout.write('@\n' + stuff + '@\n')
sys.stdout.flush()
def qdebug(cmd, args):
global __the_dumper__
method = getattr(__the_dumper__, cmd)
method(args)
__the_dumper__ = QtcInternalDumper()
__the_dumper__.runit()