Commit ef747e9a authored by Mark Florisson's avatar Mark Florisson

Reentrant gdb.execute()

cy locals, cy globals
parent d07e7567
......@@ -7,6 +7,7 @@ from Cython.Compiler.UtilNodes import *
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
......@@ -1469,11 +1470,14 @@ class DebugTransform(CythonTransform):
# 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
......
......@@ -6,8 +6,8 @@ cdef extern:
import os
cdef int c_var = 0
python_var = 0
cdef int c_var = 12
python_var = 13
def spam(a=0):
cdef:
......
......@@ -8,6 +8,7 @@ Cython.Debugger.Cygdb.make_command_file()
import os
import sys
import trace
import inspect
import warnings
import unittest
import traceback
......@@ -19,11 +20,16 @@ from Cython.Debugger import libcython
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)
......@@ -50,25 +56,26 @@ class DebugTestCase(unittest.TestCase):
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)
......@@ -115,7 +122,6 @@ class TestBreak(DebugTestCase):
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)
......@@ -140,6 +146,7 @@ class TestStep(DebugStepperTestCase):
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
......@@ -197,8 +204,28 @@ class TestNext(DebugStepperTestCase):
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:
......@@ -207,17 +234,14 @@ def _main():
"-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():
......
......@@ -229,6 +229,7 @@ class CythonBase(object):
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
......@@ -236,7 +237,7 @@ class CythonBase(object):
"""
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
......@@ -246,7 +247,13 @@ class CythonBase(object):
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):
......@@ -456,7 +463,9 @@ class CyCy(CythonCommand):
for command_name, command in commands.iteritems():
command.cy = self
setattr(self, command_name, command)
self.cy = self
# Cython module namespace
self.cython_namespace = {}
......@@ -507,6 +516,7 @@ class CyImport(CythonCommand):
# 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[
......@@ -514,9 +524,7 @@ class CyImport(CythonCommand):
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
......@@ -658,17 +666,17 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
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):
......@@ -724,8 +732,12 @@ class CyPrint(CythonCommand):
@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()
......@@ -743,32 +755,11 @@ class CyLocals(CythonCommand):
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):
"""
......@@ -779,12 +770,8 @@ 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:
......@@ -792,16 +779,29 @@ class CyGlobals(CythonCommand):
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):
......
......@@ -47,6 +47,7 @@ from __future__ import with_statement
import os
import sys
import atexit
import tempfile
import gdb
......@@ -1454,34 +1455,62 @@ class PyLocals(gdb.Command):
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):
......@@ -1494,7 +1523,9 @@ 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.
......@@ -1545,12 +1576,16 @@ class GenericCodeStepper(gdb.Command):
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
......@@ -1600,8 +1635,7 @@ class PythonCodeStepper(GenericCodeStepper):
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."
......@@ -1611,6 +1645,3 @@ class PyNext(PythonCodeStepper):
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
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