Commit 38f33b6e authored by Mark Florisson's avatar Mark Florisson

cy backtrace

parent 6a7cea25
...@@ -6,6 +6,7 @@ Cython.Debugger.Cygdb.make_command_file() ...@@ -6,6 +6,7 @@ Cython.Debugger.Cygdb.make_command_file()
""" """
import os import os
import re
import sys import sys
import trace import trace
import inspect import inspect
...@@ -42,7 +43,7 @@ class DebugTestCase(unittest.TestCase): ...@@ -42,7 +43,7 @@ class DebugTestCase(unittest.TestCase):
'codefile.eggs'] 'codefile.eggs']
def read_var(self, varname, cast_to=None): def read_var(self, varname, cast_to=None):
result = gdb.parse_and_eval('$cy_cname("%s")' % varname) result = gdb.parse_and_eval('$cy_cvalue("%s")' % varname)
if cast_to: if cast_to:
result = cast_to(result) result = cast_to(result)
...@@ -113,6 +114,7 @@ class TestDebugInformationClasses(DebugTestCase): ...@@ -113,6 +114,7 @@ class TestDebugInformationClasses(DebugTestCase):
class TestParameters(unittest.TestCase): class TestParameters(unittest.TestCase):
def test_parameters(self): def test_parameters(self):
gdb.execute('set cy_colorize_code on')
assert libcython.parameters.colorize_code assert libcython.parameters.colorize_code
gdb.execute('set cy_colorize_code off') gdb.execute('set cy_colorize_code off')
assert not libcython.parameters.colorize_code assert not libcython.parameters.colorize_code
...@@ -121,20 +123,19 @@ class TestParameters(unittest.TestCase): ...@@ -121,20 +123,19 @@ class TestParameters(unittest.TestCase):
class TestBreak(DebugTestCase): class TestBreak(DebugTestCase):
def test_break(self): def test_break(self):
result = libpython._execute('cy break codefile.spam', to_string=True) gdb.execute('cy break codefile.spam')
assert self.spam_func.cname in result
self.assertEqual(len(gdb.breakpoints()), 1) self.assertEqual(len(gdb.breakpoints()), 1)
bp, = gdb.breakpoints() bp, = gdb.breakpoints()
self.assertEqual(bp.type, gdb.BP_BREAKPOINT) self.assertEqual(bp.type, gdb.BP_BREAKPOINT)
self.assertEqual(bp.location, self.spam_func.cname) assert self.spam_func.cname in bp.location
assert bp.enabled assert bp.enabled
class DebugStepperTestCase(DebugTestCase): class DebugStepperTestCase(DebugTestCase):
def step(self, varnames_and_values, source_line=None, lineno=None): def step(self, varnames_and_values, source_line=None, lineno=None):
gdb.execute(self.command, to_string=True) gdb.execute(self.command)
for varname, value in varnames_and_values: for varname, value in varnames_and_values:
self.assertEqual(self.read_var(varname), value, self.local_info()) self.assertEqual(self.read_var(varname), value, self.local_info())
...@@ -177,14 +178,14 @@ class TestStep(DebugStepperTestCase): ...@@ -177,14 +178,14 @@ class TestStep(DebugStepperTestCase):
def test_python_step(self): def test_python_step(self):
self.break_and_run('os.path.join("foo", "bar")') self.break_and_run('os.path.join("foo", "bar")')
gdb.execute('cy step', to_string=True) result = gdb.execute('cy step', to_string=True)
curframe = gdb.selected_frame() curframe = gdb.selected_frame()
self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx') self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx')
pyframe = libpython.Frame(curframe).get_pyop() pyframe = libpython.Frame(curframe).get_pyop()
self.assertEqual(str(pyframe.co_name), 'join') self.assertEqual(str(pyframe.co_name), 'join')
assert re.match(r'\d+ def join\(', result), result
class TestNext(DebugStepperTestCase): class TestNext(DebugStepperTestCase):
...@@ -218,11 +219,64 @@ class TestLocalsGlobals(DebugTestCase): ...@@ -218,11 +219,64 @@ class TestLocalsGlobals(DebugTestCase):
self.break_and_run('int(10)') self.break_and_run('int(10)')
result = gdb.execute('cy globals', to_string=True) result = gdb.execute('cy globals', to_string=True)
assert '__name__ =' in result, repr(result) assert '__name__ ' in result, repr(result)
assert '__doc__ =' in result, repr(result) assert '__doc__ ' in result, repr(result)
assert 'os =' in result, repr(result) assert 'os ' in result, repr(result)
assert 'c_var = 12' in result, repr(result) assert 'c_var ' in result, repr(result)
assert 'python_var = 13' in result, repr(result) assert 'python_var ' in result, repr(result)
class TestBacktrace(DebugTestCase):
def test_backtrace(self):
libcython.parameters.colorize_code.value = False
self.break_and_run('os.path.join("foo", "bar")')
result = gdb.execute('cy bt', to_string=True)
assert re.search(r'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22',
result), result
assert 'os.path.join("foo", "bar")' in result, result
gdb.execute("cy step")
gdb.execute('cy bt')
result = gdb.execute('cy bt -a', to_string=True)
assert re.search(r'\#0 *0x.* in main\(\) at', result), result
class TestFunctions(DebugTestCase):
def test_functions(self):
self.break_and_run('c = 2')
result = gdb.execute('print $cy_cname("b")', to_string=True)
assert re.search('__pyx_.*b', result), result
result = gdb.execute('print $cy_lineno()', to_string=True)
supposed_lineno = test_libcython.source_to_lineno['c = 2']
assert str(supposed_lineno) in result, (supposed_lineno, result)
result = gdb.execute('print $cy_cvalue("b")', to_string=True)
assert '= 1' in result
class TestPrint(DebugTestCase):
def test_print(self):
self.break_and_run('c = 2')
result = gdb.execute('cy print b', to_string=True)
assert '= 1' in result
class TestUpDown(DebugTestCase):
def test_updown(self):
self.break_and_run('os.path.join("foo", "bar")')
gdb.execute('cy step')
self.assertRaises(RuntimeError, gdb.execute, 'cy down')
result = gdb.execute('cy up', to_string=True)
assert 'spam()' in result
assert 'os.path.join("foo", "bar")' in result
def _main(): def _main():
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
GDB extension that adds Cython support. GDB extension that adds Cython support.
""" """
import os
import sys import sys
import textwrap import textwrap
import traceback import traceback
...@@ -69,7 +70,7 @@ def dont_suppress_errors(function): ...@@ -69,7 +70,7 @@ def dont_suppress_errors(function):
def default_selected_gdb_frame(err=True): def default_selected_gdb_frame(err=True):
def decorator(function): def decorator(function):
@functools.wraps(function) @functools.wraps(function)
def wrapper(self, frame=None, **kwargs): def wrapper(self, frame=None, *args, **kwargs):
try: try:
frame = frame or gdb.selected_frame() frame = frame or gdb.selected_frame()
except RuntimeError: except RuntimeError:
...@@ -78,14 +79,16 @@ def default_selected_gdb_frame(err=True): ...@@ -78,14 +79,16 @@ def default_selected_gdb_frame(err=True):
if err and frame.name() is None: if err and frame.name() is None:
raise NoFunctionNameInFrameError() raise NoFunctionNameInFrameError()
return function(self, frame, **kwargs) return function(self, frame, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
def require_cython_frame(function): def require_cython_frame(function):
@functools.wraps(function) @functools.wraps(function)
@require_running_program
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
if not self.is_cython_function(): frame = kwargs.get('frame') or gdb.selected_frame()
if not self.is_cython_function(frame):
raise gdb.GdbError('Selected frame does not correspond with a ' raise gdb.GdbError('Selected frame does not correspond with a '
'Cython function we know about.') 'Cython function we know about.')
return function(self, *args, **kwargs) return function(self, *args, **kwargs)
...@@ -111,6 +114,17 @@ def dispatch_on_frame(c_command, python_command=None): ...@@ -111,6 +114,17 @@ def dispatch_on_frame(c_command, python_command=None):
return wrapper return wrapper
return decorator return decorator
def require_running_program(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
try:
gdb.selected_frame()
except RuntimeError:
raise gdb.GdbError("No frame is currently selected.")
return function(*args, **kwargs)
return wrapper
# Classes that represent the debug information # Classes that represent the debug information
# Don't rename the parameters of these classes, they come directly from the XML # Don't rename the parameters of these classes, they come directly from the XML
...@@ -206,12 +220,12 @@ class CythonBase(object): ...@@ -206,12 +220,12 @@ class CythonBase(object):
@default_selected_gdb_frame() @default_selected_gdb_frame()
def get_source_desc(self, frame): def get_source_desc(self, frame):
filename = lineno = lexer = None filename = lineno = lexer = None
if self.is_cython_function(): if self.is_cython_function(frame):
filename = self.get_cython_function(frame).module.filename filename = self.get_cython_function(frame).module.filename
lineno = self.get_cython_lineno(frame) lineno = self.get_cython_lineno(frame)
if pygments: if pygments:
lexer = pygments.lexers.CythonLexer() lexer = pygments.lexers.CythonLexer()
elif self.is_python_function(): elif self.is_python_function(frame):
pyframeobject = libpython.Frame(frame).get_pyop() pyframeobject = libpython.Frame(frame).get_pyop()
if not pyframeobject: if not pyframeobject:
...@@ -221,7 +235,17 @@ class CythonBase(object): ...@@ -221,7 +235,17 @@ class CythonBase(object):
lineno = pyframeobject.current_line_num() lineno = pyframeobject.current_line_num()
if pygments: if pygments:
lexer = pygments.lexers.PythonLexer() lexer = pygments.lexers.PythonLexer()
else:
symbol_and_line_obj = frame.find_sal()
if symbol_and_line_obj is None:
filename = None
lineno = 0
else:
filename = symbol_and_line_obj.symtab.filename
lineno = symbol_and_line_obj.line
if pygments:
lexer = pygments.lexers.CLexer()
return SourceFileDescriptor(filename, lexer), lineno return SourceFileDescriptor(filename, lexer), lineno
@default_selected_gdb_frame() @default_selected_gdb_frame()
...@@ -255,6 +279,61 @@ class CythonBase(object): ...@@ -255,6 +279,61 @@ class CythonBase(object):
# variable not initialized yet # variable not initialized yet
pass pass
@default_selected_gdb_frame()
def print_stackframe(self, frame, index, is_c=False):
"""
Print a C, Cython or Python stack frame and the line of source code
if available.
"""
# do this to prevent the require_cython_frame decorator from
# raising GdbError when calling self.cy.cy_cvalue.invoke()
selected_frame = gdb.selected_frame()
frame.select()
source_desc, lineno = self.get_source_desc(frame)
if not is_c and self.is_python_function(frame):
pyframe = libpython.Frame(frame).get_pyop()
if pyframe is None or pyframe.is_optimized_out():
# print this python function as a C function
return self.print_stackframe(frame, index, is_c=True)
func_name = pyframe.co_name
func_cname = 'PyEval_EvalFrameEx'
func_args = []
elif self.is_cython_function(frame):
cyfunc = self.get_cython_function(frame)
f = lambda arg: self.cy.cy_cvalue.invoke(arg, frame=frame)
func_name = cyfunc.name
func_cname = cyfunc.cname
func_args = [] # [(arg, f(arg)) for arg in cyfunc.arguments]
else:
source_desc, lineno = self.get_source_desc(frame)
func_name = frame.name()
func_cname = func_name
func_args = []
gdb_value = gdb.parse_and_eval(func_cname)
# Seriously? Why is the address not an int?
func_address = int(str(gdb_value.address).split()[0], 0)
print '#%-2d 0x%016x in %s(%s) at %s:%s' % (
index,
func_address,
func_name,
', '.join('%s=%s' % (name, val) for name, val in func_args),
source_desc.filename,
lineno)
try:
print ' ' + source_desc.get_source(lineno)
except gdb.GdbError:
pass
selected_frame.select()
class SourceFileDescriptor(object): class SourceFileDescriptor(object):
def __init__(self, filename, lexer, formatter=None): def __init__(self, filename, lexer, formatter=None):
self.filename = filename self.filename = filename
...@@ -413,14 +492,25 @@ class CythonCommand(gdb.Command, CythonBase): ...@@ -413,14 +492,25 @@ class CythonCommand(gdb.Command, CythonBase):
""" """
Base class for Cython commands Base class for Cython commands
""" """
command_class = gdb.COMMAND_NONE
completer_class = gdb.COMPLETE_NONE
@classmethod @classmethod
def register(cls, *args, **kwargs): def _register(cls, clsname, args, kwargs):
if not hasattr(cls, 'completer_class'): if not hasattr(cls, 'completer_class'):
return cls(cls.name, cls.command_class, *args, **kwargs) return cls(cls.name, cls.command_class, *args, **kwargs)
else: else:
return cls(cls.name, cls.command_class, cls.completer_class, return cls(cls.name, cls.command_class, cls.completer_class,
*args, **kwargs) *args, **kwargs)
@classmethod
def register(cls, *args, **kwargs):
alias = getattr(cls, 'alias', None)
if alias:
cls._register(cls.alias, args, kwargs)
return cls._register(cls.name, args, kwargs)
class CyCy(CythonCommand): class CyCy(CythonCommand):
...@@ -435,11 +525,11 @@ class CyCy(CythonCommand): ...@@ -435,11 +525,11 @@ class CyCy(CythonCommand):
cy cont cy cont
cy up cy up
cy down cy down
cy bt / cy backtrace
cy print cy print
cy list cy list
cy locals cy locals
cy globals cy globals
cy backtrace
""" """
name = 'cy' name = 'cy'
...@@ -458,12 +548,14 @@ class CyCy(CythonCommand): ...@@ -458,12 +548,14 @@ class CyCy(CythonCommand):
cont = CyCont.register(), cont = CyCont.register(),
up = CyUp.register(), up = CyUp.register(),
down = CyDown.register(), down = CyDown.register(),
bt = CyBacktrace.register(),
list = CyList.register(), list = CyList.register(),
print_ = CyPrint.register(), print_ = CyPrint.register(),
locals = CyLocals.register(), locals = CyLocals.register(),
globals = CyGlobals.register(), globals = CyGlobals.register(),
cy_cname = CyCName('cy_cname'), cy_cname = CyCName('cy_cname'),
cy_line = CyLine('cy_line'), cy_cvalue = CyCValue('cy_cvalue'),
cy_lineno = CyLine('cy_lineno'),
) )
for command_name, command in commands.iteritems(): for command_name, command in commands.iteritems():
...@@ -687,7 +779,7 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): ...@@ -687,7 +779,7 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
@classmethod @classmethod
def register(cls): def register(cls):
return cls(cls.name, stepper=cls.stepper) return cls(cls.name, stepper=cls.stepper)
class CyStep(CythonCodeStepper): class CyStep(CythonCodeStepper):
"Step through Python code." "Step through Python code."
...@@ -743,10 +835,20 @@ class CyUp(CythonCodeStepper): ...@@ -743,10 +835,20 @@ class CyUp(CythonCodeStepper):
_command = 'up' _command = 'up'
def invoke(self, *args): def invoke(self, *args):
self.result = gdb.execute(self._command, to_string=True) try:
while not self.is_relevant_function(gdb.selected_frame()): gdb.execute(self._command, to_string=True)
self.result = gdb.execute(self._command, to_string=True) while not self.is_relevant_function(gdb.selected_frame()):
self.end_stepping() gdb.execute(self._command, to_string=True)
except RuntimeError, e:
raise gdb.GdbError(*e.args)
frame = gdb.selected_frame()
index = 0
while frame:
frame = frame.older()
index += 1
self.print_stackframe(index=index - 1)
class CyDown(CyUp): class CyDown(CyUp):
...@@ -758,6 +860,35 @@ class CyDown(CyUp): ...@@ -758,6 +860,35 @@ class CyDown(CyUp):
_command = 'down' _command = 'down'
class CyBacktrace(CythonCommand):
'Print the Cython stack'
name = 'cy bt'
alias = 'cy backtrace'
command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE
@require_running_program
def invoke(self, args, from_tty):
# get the first frame
selected_frame = frame = gdb.selected_frame()
while frame.older():
frame = frame.older()
print_all = args == '-a'
index = 0
while frame:
is_c = False
if print_all or self.is_relevant_function(frame):
self.print_stackframe(frame, index)
index += 1
frame = frame.newer()
selected_frame.select()
class CyList(CythonCommand): class CyList(CythonCommand):
""" """
List Cython source code. To disable to customize colouring see the cy_* List Cython source code. To disable to customize colouring see the cy_*
...@@ -785,7 +916,7 @@ class CyPrint(CythonCommand): ...@@ -785,7 +916,7 @@ class CyPrint(CythonCommand):
@dispatch_on_frame(c_command='print', python_command='py-print') @dispatch_on_frame(c_command='print', python_command='py-print')
def invoke(self, name, from_tty, max_name_length=None): def invoke(self, name, from_tty, max_name_length=None):
cname = self.cy.cy_cname.invoke(name, string=True) cname = self.cy.cy_cname.invoke(name)
try: try:
value = gdb.parse_and_eval(cname) value = gdb.parse_and_eval(cname)
except RuntimeError, e: except RuntimeError, e:
...@@ -880,7 +1011,7 @@ class CyCName(gdb.Function, CythonBase): ...@@ -880,7 +1011,7 @@ class CyCName(gdb.Function, CythonBase):
""" """
@require_cython_frame @require_cython_frame
def invoke(self, cyname, string=False, frame=None): def invoke(self, cyname, frame=None):
frame = frame or gdb.selected_frame() frame = frame or gdb.selected_frame()
cname = None cname = None
...@@ -905,12 +1036,20 @@ class CyCName(gdb.Function, CythonBase): ...@@ -905,12 +1036,20 @@ class CyCName(gdb.Function, CythonBase):
if not cname: if not cname:
raise gdb.GdbError('No such Cython variable: %s' % cyname) raise gdb.GdbError('No such Cython variable: %s' % cyname)
if string: return cname
return cname
else:
return gdb.parse_and_eval(cname)
class CyCValue(CyCName):
"""
Get the value of a Cython variable.
"""
@require_cython_frame
def invoke(self, cyname, frame=None):
cname = super(CyCValue, self).invoke(cyname, frame=frame)
return gdb.parse_and_eval(cname)
class CyLine(gdb.Function, CythonBase): class CyLine(gdb.Function, CythonBase):
""" """
Get the current Cython line. Get the current Cython line.
......
...@@ -1444,12 +1444,13 @@ class PyLocals(gdb.Command): ...@@ -1444,12 +1444,13 @@ class PyLocals(gdb.Command):
namespace = self.get_namespace(pyop_frame) namespace = self.get_namespace(pyop_frame)
namespace = [(name.proxyval(set()), val) for name, val in namespace] namespace = [(name.proxyval(set()), val) for name, val in namespace]
name, val = max(namespace, key=lambda (name, val): len(name)) if namespace:
max_name_length = len(name) name, val = max(namespace, key=lambda (name, val): len(name))
max_name_length = len(name)
for name, pyop_value in namespace:
value = pyop_value.get_truncated_repr(MAX_OUTPUT_LEN) for name, pyop_value in namespace:
print ('%-*s = %s' % (max_name_length, name, value)) value = pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)
print ('%-*s = %s' % (max_name_length, name, value))
def get_namespace(self, pyop_frame): def get_namespace(self, pyop_frame):
return pyop_frame.iter_locals() return pyop_frame.iter_locals()
......
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