Reentrant gdb.execute()
authorMark Florisson <markflorisson88@gmail.com>
Mon, 1 Nov 2010 18:55:17 +0000 (19:55 +0100)
committerMark Florisson <markflorisson88@gmail.com>
Mon, 1 Nov 2010 18:55:17 +0000 (19:55 +0100)
cy locals, cy globals

Cython/Compiler/ParseTreeTransforms.py
Cython/Debugger/Tests/codefile
Cython/Debugger/Tests/test_libcython_in_gdb.py
Cython/Debugger/libcython.py
Cython/Debugger/libpython.py

index 5b34f8d6c6264c4a2c84a7a5091cfb20ec1153ef..218a97d76e20aa84d2b1347802f928741bea3ba9 100644 (file)
@@ -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
index e9c77c7dc3e952f36a809fc74525930b4e1e1d3d..53d6518d551302ecca2dc17facc91b984f1e4d97 100644 (file)
@@ -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:
index c926b526e47ffee2c8346f0351ed605b695109fa..060d64c154999103170c9010d07e0cc133ab2458 100644 (file)
@@ -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():
index c6cca5a41c75ad8e68253195d5eb01ff8598a723..272b8265a122f2cb64601b1c37a986f6dac7259a 100644 (file)
@@ -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):
index 836868d122cf6a402453d15163525052f0337b81..acd4a88ccb66bc811f78da32685a5f236cb7c2ca 100644 (file)
@@ -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