From: Mark Florisson Date: Mon, 1 Nov 2010 18:55:17 +0000 (+0100) Subject: Reentrant gdb.execute() X-Git-Tag: 0.14.beta0~1^2~50 X-Git-Url: http://git.tremily.us/gitweb.cgi?a=commitdiff_plain;h=ef747e9adb6c0cdad0321f4662861e43a526a24f;p=cython.git Reentrant gdb.execute() cy locals, cy globals --- diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 5b34f8d6..218a97d7 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -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 diff --git a/Cython/Debugger/Tests/codefile b/Cython/Debugger/Tests/codefile index e9c77c7d..53d6518d 100644 --- a/Cython/Debugger/Tests/codefile +++ b/Cython/Debugger/Tests/codefile @@ -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: diff --git a/Cython/Debugger/Tests/test_libcython_in_gdb.py b/Cython/Debugger/Tests/test_libcython_in_gdb.py index c926b526..060d64c1 100644 --- a/Cython/Debugger/Tests/test_libcython_in_gdb.py +++ b/Cython/Debugger/Tests/test_libcython_in_gdb.py @@ -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(): diff --git a/Cython/Debugger/libcython.py b/Cython/Debugger/libcython.py index c6cca5a4..272b8265 100644 --- a/Cython/Debugger/libcython.py +++ b/Cython/Debugger/libcython.py @@ -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): diff --git a/Cython/Debugger/libpython.py b/Cython/Debugger/libpython.py index 836868d1..acd4a88c 100644 --- a/Cython/Debugger/libpython.py +++ b/Cython/Debugger/libpython.py @@ -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