| Index: third_party/cython/src/Cython/Debugger/libcython.py
|
| diff --git a/third_party/cython/src/Cython/Debugger/libcython.py b/third_party/cython/src/Cython/Debugger/libcython.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a1d4221e4d5c2b8ab66065a5fac77b9618b8a361
|
| --- /dev/null
|
| +++ b/third_party/cython/src/Cython/Debugger/libcython.py
|
| @@ -0,0 +1,1405 @@
|
| +"""
|
| +GDB extension that adds Cython support.
|
| +"""
|
| +
|
| +from __future__ import with_statement
|
| +
|
| +import sys
|
| +import textwrap
|
| +import traceback
|
| +import functools
|
| +import itertools
|
| +import collections
|
| +
|
| +import gdb
|
| +
|
| +try:
|
| + from lxml import etree
|
| + have_lxml = True
|
| +except ImportError:
|
| + have_lxml = False
|
| + try:
|
| + # Python 2.5
|
| + from xml.etree import cElementTree as etree
|
| + except ImportError:
|
| + try:
|
| + # Python 2.5
|
| + from xml.etree import ElementTree as etree
|
| + except ImportError:
|
| + try:
|
| + # normal cElementTree install
|
| + import cElementTree as etree
|
| + except ImportError:
|
| + # normal ElementTree install
|
| + import elementtree.ElementTree as etree
|
| +
|
| +try:
|
| + import pygments.lexers
|
| + import pygments.formatters
|
| +except ImportError:
|
| + pygments = None
|
| + sys.stderr.write("Install pygments for colorized source code.\n")
|
| +
|
| +if hasattr(gdb, 'string_to_argv'):
|
| + from gdb import string_to_argv
|
| +else:
|
| + from shlex import split as string_to_argv
|
| +
|
| +from Cython.Debugger import libpython
|
| +
|
| +# C or Python type
|
| +CObject = 'CObject'
|
| +PythonObject = 'PythonObject'
|
| +
|
| +_data_types = dict(CObject=CObject, PythonObject=PythonObject)
|
| +_filesystemencoding = sys.getfilesystemencoding() or 'UTF-8'
|
| +
|
| +# decorators
|
| +
|
| +def dont_suppress_errors(function):
|
| + "*sigh*, readline"
|
| + @functools.wraps(function)
|
| + def wrapper(*args, **kwargs):
|
| + try:
|
| + return function(*args, **kwargs)
|
| + except Exception:
|
| + traceback.print_exc()
|
| + raise
|
| +
|
| + return wrapper
|
| +
|
| +def default_selected_gdb_frame(err=True):
|
| + def decorator(function):
|
| + @functools.wraps(function)
|
| + def wrapper(self, frame=None, *args, **kwargs):
|
| + try:
|
| + frame = frame or gdb.selected_frame()
|
| + except RuntimeError:
|
| + raise gdb.GdbError("No frame is currently selected.")
|
| +
|
| + if err and frame.name() is None:
|
| + raise NoFunctionNameInFrameError()
|
| +
|
| + return function(self, frame, *args, **kwargs)
|
| + return wrapper
|
| + return decorator
|
| +
|
| +def require_cython_frame(function):
|
| + @functools.wraps(function)
|
| + @require_running_program
|
| + def wrapper(self, *args, **kwargs):
|
| + frame = kwargs.get('frame') or gdb.selected_frame()
|
| + if not self.is_cython_function(frame):
|
| + raise gdb.GdbError('Selected frame does not correspond with a '
|
| + 'Cython function we know about.')
|
| + return function(self, *args, **kwargs)
|
| + return wrapper
|
| +
|
| +def dispatch_on_frame(c_command, python_command=None):
|
| + def decorator(function):
|
| + @functools.wraps(function)
|
| + def wrapper(self, *args, **kwargs):
|
| + is_cy = self.is_cython_function()
|
| + is_py = self.is_python_function()
|
| +
|
| + if is_cy or (is_py and not python_command):
|
| + function(self, *args, **kwargs)
|
| + elif is_py:
|
| + gdb.execute(python_command)
|
| + elif self.is_relevant_function():
|
| + gdb.execute(c_command)
|
| + else:
|
| + raise gdb.GdbError("Not a function cygdb knows about. "
|
| + "Use the normal GDB commands instead.")
|
| +
|
| + return wrapper
|
| + return decorator
|
| +
|
| +def require_running_program(function):
|
| + @functools.wraps(function)
|
| + def wrapper(*args, **kwargs):
|
| + try:
|
| + gdb.selected_frame()
|
| + except RuntimeError:
|
| + raise gdb.GdbError("No frame is currently selected.")
|
| +
|
| + return function(*args, **kwargs)
|
| + return wrapper
|
| +
|
| +
|
| +def gdb_function_value_to_unicode(function):
|
| + @functools.wraps(function)
|
| + def wrapper(self, string, *args, **kwargs):
|
| + if isinstance(string, gdb.Value):
|
| + string = string.string()
|
| +
|
| + return function(self, string, *args, **kwargs)
|
| + return wrapper
|
| +
|
| +
|
| +# Classes that represent the debug information
|
| +# Don't rename the parameters of these classes, they come directly from the XML
|
| +
|
| +class CythonModule(object):
|
| + def __init__(self, module_name, filename, c_filename):
|
| + self.name = module_name
|
| + self.filename = filename
|
| + self.c_filename = c_filename
|
| + self.globals = {}
|
| + # {cython_lineno: min(c_linenos)}
|
| + self.lineno_cy2c = {}
|
| + # {c_lineno: cython_lineno}
|
| + self.lineno_c2cy = {}
|
| + self.functions = {}
|
| +
|
| +class CythonVariable(object):
|
| +
|
| + def __init__(self, name, cname, qualified_name, type, lineno):
|
| + self.name = name
|
| + self.cname = cname
|
| + self.qualified_name = qualified_name
|
| + self.type = type
|
| + self.lineno = int(lineno)
|
| +
|
| +class CythonFunction(CythonVariable):
|
| + def __init__(self,
|
| + module,
|
| + name,
|
| + cname,
|
| + pf_cname,
|
| + qualified_name,
|
| + lineno,
|
| + type=CObject,
|
| + is_initmodule_function="False"):
|
| + super(CythonFunction, self).__init__(name,
|
| + cname,
|
| + qualified_name,
|
| + type,
|
| + lineno)
|
| + self.module = module
|
| + self.pf_cname = pf_cname
|
| + self.is_initmodule_function = is_initmodule_function == "True"
|
| + self.locals = {}
|
| + self.arguments = []
|
| + self.step_into_functions = set()
|
| +
|
| +
|
| +# General purpose classes
|
| +
|
| +class CythonBase(object):
|
| +
|
| + @default_selected_gdb_frame(err=False)
|
| + def is_cython_function(self, frame):
|
| + return frame.name() in self.cy.functions_by_cname
|
| +
|
| + @default_selected_gdb_frame(err=False)
|
| + def is_python_function(self, frame):
|
| + """
|
| + Tells if a frame is associated with a Python function.
|
| + If we can't read the Python frame information, don't regard it as such.
|
| + """
|
| + if frame.name() == 'PyEval_EvalFrameEx':
|
| + pyframe = libpython.Frame(frame).get_pyop()
|
| + return pyframe and not pyframe.is_optimized_out()
|
| + return False
|
| +
|
| + @default_selected_gdb_frame()
|
| + def get_c_function_name(self, frame):
|
| + return frame.name()
|
| +
|
| + @default_selected_gdb_frame()
|
| + def get_c_lineno(self, frame):
|
| + return frame.find_sal().line
|
| +
|
| + @default_selected_gdb_frame()
|
| + def get_cython_function(self, frame):
|
| + result = self.cy.functions_by_cname.get(frame.name())
|
| + if result is None:
|
| + raise NoCythonFunctionInFrameError()
|
| +
|
| + return result
|
| +
|
| + @default_selected_gdb_frame()
|
| + def get_cython_lineno(self, frame):
|
| + """
|
| + Get the current Cython line number. Returns 0 if there is no
|
| + correspondence between the C and Cython code.
|
| + """
|
| + cyfunc = self.get_cython_function(frame)
|
| + return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame), 0)
|
| +
|
| + @default_selected_gdb_frame()
|
| + def get_source_desc(self, frame):
|
| + filename = lineno = lexer = None
|
| + if self.is_cython_function(frame):
|
| + filename = self.get_cython_function(frame).module.filename
|
| + lineno = self.get_cython_lineno(frame)
|
| + if pygments:
|
| + lexer = pygments.lexers.CythonLexer(stripall=False)
|
| + elif self.is_python_function(frame):
|
| + pyframeobject = libpython.Frame(frame).get_pyop()
|
| +
|
| + if not pyframeobject:
|
| + raise gdb.GdbError(
|
| + 'Unable to read information on python frame')
|
| +
|
| + filename = pyframeobject.filename()
|
| + lineno = pyframeobject.current_line_num()
|
| +
|
| + if pygments:
|
| + lexer = pygments.lexers.PythonLexer(stripall=False)
|
| + else:
|
| + symbol_and_line_obj = frame.find_sal()
|
| + if not symbol_and_line_obj or not symbol_and_line_obj.symtab:
|
| + filename = None
|
| + lineno = 0
|
| + else:
|
| + filename = symbol_and_line_obj.symtab.fullname()
|
| + lineno = symbol_and_line_obj.line
|
| + if pygments:
|
| + lexer = pygments.lexers.CLexer(stripall=False)
|
| +
|
| + return SourceFileDescriptor(filename, lexer), lineno
|
| +
|
| + @default_selected_gdb_frame()
|
| + def get_source_line(self, frame):
|
| + 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
|
| + Cython code
|
| + """
|
| + name = frame.name()
|
| + older_frame = frame.older()
|
| + if self.is_cython_function(frame) or self.is_python_function(frame):
|
| + return True
|
| + elif older_frame and self.is_cython_function(older_frame):
|
| + # check for direct C function call from a Cython function
|
| + cython_func = self.get_cython_function(older_frame)
|
| + return name in cython_func.step_into_functions
|
| +
|
| + return False
|
| +
|
| + @default_selected_gdb_frame(err=False)
|
| + def print_stackframe(self, frame, index, is_c=False):
|
| + """
|
| + Print a C, Cython or Python stack frame and the line of source code
|
| + if available.
|
| + """
|
| + # do this to prevent the require_cython_frame decorator from
|
| + # raising GdbError when calling self.cy.cy_cvalue.invoke()
|
| + selected_frame = gdb.selected_frame()
|
| + frame.select()
|
| +
|
| + try:
|
| + source_desc, lineno = self.get_source_desc(frame)
|
| + except NoFunctionNameInFrameError:
|
| + print '#%-2d Unknown Frame (compile with -g)' % index
|
| + return
|
| +
|
| + if not is_c and self.is_python_function(frame):
|
| + pyframe = libpython.Frame(frame).get_pyop()
|
| + if pyframe is None or pyframe.is_optimized_out():
|
| + # print this python function as a C function
|
| + return self.print_stackframe(frame, index, is_c=True)
|
| +
|
| + func_name = pyframe.co_name
|
| + func_cname = 'PyEval_EvalFrameEx'
|
| + func_args = []
|
| + elif self.is_cython_function(frame):
|
| + cyfunc = self.get_cython_function(frame)
|
| + f = lambda arg: self.cy.cy_cvalue.invoke(arg, frame=frame)
|
| +
|
| + func_name = cyfunc.name
|
| + func_cname = cyfunc.cname
|
| + func_args = [] # [(arg, f(arg)) for arg in cyfunc.arguments]
|
| + else:
|
| + source_desc, lineno = self.get_source_desc(frame)
|
| + func_name = frame.name()
|
| + func_cname = func_name
|
| + func_args = []
|
| +
|
| + try:
|
| + gdb_value = gdb.parse_and_eval(func_cname)
|
| + except RuntimeError:
|
| + func_address = 0
|
| + else:
|
| + # Seriously? Why is the address not an int?
|
| + func_address = int(str(gdb_value.address).split()[0], 0)
|
| +
|
| + a = ', '.join('%s=%s' % (name, val) for name, val in func_args)
|
| + print '#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a),
|
| +
|
| + if source_desc.filename is not None:
|
| + print 'at %s:%s' % (source_desc.filename, lineno),
|
| +
|
| + print
|
| +
|
| + try:
|
| + print ' ' + source_desc.get_source(lineno)
|
| + except gdb.GdbError:
|
| + pass
|
| +
|
| + selected_frame.select()
|
| +
|
| + def get_remote_cython_globals_dict(self):
|
| + m = gdb.parse_and_eval('__pyx_m')
|
| +
|
| + try:
|
| + PyModuleObject = gdb.lookup_type('PyModuleObject')
|
| + except RuntimeError:
|
| + raise gdb.GdbError(textwrap.dedent("""\
|
| + Unable to lookup type PyModuleObject, did you compile python
|
| + with debugging support (-g)?"""))
|
| +
|
| + m = m.cast(PyModuleObject.pointer())
|
| + return m['md_dict']
|
| +
|
| +
|
| + def get_cython_globals_dict(self):
|
| + """
|
| + Get the Cython globals dict where the remote names are turned into
|
| + local strings.
|
| + """
|
| + remote_dict = self.get_remote_cython_globals_dict()
|
| + pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)
|
| +
|
| + result = {}
|
| + seen = set()
|
| + for k, v in pyobject_dict.iteritems():
|
| + result[k.proxyval(seen)] = v
|
| +
|
| + return result
|
| +
|
| + def print_gdb_value(self, name, value, max_name_length=None, prefix=''):
|
| + if libpython.pretty_printer_lookup(value):
|
| + typename = ''
|
| + else:
|
| + typename = '(%s) ' % (value.type,)
|
| +
|
| + if max_name_length is None:
|
| + print '%s%s = %s%s' % (prefix, name, typename, value)
|
| + else:
|
| + print '%s%-*s = %s%s' % (prefix, max_name_length, name, typename,
|
| + value)
|
| +
|
| + def is_initialized(self, cython_func, local_name):
|
| + cyvar = cython_func.locals[local_name]
|
| + cur_lineno = self.get_cython_lineno()
|
| +
|
| + if '->' in cyvar.cname:
|
| + # Closed over free variable
|
| + if cur_lineno > cython_func.lineno:
|
| + if cyvar.type == PythonObject:
|
| + return long(gdb.parse_and_eval(cyvar.cname))
|
| + return True
|
| + return False
|
| +
|
| + return cur_lineno > cyvar.lineno
|
| +
|
| +
|
| +class SourceFileDescriptor(object):
|
| + def __init__(self, filename, lexer, formatter=None):
|
| + self.filename = filename
|
| + self.lexer = lexer
|
| + self.formatter = formatter
|
| +
|
| + def valid(self):
|
| + return self.filename is not None
|
| +
|
| + def lex(self, code):
|
| + if pygments and self.lexer and parameters.colorize_code:
|
| + bg = parameters.terminal_background.value
|
| + if self.formatter is None:
|
| + formatter = pygments.formatters.TerminalFormatter(bg=bg)
|
| + else:
|
| + formatter = self.formatter
|
| +
|
| + return pygments.highlight(code, self.lexer, formatter)
|
| +
|
| + return code
|
| +
|
| + def _get_source(self, start, stop, lex_source, mark_line, lex_entire):
|
| + with open(self.filename) as f:
|
| + # to provide "correct" colouring, the entire code needs to be
|
| + # lexed. However, this makes a lot of things terribly slow, so
|
| + # we decide not to. Besides, it's unlikely to matter.
|
| +
|
| + if lex_source and lex_entire:
|
| + f = self.lex(f.read()).splitlines()
|
| +
|
| + slice = itertools.islice(f, start - 1, stop - 1)
|
| +
|
| + for idx, line in enumerate(slice):
|
| + if start + idx == mark_line:
|
| + prefix = '>'
|
| + else:
|
| + prefix = ' '
|
| +
|
| + if lex_source and not lex_entire:
|
| + line = self.lex(line)
|
| +
|
| + yield '%s %4d %s' % (prefix, start + idx, line.rstrip())
|
| +
|
| + def get_source(self, start, stop=None, lex_source=True, mark_line=0,
|
| + lex_entire=False):
|
| + exc = gdb.GdbError('Unable to retrieve source code')
|
| +
|
| + if not self.filename:
|
| + raise exc
|
| +
|
| + start = max(start, 1)
|
| + if stop is None:
|
| + stop = start + 1
|
| +
|
| + try:
|
| + return '\n'.join(
|
| + self._get_source(start, stop, lex_source, mark_line, lex_entire))
|
| + except IOError:
|
| + raise exc
|
| +
|
| +
|
| +# Errors
|
| +
|
| +class CyGDBError(gdb.GdbError):
|
| + """
|
| + Base class for Cython-command related erorrs
|
| + """
|
| +
|
| + def __init__(self, *args):
|
| + args = args or (self.msg,)
|
| + super(CyGDBError, self).__init__(*args)
|
| +
|
| +class NoCythonFunctionInFrameError(CyGDBError):
|
| + """
|
| + raised when the user requests the current cython function, which is
|
| + unavailable
|
| + """
|
| + msg = "Current function is a function cygdb doesn't know about"
|
| +
|
| +class NoFunctionNameInFrameError(NoCythonFunctionInFrameError):
|
| + """
|
| + raised when the name of the C function could not be determined
|
| + in the current C stack frame
|
| + """
|
| + msg = ('C function name could not be determined in the current C stack '
|
| + 'frame')
|
| +
|
| +
|
| +# Parameters
|
| +
|
| +class CythonParameter(gdb.Parameter):
|
| + """
|
| + Base class for cython parameters
|
| + """
|
| +
|
| + def __init__(self, name, command_class, parameter_class, default=None):
|
| + self.show_doc = self.set_doc = self.__class__.__doc__
|
| + super(CythonParameter, self).__init__(name, command_class,
|
| + parameter_class)
|
| + if default is not None:
|
| + self.value = default
|
| +
|
| + def __nonzero__(self):
|
| + return bool(self.value)
|
| +
|
| + __bool__ = __nonzero__ # python 3
|
| +
|
| +class CompleteUnqualifiedFunctionNames(CythonParameter):
|
| + """
|
| + Have 'cy break' complete unqualified function or method names.
|
| + """
|
| +
|
| +class ColorizeSourceCode(CythonParameter):
|
| + """
|
| + Tell cygdb whether to colorize source code.
|
| + """
|
| +
|
| +class TerminalBackground(CythonParameter):
|
| + """
|
| + Tell cygdb about the user's terminal background (light or dark).
|
| + """
|
| +
|
| +class CythonParameters(object):
|
| + """
|
| + Simple container class that might get more functionality in the distant
|
| + future (mostly to remind us that we're dealing with parameters).
|
| + """
|
| +
|
| + def __init__(self):
|
| + self.complete_unqualified = CompleteUnqualifiedFunctionNames(
|
| + 'cy_complete_unqualified',
|
| + gdb.COMMAND_BREAKPOINTS,
|
| + gdb.PARAM_BOOLEAN,
|
| + True)
|
| + self.colorize_code = ColorizeSourceCode(
|
| + 'cy_colorize_code',
|
| + gdb.COMMAND_FILES,
|
| + gdb.PARAM_BOOLEAN,
|
| + True)
|
| + self.terminal_background = TerminalBackground(
|
| + 'cy_terminal_background_color',
|
| + gdb.COMMAND_FILES,
|
| + gdb.PARAM_STRING,
|
| + "dark")
|
| +
|
| +parameters = CythonParameters()
|
| +
|
| +
|
| +# Commands
|
| +
|
| +class CythonCommand(gdb.Command, CythonBase):
|
| + """
|
| + Base class for Cython commands
|
| + """
|
| +
|
| + command_class = gdb.COMMAND_NONE
|
| +
|
| + @classmethod
|
| + def _register(cls, clsname, args, kwargs):
|
| + if not hasattr(cls, 'completer_class'):
|
| + return cls(clsname, cls.command_class, *args, **kwargs)
|
| + else:
|
| + return cls(clsname, cls.command_class, cls.completer_class,
|
| + *args, **kwargs)
|
| +
|
| + @classmethod
|
| + def register(cls, *args, **kwargs):
|
| + alias = getattr(cls, 'alias', None)
|
| + if alias:
|
| + cls._register(cls.alias, args, kwargs)
|
| +
|
| + return cls._register(cls.name, args, kwargs)
|
| +
|
| +
|
| +class CyCy(CythonCommand):
|
| + """
|
| + Invoke a Cython command. Available commands are:
|
| +
|
| + cy import
|
| + cy break
|
| + cy step
|
| + cy next
|
| + cy run
|
| + cy cont
|
| + cy finish
|
| + cy up
|
| + cy down
|
| + cy select
|
| + cy bt / cy backtrace
|
| + cy list
|
| + cy print
|
| + cy set
|
| + cy locals
|
| + cy globals
|
| + cy exec
|
| + """
|
| +
|
| + name = 'cy'
|
| + command_class = gdb.COMMAND_NONE
|
| + completer_class = gdb.COMPLETE_COMMAND
|
| +
|
| + def __init__(self, name, command_class, completer_class):
|
| + # keep the signature 2.5 compatible (i.e. do not use f(*a, k=v)
|
| + super(CythonCommand, self).__init__(name, command_class,
|
| + completer_class, prefix=True)
|
| +
|
| + commands = dict(
|
| + # GDB commands
|
| + import_ = CyImport.register(),
|
| + break_ = CyBreak.register(),
|
| + step = CyStep.register(),
|
| + next = CyNext.register(),
|
| + run = CyRun.register(),
|
| + cont = CyCont.register(),
|
| + finish = CyFinish.register(),
|
| + up = CyUp.register(),
|
| + down = CyDown.register(),
|
| + select = CySelect.register(),
|
| + bt = CyBacktrace.register(),
|
| + list = CyList.register(),
|
| + print_ = CyPrint.register(),
|
| + locals = CyLocals.register(),
|
| + globals = CyGlobals.register(),
|
| + exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
|
| + _exec = CyExec.register(),
|
| + set = CySet.register(),
|
| +
|
| + # GDB functions
|
| + cy_cname = CyCName('cy_cname'),
|
| + cy_cvalue = CyCValue('cy_cvalue'),
|
| + cy_lineno = CyLine('cy_lineno'),
|
| + cy_eval = CyEval('cy_eval'),
|
| + )
|
| +
|
| + for command_name, command in commands.iteritems():
|
| + command.cy = self
|
| + setattr(self, command_name, command)
|
| +
|
| + self.cy = self
|
| +
|
| + # Cython module namespace
|
| + self.cython_namespace = {}
|
| +
|
| + # maps (unique) qualified function names (e.g.
|
| + # cythonmodule.ClassName.method_name) to the CythonFunction object
|
| + self.functions_by_qualified_name = {}
|
| +
|
| + # unique cnames of Cython functions
|
| + self.functions_by_cname = {}
|
| +
|
| + # map function names like method_name to a list of all such
|
| + # CythonFunction objects
|
| + self.functions_by_name = collections.defaultdict(list)
|
| +
|
| +
|
| +class CyImport(CythonCommand):
|
| + """
|
| + Import debug information outputted by the Cython compiler
|
| + Example: cy import FILE...
|
| + """
|
| +
|
| + name = 'cy import'
|
| + command_class = gdb.COMMAND_STATUS
|
| + completer_class = gdb.COMPLETE_FILENAME
|
| +
|
| + def invoke(self, args, from_tty):
|
| + args = args.encode(_filesystemencoding)
|
| + for arg in string_to_argv(args):
|
| + try:
|
| + f = open(arg)
|
| + except OSError, e:
|
| + raise gdb.GdbError('Unable to open file %r: %s' %
|
| + (args, e.args[1]))
|
| +
|
| + t = etree.parse(f)
|
| +
|
| + for module in t.getroot():
|
| + cython_module = CythonModule(**module.attrib)
|
| + self.cy.cython_namespace[cython_module.name] = cython_module
|
| +
|
| + for variable in module.find('Globals'):
|
| + d = variable.attrib
|
| + cython_module.globals[d['name']] = CythonVariable(**d)
|
| +
|
| + for function in module.find('Functions'):
|
| + cython_function = CythonFunction(module=cython_module,
|
| + **function.attrib)
|
| +
|
| + # 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[
|
| + cython_function.qualified_name] = cython_function
|
| + self.cy.functions_by_cname[
|
| + cython_function.cname] = cython_function
|
| +
|
| + d = cython_module.functions[qname] = cython_function
|
| +
|
| + for local in function.find('Locals'):
|
| + d = local.attrib
|
| + cython_function.locals[d['name']] = CythonVariable(**d)
|
| +
|
| + for step_into_func in function.find('StepIntoFunctions'):
|
| + d = step_into_func.attrib
|
| + cython_function.step_into_functions.add(d['name'])
|
| +
|
| + cython_function.arguments.extend(
|
| + funcarg.tag for funcarg in function.find('Arguments'))
|
| +
|
| + for marker in module.find('LineNumberMapping'):
|
| + cython_lineno = int(marker.attrib['cython_lineno'])
|
| + c_linenos = map(int, marker.attrib['c_linenos'].split())
|
| + cython_module.lineno_cy2c[cython_lineno] = min(c_linenos)
|
| + for c_lineno in c_linenos:
|
| + cython_module.lineno_c2cy[c_lineno] = cython_lineno
|
| +
|
| +
|
| +class CyBreak(CythonCommand):
|
| + """
|
| + Set a breakpoint for Cython code using Cython qualified name notation, e.g.:
|
| +
|
| + cy break cython_modulename.ClassName.method_name...
|
| +
|
| + or normal notation:
|
| +
|
| + cy break function_or_method_name...
|
| +
|
| + or for a line number:
|
| +
|
| + cy break cython_module:lineno...
|
| +
|
| + Set a Python breakpoint:
|
| + Break on any function or method named 'func' in module 'modname'
|
| +
|
| + cy break -p modname.func...
|
| +
|
| + Break on any function or method named 'func'
|
| +
|
| + cy break -p func...
|
| + """
|
| +
|
| + name = 'cy break'
|
| + command_class = gdb.COMMAND_BREAKPOINTS
|
| +
|
| + def _break_pyx(self, name):
|
| + modulename, _, lineno = name.partition(':')
|
| + lineno = int(lineno)
|
| + if modulename:
|
| + cython_module = self.cy.cython_namespace[modulename]
|
| + else:
|
| + cython_module = self.get_cython_function().module
|
| +
|
| + if lineno in cython_module.lineno_cy2c:
|
| + c_lineno = cython_module.lineno_cy2c[lineno]
|
| + breakpoint = '%s:%s' % (cython_module.c_filename, c_lineno)
|
| + gdb.execute('break ' + breakpoint)
|
| + else:
|
| + raise gdb.GdbError("Not a valid line number. "
|
| + "Does it contain actual code?")
|
| +
|
| + def _break_funcname(self, funcname):
|
| + func = self.cy.functions_by_qualified_name.get(funcname)
|
| +
|
| + if func and func.is_initmodule_function:
|
| + func = None
|
| +
|
| + break_funcs = [func]
|
| +
|
| + if not func:
|
| + funcs = self.cy.functions_by_name.get(funcname) or []
|
| + funcs = [f for f in funcs if not f.is_initmodule_function]
|
| +
|
| + if not funcs:
|
| + gdb.execute('break ' + funcname)
|
| + return
|
| +
|
| + if len(funcs) > 1:
|
| + # multiple functions, let the user pick one
|
| + print 'There are multiple such functions:'
|
| + for idx, func in enumerate(funcs):
|
| + print '%3d) %s' % (idx, func.qualified_name)
|
| +
|
| + while True:
|
| + try:
|
| + result = raw_input(
|
| + "Select a function, press 'a' for all "
|
| + "functions or press 'q' or '^D' to quit: ")
|
| + except EOFError:
|
| + return
|
| + else:
|
| + if result.lower() == 'q':
|
| + return
|
| + elif result.lower() == 'a':
|
| + break_funcs = funcs
|
| + break
|
| + elif (result.isdigit() and
|
| + 0 <= int(result) < len(funcs)):
|
| + break_funcs = [funcs[int(result)]]
|
| + break
|
| + else:
|
| + print 'Not understood...'
|
| + else:
|
| + break_funcs = [funcs[0]]
|
| +
|
| + for func in break_funcs:
|
| + gdb.execute('break %s' % func.cname)
|
| + if func.pf_cname:
|
| + gdb.execute('break %s' % func.pf_cname)
|
| +
|
| + def invoke(self, function_names, from_tty):
|
| + argv = string_to_argv(function_names.encode('UTF-8'))
|
| + if function_names.startswith('-p'):
|
| + argv = argv[1:]
|
| + python_breakpoints = True
|
| + else:
|
| + python_breakpoints = False
|
| +
|
| + for funcname in argv:
|
| + if python_breakpoints:
|
| + gdb.execute('py-break %s' % funcname)
|
| + elif ':' in funcname:
|
| + self._break_pyx(funcname)
|
| + else:
|
| + self._break_funcname(funcname)
|
| +
|
| + @dont_suppress_errors
|
| + def complete(self, text, word):
|
| + # 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:
|
| + all_names = itertools.chain(qnames, names)
|
| + else:
|
| + all_names = qnames
|
| +
|
| + words = text.strip().split()
|
| + if not words or '.' not in words[-1]:
|
| + # complete unqualified
|
| + seen = set(text[:-len(word)].split())
|
| + 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):
|
| + # readline sees something (e.g. a '.') as a word boundary, so don't
|
| + # "recomplete" this prefix
|
| + strip_prefix_length = len(lastword) - len(word)
|
| + compl = [n[strip_prefix_length:] for n in compl]
|
| +
|
| + return compl
|
| +
|
| +
|
| +class CythonInfo(CythonBase, libpython.PythonInfo):
|
| + """
|
| + Implementation of the interface dictated by libpython.LanguageInfo.
|
| + """
|
| +
|
| + def lineno(self, frame):
|
| + # Take care of the Python and Cython levels. We need to care for both
|
| + # as we can't simply dispath to 'py-step', since that would work for
|
| + # stepping through Python code, but it would not step back into Cython-
|
| + # related code. The C level should be dispatched to the 'step' command.
|
| + if self.is_cython_function(frame):
|
| + return self.get_cython_lineno(frame)
|
| + return super(CythonInfo, self).lineno(frame)
|
| +
|
| + def get_source_line(self, frame):
|
| + try:
|
| + line = super(CythonInfo, self).get_source_line(frame)
|
| + except gdb.GdbError:
|
| + return None
|
| + else:
|
| + return line.strip() or None
|
| +
|
| + def exc_info(self, frame):
|
| + if self.is_python_function:
|
| + return super(CythonInfo, self).exc_info(frame)
|
| +
|
| + def runtime_break_functions(self):
|
| + if self.is_cython_function():
|
| + return self.get_cython_function().step_into_functions
|
| + return ()
|
| +
|
| + def static_break_functions(self):
|
| + result = ['PyEval_EvalFrameEx']
|
| + result.extend(self.cy.functions_by_cname)
|
| + 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):
|
| + if self.is_python_function():
|
| + self.python_step(self.stepinto)
|
| + elif not self.is_cython_function():
|
| + if self.stepinto:
|
| + command = 'step'
|
| + else:
|
| + command = 'next'
|
| +
|
| + self.finish_executing(gdb.execute(command, to_string=True))
|
| + else:
|
| + self.step(stepinto=self.stepinto)
|
| +
|
| +
|
| +class CyNext(CyStep):
|
| + "Step-over Cython, Python or C code."
|
| +
|
| + name = 'cy -next'
|
| + stepinto = False
|
| +
|
| +
|
| +class CyRun(CythonExecutionControlCommand):
|
| + """
|
| + Run a Cython program. This is like the 'run' command, except that it
|
| + displays Cython or Python source lines as well
|
| + """
|
| +
|
| + name = 'cy run'
|
| +
|
| + invoke = CythonExecutionControlCommand.run
|
| +
|
| +
|
| +class CyCont(CythonExecutionControlCommand):
|
| + """
|
| + Continue a Cython program. This is like the 'run' command, except that it
|
| + displays Cython or Python source lines as well.
|
| + """
|
| +
|
| + name = 'cy cont'
|
| + invoke = CythonExecutionControlCommand.cont
|
| +
|
| +
|
| +class CyFinish(CythonExecutionControlCommand):
|
| + """
|
| + Execute until the function returns.
|
| + """
|
| + name = 'cy finish'
|
| +
|
| + invoke = CythonExecutionControlCommand.finish
|
| +
|
| +
|
| +class CyUp(CythonCommand):
|
| + """
|
| + Go up a Cython, Python or relevant C frame.
|
| + """
|
| + name = 'cy up'
|
| + _command = 'up'
|
| +
|
| + def invoke(self, *args):
|
| + try:
|
| + gdb.execute(self._command, to_string=True)
|
| + while not self.is_relevant_function(gdb.selected_frame()):
|
| + gdb.execute(self._command, to_string=True)
|
| + except RuntimeError, e:
|
| + raise gdb.GdbError(*e.args)
|
| +
|
| + frame = gdb.selected_frame()
|
| + index = 0
|
| + while frame:
|
| + frame = frame.older()
|
| + index += 1
|
| +
|
| + self.print_stackframe(index=index - 1)
|
| +
|
| +
|
| +class CyDown(CyUp):
|
| + """
|
| + Go down a Cython, Python or relevant C frame.
|
| + """
|
| +
|
| + name = 'cy down'
|
| + _command = 'down'
|
| +
|
| +
|
| +class CySelect(CythonCommand):
|
| + """
|
| + Select a frame. Use frame numbers as listed in `cy backtrace`.
|
| + This command is useful because `cy backtrace` prints a reversed backtrace.
|
| + """
|
| +
|
| + name = 'cy select'
|
| +
|
| + def invoke(self, stackno, from_tty):
|
| + try:
|
| + stackno = int(stackno)
|
| + except ValueError:
|
| + raise gdb.GdbError("Not a valid number: %r" % (stackno,))
|
| +
|
| + frame = gdb.selected_frame()
|
| + while frame.newer():
|
| + frame = frame.newer()
|
| +
|
| + stackdepth = libpython.stackdepth(frame)
|
| +
|
| + try:
|
| + gdb.execute('select %d' % (stackdepth - stackno - 1,))
|
| + except RuntimeError, e:
|
| + raise gdb.GdbError(*e.args)
|
| +
|
| +
|
| +class CyBacktrace(CythonCommand):
|
| + 'Print the Cython stack'
|
| +
|
| + name = 'cy bt'
|
| + alias = 'cy backtrace'
|
| + command_class = gdb.COMMAND_STACK
|
| + completer_class = gdb.COMPLETE_NONE
|
| +
|
| + @require_running_program
|
| + def invoke(self, args, from_tty):
|
| + # get the first frame
|
| + frame = gdb.selected_frame()
|
| + while frame.older():
|
| + frame = frame.older()
|
| +
|
| + print_all = args == '-a'
|
| +
|
| + index = 0
|
| + while frame:
|
| + try:
|
| + is_relevant = self.is_relevant_function(frame)
|
| + except CyGDBError:
|
| + is_relevant = False
|
| +
|
| + if print_all or is_relevant:
|
| + self.print_stackframe(frame, index)
|
| +
|
| + index += 1
|
| + frame = frame.newer()
|
| +
|
| +
|
| +class CyList(CythonCommand):
|
| + """
|
| + List Cython source code. To disable to customize colouring see the cy_*
|
| + parameters.
|
| + """
|
| +
|
| + name = 'cy list'
|
| + command_class = gdb.COMMAND_FILES
|
| + completer_class = gdb.COMPLETE_NONE
|
| +
|
| + # @dispatch_on_frame(c_command='list')
|
| + def invoke(self, _, from_tty):
|
| + sd, lineno = self.get_source_desc()
|
| + source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno,
|
| + lex_entire=True)
|
| + print source
|
| +
|
| +
|
| +class CyPrint(CythonCommand):
|
| + """
|
| + Print a Cython variable using 'cy-print x' or 'cy-print module.function.x'
|
| + """
|
| +
|
| + name = 'cy print'
|
| + command_class = gdb.COMMAND_DATA
|
| +
|
| + def invoke(self, name, from_tty, max_name_length=None):
|
| + if self.is_python_function():
|
| + return gdb.execute('py-print ' + name)
|
| + elif self.is_cython_function():
|
| + value = self.cy.cy_cvalue.invoke(name.lstrip('*'))
|
| + for c in name:
|
| + if c == '*':
|
| + value = value.dereference()
|
| + else:
|
| + break
|
| +
|
| + self.print_gdb_value(name, value, max_name_length)
|
| + else:
|
| + gdb.execute('print ' + name)
|
| +
|
| + def complete(self):
|
| + if self.is_cython_function():
|
| + f = self.get_cython_function()
|
| + return list(itertools.chain(f.locals, f.globals))
|
| + else:
|
| + return []
|
| +
|
| +
|
| +sortkey = lambda (name, value): name.lower()
|
| +
|
| +class CyLocals(CythonCommand):
|
| + """
|
| + List the locals from the current Cython frame.
|
| + """
|
| +
|
| + name = 'cy locals'
|
| + command_class = gdb.COMMAND_STACK
|
| + completer_class = gdb.COMPLETE_NONE
|
| +
|
| + @dispatch_on_frame(c_command='info locals', python_command='py-locals')
|
| + def invoke(self, args, from_tty):
|
| + 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))
|
| + for name, cyvar in sorted(local_cython_vars.iteritems(), key=sortkey):
|
| + if self.is_initialized(self.get_cython_function(), cyvar.name):
|
| + value = gdb.parse_and_eval(cyvar.cname)
|
| + if not value.is_optimized_out:
|
| + self.print_gdb_value(cyvar.name, value,
|
| + max_name_length, '')
|
| +
|
| +
|
| +class CyGlobals(CyLocals):
|
| + """
|
| + List the globals from the current Cython module.
|
| + """
|
| +
|
| + name = 'cy globals'
|
| + command_class = gdb.COMMAND_STACK
|
| + completer_class = gdb.COMPLETE_NONE
|
| +
|
| + @dispatch_on_frame(c_command='info variables', python_command='py-globals')
|
| + def invoke(self, args, from_tty):
|
| + global_python_dict = self.get_cython_globals_dict()
|
| + module_globals = self.get_cython_function().module.globals
|
| +
|
| + max_globals_len = 0
|
| + max_globals_dict_len = 0
|
| + if module_globals:
|
| + max_globals_len = len(max(module_globals, key=len))
|
| + if global_python_dict:
|
| + max_globals_dict_len = len(max(global_python_dict))
|
| +
|
| + max_name_length = max(max_globals_len, max_globals_dict_len)
|
| +
|
| + seen = set()
|
| + print 'Python globals:'
|
| + for k, v in sorted(global_python_dict.iteritems(), key=sortkey):
|
| + v = v.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
|
| + seen.add(k)
|
| + print ' %-*s = %s' % (max_name_length, k, v)
|
| +
|
| + print 'C globals:'
|
| + for name, cyvar in sorted(module_globals.iteritems(), key=sortkey):
|
| + if name not in seen:
|
| + try:
|
| + value = gdb.parse_and_eval(cyvar.cname)
|
| + except RuntimeError:
|
| + pass
|
| + else:
|
| + if not value.is_optimized_out:
|
| + self.print_gdb_value(cyvar.name, value,
|
| + max_name_length, ' ')
|
| +
|
| +
|
| +
|
| +class EvaluateOrExecuteCodeMixin(object):
|
| + """
|
| + Evaluate or execute Python code in a Cython or Python frame. The 'evalcode'
|
| + method evaluations Python code, prints a traceback if an exception went
|
| + uncaught, and returns any return value as a gdb.Value (NULL on exception).
|
| + """
|
| +
|
| + def _fill_locals_dict(self, executor, local_dict_pointer):
|
| + "Fill a remotely allocated dict with values from the Cython C stack"
|
| + cython_func = self.get_cython_function()
|
| +
|
| + for name, cyvar in cython_func.locals.iteritems():
|
| + if (cyvar.type == PythonObject and
|
| + self.is_initialized(cython_func, name)):
|
| +
|
| + try:
|
| + val = gdb.parse_and_eval(cyvar.cname)
|
| + except RuntimeError:
|
| + continue
|
| + else:
|
| + if val.is_optimized_out:
|
| + continue
|
| +
|
| + pystringp = executor.alloc_pystring(name)
|
| + code = '''
|
| + (PyObject *) PyDict_SetItem(
|
| + (PyObject *) %d,
|
| + (PyObject *) %d,
|
| + (PyObject *) %s)
|
| + ''' % (local_dict_pointer, pystringp, cyvar.cname)
|
| +
|
| + try:
|
| + if gdb.parse_and_eval(code) < 0:
|
| + gdb.parse_and_eval('PyErr_Print()')
|
| + raise gdb.GdbError("Unable to execute Python code.")
|
| + finally:
|
| + # PyDict_SetItem doesn't steal our reference
|
| + executor.xdecref(pystringp)
|
| +
|
| + def _find_first_cython_or_python_frame(self):
|
| + frame = gdb.selected_frame()
|
| + while frame:
|
| + if (self.is_cython_function(frame) or
|
| + self.is_python_function(frame)):
|
| + frame.select()
|
| + return frame
|
| +
|
| + frame = frame.older()
|
| +
|
| + raise gdb.GdbError("There is no Cython or Python frame on the stack.")
|
| +
|
| +
|
| + def _evalcode_cython(self, executor, code, input_type):
|
| + with libpython.FetchAndRestoreError():
|
| + # get the dict of Cython globals and construct a dict in the
|
| + # inferior with Cython locals
|
| + global_dict = gdb.parse_and_eval(
|
| + '(PyObject *) PyModule_GetDict(__pyx_m)')
|
| + local_dict = gdb.parse_and_eval('(PyObject *) PyDict_New()')
|
| +
|
| + try:
|
| + self._fill_locals_dict(executor,
|
| + libpython.pointervalue(local_dict))
|
| + result = executor.evalcode(code, input_type, global_dict,
|
| + local_dict)
|
| + finally:
|
| + executor.xdecref(libpython.pointervalue(local_dict))
|
| +
|
| + return result
|
| +
|
| + def evalcode(self, code, input_type):
|
| + """
|
| + Evaluate `code` in a Python or Cython stack frame using the given
|
| + `input_type`.
|
| + """
|
| + frame = self._find_first_cython_or_python_frame()
|
| + executor = libpython.PythonCodeExecutor()
|
| + if self.is_python_function(frame):
|
| + return libpython._evalcode_python(executor, code, input_type)
|
| + return self._evalcode_cython(executor, code, input_type)
|
| +
|
| +
|
| +class CyExec(CythonCommand, libpython.PyExec, EvaluateOrExecuteCodeMixin):
|
| + """
|
| + Execute Python code in the nearest Python or Cython frame.
|
| + """
|
| +
|
| + name = '-cy-exec'
|
| + command_class = gdb.COMMAND_STACK
|
| + completer_class = gdb.COMPLETE_NONE
|
| +
|
| + def invoke(self, expr, from_tty):
|
| + expr, input_type = self.readcode(expr)
|
| + executor = libpython.PythonCodeExecutor()
|
| + executor.xdecref(self.evalcode(expr, executor.Py_single_input))
|
| +
|
| +
|
| +class CySet(CythonCommand):
|
| + """
|
| + Set a Cython variable to a certain value
|
| +
|
| + cy set my_cython_c_variable = 10
|
| + cy set my_cython_py_variable = $cy_eval("{'doner': 'kebab'}")
|
| +
|
| + This is equivalent to
|
| +
|
| + set $cy_value("my_cython_variable") = 10
|
| + """
|
| +
|
| + name = 'cy set'
|
| + command_class = gdb.COMMAND_DATA
|
| + completer_class = gdb.COMPLETE_NONE
|
| +
|
| + @require_cython_frame
|
| + def invoke(self, expr, from_tty):
|
| + name_and_expr = expr.split('=', 1)
|
| + if len(name_and_expr) != 2:
|
| + raise gdb.GdbError("Invalid expression. Use 'cy set var = expr'.")
|
| +
|
| + varname, expr = name_and_expr
|
| + cname = self.cy.cy_cname.invoke(varname.strip())
|
| + gdb.execute("set %s = %s" % (cname, expr))
|
| +
|
| +
|
| +# Functions
|
| +
|
| +class CyCName(gdb.Function, CythonBase):
|
| + """
|
| + Get the C name of a Cython variable in the current context.
|
| + Examples:
|
| +
|
| + print $cy_cname("function")
|
| + print $cy_cname("Class.method")
|
| + print $cy_cname("module.function")
|
| + """
|
| +
|
| + @require_cython_frame
|
| + @gdb_function_value_to_unicode
|
| + def invoke(self, cyname, frame=None):
|
| + frame = frame or gdb.selected_frame()
|
| + cname = None
|
| +
|
| + if self.is_cython_function(frame):
|
| + cython_function = self.get_cython_function(frame)
|
| + if cyname in cython_function.locals:
|
| + cname = cython_function.locals[cyname].cname
|
| + elif cyname in cython_function.module.globals:
|
| + cname = cython_function.module.globals[cyname].cname
|
| + else:
|
| + qname = '%s.%s' % (cython_function.module.name, cyname)
|
| + if qname in cython_function.module.functions:
|
| + cname = cython_function.module.functions[qname].cname
|
| +
|
| + if not cname:
|
| + cname = self.cy.functions_by_qualified_name.get(cyname)
|
| +
|
| + if not cname:
|
| + raise gdb.GdbError('No such Cython variable: %s' % cyname)
|
| +
|
| + return cname
|
| +
|
| +
|
| +class CyCValue(CyCName):
|
| + """
|
| + Get the value of a Cython variable.
|
| + """
|
| +
|
| + @require_cython_frame
|
| + @gdb_function_value_to_unicode
|
| + def invoke(self, cyname, frame=None):
|
| + 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)
|
| + return gdb.parse_and_eval(cname)
|
| + elif cyname in globals_dict:
|
| + return globals_dict[cyname]._gdbval
|
| + else:
|
| + raise gdb.GdbError("Variable %s is not initialized." % cyname)
|
| +
|
| +
|
| +class CyLine(gdb.Function, CythonBase):
|
| + """
|
| + Get the current Cython line.
|
| + """
|
| +
|
| + @require_cython_frame
|
| + def invoke(self):
|
| + return self.get_cython_lineno()
|
| +
|
| +
|
| +class CyEval(gdb.Function, CythonBase, EvaluateOrExecuteCodeMixin):
|
| + """
|
| + Evaluate Python code in the nearest Python or Cython frame and return
|
| + """
|
| +
|
| + @gdb_function_value_to_unicode
|
| + def invoke(self, python_expression):
|
| + input_type = libpython.PythonCodeExecutor.Py_eval_input
|
| + return self.evalcode(python_expression, input_type)
|
| +
|
| +
|
| +cython_info = CythonInfo()
|
| +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()
|
|
|