"""
import os
+import re
import sys
import trace
import inspect
'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)
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
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())
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):
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():
GDB extension that adds Cython support.
"""
+import os
import sys
import textwrap
import traceback
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:
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)
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
@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:
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
@default_selected_gdb_frame()
# 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
"""
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):
cy cont
cy up
cy down
+ cy bt / cy backtrace
cy print
cy list
cy locals
cy globals
- cy backtrace
"""
name = 'cy'
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():
@classmethod
def register(cls):
return cls(cls.name, stepper=cls.stepper)
-
+
class CyStep(CythonCodeStepper):
"Step through Python code."
_command = 'up'
def invoke(self, *args):
- self.result = 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()
+ try:
+ gdb.execute(self._command, to_string=True)
+ while not self.is_relevant_function(gdb.selected_frame()):
+ 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):
_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_*
@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:
"""
@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
if not cname:
raise gdb.GdbError('No such Cython variable: %s' % cyname)
- if string:
- return cname
- else:
- return gdb.parse_and_eval(cname)
+ return 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):
"""
Get the current Cython line.