From 1327c85b930e6de3f7c4100a22d1c244ceb3e74d Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Sun, 31 Oct 2010 22:34:38 +0100 Subject: [PATCH] dispatch based on frame python code stepping (for libpython and libcython) generic stepper class fix step-into functions have cygdb accept a '--' command line argument to disable automatic importing replace gdb.execute() with something that actually captures all output have 'cy break' break properly on line numbers --- Cython/Compiler/Main.py | 2 +- Cython/Compiler/ParseTreeTransforms.py | 22 +- Cython/Debugger/Cygdb.py | 26 +- Cython/Debugger/DebugWriter.py | 71 +++++ Cython/Debugger/libcython.py | 379 ++++++++++++++++--------- Cython/Debugger/libpython.py | 168 ++++++++++- bin/cygdb | 11 +- cygdb.py | 11 +- 8 files changed, 540 insertions(+), 150 deletions(-) create mode 100644 Cython/Debugger/DebugWriter.py diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py index 6275fb12..75a80414 100644 --- a/Cython/Compiler/Main.py +++ b/Cython/Compiler/Main.py @@ -185,7 +185,7 @@ class Context(object): from ParseTreeTransforms import DebugTransform self.debug_outputwriter = DebugWriter.CythonDebugWriter( options.output_dir) - debug_transform = [DebugTransform(self)] + debug_transform = [DebugTransform(self, options)] else: debug_transform = [] diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index cfc9f90c..5b34f8d6 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -1441,18 +1441,23 @@ class DebugTransform(CythonTransform): to enable debugging. """ - def __init__(self, context): + def __init__(self, context, options): super(DebugTransform, self).__init__(context) self.visited = set() # our treebuilder and debug output writer # (see Cython.Debugger.debug_output.CythonDebugWriter) self.tb = self.context.debug_outputwriter + self.c_output_file = options.output_file + + # tells visit_NameNode whether it should register step-into functions + self.register_stepinto = False def visit_ModuleNode(self, node): self.tb.module_name = node.full_module_name attrs = dict( module_name=node.full_module_name, - filename=node.pos[0].filename) + filename=node.pos[0].filename, + c_filename=self.c_output_file) self.tb.start('Module', attrs) @@ -1503,20 +1508,25 @@ class DebugTransform(CythonTransform): self.tb.end('Arguments') self.tb.start('StepIntoFunctions') + self.register_stepinto = True self.visitchildren(node) + self.register_stepinto = False self.tb.end('StepIntoFunctions') self.tb.end('Function') return node def visit_NameNode(self, node): - if (node.type.is_cfunction and - node.is_called and - node.entry.in_cinclude): + if (self.register_stepinto and node.type.is_cfunction and node.is_called): + # don't check node.entry.in_cinclude, as 'cdef extern: ...' + # declared functions are not 'in_cinclude'. + # This means we will list called 'cdef' functions as + # "step into functions", but this is not an issue as they will be + # recognized as Cython functions anyway. attrs = dict(name=node.entry.func_cname) self.tb.start('StepIntoFunction', attrs=attrs) self.tb.end('StepIntoFunction') - + self.visitchildren(node) return node diff --git a/Cython/Debugger/Cygdb.py b/Cython/Debugger/Cygdb.py index 67f5be91..6045659b 100644 --- a/Cython/Debugger/Cygdb.py +++ b/Cython/Debugger/Cygdb.py @@ -20,25 +20,29 @@ import subprocess def usage(): print("Usage: cygdb [PATH GDB_ARGUMENTS]") -def make_command_file(path_to_debug_info): - debug_files = glob.glob( - os.path.join(path_to_debug_info, 'cython_debug/cython_debug_info_*')) - - if not debug_files: - usage() - sys.exit('No debug files were found in %s. Aborting.' % ( - os.path.abspath(path_to_debug_info))) +def make_command_file(path_to_debug_info, prefix_code='', no_import=False): + if not no_import: + pattern = os.path.join(path_to_debug_info, + 'cython_debug/cython_debug_info_*') + debug_files = glob.glob(pattern) + + if not debug_files: + usage() + sys.exit('No debug files were found in %s. Aborting.' % ( + os.path.abspath(path_to_debug_info))) fd, tempfilename = tempfile.mkstemp() f = os.fdopen(fd, 'w') + f.write(prefix_code) f.write('set breakpoint pending on\n') f.write('python from Cython.Debugger import libcython\n') - f.write('\n'.join('cy import %s\n' % fn for fn in debug_files)) + if not no_import: + f.write('\n'.join('cy import %s\n' % fn for fn in debug_files)) f.close() return tempfilename -def main(gdb_argv=[], path_to_debug_info=os.curdir): +def main(path_to_debug_info=os.curdir, gdb_argv=[], no_import=False): """ Start the Cython debugger. This tells gdb to import the Cython and Python extensions (libpython.py and libcython.py) and it enables gdb's pending @@ -46,7 +50,7 @@ def main(gdb_argv=[], path_to_debug_info=os.curdir): path_to_debug_info is the path to the cython_debug directory """ - tempfilename = make_command_file(path_to_debug_info) + tempfilename = make_command_file(path_to_debug_info, no_import=no_import) p = subprocess.Popen(['gdb', '-command', tempfilename] + gdb_argv) while True: try: diff --git a/Cython/Debugger/DebugWriter.py b/Cython/Debugger/DebugWriter.py new file mode 100644 index 00000000..6ec0fab4 --- /dev/null +++ b/Cython/Debugger/DebugWriter.py @@ -0,0 +1,71 @@ +import os +import errno + +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: + try: + # normal ElementTree install + import elementtree.ElementTree as etree + except ImportError: + etree = None + +from Cython.Compiler import Errors + + +class CythonDebugWriter(object): + """ + Class to output debugging information for cygdb + + It writes debug information to cython_debug/cython_debug_info_ + in the build directory. + """ + + def __init__(self, output_dir): + if etree is None: + raise Errors.NoElementTreeInstalledException() + + self.output_dir = os.path.join(output_dir, 'cython_debug') + self.tb = etree.TreeBuilder() + # set by Cython.Compiler.ParseTreeTransforms.DebugTransform + self.module_name = None + self.start('cython_debug', attrs=dict(version='1.0')) + + def start(self, name, attrs=None): + self.tb.start(name, attrs or {}) + + def end(self, name): + self.tb.end(name) + + def serialize(self): + self.tb.end('Module') + self.tb.end('cython_debug') + xml_root_element = self.tb.close() + + try: + os.makedirs(self.output_dir) + except OSError, e: + if e.errno != errno.EEXIST: + raise + + et = etree.ElementTree(xml_root_element) + kw = {} + if have_lxml: + kw['pretty_print'] = True + + fn = "cython_debug_info_" + self.module_name + et.write(os.path.join(self.output_dir, fn), encoding="UTF-8", **kw) \ No newline at end of file diff --git a/Cython/Debugger/libcython.py b/Cython/Debugger/libcython.py index b0aa69f6..c6cca5a4 100644 --- a/Cython/Debugger/libcython.py +++ b/Cython/Debugger/libcython.py @@ -90,20 +90,43 @@ def require_cython_frame(function): '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 + + # 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): + 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 = {} + def qualified_name(self, varname): return '.'.join(self.name, varname) @@ -146,12 +169,15 @@ class CythonBase(object): @default_selected_gdb_frame(err=False) def is_python_function(self, frame): - return frame.name() == 'PyEval_EvalFrameEx' - - @default_selected_gdb_frame() - def is_python_function(self, frame): - return libpython.Frame(frame).is_evalframeex() - + """ + 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() @@ -170,9 +196,13 @@ class CythonBase(object): @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)) - + 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 @@ -185,7 +215,7 @@ class CythonBase(object): pyframeobject = libpython.Frame(frame).get_pyop() if not pyframeobject: - raise GdbError('Unable to read information on python frame') + raise gdb.GdbError('Unable to read information on python frame') filename = pyframeobject.filename() lineno = pyframeobject.current_line_num() @@ -198,6 +228,25 @@ class CythonBase(object): def get_source_line(self, frame): source_desc, lineno = self.get_source_desc() return source_desc.get_source(lineno) + + 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 (parameters.step_into_c_code and + older_frame and self.is_cython_function(older_frame)): + # 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 + class SourceFileDescriptor(object): def __init__(self, filename, lexer, formatter=None): @@ -238,13 +287,20 @@ class SourceFileDescriptor(object): yield '%s %4d %s' % (prefix, start + idx, line) def get_source(self, start, stop=None, lex_source=True, mark_line=0): + exc = gdb.GdbError('Unable to retrieve source code') + if not self.filename: - raise GdbError('Unable to retrieve source code') - + raise exc + if stop is None: stop = start + 1 - return '\n'.join(self._get_source(start, stop, lex_source, mark_line)) - + + try: + return '\n'.join( + self._get_source(start, stop, lex_source, mark_line)) + except IOError: + raise exc + # Errors @@ -299,18 +355,24 @@ class CompleteUnqualifiedFunctionNames(CythonParameter): class ColorizeSourceCode(CythonParameter): """ - Tell cygdb whether to colorize source code + Tell cygdb whether to colorize source code. """ class TerminalBackground(CythonParameter): """ - Tell cygdb about the user's terminal background (light or dark) + Tell cygdb about the user's terminal background (light or dark). """ - + +class StepIntoCCode(CythonParameter): + """ + Tells cygdb whether to step into C functions called directly from Cython + code. + """ + 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) + future (mostly to remind us that we're dealing with parameters). """ def __init__(self): @@ -329,7 +391,12 @@ class CythonParameters(object): gdb.COMMAND_FILES, gdb.PARAM_STRING, "dark") - + self.step_into_c_code = StepIntoCCode( + 'cy_step_into_c_code', + gdb.COMMAND_RUNNING, + gdb.PARAM_BOOLEAN, + True) + parameters = CythonParameters() @@ -340,6 +407,14 @@ class CythonCommand(gdb.Command, CythonBase): Base class for Cython commands """ + @classmethod + def register(cls, *args, **kwargs): + if not hasattr(cls, 'completer_class'): + return cls(cls.name, cls.command_class, *args, **kwargs) + else: + return cls(cls.name, cls.command_class, cls.completer_class, + *args, **kwargs) + class CyCy(CythonCommand): """ @@ -348,6 +423,7 @@ class CyCy(CythonCommand): cy import cy break cy step + cy next cy print cy list cy locals @@ -357,31 +433,29 @@ class CyCy(CythonCommand): cy down """ - def __init__(self): - super(CythonCommand, self).__init__( - 'cy', gdb.COMMAND_NONE, gdb.COMPLETE_COMMAND, prefix=True) - - self.import_ = CyImport( - 'cy import', gdb.COMMAND_STATUS, gdb.COMPLETE_FILENAME) - - self.break_ = CyBreak('cy break', gdb.COMMAND_BREAKPOINTS) - self.step = CyStep('cy step', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE) - self.next = CyNext('cy next', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE) - self.list = CyList('cy list', gdb.COMMAND_FILES, gdb.COMPLETE_NONE) - self.print_ = CyPrint('cy print', gdb.COMMAND_DATA) + name = 'cy' + command_class = gdb.COMMAND_NONE + completer_class = gdb.COMPLETE_COMMAND + + def __init__(self, *args): + super(CythonCommand, self).__init__(*args, prefix=True) - self.locals = CyLocals( - 'cy locals', gdb.COMMAND_STACK, gdb.COMPLETE_NONE) - self.globals = CyGlobals( - 'cy globals', gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + commands = dict( + import_ = CyImport.register(), + break_ = CyBreak.register(), + step = CyStep.register(), + next = CyNext.register(), + list = CyList.register(), + print_ = CyPrint.register(), + locals = CyLocals.register(), + globals = CyGlobals.register(), + cy_cname = CyCName('cy_cname'), + cy_line = CyLine('cy_line'), + ) - self.cy_cname = CyCName('cy_cname') - - objs = (self.import_, self.break_, self.step, self.list, self.print_, - self.locals, self.globals, self.cy_cname) - - for obj in objs: - obj.cy = self + for command_name, command in commands.iteritems(): + command.cy = self + setattr(self, command_name, command) # Cython module namespace self.cython_namespace = {} @@ -403,15 +477,19 @@ 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: - print('Unable to open file %r: %s' % (args, e.args[1])) - return + raise gdb.GdbError('Unable to open file %r: %s' % + (args, e.args[1])) t = etree.parse(f) @@ -426,15 +504,20 @@ class CyImport(CythonCommand): for function in module.find('Functions'): cython_function = CythonFunction(module=cython_module, **function.attrib) - + # update the global function mappings - self.cy.functions_by_name[cython_function.name].append( - cython_function) + name = cython_function.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 + L = d.setdefault(cython_function.qualified_name, []) + L.append(cython_function) + for local in function.find('Locals'): d = local.attrib cython_function.locals[d['name']] = CythonVariable(**d) @@ -469,13 +552,16 @@ class CyBreak(CythonCommand): cy break cython_module:lineno... """ + name = 'cy break' + command_class = gdb.COMMAND_BREAKPOINTS + def _break_pyx(self, name): modulename, _, lineno = name.partition(':') lineno = int(lineno) cython_module = self.cy.cython_namespace[modulename] if lineno in cython_module.lineno_cy2c: c_lineno = cython_module.lineno_cy2c[lineno] - breakpoint = '%s:%s' % (cython_module.name, c_lineno) + breakpoint = '%s:%s' % (cython_module.c_filename, c_lineno) gdb.execute('break ' + breakpoint) else: raise GdbError("Not a valid line number. " @@ -554,78 +640,74 @@ class CyBreak(CythonCommand): return compl -class CodeStepperMixin(object): +class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper): + """ + Base class for CyStep and CyNext. It implements the interface dictated by + libpython.GenericCodeStepper. + """ - def init_stepping(self): - self.cython_func = self.get_cython_function() - self.beginline = self.get_cython_lineno() - self.curframe = gdb.selected_frame() - - def next_step(self, command): - "returns whether to continue stepping" - result = gdb.execute(command, to_string=True) - newframe = gdb.selected_frame() + 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) + else: + return libpython.py_step.lineno(frame) + + def get_source_line(self, frame): + # We may have ended up in a Python, Cython, or C function + # In case of C, don't display any additional data (gdb already + # does this) + result = '' + + if self.is_cython_function(frame) or self.is_python_function(frame): + try: + result = super(CythonCodeStepper, self).get_source_line(frame) + except gdb.GdbError: + result = '' - c1 = result.startswith('Breakpoint') - c2 = (newframe == self.curframe and - self.get_cython_lineno() > self.beginline) - return not c1 and not c2 + return result.lstrip() - def end_stepping(self): - sys.stdout.write(self.get_source_line()) + @classmethod + def register(cls): + return cls(cls.name, stepper=cls.stepper) + +class CyStep(CythonCodeStepper): + "Step through Python code." + + name = 'cy step' + stepper = True + + @dispatch_on_frame(c_command='step') + def invoke(self, *args, **kwargs): + super(CythonCodeStepper, self).invoke(*args, **kwargs) -class CyStep(CythonCommand, CodeStepperMixin): - def step(self, nsteps=1): - for nthstep in xrange(nsteps): - self.init_stepping() - - while self.next_step('step'): - newframe = gdb.selected_frame() - if newframe != self.curframe: - # we entered a function - funcname = self.get_c_function_name(newframe) - if (self.is_cython_function() or - self.is_python_function() or - funcname in cython_function.step_into_functions): - break - - self.end_stepping() - - def invoke(self, steps, from_tty): - if self.is_cython_function(): - if steps: - self.step(int(steps)) - else: - self.step() - else: - gdb.execute('step ' + steps, from_tty) +class CyNext(CythonCodeStepper): + "Step-over Python code." + name = 'cy next' + stepper = False -class CyNext(CythonCommand, CodeStepperMixin): - - def next(self, nsteps=1): - for nthstep in xrange(nsteps): - self.init_stepping() - - while self.next_step('next'): - pass - - self.end_stepping() - - def invoke(self, steps, from_tty): - if self.is_cython_function(): - if steps: - self.next(int(steps)) - else: - self.next() - else: - gdb.execute('next ' + steps, from_tty) + @dispatch_on_frame(c_command='next') + def invoke(self, *args, **kwargs): + super(CythonCodeStepper, self).invoke(*args, **kwargs) 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) @@ -637,27 +719,34 @@ 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 + + @dispatch_on_frame(c_command='print', python_command='py-print') def invoke(self, name, from_tty): - try: - cname = cy.cy_cname.invoke(name) - except gdb.GdbError: - cname = name - - gdb.execute('print ' + cname) - + gdb.execute('print ' + self.cy.cy_cname.invoke(name, string=True)) def complete(self): if self.is_cython_function(): f = self.get_cython_function() return list(itertools.chain(f.locals, f.globals)) - return [] + else: + return [] class CyLocals(CythonCommand): + """ + List the locals from the current Cython frame. + """ + + name = 'cy locals' + command_class = gdb.COMMAND_STACK + completer_class = gdb.COMPLETE_NONE + def ns(self): return self.get_cython_function().locals - @require_cython_frame + @dispatch_on_frame(c_command='info locals', python_command='py-locals') def invoke(self, name, from_tty): try: ns = self.ns() @@ -682,10 +771,18 @@ class CyLocals(CythonCommand): class CyGlobals(CythonCommand): + """ + List the globals from the current Cython module. + """ + + name = 'cy globals' + command_class = gdb.COMMAND_STACK + completer_class = gdb.COMPLETE_NONE + def ns(self): return self.get_cython_function().globals - @require_cython_frame + @dispatch_on_frame(c_command='info variables', python_command='py-globals') def invoke(self, name, from_tty): # include globals from the debug info XML file! m = gdb.parse_and_eval('__pyx_m') @@ -709,26 +806,54 @@ class CyGlobals(CythonCommand): class CyCName(gdb.Function, CythonBase): """ - Get the C name of a Cython variable. + 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 - def invoke(self, cyname, frame=None): + def invoke(self, cyname, string=False, frame=None): frame = frame or gdb.selected_frame() cname = None - cyname = cyname.string() + if isinstance(cyname, gdb.Value): + # convert to a python string so it supports proper hashing + cyname = cyname.string() + 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 - + if string: + return cname + else: + return gdb.parse_and_eval(cname) + + +class CyLine(gdb.Function, CythonBase): + """ + Get the current Cython line. + """ + + @require_cython_frame + def invoke(self): + return self.get_cython_lineno() + -cy = CyCy() \ No newline at end of file +cy = CyCy.register() \ No newline at end of file diff --git a/Cython/Debugger/libpython.py b/Cython/Debugger/libpython.py index 323ae0cf..836868d1 100644 --- a/Cython/Debugger/libpython.py +++ b/Cython/Debugger/libpython.py @@ -44,8 +44,12 @@ the type names are known to the debugger The module also extends gdb with some python-specific commands. ''' from __future__ import with_statement -import gdb + +import os import sys +import tempfile + +import gdb # Look up the gdb.Type for some standard types: _type_char_ptr = gdb.lookup_type('char').pointer() # char* @@ -1448,3 +1452,165 @@ class PyLocals(gdb.Command): pyop_value.get_truncated_repr(MAX_OUTPUT_LEN))) PyLocals() + + +def execute(command, from_tty=False, to_string=False): + """ + Replace gdb.execute() with this function and have it accept a 'to_string' + argument (new in 7.2). Have it properly capture stderr also. + + Unfortuntaly, this function is not reentrant. + """ + if not to_string: + return _execute(command, from_tty) + + fd, filename = tempfile.mkstemp() + + try: + _execute("set logging file %s" % filename) + _execute("set logging redirect on") + _execute("set logging on") + _execute("set pagination off") + + _execute(command, from_tty) + finally: + data = os.fdopen(fd).read() + os.remove(filename) + _execute("set logging off") + _execute("set pagination on") + return data + +_execute = gdb.execute +gdb.execute = execute + + +class GenericCodeStepper(gdb.Command): + """ + Superclass for code stepping. Subclasses must implement the following + methods: + + lineno(frame) - tells the current line number (only + called for a relevant frame) + is_relevant_function(frame) - tells whether we care about frame 'frame' + get_source_line(frame) - get the line of source code for the + current line (only called for a relevant + frame) + + This class provides an 'invoke' method that invokes a 'step' or 'step-over' + depending on the 'stepper' argument. + """ + + def __init__(self, name, stepper=False): + super(GenericCodeStepper, self).__init__(name, + gdb.COMMAND_RUNNING, + gdb.COMPLETE_NONE) + self.stepper = stepper + + def _init_stepping(self): + self.beginframe = gdb.selected_frame() + self.beginline = self.lineno(self.beginframe) + if not self.stepper: + self.depth = self._stackdepth(self.beginframe) + + def _next_step(self, gdb_command): + """ + Teturns whether to continue stepping. This method sets the instance + attributes 'result' and 'stopped_running'. 'result' hold the output + of the executed gdb command ('step' or 'next') + """ + self.result = gdb.execute(gdb_command, to_string=True) + self.stopped_running = gdb.inferiors()[0].pid == 0 + + if self.stopped_running: + # We stopped running + return False + + newframe = gdb.selected_frame() + + hit_breakpoint = self.result.startswith('Breakpoint') + is_relevant_function = self.is_relevant_function(newframe) + + if newframe != self.beginframe: + # new function + if not self.stepper: + is_relevant_function = ( + self._stackdepth(newframe) < self.depth and + is_relevant_function) + + new_lineno = False + else: + is_relevant_function = False + new_lineno = self.lineno(newframe) > self.beginline + + return not (hit_breakpoint or new_lineno or is_relevant_function) + + def _end_stepping(self): + # sys.stdout.write(self.result) + + if not self.stopped_running: + frame = gdb.selected_frame() + if self.is_relevant_function(frame): + print self.get_source_line(frame) + + def _stackdepth(self, frame): + depth = 0 + while frame: + frame = frame.older() + depth += 1 + + return depth + + def invoke(self, args, from_tty): + if args: + nsteps = int(args) + else: + nsteps = 1 + + if self.stepper: + gdb_command = 'step' + else: + gdb_command= 'next' + + for nthstep in xrange(nsteps): + self._init_stepping() + while self._next_step(gdb_command): + pass + + self._end_stepping() + + +class PythonCodeStepper(GenericCodeStepper): + + def pyframe(self, frame): + pyframe = Frame(frame).get_pyop() + if pyframe: + return pyframe + else: + raise gdb.GdbError( + "Unable to find the Python frame, run your code with a debug " + "build (configure with --with-pydebug or compile with -g).") + + def lineno(self, frame): + return self.pyframe(frame).current_line_num() + + def is_relevant_function(self, frame): + return Frame(frame).is_evalframeex() + + def get_source_line(self, frame): + try: + return self.pyframe(frame).current_line().rstrip() + except IOError, e: + gdb.GdbError('Unable to retrieve source code: %s' % (e,)) + + +class PyStep(PythonCodeStepper): + "Step through Python code." + +class PyNext(PythonCodeStepper): + "Step-over Python code." + +py_step = PyStep('py-step', stepper=True) +py_next = PyNext('py-next', stepper=False) + +class PyShowCCode(gdb.Parameter): + pass \ No newline at end of file diff --git a/bin/cygdb b/bin/cygdb index 7a7f5158..e573531d 100755 --- a/bin/cygdb +++ b/bin/cygdb @@ -6,7 +6,14 @@ from Cython.Debugger import Cygdb as cygdb if __name__ == '__main__': if len(sys.argv) > 1: - cygdb.main(path_to_debug_info=sys.argv[1], - gdb_argv=sys.argv[2:]) + path_to_debug_info = sys.argv[1] + + no_import = False + if path_to_debug_info == '--': + no_import = True + + cygdb.main(path_to_debug_info, + gdb_argv=sys.argv[2:], + no_import=no_import) else: cygdb.main() diff --git a/cygdb.py b/cygdb.py index 7a7f5158..e573531d 100644 --- a/cygdb.py +++ b/cygdb.py @@ -6,7 +6,14 @@ from Cython.Debugger import Cygdb as cygdb if __name__ == '__main__': if len(sys.argv) > 1: - cygdb.main(path_to_debug_info=sys.argv[1], - gdb_argv=sys.argv[2:]) + path_to_debug_info = sys.argv[1] + + no_import = False + if path_to_debug_info == '--': + no_import = True + + cygdb.main(path_to_debug_info, + gdb_argv=sys.argv[2:], + no_import=no_import) else: cygdb.main() -- 2.26.2