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 * ...@@ -7,6 +7,7 @@ from Cython.Compiler.UtilNodes import *
from Cython.Compiler.TreeFragment import TreeFragment, TemplateTransform from Cython.Compiler.TreeFragment import TreeFragment, TemplateTransform
from Cython.Compiler.StringEncoding import EncodedString from Cython.Compiler.StringEncoding import EncodedString
from Cython.Compiler.Errors import error, CompileError from Cython.Compiler.Errors import error, CompileError
from Cython.Compiler import PyrexTypes
try: try:
set set
...@@ -1469,11 +1470,14 @@ class DebugTransform(CythonTransform): ...@@ -1469,11 +1470,14 @@ class DebugTransform(CythonTransform):
# 2.3 compatibility. Serialize global variables # 2.3 compatibility. Serialize global variables
self.tb.start('Globals') self.tb.start('Globals')
entries = {} entries = {}
for k, v in node.scope.entries.iteritems(): for k, v in node.scope.entries.iteritems():
if (v.qualified_name not in self.visited and if (v.qualified_name not in self.visited and not
not v.name.startswith('__pyx_')): v.name.startswith('__pyx_') and not
v.type.is_cfunction and not
v.type.is_extension_type):
entries[k]= v entries[k]= v
self.serialize_local_variables(entries) self.serialize_local_variables(entries)
self.tb.end('Globals') self.tb.end('Globals')
# self.tb.end('Module') # end Module after the line number mapping in # self.tb.end('Module') # end Module after the line number mapping in
......
...@@ -6,8 +6,8 @@ cdef extern: ...@@ -6,8 +6,8 @@ cdef extern:
import os import os
cdef int c_var = 0 cdef int c_var = 12
python_var = 0 python_var = 13
def spam(a=0): def spam(a=0):
cdef: cdef:
......
...@@ -8,6 +8,7 @@ Cython.Debugger.Cygdb.make_command_file() ...@@ -8,6 +8,7 @@ Cython.Debugger.Cygdb.make_command_file()
import os import os
import sys import sys
import trace import trace
import inspect
import warnings import warnings
import unittest import unittest
import traceback import traceback
...@@ -19,11 +20,16 @@ from Cython.Debugger import libcython ...@@ -19,11 +20,16 @@ from Cython.Debugger import libcython
from Cython.Debugger import libpython from Cython.Debugger import libpython
from Cython.Debugger.Tests import TestLibCython as test_libcython from Cython.Debugger.Tests import TestLibCython as test_libcython
# for some reason sys.argv is missing in gdb # for some reason sys.argv is missing in gdb
sys.argv = ['gdb'] sys.argv = ['gdb']
class DebugTestCase(unittest.TestCase): class DebugTestCase(unittest.TestCase):
"""
Base class for test cases. On teardown it kills the inferior and unsets
all breakpoints.
"""
def __init__(self, name): def __init__(self, name):
super(DebugTestCase, self).__init__(name) super(DebugTestCase, self).__init__(name)
...@@ -50,25 +56,26 @@ class DebugTestCase(unittest.TestCase): ...@@ -50,25 +56,26 @@ class DebugTestCase(unittest.TestCase):
lineno = test_libcython.source_to_lineno[source_line] lineno = test_libcython.source_to_lineno[source_line]
frame = gdb.selected_frame() frame = gdb.selected_frame()
self.assertEqual(libcython.cy.step.lineno(frame), lineno) 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): def tearDown(self):
gdb.execute('delete breakpoints', to_string=True) gdb.execute('delete breakpoints', to_string=True)
try: try:
gdb.execute('kill inferior 1', to_string=True) gdb.execute('kill inferior 1', to_string=True)
except RuntimeError: except RuntimeError:
pass 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): class TestDebugInformationClasses(DebugTestCase):
def test_CythonModule(self): def test_CythonModule(self):
"test that debug information was parsed properly into data structures" "test that debug information was parsed properly into data structures"
self.assertEqual(self.module.name, 'codefile') self.assertEqual(self.module.name, 'codefile')
global_vars = ('c_var', 'python_var', 'SomeClass', '__name__', global_vars = ('c_var', 'python_var', '__name__',
'__builtins__', '__doc__', '__file__') '__builtins__', '__doc__', '__file__')
assert set(global_vars).issubset(self.module.globals) assert set(global_vars).issubset(self.module.globals)
...@@ -115,7 +122,6 @@ class TestBreak(DebugTestCase): ...@@ -115,7 +122,6 @@ class TestBreak(DebugTestCase):
def test_break(self): def test_break(self):
result = libpython._execute('cy break codefile.spam', to_string=True) result = libpython._execute('cy break codefile.spam', to_string=True)
print >>sys.stderr, repr(result)
assert self.spam_func.cname in result assert self.spam_func.cname in result
self.assertEqual(len(gdb.breakpoints()), 1) self.assertEqual(len(gdb.breakpoints()), 1)
...@@ -140,6 +146,7 @@ class TestStep(DebugStepperTestCase): ...@@ -140,6 +146,7 @@ class TestStep(DebugStepperTestCase):
Test stepping. Stepping happens in the code found in Test stepping. Stepping happens in the code found in
Cython/Debugger/Tests/codefile. Cython/Debugger/Tests/codefile.
""" """
def test_cython_step(self): def test_cython_step(self):
gdb.execute('cy break codefile.spam') gdb.execute('cy break codefile.spam')
libcython.parameters.step_into_c_code.value = False libcython.parameters.step_into_c_code.value = False
...@@ -197,8 +204,28 @@ class TestNext(DebugStepperTestCase): ...@@ -197,8 +204,28 @@ class TestNext(DebugStepperTestCase):
self.lineno_equals(line) 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(): def _main():
# unittest.main(module=__import__(__name__, fromlist=['']))
try: try:
gdb.lookup_type('PyModuleObject') gdb.lookup_type('PyModuleObject')
except RuntimeError: except RuntimeError:
...@@ -207,17 +234,14 @@ def _main(): ...@@ -207,17 +234,14 @@ def _main():
"-g or get a debug build (configure with --with-pydebug).") "-g or get a debug build (configure with --with-pydebug).")
warnings.warn(msg) warnings.warn(msg)
else: else:
tests = ( m = __import__(__name__, fromlist=[''])
TestDebugInformationClasses, tests = inspect.getmembers(m, inspect.isclass)
TestParameters,
TestBreak,
TestStep,
TestNext,
)
# test_support.run_unittest(tests) # test_support.run_unittest(tests)
test_loader = unittest.TestLoader() test_loader = unittest.TestLoader()
suite = unittest.TestSuite( 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) result = unittest.TextTestRunner(verbosity=1).run(suite)
if not result.wasSuccessful(): if not result.wasSuccessful():
......
...@@ -229,6 +229,7 @@ class CythonBase(object): ...@@ -229,6 +229,7 @@ class CythonBase(object):
source_desc, lineno = self.get_source_desc() source_desc, lineno = self.get_source_desc()
return source_desc.get_source(lineno) return source_desc.get_source(lineno)
@default_selected_gdb_frame()
def is_relevant_function(self, frame): def is_relevant_function(self, frame):
""" """
returns whether we care about a frame on the user-level when debugging returns whether we care about a frame on the user-level when debugging
...@@ -236,7 +237,7 @@ class CythonBase(object): ...@@ -236,7 +237,7 @@ class CythonBase(object):
""" """
name = frame.name() name = frame.name()
older_frame = frame.older() older_frame = frame.older()
# print 'is_relevant_function', name
if self.is_cython_function(frame) or self.is_python_function(frame): if self.is_cython_function(frame) or self.is_python_function(frame):
return True return True
elif (parameters.step_into_c_code and elif (parameters.step_into_c_code and
...@@ -246,7 +247,13 @@ class CythonBase(object): ...@@ -246,7 +247,13 @@ class CythonBase(object):
return name in cython_func.step_into_functions return name in cython_func.step_into_functions
return False 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): class SourceFileDescriptor(object):
def __init__(self, filename, lexer, formatter=None): def __init__(self, filename, lexer, formatter=None):
...@@ -456,7 +463,9 @@ class CyCy(CythonCommand): ...@@ -456,7 +463,9 @@ class CyCy(CythonCommand):
for command_name, command in commands.iteritems(): for command_name, command in commands.iteritems():
command.cy = self command.cy = self
setattr(self, command_name, command) setattr(self, command_name, command)
self.cy = self
# Cython module namespace # Cython module namespace
self.cython_namespace = {} self.cython_namespace = {}
...@@ -507,6 +516,7 @@ class CyImport(CythonCommand): ...@@ -507,6 +516,7 @@ class CyImport(CythonCommand):
# update the global function mappings # update the global function mappings
name = cython_function.name name = cython_function.name
qname = cython_function.qualified_name
self.cy.functions_by_name[name].append(cython_function) self.cy.functions_by_name[name].append(cython_function)
self.cy.functions_by_qualified_name[ self.cy.functions_by_qualified_name[
...@@ -514,9 +524,7 @@ class CyImport(CythonCommand): ...@@ -514,9 +524,7 @@ class CyImport(CythonCommand):
self.cy.functions_by_cname[ self.cy.functions_by_cname[
cython_function.cname] = cython_function cython_function.cname] = cython_function
d = cython_module.functions d = cython_module.functions[qname] = cython_function
L = d.setdefault(cython_function.qualified_name, [])
L.append(cython_function)
for local in function.find('Locals'): for local in function.find('Locals'):
d = local.attrib d = local.attrib
...@@ -658,17 +666,17 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): ...@@ -658,17 +666,17 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
def get_source_line(self, frame): def get_source_line(self, frame):
# We may have ended up in a Python, Cython, or C function # We may have ended up in a Python, Cython, or C function
# In case of C, don't display any additional data (gdb already result = None
# does this)
result = ''
if self.is_cython_function(frame) or self.is_python_function(frame): if self.is_cython_function(frame) or self.is_python_function(frame):
try: try:
result = super(CythonCodeStepper, self).get_source_line(frame) line = super(CythonCodeStepper, self).get_source_line(frame)
except gdb.GdbError: except gdb.GdbError:
result = '' pass
else:
return result.lstrip() result = line.lstrip()
return result
@classmethod @classmethod
def register(cls): def register(cls):
...@@ -724,8 +732,12 @@ class CyPrint(CythonCommand): ...@@ -724,8 +732,12 @@ 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): 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): def complete(self):
if self.is_cython_function(): if self.is_cython_function():
f = self.get_cython_function() f = self.get_cython_function()
...@@ -743,32 +755,11 @@ class CyLocals(CythonCommand): ...@@ -743,32 +755,11 @@ class CyLocals(CythonCommand):
command_class = gdb.COMMAND_STACK command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE 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') @dispatch_on_frame(c_command='info locals', python_command='py-locals')
def invoke(self, name, from_tty): def invoke(self, args, from_tty):
try: for varname in self.get_cython_function().locals:
ns = self.ns() self.print_cython_var_if_initialized(varname)
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
print '%s = %s' % (var.name, result)
class CyGlobals(CythonCommand): class CyGlobals(CythonCommand):
""" """
...@@ -779,12 +770,8 @@ class CyGlobals(CythonCommand): ...@@ -779,12 +770,8 @@ class CyGlobals(CythonCommand):
command_class = gdb.COMMAND_STACK command_class = gdb.COMMAND_STACK
completer_class = gdb.COMPLETE_NONE 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') @dispatch_on_frame(c_command='info variables', python_command='py-globals')
def invoke(self, name, from_tty): def invoke(self, args, from_tty):
# include globals from the debug info XML file!
m = gdb.parse_and_eval('__pyx_m') m = gdb.parse_and_eval('__pyx_m')
try: try:
...@@ -792,16 +779,29 @@ class CyGlobals(CythonCommand): ...@@ -792,16 +779,29 @@ class CyGlobals(CythonCommand):
except RuntimeError: except RuntimeError:
raise gdb.GdbError(textwrap.dedent(""" raise gdb.GdbError(textwrap.dedent("""
Unable to lookup type PyModuleObject, did you compile python Unable to lookup type PyModuleObject, did you compile python
with debugging support (-g)? If this installation is from your with debugging support (-g)?
package manager, install python-dbg and run the debug version
of python or compile it yourself.
""")) """))
m = m.cast(PyModuleObject.pointer()) m = m.cast(PyModuleObject.pointer())
d = libpython.PyObjectPtr.from_pyobject_ptr(m['md_dict']) 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 # Functions
class CyCName(gdb.Function, CythonBase): class CyCName(gdb.Function, CythonBase):
......
...@@ -47,6 +47,7 @@ from __future__ import with_statement ...@@ -47,6 +47,7 @@ from __future__ import with_statement
import os import os
import sys import sys
import atexit
import tempfile import tempfile
import gdb import gdb
...@@ -1454,34 +1455,62 @@ class PyLocals(gdb.Command): ...@@ -1454,34 +1455,62 @@ class PyLocals(gdb.Command):
PyLocals() 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' State that helps to provide a reentrant gdb.execute() function.
argument (new in 7.2). Have it properly capture stderr also.
Unfortuntaly, this function is not reentrant.
""" """
if not to_string:
return _execute(command, from_tty)
fd, filename = tempfile.mkstemp()
try: def __init__(self):
_execute("set logging file %s" % filename) self.fd, self.filename = tempfile.mkstemp()
_execute("set logging redirect on") self.file = os.fdopen(self.fd, 'r+')
_execute("set logging on") _execute("set logging file %s" % self.filename)
_execute("set pagination off") self.file_position_stack = []
_execute(command, from_tty) atexit.register(os.close, self.fd)
finally: atexit.register(os.remove, self.filename)
data = os.fdopen(fd).read()
os.remove(filename) def __enter__(self):
_execute("set logging off") if not self.file_position_stack:
_execute("set pagination on") _execute("set logging redirect on")
return data _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 _execute = gdb.execute
gdb.execute = execute gdb.execute = execute
_logging_state = _LoggingState()
class GenericCodeStepper(gdb.Command): class GenericCodeStepper(gdb.Command):
...@@ -1494,7 +1523,9 @@ class GenericCodeStepper(gdb.Command): ...@@ -1494,7 +1523,9 @@ class GenericCodeStepper(gdb.Command):
is_relevant_function(frame) - tells whether we care about frame 'frame' is_relevant_function(frame) - tells whether we care about frame 'frame'
get_source_line(frame) - get the line of source code for the get_source_line(frame) - get the line of source code for the
current line (only called for a relevant 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' This class provides an 'invoke' method that invokes a 'step' or 'step-over'
depending on the 'stepper' argument. depending on the 'stepper' argument.
...@@ -1545,12 +1576,16 @@ class GenericCodeStepper(gdb.Command): ...@@ -1545,12 +1576,16 @@ class GenericCodeStepper(gdb.Command):
return not (hit_breakpoint or new_lineno or is_relevant_function) return not (hit_breakpoint or new_lineno or is_relevant_function)
def _end_stepping(self): def _end_stepping(self):
# sys.stdout.write(self.result) if self.stopped_running:
sys.stdout.write(self.result)
if not self.stopped_running: else:
frame = gdb.selected_frame() frame = gdb.selected_frame()
if self.is_relevant_function(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): def _stackdepth(self, frame):
depth = 0 depth = 0
...@@ -1600,8 +1635,7 @@ class PythonCodeStepper(GenericCodeStepper): ...@@ -1600,8 +1635,7 @@ class PythonCodeStepper(GenericCodeStepper):
try: try:
return self.pyframe(frame).current_line().rstrip() return self.pyframe(frame).current_line().rstrip()
except IOError, e: except IOError, e:
gdb.GdbError('Unable to retrieve source code: %s' % (e,)) return None
class PyStep(PythonCodeStepper): class PyStep(PythonCodeStepper):
"Step through Python code." "Step through Python code."
...@@ -1611,6 +1645,3 @@ class PyNext(PythonCodeStepper): ...@@ -1611,6 +1645,3 @@ class PyNext(PythonCodeStepper):
py_step = PyStep('py-step', stepper=True) py_step = PyStep('py-step', stepper=True)
py_next = PyNext('py-next', stepper=False) 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