Commit c5e16a3f authored by Robert Bradshaw's avatar Robert Bradshaw

Merge branch 'master' of https://github.com/markflorisson88/cython into markflorisson88-master

parents b4c160b5 05533ff0
...@@ -18,7 +18,11 @@ from time import time ...@@ -18,7 +18,11 @@ from time import time
import Code import Code
import Errors import Errors
import Parsing # Do not import Parsing here, import it when needed, because Parsing imports
# Nodes, which globally needs debug command line options initialized to set a
# conditional metaclass. These options are processed by CmdLine called from
# main() in this file.
# import Parsing
import Version import Version
from Scanning import PyrexScanner, FileSourceDescriptor from Scanning import PyrexScanner, FileSourceDescriptor
from Errors import PyrexError, CompileError, InternalError, AbortError, error, warning from Errors import PyrexError, CompileError, InternalError, AbortError, error, warning
...@@ -496,6 +500,7 @@ class Context(object): ...@@ -496,6 +500,7 @@ class Context(object):
try: try:
f = Utils.open_source_file(source_filename, "rU") f = Utils.open_source_file(source_filename, "rU")
try: try:
import Parsing
s = PyrexScanner(f, source_desc, source_encoding = f.encoding, s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
scope = scope, context = self) scope = scope, context = self)
tree = Parsing.p_module(s, pxd, full_module_name) tree = Parsing.p_module(s, pxd, full_module_name)
......
...@@ -1591,6 +1591,10 @@ class DebugTransform(CythonTransform): ...@@ -1591,6 +1591,10 @@ class DebugTransform(CythonTransform):
#self.c_output_file = options.output_file #self.c_output_file = options.output_file
self.c_output_file = result.c_file self.c_output_file = result.c_file
# Closure support, basically treat nested functions as if the AST were
# never nested
self.nested_funcdefs = []
# tells visit_NameNode whether it should register step-into functions # tells visit_NameNode whether it should register step-into functions
self.register_stepinto = False self.register_stepinto = False
...@@ -1605,7 +1609,16 @@ class DebugTransform(CythonTransform): ...@@ -1605,7 +1609,16 @@ class DebugTransform(CythonTransform):
# serialize functions # serialize functions
self.tb.start('Functions') self.tb.start('Functions')
# First, serialize functions normally...
self.visitchildren(node) self.visitchildren(node)
# ... then, serialize nested functions
for nested_funcdef in self.nested_funcdefs:
self.visit_FuncDefNode(nested_funcdef)
self.register_stepinto = True
self.serialize_modulenode_as_function(node)
self.register_stepinto = False
self.tb.end('Functions') self.tb.end('Functions')
# 2.3 compatibility. Serialize global variables # 2.3 compatibility. Serialize global variables
...@@ -1627,6 +1640,14 @@ class DebugTransform(CythonTransform): ...@@ -1627,6 +1640,14 @@ class DebugTransform(CythonTransform):
def visit_FuncDefNode(self, node): def visit_FuncDefNode(self, node):
self.visited.add(node.local_scope.qualified_name) self.visited.add(node.local_scope.qualified_name)
if getattr(node, 'is_wrapper', False):
return node
if self.register_stepinto:
self.nested_funcdefs.append(node)
return node
# node.entry.visibility = 'extern' # node.entry.visibility = 'extern'
if node.py_func is None: if node.py_func is None:
pf_cname = '' pf_cname = ''
...@@ -1678,6 +1699,51 @@ class DebugTransform(CythonTransform): ...@@ -1678,6 +1699,51 @@ class DebugTransform(CythonTransform):
self.visitchildren(node) self.visitchildren(node)
return node return node
def serialize_modulenode_as_function(self, node):
"""
Serialize the module-level code as a function so the debugger will know
it's a "relevant frame" and it will know where to set the breakpoint
for 'break modulename'.
"""
name = node.full_module_name.rpartition('.')[-1]
cname_py2 = 'init' + name
cname_py3 = 'PyInit_' + name
py2_attrs = dict(
name=name,
cname=cname_py2,
pf_cname='',
# Ignore the qualified_name, breakpoints should be set using
# `cy break modulename:lineno` for module-level breakpoints.
qualified_name='',
lineno='1',
is_initmodule_function="True",
)
py3_attrs = dict(py2_attrs, cname=cname_py3)
self._serialize_modulenode_as_function(node, py2_attrs)
self._serialize_modulenode_as_function(node, py3_attrs)
def _serialize_modulenode_as_function(self, node, attrs):
self.tb.start('Function', attrs=attrs)
self.tb.start('Locals')
self.serialize_local_variables(node.scope.entries)
self.tb.end('Locals')
self.tb.start('Arguments')
self.tb.end('Arguments')
self.tb.start('StepIntoFunctions')
self.register_stepinto = True
self.visitchildren(node)
self.register_stepinto = False
self.tb.end('StepIntoFunctions')
self.tb.end('Function')
def serialize_local_variables(self, entries): def serialize_local_variables(self, entries):
for entry in entries.values(): for entry in entries.values():
if entry.type.is_pyobject: if entry.type.is_pyobject:
...@@ -1685,9 +1751,22 @@ class DebugTransform(CythonTransform): ...@@ -1685,9 +1751,22 @@ class DebugTransform(CythonTransform):
else: else:
vartype = 'CObject' vartype = 'CObject'
if entry.from_closure:
# We're dealing with a closure where a variable from an outer
# scope is accessed, get it from the scope object.
cname = '%s->%s' % (Naming.cur_scope_cname,
entry.outer_entry.cname)
qname = '%s.%s.%s' % (entry.scope.outer_scope.qualified_name,
entry.scope.name,
entry.name)
elif entry.in_closure:
cname = '%s->%s' % (Naming.cur_scope_cname,
entry.cname)
qname = entry.qualified_name
else:
cname = entry.cname cname = entry.cname
# if entry.type.is_extension_type: qname = entry.qualified_name
# cname = entry.type.typeptr_cname
if not entry.pos: if not entry.pos:
# this happens for variables that are not in the user's code, # this happens for variables that are not in the user's code,
...@@ -1700,7 +1779,7 @@ class DebugTransform(CythonTransform): ...@@ -1700,7 +1779,7 @@ class DebugTransform(CythonTransform):
attrs = dict( attrs = dict(
name=entry.name, name=entry.name,
cname=cname, cname=cname,
qualified_name=entry.qualified_name, qualified_name=qname,
type=vartype, type=vartype,
lineno=lineno) lineno=lineno)
......
...@@ -182,7 +182,8 @@ class TestDebugTransform(DebuggerTestCase): ...@@ -182,7 +182,8 @@ class TestDebugTransform(DebuggerTestCase):
self.assertEqual('PythonObject', xml_globals.get('python_var')) self.assertEqual('PythonObject', xml_globals.get('python_var'))
# test functions # test functions
funcnames = 'codefile.spam', 'codefile.ham', 'codefile.eggs' funcnames = ('codefile.spam', 'codefile.ham', 'codefile.eggs',
'codefile.closure', 'codefile.inner')
required_xml_attrs = 'name', 'cname', 'qualified_name' required_xml_attrs = 'name', 'cname', 'qualified_name'
assert all([f in xml_funcs for f in funcnames]) assert all([f in xml_funcs for f in funcnames])
spam, ham, eggs = [xml_funcs[funcname] for funcname in funcnames] spam, ham, eggs = [xml_funcs[funcname] for funcname in funcnames]
......
...@@ -16,6 +16,7 @@ from distutils import ccompiler ...@@ -16,6 +16,7 @@ from distutils import ccompiler
import runtests import runtests
import Cython.Distutils.extension import Cython.Distutils.extension
import Cython.Distutils.build_ext
from Cython.Debugger import Cygdb as cygdb from Cython.Debugger import Cygdb as cygdb
root = os.path.dirname(os.path.abspath(__file__)) root = os.path.dirname(os.path.abspath(__file__))
...@@ -24,6 +25,10 @@ cfuncs_file = os.path.join(root, 'cfuncs.c') ...@@ -24,6 +25,10 @@ cfuncs_file = os.path.join(root, 'cfuncs.c')
with open(codefile) as f: with open(codefile) as f:
source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f)) source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f))
# Cython.Distutils.__init__ imports build_ext from build_ext which means we
# can't access the module anymore. Get it from sys.modules instead.
build_ext = sys.modules['Cython.Distutils.build_ext']
class DebuggerTestCase(unittest.TestCase): class DebuggerTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -52,6 +57,9 @@ class DebuggerTestCase(unittest.TestCase): ...@@ -52,6 +57,9 @@ class DebuggerTestCase(unittest.TestCase):
module='codefile', module='codefile',
) )
optimization_disabler = build_ext.Optimization()
optimization_disabler.disable_optimization()
cython_compile_testcase = runtests.CythonCompileTestCase( cython_compile_testcase = runtests.CythonCompileTestCase(
workdir=self.tempdir, workdir=self.tempdir,
# we clean up everything (not only compiled files) # we clean up everything (not only compiled files)
...@@ -77,6 +85,8 @@ class DebuggerTestCase(unittest.TestCase): ...@@ -77,6 +85,8 @@ class DebuggerTestCase(unittest.TestCase):
**opts **opts
) )
optimization_disabler.restore_state()
# ext = Cython.Distutils.extension.Extension( # ext = Cython.Distutils.extension.Extension(
# 'codefile', # 'codefile',
# ['codefile.pyx'], # ['codefile.pyx'],
...@@ -95,6 +105,7 @@ class DebuggerTestCase(unittest.TestCase): ...@@ -95,6 +105,7 @@ class DebuggerTestCase(unittest.TestCase):
class GdbDebuggerTestCase(DebuggerTestCase): class GdbDebuggerTestCase(DebuggerTestCase):
def setUp(self): def setUp(self):
super(GdbDebuggerTestCase, self).setUp() super(GdbDebuggerTestCase, self).setUp()
......
...@@ -22,15 +22,26 @@ def spam(a=0): ...@@ -22,15 +22,26 @@ def spam(a=0):
os.path.join("foo", "bar") os.path.join("foo", "bar")
some_c_function() some_c_function()
cdef ham(): cpdef eggs():
pass pass
cpdef eggs(): cdef ham():
pass pass
cdef class SomeClass(object): cdef class SomeClass(object):
def spam(self): def spam(self):
pass pass
def outer():
cdef object a = "an object"
def inner():
b = 2
# access closed over variables
print a, b
return inner
outer()()
spam() spam()
print "bye!" print "bye!"
\ No newline at end of file
...@@ -14,6 +14,7 @@ import warnings ...@@ -14,6 +14,7 @@ import warnings
import unittest import unittest
import textwrap import textwrap
import tempfile import tempfile
import functools
import traceback import traceback
import itertools import itertools
from test import test_support from test import test_support
...@@ -28,12 +29,35 @@ from Cython.Debugger.Tests import TestLibCython as test_libcython ...@@ -28,12 +29,35 @@ from Cython.Debugger.Tests import TestLibCython as test_libcython
sys.argv = ['gdb'] sys.argv = ['gdb']
def print_on_call_decorator(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
_debug(type(self).__name__, func.__name__)
try:
return func(self, *args, **kwargs)
except Exception, e:
_debug("An exception occurred:", traceback.format_exc(e))
raise
return wrapper
class TraceMethodCallMeta(type):
def __init__(self, name, bases, dict):
for func_name, func in dict.iteritems():
if inspect.isfunction(func):
setattr(self, func_name, print_on_call_decorator(func))
class DebugTestCase(unittest.TestCase): class DebugTestCase(unittest.TestCase):
""" """
Base class for test cases. On teardown it kills the inferior and unsets Base class for test cases. On teardown it kills the inferior and unsets
all breakpoints. all breakpoints.
""" """
__metaclass__ = TraceMethodCallMeta
def __init__(self, name): def __init__(self, name):
super(DebugTestCase, self).__init__(name) super(DebugTestCase, self).__init__(name)
self.cy = libcython.cy self.cy = libcython.cy
...@@ -58,7 +82,7 @@ class DebugTestCase(unittest.TestCase): ...@@ -58,7 +82,7 @@ class DebugTestCase(unittest.TestCase):
if source_line is not None: if source_line is not None:
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.cython_info.lineno(frame), lineno)
def break_and_run(self, source_line): def break_and_run(self, source_line):
break_lineno = test_libcython.source_to_lineno[source_line] break_lineno = test_libcython.source_to_lineno[source_line]
...@@ -74,9 +98,6 @@ class DebugTestCase(unittest.TestCase): ...@@ -74,9 +98,6 @@ class DebugTestCase(unittest.TestCase):
gdb.execute('set args -c "import codefile"') gdb.execute('set args -c "import codefile"')
libcython.cy.step.static_breakpoints.clear()
libcython.cy.step.runtime_breakpoints.clear()
libcython.cy.step.init_breakpoints()
class TestDebugInformationClasses(DebugTestCase): class TestDebugInformationClasses(DebugTestCase):
...@@ -101,7 +122,7 @@ class TestDebugInformationClasses(DebugTestCase): ...@@ -101,7 +122,7 @@ class TestDebugInformationClasses(DebugTestCase):
'codefile.SomeClass.spam') 'codefile.SomeClass.spam')
self.assertEqual(self.spam_func.module, self.module) self.assertEqual(self.spam_func.module, self.module)
assert self.eggs_func.pf_cname assert self.eggs_func.pf_cname, (self.eggs_func, self.eggs_func.pf_cname)
assert not self.ham_func.pf_cname assert not self.ham_func.pf_cname
assert not self.spam_func.pf_cname assert not self.spam_func.pf_cname
assert not self.spam_meth.pf_cname assert not self.spam_meth.pf_cname
...@@ -130,7 +151,7 @@ class TestParameters(unittest.TestCase): ...@@ -130,7 +151,7 @@ class TestParameters(unittest.TestCase):
class TestBreak(DebugTestCase): class TestBreak(DebugTestCase):
def test_break(self): def test_break(self):
breakpoint_amount = len(gdb.breakpoints()) breakpoint_amount = len(gdb.breakpoints() or ())
gdb.execute('cy break codefile.spam') gdb.execute('cy break codefile.spam')
self.assertEqual(len(gdb.breakpoints()), breakpoint_amount + 1) self.assertEqual(len(gdb.breakpoints()), breakpoint_amount + 1)
...@@ -143,6 +164,16 @@ class TestBreak(DebugTestCase): ...@@ -143,6 +164,16 @@ class TestBreak(DebugTestCase):
gdb.execute('cy break -p join') gdb.execute('cy break -p join')
assert 'def join(' in gdb.execute('cy run', to_string=True) assert 'def join(' in gdb.execute('cy run', to_string=True)
def test_break_lineno(self):
beginline = 'import os'
nextline = 'cdef int c_var = 12'
self.break_and_run(beginline)
self.lineno_equals(beginline)
step_result = gdb.execute('cy step', to_string=True)
self.lineno_equals(nextline)
assert step_result.rstrip().endswith(nextline)
class TestKilled(DebugTestCase): class TestKilled(DebugTestCase):
...@@ -151,6 +182,7 @@ class TestKilled(DebugTestCase): ...@@ -151,6 +182,7 @@ class TestKilled(DebugTestCase):
output = gdb.execute('cy run', to_string=True) output = gdb.execute('cy run', to_string=True)
assert 'abort' in output.lower() assert 'abort' in output.lower()
class DebugStepperTestCase(DebugTestCase): class DebugStepperTestCase(DebugTestCase):
def step(self, varnames_and_values, source_line=None, lineno=None): def step(self, varnames_and_values, source_line=None, lineno=None):
...@@ -262,7 +294,7 @@ class TestBacktrace(DebugTestCase): ...@@ -262,7 +294,7 @@ class TestBacktrace(DebugTestCase):
gdb.execute('cy bt') gdb.execute('cy bt')
result = gdb.execute('cy bt -a', to_string=True) result = gdb.execute('cy bt -a', to_string=True)
assert re.search(r'\#0 *0x.* in main\(\) at', result), result assert re.search(r'\#0 *0x.* in main\(\)', result), result
class TestFunctions(DebugTestCase): class TestFunctions(DebugTestCase):
...@@ -344,6 +376,36 @@ class TestExec(DebugTestCase): ...@@ -344,6 +376,36 @@ class TestExec(DebugTestCase):
gdb.execute('cy exec some_random_var = 14') gdb.execute('cy exec some_random_var = 14')
self.assertEqual('14', self.eval_command('some_random_var')) self.assertEqual('14', self.eval_command('some_random_var'))
class TestClosure(DebugTestCase):
def break_and_run_func(self, funcname):
gdb.execute('cy break ' + funcname)
gdb.execute('cy run')
def test_inner(self):
self.break_and_run_func('inner')
self.assertEqual('', gdb.execute('cy locals', to_string=True))
# Allow the Cython-generated code to initialize the scope variable
gdb.execute('cy step')
self.assertEqual(str(self.read_var('a')), "'an object'")
print_result = gdb.execute('cy print a', to_string=True).strip()
self.assertEqual(print_result, "a = 'an object'")
def test_outer(self):
self.break_and_run_func('outer')
self.assertEqual('', gdb.execute('cy locals', to_string=True))
# Initialize scope with 'a' uninitialized
gdb.execute('cy step')
self.assertEqual('', gdb.execute('cy locals', to_string=True))
# Initialize 'a' to 1
gdb.execute('cy step')
print_result = gdb.execute('cy print a', to_string=True).strip()
self.assertEqual(print_result, "a = 'an object'")
_do_debug = os.environ.get('GDB_DEBUG') _do_debug = os.environ.get('GDB_DEBUG')
if _do_debug: if _do_debug:
......
...@@ -154,9 +154,6 @@ class CythonModule(object): ...@@ -154,9 +154,6 @@ class CythonModule(object):
self.lineno_c2cy = {} self.lineno_c2cy = {}
self.functions = {} self.functions = {}
def qualified_name(self, varname):
return '.'.join(self.name, varname)
class CythonVariable(object): class CythonVariable(object):
def __init__(self, name, cname, qualified_name, type, lineno): def __init__(self, name, cname, qualified_name, type, lineno):
...@@ -174,7 +171,8 @@ class CythonFunction(CythonVariable): ...@@ -174,7 +171,8 @@ class CythonFunction(CythonVariable):
pf_cname, pf_cname,
qualified_name, qualified_name,
lineno, lineno,
type=CObject): type=CObject,
is_initmodule_function="False"):
super(CythonFunction, self).__init__(name, super(CythonFunction, self).__init__(name,
cname, cname,
qualified_name, qualified_name,
...@@ -182,6 +180,7 @@ class CythonFunction(CythonVariable): ...@@ -182,6 +180,7 @@ class CythonFunction(CythonVariable):
lineno) lineno)
self.module = module self.module = module
self.pf_cname = pf_cname self.pf_cname = pf_cname
self.is_initmodule_function = is_initmodule_function == "True"
self.locals = {} self.locals = {}
self.arguments = [] self.arguments = []
self.step_into_functions = set() self.step_into_functions = set()
...@@ -243,7 +242,8 @@ class CythonBase(object): ...@@ -243,7 +242,8 @@ class CythonBase(object):
pyframeobject = libpython.Frame(frame).get_pyop() pyframeobject = libpython.Frame(frame).get_pyop()
if not pyframeobject: if not pyframeobject:
raise gdb.GdbError('Unable to read information on python frame') raise gdb.GdbError(
'Unable to read information on python frame')
filename = pyframeobject.filename() filename = pyframeobject.filename()
lineno = pyframeobject.current_line_num() lineno = pyframeobject.current_line_num()
...@@ -256,7 +256,7 @@ class CythonBase(object): ...@@ -256,7 +256,7 @@ class CythonBase(object):
filename = None filename = None
lineno = 0 lineno = 0
else: else:
filename = symbol_and_line_obj.symtab.filename filename = symbol_and_line_obj.symtab.fullname()
lineno = symbol_and_line_obj.line lineno = symbol_and_line_obj.line
if pygments: if pygments:
lexer = pygments.lexers.CLexer(stripall=False) lexer = pygments.lexers.CLexer(stripall=False)
...@@ -389,10 +389,20 @@ class CythonBase(object): ...@@ -389,10 +389,20 @@ class CythonBase(object):
value) value)
def is_initialized(self, cython_func, local_name): def is_initialized(self, cython_func, local_name):
islocal = local_name in cython_func.locals
if islocal:
cyvar = cython_func.locals[local_name]
if '->' in cyvar.cname:
# Closed over free variable
if self.get_cython_lineno() >= cython_func.lineno + 1:
if cyvar.type == PythonObject:
return long(gdb.parse_and_eval(cyvar.cname))
return True
return False
cur_lineno = self.get_cython_lineno() cur_lineno = self.get_cython_lineno()
return (local_name in cython_func.arguments or return (local_name in cython_func.arguments or
(local_name in cython_func.locals and (islocal and cur_lineno > cyvar.lineno))
cur_lineno > cython_func.locals[local_name].lineno))
class SourceFileDescriptor(object): class SourceFileDescriptor(object):
def __init__(self, filename, lexer, formatter=None): def __init__(self, filename, lexer, formatter=None):
...@@ -706,8 +716,6 @@ class CyImport(CythonCommand): ...@@ -706,8 +716,6 @@ class CyImport(CythonCommand):
for c_lineno in c_linenos: for c_lineno in c_linenos:
cython_module.lineno_c2cy[c_lineno] = cython_lineno cython_module.lineno_c2cy[c_lineno] = cython_lineno
self.cy.step.init_breakpoints()
class CyBreak(CythonCommand): class CyBreak(CythonCommand):
""" """
...@@ -754,10 +762,16 @@ class CyBreak(CythonCommand): ...@@ -754,10 +762,16 @@ class CyBreak(CythonCommand):
def _break_funcname(self, funcname): def _break_funcname(self, funcname):
func = self.cy.functions_by_qualified_name.get(funcname) func = self.cy.functions_by_qualified_name.get(funcname)
if func and func.is_initmodule_function:
func = None
break_funcs = [func] break_funcs = [func]
if not func: if not func:
funcs = self.cy.functions_by_name.get(funcname) funcs = self.cy.functions_by_name.get(funcname) or []
funcs = [f for f in funcs if not f.is_initmodule_function]
if not funcs: if not funcs:
gdb.execute('break ' + funcname) gdb.execute('break ' + funcname)
return return
...@@ -813,18 +827,28 @@ class CyBreak(CythonCommand): ...@@ -813,18 +827,28 @@ class CyBreak(CythonCommand):
@dont_suppress_errors @dont_suppress_errors
def complete(self, text, word): def complete(self, text, word):
names = self.cy.functions_by_qualified_name # Filter init-module functions (breakpoints can be set using
# modulename:linenumber).
names = [n for n, L in self.cy.functions_by_name.iteritems()
if any(not f.is_initmodule_function for f in L)]
qnames = [n for n, f in self.cy.functions_by_qualified_name.iteritems()
if not f.is_initmodule_function]
if parameters.complete_unqualified: if parameters.complete_unqualified:
names = itertools.chain(names, self.cy.functions_by_name) all_names = itertools.chain(qnames, names)
else:
all_names = qnames
words = text.strip().split() words = text.strip().split()
if words and '.' in words[-1]: if not words or '.' not in words[-1]:
lastword = words[-1] # complete unqualified
compl = [n for n in self.cy.functions_by_qualified_name
if n.startswith(lastword)]
else:
seen = set(text[:-len(word)].split()) seen = set(text[:-len(word)].split())
return [n for n in names if n.startswith(word) and n not in seen] return [n for n in all_names
if n.startswith(word) and n not in seen]
# complete qualified name
lastword = words[-1]
compl = [n for n in qnames if n.startswith(lastword)]
if len(lastword) > len(word): if len(lastword) > len(word):
# readline sees something (e.g. a '.') as a word boundary, so don't # readline sees something (e.g. a '.') as a word boundary, so don't
...@@ -835,10 +859,9 @@ class CyBreak(CythonCommand): ...@@ -835,10 +859,9 @@ class CyBreak(CythonCommand):
return compl return compl
class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): class CythonInfo(CythonBase, libpython.PythonInfo):
""" """
Base class for CyStep and CyNext. It implements the interface dictated by Implementation of the interface dictated by libpython.LanguageInfo.
libpython.GenericCodeStepper.
""" """
def lineno(self, frame): def lineno(self, frame):
...@@ -848,32 +871,49 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): ...@@ -848,32 +871,49 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
# related code. The C level should be dispatched to the 'step' command. # related code. The C level should be dispatched to the 'step' command.
if self.is_cython_function(frame): if self.is_cython_function(frame):
return self.get_cython_lineno(frame) return self.get_cython_lineno(frame)
else: return super(CythonInfo, self).lineno(frame)
return libpython.py_step.lineno(frame)
def get_source_line(self, frame): def get_source_line(self, frame):
try: try:
line = super(CythonCodeStepper, self).get_source_line(frame) line = super(CythonInfo, self).get_source_line(frame)
except gdb.GdbError: except gdb.GdbError:
return None return None
else: else:
return line.strip() or None return line.strip() or None
@classmethod def exc_info(self, frame):
def register(cls): if self.is_python_function:
return cls(cls.name, stepinto=getattr(cls, 'stepinto', False)) return super(CythonInfo, self).exc_info(frame)
def runtime_break_functions(self): def runtime_break_functions(self):
if self.is_cython_function(): if self.is_cython_function():
return self.get_cython_function().step_into_functions return self.get_cython_function().step_into_functions
return ()
def static_break_functions(self): def static_break_functions(self):
result = ['PyEval_EvalFrameEx'] result = ['PyEval_EvalFrameEx']
result.extend(self.cy.functions_by_cname) result.extend(self.cy.functions_by_cname)
return result return result
class CythonExecutionControlCommand(CythonCommand,
libpython.ExecutionControlCommandBase):
@classmethod
def register(cls):
return cls(cls.name, cython_info)
class CyStep(CythonExecutionControlCommand, libpython.PythonStepperMixin):
"Step through Cython, Python or C code."
name = 'cy -step'
stepinto = True
def invoke(self, args, from_tty): def invoke(self, args, from_tty):
if not self.is_cython_function() and not self.is_python_function(): if self.is_python_function():
self.python_step(self.stepinto)
elif not self.is_cython_function():
if self.stepinto: if self.stepinto:
command = 'step' command = 'step'
else: else:
...@@ -881,24 +921,17 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): ...@@ -881,24 +921,17 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
self.finish_executing(gdb.execute(command, to_string=True)) self.finish_executing(gdb.execute(command, to_string=True))
else: else:
self.step() self.step(stepinto=self.stepinto)
class CyStep(CythonCodeStepper):
"Step through Cython, Python or C code."
name = 'cy step'
stepinto = True
class CyNext(CythonCodeStepper): class CyNext(CyStep):
"Step-over Python code." "Step-over Cython, Python or C code."
name = 'cy next' name = 'cy -next'
stepinto = False stepinto = False
class CyRun(CythonCodeStepper): class CyRun(CythonExecutionControlCommand):
""" """
Run a Cython program. This is like the 'run' command, except that it Run a Cython program. This is like the 'run' command, except that it
displays Cython or Python source lines as well displays Cython or Python source lines as well
...@@ -906,26 +939,26 @@ class CyRun(CythonCodeStepper): ...@@ -906,26 +939,26 @@ class CyRun(CythonCodeStepper):
name = 'cy run' name = 'cy run'
invoke = CythonCodeStepper.run invoke = CythonExecutionControlCommand.run
class CyCont(CyRun): class CyCont(CythonExecutionControlCommand):
""" """
Continue a Cython program. This is like the 'run' command, except that it Continue a Cython program. This is like the 'run' command, except that it
displays Cython or Python source lines as well. displays Cython or Python source lines as well.
""" """
name = 'cy cont' name = 'cy cont'
invoke = CythonCodeStepper.cont invoke = CythonExecutionControlCommand.cont
class CyFinish(CyRun): class CyFinish(CythonExecutionControlCommand):
""" """
Execute until the function returns. Execute until the function returns.
""" """
name = 'cy finish' name = 'cy finish'
invoke = CythonCodeStepper.finish invoke = CythonExecutionControlCommand.finish
class CyUp(CythonCommand): class CyUp(CythonCommand):
...@@ -961,7 +994,7 @@ class CyDown(CyUp): ...@@ -961,7 +994,7 @@ class CyDown(CyUp):
_command = 'down' _command = 'down'
class CySelect(CythonCodeStepper): class CySelect(CythonCommand):
""" """
Select a frame. Use frame numbers as listed in `cy backtrace`. Select a frame. Use frame numbers as listed in `cy backtrace`.
This command is useful because `cy backtrace` prints a reversed backtrace. This command is useful because `cy backtrace` prints a reversed backtrace.
...@@ -979,7 +1012,7 @@ class CySelect(CythonCodeStepper): ...@@ -979,7 +1012,7 @@ class CySelect(CythonCodeStepper):
while frame.newer(): while frame.newer():
frame = frame.newer() frame = frame.newer()
stackdepth = self._stackdepth(frame) stackdepth = libpython.stackdepth(frame)
try: try:
gdb.execute('select %d' % (stackdepth - stackno - 1,)) gdb.execute('select %d' % (stackdepth - stackno - 1,))
...@@ -1033,7 +1066,7 @@ class CyList(CythonCommand): ...@@ -1033,7 +1066,7 @@ class CyList(CythonCommand):
command_class = gdb.COMMAND_FILES command_class = gdb.COMMAND_FILES
completer_class = gdb.COMPLETE_NONE completer_class = gdb.COMPLETE_NONE
@dispatch_on_frame(c_command='list') # @dispatch_on_frame(c_command='list')
def invoke(self, _, from_tty): def invoke(self, _, from_tty):
sd, lineno = self.get_source_desc() sd, lineno = self.get_source_desc()
source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno, source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno,
...@@ -1085,7 +1118,13 @@ class CyLocals(CythonCommand): ...@@ -1085,7 +1118,13 @@ class CyLocals(CythonCommand):
@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, args, from_tty): def invoke(self, args, from_tty):
local_cython_vars = self.get_cython_function().locals cython_function = self.get_cython_function()
if cython_function.is_initmodule_function:
self.cy.globals.invoke(args, from_tty)
return
local_cython_vars = cython_function.locals
max_name_length = len(max(local_cython_vars, key=len)) max_name_length = len(max(local_cython_vars, key=len))
for name, cyvar in sorted(local_cython_vars.iteritems(), key=sortkey): for name, cyvar in sorted(local_cython_vars.iteritems(), key=sortkey):
if self.is_initialized(self.get_cython_function(), cyvar.name): if self.is_initialized(self.get_cython_function(), cyvar.name):
...@@ -1263,17 +1302,16 @@ class CyCValue(CyCName): ...@@ -1263,17 +1302,16 @@ class CyCValue(CyCName):
@require_cython_frame @require_cython_frame
@gdb_function_value_to_unicode @gdb_function_value_to_unicode
def invoke(self, cyname, frame=None): def invoke(self, cyname, frame=None):
try: globals_dict = self.get_cython_globals_dict()
cython_function = self.get_cython_function(frame)
if self.is_initialized(cython_function, cyname):
cname = super(CyCValue, self).invoke(cyname, frame=frame) cname = super(CyCValue, self).invoke(cyname, frame=frame)
return gdb.parse_and_eval(cname) return gdb.parse_and_eval(cname)
except (gdb.GdbError, RuntimeError), e: elif cyname in globals_dict:
# variable exists but may not have been initialized yet, or may be return globals_dict[cyname]._gdbval
# in the globals dict of the Cython module else:
d = self.get_cython_globals_dict() raise gdb.GdbError("Variable %s is not initialized." % cyname)
if cyname in d:
return d[cyname]._gdbval
raise gdb.GdbError(str(e))
class CyLine(gdb.Function, CythonBase): class CyLine(gdb.Function, CythonBase):
...@@ -1285,5 +1323,27 @@ class CyLine(gdb.Function, CythonBase): ...@@ -1285,5 +1323,27 @@ class CyLine(gdb.Function, CythonBase):
def invoke(self): def invoke(self):
return self.get_cython_lineno() return self.get_cython_lineno()
cython_info = CythonInfo()
cy = CyCy.register() cy = CyCy.register()
cython_info.cy = cy
def register_defines():
libpython.source_gdb_script(textwrap.dedent("""\
define cy step
cy -step
end
define cy next
cy -next
end
document cy step
%s
end
document cy next
%s
end
""") % (CyStep.__doc__, CyNext.__doc__))
register_defines()
\ No newline at end of file
...@@ -369,8 +369,8 @@ class PyObjectPtr(object): ...@@ -369,8 +369,8 @@ class PyObjectPtr(object):
if tp_name in name_map: if tp_name in name_map:
return name_map[tp_name] return name_map[tp_name]
if tp_flags & Py_TPFLAGS_HEAPTYPE: if tp_flags & (Py_TPFLAGS_HEAPTYPE|Py_TPFLAGS_TYPE_SUBCLASS):
return HeapTypeObjectPtr return PyTypeObjectPtr
if tp_flags & Py_TPFLAGS_INT_SUBCLASS: if tp_flags & Py_TPFLAGS_INT_SUBCLASS:
return PyIntObjectPtr return PyIntObjectPtr
...@@ -392,8 +392,6 @@ class PyObjectPtr(object): ...@@ -392,8 +392,6 @@ class PyObjectPtr(object):
return PyDictObjectPtr return PyDictObjectPtr
if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS: if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS:
return PyBaseExceptionObjectPtr return PyBaseExceptionObjectPtr
#if tp_flags & Py_TPFLAGS_TYPE_SUBCLASS:
# return PyTypeObjectPtr
# Use the base class: # Use the base class:
return cls return cls
...@@ -484,8 +482,8 @@ def _PyObject_VAR_SIZE(typeobj, nitems): ...@@ -484,8 +482,8 @@ def _PyObject_VAR_SIZE(typeobj, nitems):
) & ~(SIZEOF_VOID_P - 1) ) & ~(SIZEOF_VOID_P - 1)
).cast(gdb.lookup_type('size_t')) ).cast(gdb.lookup_type('size_t'))
class HeapTypeObjectPtr(PyObjectPtr): class PyTypeObjectPtr(PyObjectPtr):
_typename = 'PyObject' _typename = 'PyTypeObject'
def get_attr_dict(self): def get_attr_dict(self):
''' '''
...@@ -546,9 +544,16 @@ class HeapTypeObjectPtr(PyObjectPtr): ...@@ -546,9 +544,16 @@ class HeapTypeObjectPtr(PyObjectPtr):
return return
visited.add(self.as_address()) visited.add(self.as_address())
pyop_attrdict = self.get_attr_dict() try:
_write_instance_repr(out, visited, tp_name = self.field('tp_name').string()
self.safe_tp_name(), pyop_attrdict, self.as_address()) except RuntimeError:
tp_name = 'unknown'
out.write('<type %s at remote 0x%x>' % (tp_name,
self.as_address()))
# pyop_attrdict = self.get_attr_dict()
# _write_instance_repr(out, visited,
# self.safe_tp_name(), pyop_attrdict, self.as_address())
class ProxyException(Exception): class ProxyException(Exception):
def __init__(self, tp_name, args): def __init__(self, tp_name, args):
...@@ -1136,9 +1141,6 @@ class PyTupleObjectPtr(PyObjectPtr): ...@@ -1136,9 +1141,6 @@ class PyTupleObjectPtr(PyObjectPtr):
else: else:
out.write(')') out.write(')')
class PyTypeObjectPtr(PyObjectPtr):
_typename = 'PyTypeObject'
def _unichr_is_printable(char): def _unichr_is_printable(char):
# Logic adapted from Python 3's Tools/unicode/makeunicodedata.py # Logic adapted from Python 3's Tools/unicode/makeunicodedata.py
...@@ -1715,6 +1717,8 @@ class PyNameEquals(gdb.Function): ...@@ -1715,6 +1717,8 @@ class PyNameEquals(gdb.Function):
if frame.is_evalframeex(): if frame.is_evalframeex():
pyframe = frame.get_pyop() pyframe = frame.get_pyop()
if pyframe is None: if pyframe is None:
warnings.warn("Use a Python debug build, Python breakpoints "
"won't work otherwise.")
return None return None
return getattr(pyframe, attr).proxyval(set()) return getattr(pyframe, attr).proxyval(set())
...@@ -1839,132 +1843,98 @@ def get_selected_inferior(): ...@@ -1839,132 +1843,98 @@ def get_selected_inferior():
if thread == selected_thread: if thread == selected_thread:
return inferior return inferior
def source_gdb_script(script_contents, to_string=False):
class GenericCodeStepper(gdb.Command):
""" """
Superclass for code stepping. Subclasses must implement the following Source a gdb script with script_contents passed as a string. This is useful
methods: to provide defines for py-step and py-next to make them repeatable (this is
not possible with gdb.execute()). See
lineno(frame) http://sourceware.org/bugzilla/show_bug.cgi?id=12216
tells the current line number (only called for a relevant frame)
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). If the source code cannot be retrieved this
function should return None
static_break_functions()
returns an iterable of function names that are considered relevant
and should halt step-into execution. This is needed to provide a
performing step-into
runtime_break_functions
list of functions that we should break into depending on the
context
This class provides an 'invoke' method that invokes a 'step' or 'step-over'
depending on the 'stepinto' argument.
""" """
fd, filename = tempfile.mkstemp()
f = os.fdopen(fd, 'w')
f.write(script_contents)
f.close()
gdb.execute("source %s" % filename, to_string=to_string)
os.remove(filename)
def register_defines():
source_gdb_script(textwrap.dedent("""\
define py-step
-py-step
end
define py-next
-py-next
end
document py-step
%s
end
document py-next
%s
end
""") % (PyStep.__doc__, PyNext.__doc__))
def stackdepth(frame):
"Tells the stackdepth of a gdb frame."
depth = 0
while frame:
frame = frame.older()
depth += 1
stepper = False return depth
static_breakpoints = {}
runtime_breakpoints = {}
def __init__(self, name, stepinto=False):
super(GenericCodeStepper, self).__init__(name,
gdb.COMMAND_RUNNING,
gdb.COMPLETE_NONE)
self.stepinto = stepinto
def _break_func(self, funcname):
result = gdb.execute('break %s' % funcname, to_string=True)
return re.search(r'Breakpoint (\d+)', result).group(1)
def init_breakpoints(self):
"""
Keep all breakpoints around and simply disable/enable them each time
we are stepping. We need this because if you set and delete a
breakpoint, gdb will not repeat your command (this is due to 'delete').
We also can't use the breakpoint API because there's no option to make
breakpoint setting silent.
This method must be called whenever the list of functions we should
step into changes. It can be called on any GenericCodeStepper instance.
"""
break_funcs = set(self.static_break_functions())
for funcname in break_funcs:
if funcname not in self.static_breakpoints:
try:
gdb.Breakpoint('', gdb.BP_BREAKPOINT, internal=True)
except (AttributeError, TypeError):
# gdb.Breakpoint does not take an 'internal' argument, or
# gdb.Breakpoint does not exist.
breakpoint = self._break_func(funcname)
except RuntimeError:
# gdb.Breakpoint does take an 'internal' argument, use it
# and hide output
result = gdb.execute(textwrap.dedent("""\
python bp = gdb.Breakpoint(%r, gdb.BP_BREAKPOINT, \
internal=True); \
print bp.number""",
to_string=True))
breakpoint = int(result)
self.static_breakpoints[funcname] = breakpoint
for bp in set(self.static_breakpoints) - break_funcs:
gdb.execute("delete " + self.static_breakpoints[bp])
self.disable_breakpoints()
def enable_breakpoints(self):
for bp in self.static_breakpoints.itervalues():
gdb.execute('enable ' + bp)
runtime_break_functions = self.runtime_break_functions()
if runtime_break_functions is None:
return
for funcname in runtime_break_functions:
if (funcname not in self.static_breakpoints and
funcname not in self.runtime_breakpoints):
self.runtime_breakpoints[funcname] = self._break_func(funcname)
elif funcname in self.runtime_breakpoints:
gdb.execute('enable ' + self.runtime_breakpoints[funcname])
def disable_breakpoints(self):
chain = itertools.chain(self.static_breakpoints.itervalues(),
self.runtime_breakpoints.itervalues())
for bp in chain:
gdb.execute('disable ' + bp)
def runtime_break_functions(self): class ExecutionControlCommandBase(gdb.Command):
""" """
Implement this if the list of step-into functions depends on the Superclass for language specific execution control. Language specific
context. features should be implemented by lang_info using the LanguageInfo
interface. 'name' is the name of the command.
""" """
def stopped(self, result): def __init__(self, name, lang_info):
match = re.search('^Program received signal .*', result, re.MULTILINE) super(ExecutionControlCommandBase, self).__init__(
name, gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
self.lang_info = lang_info
def install_breakpoints(self):
all_locations = itertools.chain(
self.lang_info.static_break_functions(),
self.lang_info.runtime_break_functions())
for location in all_locations:
result = gdb.execute('break %s' % location, to_string=True)
yield re.search(r'Breakpoint (\d+)', result).group(1)
def delete_breakpoints(self, breakpoint_list):
for bp in breakpoint_list:
gdb.execute("delete %s" % bp)
def filter_output(self, result):
output = []
match_finish = re.search(r'^Value returned is \$\d+ = (.*)', result,
re.MULTILINE)
if match_finish:
output.append('Value returned: %s' % match_finish.group(1))
reflags = re.MULTILINE
regexes = [
(r'^Program received signal .*', reflags|re.DOTALL),
(r'.*[Ww]arning.*', 0),
(r'^Program exited .*', reflags),
]
for regex, flags in regexes:
match = re.search(regex, result, flags)
if match: if match:
return match.group(0) output.append(match.group(0))
elif get_selected_inferior().pid == 0:
return result
else:
return None
def _stackdepth(self, frame): return '\n'.join(output)
depth = 0
while frame:
frame = frame.older()
depth += 1
return depth def stopped(self):
return get_selected_inferior().pid == 0
def finish_executing(self, result): def finish_executing(self, result):
""" """
...@@ -1972,32 +1942,19 @@ class GenericCodeStepper(gdb.Command): ...@@ -1972,32 +1942,19 @@ class GenericCodeStepper(gdb.Command):
of source code or the result of the last executed gdb command (passed of source code or the result of the last executed gdb command (passed
in as the `result` argument). in as the `result` argument).
""" """
result = self.stopped(result) result = self.filter_output(result)
if result:
if self.stopped():
print result.strip() print result.strip()
# check whether the program was killed by a signal, it should still
# have a stack.
try:
frame = gdb.selected_frame()
except RuntimeError:
pass
else:
print self.get_source_line(frame)
else: else:
frame = gdb.selected_frame() frame = gdb.selected_frame()
output = None if self.lang_info.is_relevant_function(frame):
raised_exception = self.lang_info.exc_info(frame)
if self.is_relevant_function(frame): if raised_exception:
output = self.get_source_line(frame) print raised_exception
print self.lang_info.get_source_line(frame) or result
if output is None:
pframe = getattr(self, 'print_stackframe', None)
if pframe:
pframe(frame, index=0)
else:
print result.strip()
else: else:
print output print result
def _finish(self): def _finish(self):
""" """
...@@ -2010,11 +1967,10 @@ class GenericCodeStepper(gdb.Command): ...@@ -2010,11 +1967,10 @@ class GenericCodeStepper(gdb.Command):
# outermost frame, continue # outermost frame, continue
return gdb.execute('cont', to_string=True) return gdb.execute('cont', to_string=True)
def finish(self, *args): def _finish_frame(self):
""" """
Execute until the function returns to a relevant caller. Execute until the function returns to a relevant caller.
""" """
while True: while True:
result = self._finish() result = self._finish()
...@@ -2024,39 +1980,64 @@ class GenericCodeStepper(gdb.Command): ...@@ -2024,39 +1980,64 @@ class GenericCodeStepper(gdb.Command):
break break
hitbp = re.search(r'Breakpoint (\d+)', result) hitbp = re.search(r'Breakpoint (\d+)', result)
is_relavant = self.is_relevant_function(frame) is_relevant = self.lang_info.is_relevant_function(frame)
if hitbp or is_relavant or self.stopped(result): if hitbp or is_relevant or self.stopped():
break break
return result
def finish(self, *args):
"Implements the finish command."
result = self._finish_frame()
self.finish_executing(result) self.finish_executing(result)
def _step(self): def step(self, stepinto, stepover_command='next'):
""" """
Do a single step or step-over. Returns the result of the last gdb Do a single step or step-over. Returns the result of the last gdb
command that made execution stop. command that made execution stop.
This implementation, for stepping, sets (conditional) breakpoints for
all functions that are deemed relevant. It then does a step over until
either something halts execution, or until the next line is reached.
If, however, stepover_command is given, it should be a string gdb
command that continues execution in some way. The idea is that the
caller has set a (conditional) breakpoint or watchpoint that can work
more efficiently than the step-over loop. For Python this means setting
a watchpoint for f->f_lasti, which means we can then subsequently
"finish" frames.
We want f->f_lasti instead of f->f_lineno, because the latter only
works properly with local trace functions, see
PyFrameObjectPtr.current_line_num and PyFrameObjectPtr.addr2line.
""" """
if self.stepinto: if stepinto:
self.enable_breakpoints() breakpoint_list = list(self.install_breakpoints())
beginframe = gdb.selected_frame() beginframe = gdb.selected_frame()
beginline = self.lineno(beginframe)
if not self.stepinto: if self.lang_info.is_relevant_function(beginframe):
depth = self._stackdepth(beginframe) # If we start in a relevant frame, initialize stuff properly. If
# we don't start in a relevant frame, the loop will halt
# immediately. So don't call self.lang_info.lineno() as it may
# raise for irrelevant frames.
beginline = self.lang_info.lineno(beginframe)
if not stepinto:
depth = stackdepth(beginframe)
newframe = beginframe newframe = beginframe
result = ''
while True: while True:
if self.is_relevant_function(newframe): if self.lang_info.is_relevant_function(newframe):
result = gdb.execute('next', to_string=True) result = gdb.execute(stepover_command, to_string=True)
else: else:
result = self._finish() result = self._finish_frame()
if self.stopped(result): if self.stopped():
break break
newframe = gdb.selected_frame() newframe = gdb.selected_frame()
is_relevant_function = self.is_relevant_function(newframe) is_relevant_function = self.lang_info.is_relevant_function(newframe)
try: try:
framename = newframe.name() framename = newframe.name()
except RuntimeError: except RuntimeError:
...@@ -2064,8 +2045,7 @@ class GenericCodeStepper(gdb.Command): ...@@ -2064,8 +2045,7 @@ class GenericCodeStepper(gdb.Command):
m = re.search(r'Breakpoint (\d+)', result) m = re.search(r'Breakpoint (\d+)', result)
if m: if m:
bp = self.runtime_breakpoints.get(framename) if is_relevant_function and m.group(1) in breakpoint_list:
if bp is None or (m.group(1) == bp and is_relevant_function):
# although we hit a breakpoint, we still need to check # although we hit a breakpoint, we still need to check
# that the function, in case hit by a runtime breakpoint, # that the function, in case hit by a runtime breakpoint,
# is in the right context # is in the right context
...@@ -2074,25 +2054,25 @@ class GenericCodeStepper(gdb.Command): ...@@ -2074,25 +2054,25 @@ class GenericCodeStepper(gdb.Command):
if newframe != beginframe: if newframe != beginframe:
# new function # new function
if not self.stepinto: if not stepinto:
# see if we returned to the caller # see if we returned to the caller
newdepth = self._stackdepth(newframe) newdepth = stackdepth(newframe)
is_relevant_function = (newdepth < depth and is_relevant_function = (newdepth < depth and
is_relevant_function) is_relevant_function)
if is_relevant_function: if is_relevant_function:
break break
else: else:
if self.lineno(newframe) > beginline: # newframe equals beginframe, check for a difference in the
# line number
lineno = self.lang_info.lineno(newframe)
if lineno and lineno != beginline:
break break
if self.stepinto: if stepinto:
self.disable_breakpoints() self.delete_breakpoints(breakpoint_list)
return result
def step(self, *args): self.finish_executing(result)
return self.finish_executing(self._step())
def run(self, *args): def run(self, *args):
self.finish_executing(gdb.execute('run', to_string=True)) self.finish_executing(gdb.execute('run', to_string=True))
...@@ -2101,14 +2081,57 @@ class GenericCodeStepper(gdb.Command): ...@@ -2101,14 +2081,57 @@ class GenericCodeStepper(gdb.Command):
self.finish_executing(gdb.execute('cont', to_string=True)) self.finish_executing(gdb.execute('cont', to_string=True))
class PythonCodeStepper(GenericCodeStepper): class LanguageInfo(object):
"""
This class defines the interface that ExecutionControlCommandBase needs to
provide language-specific execution control.
Classes that implement this interface should implement:
lineno(frame)
Tells the current line number (only called for a relevant frame).
If lineno is a false value it is not checked for a difference.
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). If the source code cannot be retrieved this
function should return None
exc_info(frame) -- optional
tells whether an exception was raised, if so, it should return a
string representation of the exception value, None otherwise.
static_break_functions()
returns an iterable of function names that are considered relevant
and should halt step-into execution. This is needed to provide a
performing step-into
runtime_break_functions() -- optional
list of functions that we should break into depending on the
context
"""
def exc_info(self, frame):
"See this class' docstring."
def runtime_break_functions(self):
"""
Implement this if the list of step-into functions depends on the
context.
"""
return ()
class PythonInfo(LanguageInfo):
def pyframe(self, frame): def pyframe(self, frame):
pyframe = Frame(frame).get_pyop() pyframe = Frame(frame).get_pyop()
if pyframe: if pyframe:
return pyframe return pyframe
else: else:
raise gdb.GdbError( raise gdb.RuntimeError(
"Unable to find the Python frame, run your code with a debug " "Unable to find the Python frame, run your code with a debug "
"build (configure with --with-pydebug or compile with -g).") "build (configure with --with-pydebug or compile with -g).")
...@@ -2126,47 +2149,67 @@ class PythonCodeStepper(GenericCodeStepper): ...@@ -2126,47 +2149,67 @@ class PythonCodeStepper(GenericCodeStepper):
except IOError, e: except IOError, e:
return None return None
def exc_info(self, frame):
try:
tstate = frame.read_var('tstate').dereference()
if gdb.parse_and_eval('tstate->frame == f'):
# tstate local variable initialized
inf_type = tstate['curexc_type']
inf_value = tstate['curexc_value']
if inf_type:
return 'An exception was raised: %s(%s)' % (inf_type,
inf_value)
except (ValueError, RuntimeError), e:
# Could not read the variable tstate or it's memory, it's ok
pass
def static_break_functions(self): def static_break_functions(self):
yield 'PyEval_EvalFrameEx' yield 'PyEval_EvalFrameEx'
class PyStep(PythonCodeStepper): class PythonStepperMixin(object):
"""
Make this a mixin so CyStep can also inherit from this and use a
CythonCodeStepper at the same time.
"""
def python_step(self, stepinto):
frame = gdb.selected_frame()
framewrapper = Frame(frame)
output = gdb.execute('watch f->f_lasti', to_string=True)
watchpoint = int(re.search(r'[Ww]atchpoint (\d+):', output).group(1))
self.step(stepinto=stepinto, stepover_command='finish')
gdb.execute('delete %s' % watchpoint)
class PyStep(ExecutionControlCommandBase, PythonStepperMixin):
"Step through Python code." "Step through Python code."
invoke = PythonCodeStepper.step stepinto = True
def invoke(self, args, from_tty):
self.python_step(stepinto=self.stepinto)
class PyNext(PythonCodeStepper): class PyNext(PyStep):
"Step-over Python code." "Step-over Python code."
invoke = PythonCodeStepper.step stepinto = False
class PyFinish(PythonCodeStepper): class PyFinish(ExecutionControlCommandBase):
"Execute until function returns to a caller." "Execute until function returns to a caller."
invoke = PythonCodeStepper.finish invoke = ExecutionControlCommandBase.finish
class PyRun(PythonCodeStepper): class PyRun(ExecutionControlCommandBase):
"Run the program." "Run the program."
invoke = PythonCodeStepper.run invoke = ExecutionControlCommandBase.run
class PyCont(PythonCodeStepper):
invoke = PythonCodeStepper.cont class PyCont(ExecutionControlCommandBase):
invoke = ExecutionControlCommandBase.cont
py_step = PyStep('py-step', stepinto=True)
py_next = PyNext('py-next', stepinto=False)
py_finish = PyFinish('py-finish')
py_run = PyRun('py-run')
py_cont = PyCont('py-cont')
gdb.execute('set breakpoint pending on')
py_step.init_breakpoints()
Py_single_input = 256
Py_file_input = 257
Py_eval_input = 258
def _pointervalue(gdbval): def _pointervalue(gdbval):
""" """
...@@ -2210,6 +2253,10 @@ def get_inferior_unicode_postfix(): ...@@ -2210,6 +2253,10 @@ def get_inferior_unicode_postfix():
class PythonCodeExecutor(object): class PythonCodeExecutor(object):
Py_single_input = 256
Py_file_input = 257
Py_eval_input = 258
def malloc(self, size): def malloc(self, size):
chunk = (gdb.parse_and_eval("(void *) malloc((size_t) %d)" % size)) chunk = (gdb.parse_and_eval("(void *) malloc((size_t) %d)" % size))
...@@ -2382,7 +2429,7 @@ class PyExec(gdb.Command): ...@@ -2382,7 +2429,7 @@ class PyExec(gdb.Command):
def readcode(self, expr): def readcode(self, expr):
if expr: if expr:
return expr, Py_single_input return expr, PythonCodeExecutor.Py_single_input
else: else:
lines = [] lines = []
while True: while True:
...@@ -2412,5 +2459,19 @@ class PyExec(gdb.Command): ...@@ -2412,5 +2459,19 @@ class PyExec(gdb.Command):
executor.evalcode(expr, input_type, global_dict, local_dict) executor.evalcode(expr, input_type, global_dict, local_dict)
py_exec = FixGdbCommand('py-exec', '-py-exec')
_py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) gdb.execute('set breakpoint pending on')
if hasattr(gdb, 'GdbError'):
# Wrap py-step and py-next in gdb defines to make them repeatable.
py_step = PyStep('-py-step', PythonInfo())
py_next = PyNext('-py-next', PythonInfo())
register_defines()
py_finish = PyFinish('py-finish', PythonInfo())
py_run = PyRun('py-run', PythonInfo())
py_cont = PyCont('py-cont', PythonInfo())
py_exec = FixGdbCommand('py-exec', '-py-exec')
_py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
else:
warnings.warn("Use gdb 7.2 or higher to use the py-exec command.")
\ No newline at end of file
...@@ -675,12 +675,12 @@ class CythonPyregrTestCase(CythonRunTestCase): ...@@ -675,12 +675,12 @@ class CythonPyregrTestCase(CythonRunTestCase):
except (unittest.SkipTest, support.ResourceDenied): except (unittest.SkipTest, support.ResourceDenied):
result.addSkip(self, 'ok') result.addSkip(self, 'ok')
# Someone wrapped this in a:
try: # 'try: import gdb; ... except: include_debugger = False' thing, but don't do
import gdb # this, it doesn't work as gdb is a builtin module in GDB. The tests themselves
include_debugger = sys.version_info[:2] > (2, 5) # are doing the skipping. If there's a problem with the tests, please file an
except: # issue.
include_debugger = False include_debugger = sys.version_info[:2] > (2, 5)
def collect_unittests(path, module_prefix, suite, selectors): def collect_unittests(path, module_prefix, suite, selectors):
def file_matches(filename): def file_matches(filename):
......
...@@ -100,7 +100,8 @@ def compile_cython_modules(profile=False, compile_more=False, cython_with_refnan ...@@ -100,7 +100,8 @@ def compile_cython_modules(profile=False, compile_more=False, cython_with_refnan
"Cython.Compiler.Parsing", "Cython.Compiler.Parsing",
"Cython.Compiler.Visitor", "Cython.Compiler.Visitor",
"Cython.Compiler.Code", "Cython.Compiler.Code",
"Cython.Runtime.refnanny"] "Cython.Runtime.refnanny",
"Cython.Debugger.do_repeat",]
if compile_more: if compile_more:
compiled_modules.extend([ compiled_modules.extend([
"Cython.Compiler.ParseTreeTransforms", "Cython.Compiler.ParseTreeTransforms",
......
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