diff options
author | Anton Samokhvalov <pg83@yandex.ru> | 2022-02-10 16:45:17 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:45:17 +0300 |
commit | d3a398281c6fd1d3672036cb2d63f842d2cb28c5 (patch) | |
tree | dd4bd3ca0f36b817e96812825ffaf10d645803f2 /contrib/tools/cython/Cython/Debugger/libcython.py | |
parent | 72cb13b4aff9bc9cf22e49251bc8fd143f82538f (diff) | |
download | ydb-d3a398281c6fd1d3672036cb2d63f842d2cb28c5.tar.gz |
Restoring authorship annotation for Anton Samokhvalov <pg83@yandex.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/tools/cython/Cython/Debugger/libcython.py')
-rw-r--r-- | contrib/tools/cython/Cython/Debugger/libcython.py | 2792 |
1 files changed, 1396 insertions, 1396 deletions
diff --git a/contrib/tools/cython/Cython/Debugger/libcython.py b/contrib/tools/cython/Cython/Debugger/libcython.py index 2ddf43922e..23153789b6 100644 --- a/contrib/tools/cython/Cython/Debugger/libcython.py +++ b/contrib/tools/cython/Cython/Debugger/libcython.py @@ -1,23 +1,23 @@ -""" -GDB extension that adds Cython support. -""" - -from __future__ import print_function - +""" +GDB extension that adds Cython support. +""" + +from __future__ import print_function + try: input = raw_input except NameError: pass -import sys -import textwrap -import traceback -import functools -import itertools -import collections - -import gdb - +import sys +import textwrap +import traceback +import functools +import itertools +import collections + +import gdb + try: # python 2 UNICODE = unicode BYTES = str @@ -25,1410 +25,1410 @@ except NameError: # python 3 UNICODE = str BYTES = bytes -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: +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: func_address = gdb_value.address if not isinstance(func_address, int): # Seriously? Why is the address not an int? if not isinstance(func_address, (str, bytes)): func_address = str(func_address) func_address = int(func_address.split()[0], 0) - - a = ', '.join('%s=%s' % (name, val) for name, val in func_args) - sys.stdout.write('#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a)) - - if source_desc.filename is not None: - sys.stdout.write(' at %s:%s' % (source_desc.filename, lineno)) - - sys.stdout.write('\n') - - try: - sys.stdout.write(' ' + 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() + + a = ', '.join('%s=%s' % (name, val) for name, val in func_args) + sys.stdout.write('#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a)) + + if source_desc.filename is not None: + sys.stdout.write(' at %s:%s' % (source_desc.filename, lineno)) + + sys.stdout.write('\n') + + try: + sys.stdout.write(' ' + 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.items(): - 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: + 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 int(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): - """ + 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 errors - """ - - 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 __bool__(self): - return bool(self.value) - - __nonzero__ = __bool__ # Python 2 - - - -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.items(): - 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): + """ + + 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 __bool__(self): + return bool(self.value) + + __nonzero__ = __bool__ # Python 2 + + + +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.items(): + 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): if isinstance(args, BYTES): args = args.decode(_filesystemencoding) - for arg in string_to_argv(args): - try: - f = open(arg) - except OSError as 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']) + for arg in string_to_argv(args): + try: + f = open(arg) + except OSError as 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 = list(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: + 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 = 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): + "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): if isinstance(function_names, BYTES): function_names = function_names.decode(_filesystemencoding) argv = string_to_argv(function_names) - 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). + 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.items() if any(not f.is_initmodule_function for f in L)] qnames = [n for n, f in self.cy.functions_by_qualified_name.items() 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 + + 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 dispatch 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 as 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 as 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 item: item[0].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)) + # 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 as 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 as 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 item: item[0].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.items(), 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:') + 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.items(), 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:') + 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.items(), 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() - + 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.items(): 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() + 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() |