Commit 117ef042 authored by Mark Florisson's avatar Mark Florisson

Refactor inferior execution control code, better gdb message handling and re-fix runtests.py

parent bd41e488
...@@ -58,7 +58,7 @@ class DebugTestCase(unittest.TestCase): ...@@ -58,7 +58,7 @@ class DebugTestCase(unittest.TestCase):
if source_line is not None: if source_line is not None:
lineno = test_libcython.source_to_lineno[source_line] lineno = test_libcython.source_to_lineno[source_line]
frame = gdb.selected_frame() frame = gdb.selected_frame()
self.assertEqual(libcython.cy.step.lineno(frame), lineno) self.assertEqual(libcython.cython_info.lineno(frame), lineno)
def break_and_run(self, source_line): def break_and_run(self, source_line):
break_lineno = test_libcython.source_to_lineno[source_line] break_lineno = test_libcython.source_to_lineno[source_line]
......
...@@ -835,10 +835,9 @@ class CyBreak(CythonCommand): ...@@ -835,10 +835,9 @@ class CyBreak(CythonCommand):
return compl return compl
class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): class CythonInfo(CythonBase, libpython.LanguageInfo):
""" """
Base class for CyStep and CyNext. It implements the interface dictated by Implementation of the interface dictated by libpython.LanguageInfo.
libpython.GenericCodeStepper.
""" """
def lineno(self, frame): def lineno(self, frame):
...@@ -849,20 +848,16 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): ...@@ -849,20 +848,16 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
if self.is_cython_function(frame): if self.is_cython_function(frame):
return self.get_cython_lineno(frame) return self.get_cython_lineno(frame)
else: else:
return libpython.py_step.lineno(frame) return libpython.py_step.lang_info.lineno(frame)
def get_source_line(self, frame): def get_source_line(self, frame):
try: try:
line = super(CythonCodeStepper, self).get_source_line(frame) line = super(CythonInfo, self).get_source_line(frame)
except gdb.GdbError: except gdb.GdbError:
return None return None
else: else:
return line.strip() or None return line.strip() or None
@classmethod
def register(cls):
return cls(cls.name, stepinto=getattr(cls, 'stepinto', False))
def runtime_break_functions(self): def runtime_break_functions(self):
if self.is_cython_function(): if self.is_cython_function():
return self.get_cython_function().step_into_functions return self.get_cython_function().step_into_functions
...@@ -873,7 +868,15 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): ...@@ -873,7 +868,15 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
return result return result
class CyStep(CythonCodeStepper): class CythonExecutionControlCommand(CythonCommand,
libpython.ExecutionControlCommandBase):
@classmethod
def register(cls):
return cls(cls.name, cython_info)
class CyStep(CythonExecutionControlCommand, libpython.PythonStepperMixin):
"Step through Cython, Python or C code." "Step through Cython, Python or C code."
name = 'cy step' name = 'cy step'
...@@ -881,8 +884,7 @@ class CyStep(CythonCodeStepper): ...@@ -881,8 +884,7 @@ class CyStep(CythonCodeStepper):
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
if self.is_python_function(): if self.is_python_function():
libpython.py_step.get_source_line = self.get_source_line self.python_step(self.stepinto)
libpython.py_step.invoke(args, from_tty)
elif not self.is_cython_function(): elif not self.is_cython_function():
if self.stepinto: if self.stepinto:
command = 'step' command = 'step'
...@@ -891,7 +893,7 @@ class CyStep(CythonCodeStepper): ...@@ -891,7 +893,7 @@ class CyStep(CythonCodeStepper):
self.finish_executing(gdb.execute(command, to_string=True)) self.finish_executing(gdb.execute(command, to_string=True))
else: else:
self.step() self.step(stepinto=self.stepinto)
class CyNext(CyStep): class CyNext(CyStep):
...@@ -901,7 +903,7 @@ class CyNext(CyStep): ...@@ -901,7 +903,7 @@ class CyNext(CyStep):
stepinto = False stepinto = False
class CyRun(CythonCodeStepper): class CyRun(CythonExecutionControlCommand):
""" """
Run a Cython program. This is like the 'run' command, except that it Run a Cython program. This is like the 'run' command, except that it
displays Cython or Python source lines as well displays Cython or Python source lines as well
...@@ -909,26 +911,26 @@ class CyRun(CythonCodeStepper): ...@@ -909,26 +911,26 @@ class CyRun(CythonCodeStepper):
name = 'cy run' name = 'cy run'
invoke = CythonCodeStepper.run invoke = CythonExecutionControlCommand.run
class CyCont(CyRun): class CyCont(CythonExecutionControlCommand):
""" """
Continue a Cython program. This is like the 'run' command, except that it Continue a Cython program. This is like the 'run' command, except that it
displays Cython or Python source lines as well. displays Cython or Python source lines as well.
""" """
name = 'cy cont' name = 'cy cont'
invoke = CythonCodeStepper.cont invoke = CythonExecutionControlCommand.cont
class CyFinish(CyRun): class CyFinish(CythonExecutionControlCommand):
""" """
Execute until the function returns. Execute until the function returns.
""" """
name = 'cy finish' name = 'cy finish'
invoke = CythonCodeStepper.finish invoke = CythonExecutionControlCommand.finish
class CyUp(CythonCommand): class CyUp(CythonCommand):
...@@ -964,7 +966,7 @@ class CyDown(CyUp): ...@@ -964,7 +966,7 @@ class CyDown(CyUp):
_command = 'down' _command = 'down'
class CySelect(CythonCodeStepper): class CySelect(CythonCommand):
""" """
Select a frame. Use frame numbers as listed in `cy backtrace`. Select a frame. Use frame numbers as listed in `cy backtrace`.
This command is useful because `cy backtrace` prints a reversed backtrace. This command is useful because `cy backtrace` prints a reversed backtrace.
...@@ -982,7 +984,7 @@ class CySelect(CythonCodeStepper): ...@@ -982,7 +984,7 @@ class CySelect(CythonCodeStepper):
while frame.newer(): while frame.newer():
frame = frame.newer() frame = frame.newer()
stackdepth = self._stackdepth(frame) stackdepth = libpython.stackdepth(frame)
try: try:
gdb.execute('select %d' % (stackdepth - stackno - 1,)) gdb.execute('select %d' % (stackdepth - stackno - 1,))
...@@ -1288,5 +1290,6 @@ class CyLine(gdb.Function, CythonBase): ...@@ -1288,5 +1290,6 @@ class CyLine(gdb.Function, CythonBase):
def invoke(self): def invoke(self):
return self.get_cython_lineno() return self.get_cython_lineno()
cython_info = CythonInfo()
cy = CyCy.register() cy = CyCy.register()
cython_info.cy = cy
\ No newline at end of file
...@@ -1841,46 +1841,30 @@ def get_selected_inferior(): ...@@ -1841,46 +1841,30 @@ def get_selected_inferior():
if thread == selected_thread: if thread == selected_thread:
return inferior return inferior
def stackdepth(frame):
class GenericCodeStepper(gdb.Command): "Tells the stackdepth of a gdb frame."
""" depth = 0
Superclass for code stepping. Subclasses must implement the following while frame:
methods: frame = frame.older()
depth += 1
lineno(frame)
Tells the current line number (only called for a relevant frame).
If lineno is a false value it is not checked for a difference.
is_relevant_function(frame)
tells whether we care about frame 'frame'
get_source_line(frame)
get the line of source code for the current line (only called for a
relevant frame). If the source code cannot be retrieved this
function should return None
static_break_functions() return depth
returns an iterable of function names that are considered relevant
and should halt step-into execution. This is needed to provide a
performing step-into
runtime_break_functions
list of functions that we should break into depending on the
context
This class provides an 'invoke' method that invokes a 'step' or 'step-over' class ExecutionControlCommandBase(gdb.Command):
depending on the 'stepinto' argument. """
Superclass for language specific execution control. Language specific
features should be implemented by lang_info using the LanguageInfo
interface. 'name' is the name of the command.
""" """
stepper = False stepper = False
static_breakpoints = {} static_breakpoints = {}
runtime_breakpoints = {} runtime_breakpoints = {}
def __init__(self, name, stepinto=False): def __init__(self, name, lang_info):
super(GenericCodeStepper, self).__init__(name, super(ExecutionControlCommandBase, self).__init__(
gdb.COMMAND_RUNNING, name, gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
gdb.COMPLETE_NONE) self.lang_info = lang_info
self.stepinto = stepinto
def _break_func(self, funcname): def _break_func(self, funcname):
result = gdb.execute('break %s' % funcname, to_string=True) result = gdb.execute('break %s' % funcname, to_string=True)
...@@ -1897,7 +1881,7 @@ class GenericCodeStepper(gdb.Command): ...@@ -1897,7 +1881,7 @@ class GenericCodeStepper(gdb.Command):
This method must be called whenever the list of functions we should This method must be called whenever the list of functions we should
step into changes. It can be called on any GenericCodeStepper instance. step into changes. It can be called on any GenericCodeStepper instance.
""" """
break_funcs = set(self.static_break_functions()) break_funcs = set(self.lang_info.static_break_functions())
for funcname in break_funcs: for funcname in break_funcs:
if funcname not in self.static_breakpoints: if funcname not in self.static_breakpoints:
...@@ -1929,7 +1913,7 @@ class GenericCodeStepper(gdb.Command): ...@@ -1929,7 +1913,7 @@ class GenericCodeStepper(gdb.Command):
for bp in self.static_breakpoints.itervalues(): for bp in self.static_breakpoints.itervalues():
gdb.execute('enable ' + bp) gdb.execute('enable ' + bp)
runtime_break_functions = self.runtime_break_functions() runtime_break_functions = self.lang_info.runtime_break_functions()
if runtime_break_functions is None: if runtime_break_functions is None:
return return
...@@ -1945,72 +1929,49 @@ class GenericCodeStepper(gdb.Command): ...@@ -1945,72 +1929,49 @@ class GenericCodeStepper(gdb.Command):
self.runtime_breakpoints.itervalues()) self.runtime_breakpoints.itervalues())
for bp in chain: for bp in chain:
gdb.execute('disable ' + bp) gdb.execute('disable ' + bp)
def runtime_break_functions(self): def filter_output(self, result):
""" output = []
Implement this if the list of step-into functions depends on the
context. match_finish = re.search(r'^Value returned is \$\d+ = (.*)', result,
""" re.MULTILINE)
if match_finish:
def stopped(self, result): output.append('Value returned: %s' % match_finish.group(1))
match = re.search('^Program received signal .*', result, re.MULTILINE)
if match: reflags = re.MULTILINE
return match.group(0) regexes = [
elif get_selected_inferior().pid == 0: (r'^Program received signal .*', reflags|re.DOTALL),
return result (r'.*[Ww]arning.*', 0),
else: (r'^Program exited .*', reflags),
match = re.search('.*[Ww]arning.*', result, re.MULTILINE) ]
for regex, flags in regexes:
match = re.search(regex, result, flags)
if match: if match:
print match.group(0) output.append(match.group(0))
return ''
def _stackdepth(self, frame): return '\n'.join(output)
depth = 0
while frame: def stopped(self):
frame = frame.older() return get_selected_inferior().pid == 0
depth += 1
return depth
def finish_executing(self, output): def finish_executing(self, result):
""" """
After doing some kind of code running in the inferior, print the line After doing some kind of code running in the inferior, print the line
of source code or the result of the last executed gdb command (passed of source code or the result of the last executed gdb command (passed
in as the `result` argument). in as the `result` argument).
""" """
result = self.stopped(output) result = self.filter_output(result)
if result:
if self.stopped():
print result.strip() print result.strip()
# check whether the program was killed by a signal, it should still
# have a stack.
try:
frame = gdb.selected_frame()
except RuntimeError:
pass
else:
line = self.get_source_line(frame)
if line is None:
print output
else:
print result
print line
else: else:
frame = gdb.selected_frame() frame = gdb.selected_frame()
output = None if self.lang_info.is_relevant_function(frame):
print self.lang_info.get_source_line(frame) or result
if self.is_relevant_function(frame):
output = self.get_source_line(frame)
if output is None:
pframe = getattr(self, 'print_stackframe', None)
if pframe:
pframe(frame, index=0)
else:
print result.strip()
else: else:
print output print result
def _finish(self): def _finish(self):
""" """
...@@ -2023,11 +1984,10 @@ class GenericCodeStepper(gdb.Command): ...@@ -2023,11 +1984,10 @@ class GenericCodeStepper(gdb.Command):
# outermost frame, continue # outermost frame, continue
return gdb.execute('cont', to_string=True) return gdb.execute('cont', to_string=True)
def finish(self, *args): def _finish_frame(self):
""" """
Execute until the function returns to a relevant caller. Execute until the function returns to a relevant caller.
""" """
while True: while True:
result = self._finish() result = self._finish()
...@@ -2037,13 +1997,18 @@ class GenericCodeStepper(gdb.Command): ...@@ -2037,13 +1997,18 @@ class GenericCodeStepper(gdb.Command):
break break
hitbp = re.search(r'Breakpoint (\d+)', result) hitbp = re.search(r'Breakpoint (\d+)', result)
is_relevant = self.is_relevant_function(frame) is_relevant = self.lang_info.is_relevant_function(frame)
if hitbp or is_relevant or self.stopped(result): if hitbp or is_relevant or self.stopped():
break break
return result
def finish(self, *args):
"Implements the finish command."
result = self._finish_frame()
self.finish_executing(result) self.finish_executing(result)
def step(self, stepover_command='next'): def step(self, stepinto, stepover_command='next'):
""" """
Do a single step or step-over. Returns the result of the last gdb Do a single step or step-over. Returns the result of the last gdb
command that made execution stop. command that made execution stop.
...@@ -2062,34 +2027,34 @@ class GenericCodeStepper(gdb.Command): ...@@ -2062,34 +2027,34 @@ class GenericCodeStepper(gdb.Command):
works properly with local trace functions, see works properly with local trace functions, see
PyFrameObjectPtr.current_line_num and PyFrameObjectPtr.addr2line. PyFrameObjectPtr.current_line_num and PyFrameObjectPtr.addr2line.
""" """
if self.stepinto: if stepinto:
self.enable_breakpoints() self.enable_breakpoints()
beginframe = gdb.selected_frame() beginframe = gdb.selected_frame()
if self.is_relevant_function(beginframe): if self.lang_info.is_relevant_function(beginframe):
# If we start in a relevant frame, initialize stuff properly. If # If we start in a relevant frame, initialize stuff properly. If
# we don't start in a relevant frame, the loop will halt # we don't start in a relevant frame, the loop will halt
# immediately. So don't call self.lineno() as it may raise for # immediately. So don't call self.lang_info.lineno() as it may
# irrelevant frames. # raise for irrelevant frames.
beginline = self.lineno(beginframe) beginline = self.lang_info.lineno(beginframe)
if not self.stepinto: if not stepinto:
depth = self._stackdepth(beginframe) depth = stackdepth(beginframe)
newframe = beginframe newframe = beginframe
while True: while True:
if self.is_relevant_function(newframe): if self.lang_info.is_relevant_function(newframe):
result = gdb.execute(stepover_command, to_string=True) result = gdb.execute(stepover_command, to_string=True)
else: else:
self.finish() result = self._finish_frame()
if self.stopped(result): if self.stopped():
break break
newframe = gdb.selected_frame() newframe = gdb.selected_frame()
is_relevant_function = self.is_relevant_function(newframe) is_relevant_function = self.lang_info.is_relevant_function(newframe)
try: try:
framename = newframe.name() framename = newframe.name()
except RuntimeError: except RuntimeError:
...@@ -2108,9 +2073,9 @@ class GenericCodeStepper(gdb.Command): ...@@ -2108,9 +2073,9 @@ class GenericCodeStepper(gdb.Command):
if newframe != beginframe: if newframe != beginframe:
# new function # new function
if not self.stepinto: if not stepinto:
# see if we returned to the caller # see if we returned to the caller
newdepth = self._stackdepth(newframe) newdepth = stackdepth(newframe)
is_relevant_function = (newdepth < depth and is_relevant_function = (newdepth < depth and
is_relevant_function) is_relevant_function)
...@@ -2119,11 +2084,11 @@ class GenericCodeStepper(gdb.Command): ...@@ -2119,11 +2084,11 @@ class GenericCodeStepper(gdb.Command):
else: else:
# newframe equals beginframe, check for a difference in the # newframe equals beginframe, check for a difference in the
# line number # line number
lineno = self.lineno(newframe) lineno = self.lang_info.lineno(newframe)
if lineno and lineno != beginline: if lineno and lineno != beginline:
break break
if self.stepinto: if stepinto:
self.disable_breakpoints() self.disable_breakpoints()
self.finish_executing(result) self.finish_executing(result)
...@@ -2135,7 +2100,50 @@ class GenericCodeStepper(gdb.Command): ...@@ -2135,7 +2100,50 @@ class GenericCodeStepper(gdb.Command):
self.finish_executing(gdb.execute('cont', to_string=True)) self.finish_executing(gdb.execute('cont', to_string=True))
class PythonCodeStepper(GenericCodeStepper): class LanguageInfo(object):
"""
This class defines the interface that ExecutionControlCommandBase needs to
provide language-specific execution control.
Classes that implement this interface should implement:
lineno(frame)
Tells the current line number (only called for a relevant frame).
If lineno is a false value it is not checked for a difference.
is_relevant_function(frame)
tells whether we care about frame 'frame'
get_source_line(frame)
get the line of source code for the current line (only called for a
relevant frame). If the source code cannot be retrieved this
function should return None
exc_info(frame) -- optional
tells whether an exception was raised, if so, it should return a
string representation of the exception value, None otherwise.
static_break_functions()
returns an iterable of function names that are considered relevant
and should halt step-into execution. This is needed to provide a
performing step-into
runtime_break_functions() -- optional
list of functions that we should break into depending on the
context
"""
def exc_info(self, frame):
"See this class' docstring."
def runtime_break_functions(self):
"""
Implement this if the list of step-into functions depends on the
context.
"""
class PythonInfo(LanguageInfo):
def pyframe(self, frame): def pyframe(self, frame):
pyframe = Frame(frame).get_pyop() pyframe = Frame(frame).get_pyop()
...@@ -2164,14 +2172,13 @@ class PythonCodeStepper(GenericCodeStepper): ...@@ -2164,14 +2172,13 @@ class PythonCodeStepper(GenericCodeStepper):
yield 'PyEval_EvalFrameEx' yield 'PyEval_EvalFrameEx'
class PyStep(PythonCodeStepper): class PythonStepperMixin(object):
"Step through Python code." """
Make this a mixin so CyStep can also inherit from this and use a
def __init__(self, *args, **kwargs): CythonCodeStepper at the same time
super(PyStep, self).__init__(*args, **kwargs) """
self.lastframe = None
def invoke(self, args, from_tty): def python_step(self, stepinto):
# Set a watchpoint for a frame once as deleting it will make py-step # Set a watchpoint for a frame once as deleting it will make py-step
# unrepeatable. # unrepeatable.
# See http://sourceware.org/bugzilla/show_bug.cgi?id=12216 # See http://sourceware.org/bugzilla/show_bug.cgi?id=12216
...@@ -2180,45 +2187,52 @@ class PyStep(PythonCodeStepper): ...@@ -2180,45 +2187,52 @@ class PyStep(PythonCodeStepper):
newframe = gdb.selected_frame() newframe = gdb.selected_frame()
framewrapper = Frame(newframe) framewrapper = Frame(newframe)
if newframe != self.lastframe and framewrapper.is_evalframeex(): if (newframe != getattr(self, 'lastframe', None) and
framewrapper.is_evalframeex()):
self.lastframe = newframe self.lastframe = newframe
output = gdb.execute('watch f->f_lasti', to_string=True) output = gdb.execute('watch f->f_lasti', to_string=True)
self.step(stepover_command='py-finish') self.step(stepinto=stepinto, stepover_command='finish')
# match = re.search(r'[Ww]atchpoint (\d+):', output) # match = re.search(r'[Ww]atchpoint (\d+):', output)
# if match: # if match:
# watchpoint = match.group(1) # watchpoint = match.group(1)
# gdb.execute('delete %s' % watchpoint) # gdb.execute('delete %s' % watchpoint)
class PyStep(ExecutionControlCommandBase, PythonStepperMixin):
"Step through Python code."
stepinto = True
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
self.step() self.python_step(stepinto=self.stepinto)
class PyNext(PythonCodeStepper): class PyNext(PyStep):
"Step-over Python code." "Step-over Python code."
invoke = PythonCodeStepper.step stepinto = False
class PyFinish(PythonCodeStepper): class PyFinish(ExecutionControlCommandBase):
"Execute until function returns to a caller." "Execute until function returns to a caller."
invoke = PythonCodeStepper.finish invoke = ExecutionControlCommandBase.finish
class PyRun(PythonCodeStepper): class PyRun(ExecutionControlCommandBase):
"Run the program." "Run the program."
invoke = PythonCodeStepper.run invoke = ExecutionControlCommandBase.run
class PyCont(PythonCodeStepper): class PyCont(ExecutionControlCommandBase):
invoke = PythonCodeStepper.cont invoke = ExecutionControlCommandBase.cont
py_step = PyStep('py-step', stepinto=True) py_step = PyStep('py-step', PythonInfo())
py_next = PyNext('py-next', stepinto=False) py_next = PyNext('py-next', PythonInfo())
py_finish = PyFinish('py-finish') py_finish = PyFinish('py-finish', PythonInfo())
py_run = PyRun('py-run') py_run = PyRun('py-run', PythonInfo())
py_cont = PyCont('py-cont') py_cont = PyCont('py-cont', PythonInfo())
gdb.execute('set breakpoint pending on') gdb.execute('set breakpoint pending on')
py_step.init_breakpoints() py_step.init_breakpoints()
......
...@@ -644,12 +644,12 @@ class CythonUnitTestCase(CythonCompileTestCase): ...@@ -644,12 +644,12 @@ class CythonUnitTestCase(CythonCompileTestCase):
except Exception: except Exception:
pass pass
# Someone wrapped this in a:
try: # 'try: import gdb; ... except: include_debugger = False' thing, but don't do
import gdb # this, it doesn't work as gdb is a builtin module in GDB. The tests themselves
include_debugger = sys.version_info[:2] > (2, 5) # are doing the skipping. If there's a problem with the tests, please file an
except: # issue.
include_debugger = False include_debugger = sys.version_info[:2] > (2, 5)
def collect_unittests(path, module_prefix, suite, selectors): def collect_unittests(path, module_prefix, suite, selectors):
def file_matches(filename): def file_matches(filename):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment