from Cython.Compiler.TreeFragment import TreeFragment, TemplateTransform
from Cython.Compiler.StringEncoding import EncodedString
from Cython.Compiler.Errors import error, CompileError
+from Cython.Compiler import PyrexTypes
try:
set
# 2.3 compatibility. Serialize global variables
self.tb.start('Globals')
entries = {}
+
for k, v in node.scope.entries.iteritems():
- if (v.qualified_name not in self.visited and
- not v.name.startswith('__pyx_')):
+ if (v.qualified_name not in self.visited and not
+ v.name.startswith('__pyx_') and not
+ v.type.is_cfunction and not
+ v.type.is_extension_type):
entries[k]= v
-
+
self.serialize_local_variables(entries)
self.tb.end('Globals')
# self.tb.end('Module') # end Module after the line number mapping in
import os
-cdef int c_var = 0
-python_var = 0
+cdef int c_var = 12
+python_var = 13
def spam(a=0):
cdef:
import os
import sys
import trace
+import inspect
import warnings
import unittest
import traceback
from Cython.Debugger import libpython
from Cython.Debugger.Tests import TestLibCython as test_libcython
+
# for some reason sys.argv is missing in gdb
sys.argv = ['gdb']
class DebugTestCase(unittest.TestCase):
+ """
+ Base class for test cases. On teardown it kills the inferior and unsets
+ all breakpoints.
+ """
def __init__(self, name):
super(DebugTestCase, self).__init__(name)
lineno = test_libcython.source_to_lineno[source_line]
frame = gdb.selected_frame()
self.assertEqual(libcython.cy.step.lineno(frame), lineno)
-
+
+ def break_and_run(self, source_line):
+ break_lineno = test_libcython.source_to_lineno[source_line]
+ gdb.execute('cy break codefile:%d' % break_lineno, to_string=True)
+ gdb.execute('run', to_string=True)
+
def tearDown(self):
gdb.execute('delete breakpoints', to_string=True)
try:
gdb.execute('kill inferior 1', to_string=True)
except RuntimeError:
pass
-
- def break_and_run(self, source_line):
- break_lineno = test_libcython.source_to_lineno[source_line]
- gdb.execute('cy break codefile:%d' % break_lineno, to_string=True)
- gdb.execute('run', to_string=True)
+
class TestDebugInformationClasses(DebugTestCase):
def test_CythonModule(self):
"test that debug information was parsed properly into data structures"
self.assertEqual(self.module.name, 'codefile')
- global_vars = ('c_var', 'python_var', 'SomeClass', '__name__',
+ global_vars = ('c_var', 'python_var', '__name__',
'__builtins__', '__doc__', '__file__')
assert set(global_vars).issubset(self.module.globals)
def test_break(self):
result = libpython._execute('cy break codefile.spam', to_string=True)
- print >>sys.stderr, repr(result)
assert self.spam_func.cname in result
self.assertEqual(len(gdb.breakpoints()), 1)
Test stepping. Stepping happens in the code found in
Cython/Debugger/Tests/codefile.
"""
+
def test_cython_step(self):
gdb.execute('cy break codefile.spam')
libcython.parameters.step_into_c_code.value = False
self.lineno_equals(line)
+class TestLocalsGlobals(DebugTestCase):
+
+ def test_locals(self):
+ self.break_and_run('int(10)')
+
+ result = gdb.execute('cy locals', to_string=True)
+ assert 'a = 0' in result, repr(result)
+ assert 'b = 1' in result, repr(result)
+ assert 'c = 2' in result, repr(result)
+
+ def test_globals(self):
+ 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)
+
+
def _main():
- # unittest.main(module=__import__(__name__, fromlist=['']))
try:
gdb.lookup_type('PyModuleObject')
except RuntimeError:
"-g or get a debug build (configure with --with-pydebug).")
warnings.warn(msg)
else:
- tests = (
- TestDebugInformationClasses,
- TestParameters,
- TestBreak,
- TestStep,
- TestNext,
- )
+ m = __import__(__name__, fromlist=[''])
+ tests = inspect.getmembers(m, inspect.isclass)
+
# test_support.run_unittest(tests)
+
test_loader = unittest.TestLoader()
suite = unittest.TestSuite(
- [test_loader.loadTestsFromTestCase(cls) for cls in tests])
+ [test_loader.loadTestsFromTestCase(cls) for name, cls in tests])
result = unittest.TextTestRunner(verbosity=1).run(suite)
if not result.wasSuccessful():
source_desc, lineno = self.get_source_desc()
return source_desc.get_source(lineno)
+ @default_selected_gdb_frame()
def is_relevant_function(self, frame):
"""
returns whether we care about a frame on the user-level when debugging
"""
name = frame.name()
older_frame = frame.older()
-
+ # print 'is_relevant_function', name
if self.is_cython_function(frame) or self.is_python_function(frame):
return True
elif (parameters.step_into_c_code and
return name in cython_func.step_into_functions
return False
-
+
+ def print_cython_var_if_initialized(self, varname):
+ try:
+ self.cy.print_.invoke(varname, True)
+ except gdb.GdbError:
+ # variable not initialized yet
+ pass
class SourceFileDescriptor(object):
def __init__(self, filename, lexer, formatter=None):
for command_name, command in commands.iteritems():
command.cy = self
setattr(self, command_name, command)
-
+
+ self.cy = self
+
# Cython module namespace
self.cython_namespace = {}
# update the global function mappings
name = cython_function.name
+ qname = cython_function.qualified_name
self.cy.functions_by_name[name].append(cython_function)
self.cy.functions_by_qualified_name[
self.cy.functions_by_cname[
cython_function.cname] = cython_function
- d = cython_module.functions
- L = d.setdefault(cython_function.qualified_name, [])
- L.append(cython_function)
+ d = cython_module.functions[qname] = cython_function
for local in function.find('Locals'):
d = local.attrib
def get_source_line(self, frame):
# We may have ended up in a Python, Cython, or C function
- # In case of C, don't display any additional data (gdb already
- # does this)
- result = ''
+ result = None
if self.is_cython_function(frame) or self.is_python_function(frame):
try:
- result = super(CythonCodeStepper, self).get_source_line(frame)
+ line = super(CythonCodeStepper, self).get_source_line(frame)
except gdb.GdbError:
- result = ''
-
- return result.lstrip()
+ pass
+ else:
+ result = line.lstrip()
+
+ return result
@classmethod
def register(cls):
@dispatch_on_frame(c_command='print', python_command='py-print')
def invoke(self, name, from_tty):
- gdb.execute('print ' + self.cy.cy_cname.invoke(name, string=True))
-
+ cname = self.cy.cy_cname.invoke(name, string=True)
+ try:
+ print '%s = %s' % (name, gdb.parse_and_eval(cname))
+ except RuntimeError, e:
+ raise gdb.GdbError("Variable %s is not initialized yet." % (name,))
+
def complete(self):
if self.is_cython_function():
f = self.get_cython_function()
command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE
- def ns(self):
- return self.get_cython_function().locals
-
@dispatch_on_frame(c_command='info locals', python_command='py-locals')
- def invoke(self, name, from_tty):
- try:
- ns = self.ns()
- except RuntimeError, e:
- print e.args[0]
- return
-
- if ns is None:
- raise gdb.GdbError(
- 'Information of Cython locals could not be obtained. '
- 'Is this an actual Cython function and did you '
- "'cy import' the debug information?")
-
- for var in ns.itervalues():
- val = gdb.parse_and_eval(var.cname)
- if var.type == PythonObject:
- result = libpython.PyObjectPtr.from_pyobject_ptr(val)
- else:
- result = val
+ def invoke(self, args, from_tty):
+ for varname in self.get_cython_function().locals:
+ self.print_cython_var_if_initialized(varname)
- print '%s = %s' % (var.name, result)
-
class CyGlobals(CythonCommand):
"""
command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE
- def ns(self):
- return self.get_cython_function().globals
-
@dispatch_on_frame(c_command='info variables', python_command='py-globals')
- def invoke(self, name, from_tty):
- # include globals from the debug info XML file!
+ def invoke(self, args, from_tty):
m = gdb.parse_and_eval('__pyx_m')
try:
except RuntimeError:
raise gdb.GdbError(textwrap.dedent("""
Unable to lookup type PyModuleObject, did you compile python
- with debugging support (-g)? If this installation is from your
- package manager, install python-dbg and run the debug version
- of python or compile it yourself.
+ with debugging support (-g)?
"""))
m = m.cast(PyModuleObject.pointer())
d = libpython.PyObjectPtr.from_pyobject_ptr(m['md_dict'])
- print d.get_truncated_repr(1000)
-
-
+
+ seen = set()
+ for k, v in d.iteritems():
+ # Note: k and v are values in the inferior, they are
+ # libpython.PyObjectPtr objects
+
+ k = k.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
+ # make it look like an actual name (inversion of repr())
+ k = k[1:-1].decode('string-escape')
+ v = v.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
+
+ seen.add(k)
+ print '%s = %s' % (k, v)
+
+ module_globals = self.get_cython_function().module.globals
+ for varname in seen.symmetric_difference(module_globals):
+ self.print_cython_var_if_initialized(varname)
+
# Functions
class CyCName(gdb.Function, CythonBase):
import os
import sys
+import atexit
import tempfile
import gdb
PyLocals()
-def execute(command, from_tty=False, to_string=False):
+class _LoggingState(object):
"""
- Replace gdb.execute() with this function and have it accept a 'to_string'
- argument (new in 7.2). Have it properly capture stderr also.
-
- Unfortuntaly, this function is not reentrant.
+ State that helps to provide a reentrant gdb.execute() function.
"""
- if not to_string:
- return _execute(command, from_tty)
-
- fd, filename = tempfile.mkstemp()
- try:
- _execute("set logging file %s" % filename)
- _execute("set logging redirect on")
- _execute("set logging on")
- _execute("set pagination off")
+ def __init__(self):
+ self.fd, self.filename = tempfile.mkstemp()
+ self.file = os.fdopen(self.fd, 'r+')
+ _execute("set logging file %s" % self.filename)
+ self.file_position_stack = []
- _execute(command, from_tty)
- finally:
- data = os.fdopen(fd).read()
- os.remove(filename)
- _execute("set logging off")
- _execute("set pagination on")
- return data
+ atexit.register(os.close, self.fd)
+ atexit.register(os.remove, self.filename)
+
+ def __enter__(self):
+ if not self.file_position_stack:
+ _execute("set logging redirect on")
+ _execute("set logging on")
+ _execute("set pagination off")
+ self.file_position_stack.append(os.fstat(self.fd).st_size)
+ return self
+
+ def getoutput(self):
+ gdb.flush()
+ self.file.seek(self.file_position_stack[-1])
+ result = self.file.read()
+ return result
+
+ def __exit__(self, exc_type, exc_val, tb):
+ startpos = self.file_position_stack.pop()
+ self.file.seek(startpos)
+ self.file.truncate()
+ if not self.file_position_stack:
+ _execute("set logging off")
+ _execute("set logging redirect off")
+ _execute("set pagination on")
+
+
+def execute(command, from_tty=True, to_string=False):
+ """
+ Replace gdb.execute() with this function and have it accept a 'to_string'
+ argument (new in 7.2). Have it properly capture stderr also. Ensure
+ reentrancy.
+ """
+ if to_string:
+ with _logging_state as state:
+ _execute(command, from_tty)
+ return state.getoutput()
+ else:
+ _execute(command, from_tty)
+
+
_execute = gdb.execute
gdb.execute = execute
+_logging_state = _LoggingState()
class GenericCodeStepper(gdb.Command):
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)
+ frame). If the source code cannot be
+ retrieved this function should
+ return None
This class provides an 'invoke' method that invokes a 'step' or 'step-over'
depending on the 'stepper' argument.
return not (hit_breakpoint or new_lineno or is_relevant_function)
def _end_stepping(self):
- # sys.stdout.write(self.result)
-
- if not self.stopped_running:
+ if self.stopped_running:
+ sys.stdout.write(self.result)
+ else:
frame = gdb.selected_frame()
if self.is_relevant_function(frame):
- print self.get_source_line(frame)
+ output = self.get_source_line(frame)
+ if output is None:
+ sys.stdout.write(self.result)
+ else:
+ print output
def _stackdepth(self, frame):
depth = 0
try:
return self.pyframe(frame).current_line().rstrip()
except IOError, e:
- gdb.GdbError('Unable to retrieve source code: %s' % (e,))
-
+ return None
class PyStep(PythonCodeStepper):
"Step through Python code."
py_step = PyStep('py-step', stepper=True)
py_next = PyNext('py-next', stepper=False)
-
-class PyShowCCode(gdb.Parameter):
- pass
\ No newline at end of file