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

cy backtrace

parent 6a7cea25
......@@ -6,6 +6,7 @@ Cython.Debugger.Cygdb.make_command_file()
"""
import os
import re
import sys
import trace
import inspect
......@@ -42,7 +43,7 @@ class DebugTestCase(unittest.TestCase):
'codefile.eggs']
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:
result = cast_to(result)
......@@ -113,6 +114,7 @@ class TestDebugInformationClasses(DebugTestCase):
class TestParameters(unittest.TestCase):
def test_parameters(self):
gdb.execute('set cy_colorize_code on')
assert libcython.parameters.colorize_code
gdb.execute('set cy_colorize_code off')
assert not libcython.parameters.colorize_code
......@@ -121,20 +123,19 @@ class TestParameters(unittest.TestCase):
class TestBreak(DebugTestCase):
def test_break(self):
result = libpython._execute('cy break codefile.spam', to_string=True)
assert self.spam_func.cname in result
gdb.execute('cy break codefile.spam')
self.assertEqual(len(gdb.breakpoints()), 1)
bp, = gdb.breakpoints()
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
class DebugStepperTestCase(DebugTestCase):
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:
self.assertEqual(self.read_var(varname), value, self.local_info())
......@@ -177,14 +178,14 @@ class TestStep(DebugStepperTestCase):
def test_python_step(self):
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()
self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx')
pyframe = libpython.Frame(curframe).get_pyop()
self.assertEqual(str(pyframe.co_name), 'join')
assert re.match(r'\d+ def join\(', result), result
class TestNext(DebugStepperTestCase):
......@@ -218,11 +219,64 @@ class TestLocalsGlobals(DebugTestCase):
self.break_and_run('int(10)')
result = gdb.execute('cy globals', to_string=True)
assert '__name__ =' in result, repr(result)
assert '__doc__ =' in result, repr(result)
assert 'os =' in result, repr(result)
assert 'c_var = 12' in result, repr(result)
assert 'python_var = 13' in result, repr(result)
assert '__name__ ' in result, repr(result)
assert '__doc__ ' in result, repr(result)
assert 'os ' in result, repr(result)
assert 'c_var ' 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():
......
......@@ -2,6 +2,7 @@
GDB extension that adds Cython support.
"""
import os
import sys
import textwrap
import traceback
......@@ -69,7 +70,7 @@ def dont_suppress_errors(function):
def default_selected_gdb_frame(err=True):
def decorator(function):
@functools.wraps(function)
def wrapper(self, frame=None, **kwargs):
def wrapper(self, frame=None, *args, **kwargs):
try:
frame = frame or gdb.selected_frame()
except RuntimeError:
......@@ -78,14 +79,16 @@ def default_selected_gdb_frame(err=True):
if err and frame.name() is None:
raise NoFunctionNameInFrameError()
return function(self, frame, **kwargs)
return function(self, frame, *args, **kwargs)
return wrapper
return decorator
def require_cython_frame(function):
@functools.wraps(function)
@require_running_program
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 '
'Cython function we know about.')
return function(self, *args, **kwargs)
......@@ -111,6 +114,17 @@ def dispatch_on_frame(c_command, python_command=None):
return wrapper
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
# Don't rename the parameters of these classes, they come directly from the XML
......@@ -206,12 +220,12 @@ class CythonBase(object):
@default_selected_gdb_frame()
def get_source_desc(self, frame):
filename = lineno = lexer = None
if self.is_cython_function():
if self.is_cython_function(frame):
filename = self.get_cython_function(frame).module.filename
lineno = self.get_cython_lineno(frame)
if pygments:
lexer = pygments.lexers.CythonLexer()
elif self.is_python_function():
elif self.is_python_function(frame):
pyframeobject = libpython.Frame(frame).get_pyop()
if not pyframeobject:
......@@ -221,6 +235,16 @@ class CythonBase(object):
lineno = pyframeobject.current_line_num()
if pygments:
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
......@@ -255,6 +279,61 @@ class CythonBase(object):
# variable not initialized yet
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):
def __init__(self, filename, lexer, formatter=None):
self.filename = filename
......@@ -414,14 +493,25 @@ class CythonCommand(gdb.Command, CythonBase):
Base class for Cython commands
"""
command_class = gdb.COMMAND_NONE
completer_class = gdb.COMPLETE_NONE
@classmethod
def register(cls, *args, **kwargs):
def _register(cls, clsname, args, kwargs):
if not hasattr(cls, 'completer_class'):
return cls(cls.name, cls.command_class, *args, **kwargs)
else:
return cls(cls.name, cls.command_class, cls.completer_class,
*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):
"""
......@@ -435,11 +525,11 @@ class CyCy(CythonCommand):
cy cont
cy up
cy down
cy bt / cy backtrace
cy print
cy list
cy locals
cy globals
cy backtrace
"""
name = 'cy'
......@@ -458,12 +548,14 @@ class CyCy(CythonCommand):
cont = CyCont.register(),
up = CyUp.register(),
down = CyDown.register(),
bt = CyBacktrace.register(),
list = CyList.register(),
print_ = CyPrint.register(),
locals = CyLocals.register(),
globals = CyGlobals.register(),
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():
......@@ -743,10 +835,20 @@ class CyUp(CythonCodeStepper):
_command = 'up'
def invoke(self, *args):
self.result = gdb.execute(self._command, to_string=True)
try:
gdb.execute(self._command, to_string=True)
while not self.is_relevant_function(gdb.selected_frame()):
self.result = gdb.execute(self._command, to_string=True)
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):
......@@ -758,6 +860,35 @@ class CyDown(CyUp):
_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):
"""
List Cython source code. To disable to customize colouring see the cy_*
......@@ -785,7 +916,7 @@ class CyPrint(CythonCommand):
@dispatch_on_frame(c_command='print', python_command='py-print')
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:
value = gdb.parse_and_eval(cname)
except RuntimeError, e:
......@@ -880,7 +1011,7 @@ class CyCName(gdb.Function, CythonBase):
"""
@require_cython_frame
def invoke(self, cyname, string=False, frame=None):
def invoke(self, cyname, frame=None):
frame = frame or gdb.selected_frame()
cname = None
......@@ -905,9 +1036,17 @@ class CyCName(gdb.Function, CythonBase):
if not cname:
raise gdb.GdbError('No such Cython variable: %s' % cyname)
if string:
return cname
else:
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)
......
......@@ -1444,6 +1444,7 @@ class PyLocals(gdb.Command):
namespace = self.get_namespace(pyop_frame)
namespace = [(name.proxyval(set()), val) for name, val in namespace]
if namespace:
name, val = max(namespace, key=lambda (name, val): len(name))
max_name_length = len(name)
......
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