From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 32152 invoked by alias); 19 Jan 2011 19:07:16 -0000 Mailing-List: contact archer-help@sourceware.org; run by ezmlm Sender: Precedence: bulk List-Post: List-Help: List-Subscribe: List-Id: Received: (qmail 32140 invoked by uid 22791); 19 Jan 2011 19:07:12 -0000 X-SWARE-Spam-Status: No, hits=-6.1 required=5.0 tests=AWL,BAYES_00,KAM_STOCKGEN,RCVD_IN_DNSWL_HI,SPF_HELO_PASS,T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org From: Tom Tromey To: Project Archer Subject: frame filters Date: Wed, 19 Jan 2011 19:07:00 -0000 Message-ID: User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-SW-Source: 2011-q1/txt/msg00020.txt.bz2 One of the last tasks for killing archer-tromey-python is dealing with the frame filtering and new-backtrace code. Phil expressed an interest in working on this, so I'm sending my current patch and some notes about how I think we ought to proceed. The old code had a few problems: * The command was called "new-backtrace", but it would be nicer if it were just the default. * It didn't integrate with MI. * Problems pointed out by Alexander Larsson on this list. * No way to enable or disable filters -- this is a mistake we made with pretty-printing that we should not repeat. This patch takes a stab at the last couple of problems. First, it separates frames from frame-printing. I think this should help with Alexander's issues. It defines an API for "frame wrappers". A frame filter can return any object satisfying this API. A frame wrapper is a lot like a gdb.Frame, but a few methods are replaced with simpler-to-use methods, and it adds an optional 'children' method. The idea here is that if a filter collapses some frames, it should keep the originals around as children. That way we can print a "full" stack trace like: frame frame collapsed frame original frame original frame frame ... Maybe we should just add methods to gdb.Frame to avoid the middle-man. I am not certain about this. A frame filter is now a subclass of Parameter, so it always has a name and a user-visible setting. The parameter's value is an integer; users can use these to order or disable frame filters. (The "lame" stuff in the diff is just to work around an order-of-initialization problem that I think is fixed on trunk, I just didn't do a merge when I was hacking on this.) I think the way forward is to move some of the new stuff into C and make it work with both MI and the CLI: * Extend "backtrace" to accept some new qualifiers, not just "full". new-backtrace has a couple here: "reverse" (though this should just be reimplemented as a frame filter if that is possible -- then we don't need this one), "raw" (akin to print/r), and "all" (show the child frames as well). * Change the CLI backtrace command to use frame filters when enabled and available. Add new printing functions to print a Python object using the various frame filter methods. * Add qualifier-like arguments to MI's -stack-list-frames et al. I don't think "reverse" is that important here, but at least "raw" is. * Make other MI commands filter-aware as needed, e.g., -stack-info-depth. * Change MI frame-printing to use filters as appropriate. This may necessitate changes to the frame filter API, I am not sure. MI should probably always print the child frames. * In order to test this, write a few real-world frame filters. At least, adapt the Gtk and Python ones. * Then I think we should look at making the frame filter abstraction more than just a display thing. That means adding filtering support to "up", "down", and "frame". These would call the `select' method on the appropriate filtered frame, which would have to eventually delegate to some real frame. These would also need "raw" arguments of some form (e.g, "up/r"). Tom diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in index c9ff0de..aa09b97 100644 --- a/gdb/data-directory/Makefile.in +++ b/gdb/data-directory/Makefile.in @@ -51,22 +51,20 @@ SYSCALLS_FILES = \ PYTHON_DIR = python PYTHON_INSTALL_DIR = $(DESTDIR)$(GDB_DATADIR)/$(PYTHON_DIR) PYTHON_FILES = \ - gdb/FrameIterator.py \ - gdb/FrameWrapper.py \ - gdb/__init__.py \ - gdb/backtrace.py \ - gdb/command/__init__.py \ gdb/command/alias.py \ gdb/command/backtrace.py \ gdb/command/ignore_errors.py \ + gdb/command/__init__.py \ gdb/command/pahole.py \ gdb/command/pretty_printers.py \ - gdb/command/require.py \ gdb/command/upto.py \ - gdb/function/__init__.py \ + gdb/frame.py \ gdb/function/caller_is.py \ + gdb/function/__init__.py \ gdb/function/in_scope.py \ - gdb/printing.py \ + gdb/__init__.py \ + gdb/libgcj/v10/printers.py \ + gdb/printing.py \ gdb/types.py FLAGS_TO_PASS = \ diff --git a/gdb/python/lib/gdb/FrameIterator.py b/gdb/python/lib/gdb/FrameIterator.py deleted file mode 100644 index 5654546..0000000 --- a/gdb/python/lib/gdb/FrameIterator.py +++ /dev/null @@ -1,33 +0,0 @@ -# Iterator over frames. - -# Copyright (C) 2008, 2009 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -class FrameIterator: - """An iterator that iterates over frames.""" - - def __init__ (self, frame): - "Initialize a FrameIterator. FRAME is the starting frame." - self.frame = frame - - def __iter__ (self): - return self - - def next (self): - result = self.frame - if result is None: - raise StopIteration - self.frame = result.older () - return result diff --git a/gdb/python/lib/gdb/FrameWrapper.py b/gdb/python/lib/gdb/FrameWrapper.py deleted file mode 100644 index b790a54..0000000 --- a/gdb/python/lib/gdb/FrameWrapper.py +++ /dev/null @@ -1,112 +0,0 @@ -# Wrapper API for frames. - -# Copyright (C) 2008, 2009 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import gdb - -# FIXME: arguably all this should be on Frame somehow. -class FrameWrapper: - def __init__ (self, frame): - self.frame = frame; - - def write_symbol (self, stream, sym, block): - if len (sym.linkage_name): - nsym, is_field_of_this = gdb.lookup_symbol (sym.linkage_name, block) - if nsym.addr_class != gdb.SYMBOL_LOC_REGISTER: - sym = nsym - - stream.write (sym.print_name + "=") - try: - val = self.read_var (sym) - if val != None: - val = str (val) - # FIXME: would be nice to have a more precise exception here. - except RuntimeError, text: - val = text - if val == None: - stream.write ("???") - else: - stream.write (str (val)) - - def print_frame_locals (self, stream, func): - if not func: - return - - first = True - block = func.value - - for sym in block: - if sym.is_argument: - continue; - - self.write_symbol (stream, sym, block) - stream.write ('\n') - - def print_frame_args (self, stream, func): - if not func: - return - - first = True - block = func.value - - for sym in block: - if not sym.is_argument: - continue; - - if not first: - stream.write (", ") - - self.write_symbol (stream, sym, block) - first = False - - # FIXME: this should probably just be a method on gdb.Frame. - # But then we need stream wrappers. - def describe (self, stream, full): - if self.type () == gdb.DUMMY_FRAME: - stream.write (" \n") - elif self.type () == gdb.SIGTRAMP_FRAME: - stream.write (" \n") - else: - sal = self.find_sal () - pc = self.pc () - name = self.name () - if not name: - name = "??" - if pc != sal.pc or not sal.symtab: - stream.write (" 0x%08x in" % pc) - stream.write (" " + name + " (") - - func = self.function () - self.print_frame_args (stream, func) - - stream.write (")") - - if sal.symtab and sal.symtab.filename: - stream.write (" at " + sal.symtab.filename) - stream.write (":" + str (sal.line)) - - if not self.name () or (not sal.symtab or not sal.symtab.filename): - lib = gdb.solib_address (pc) - if lib: - stream.write (" from " + lib) - - stream.write ("\n") - - if full: - self.print_frame_locals (stream, func) - - def __getattr__ (self, name): - return getattr (self.frame, name) diff --git a/gdb/python/lib/gdb/backtrace.py b/gdb/python/lib/gdb/backtrace.py deleted file mode 100644 index 6bb4fb1..0000000 --- a/gdb/python/lib/gdb/backtrace.py +++ /dev/null @@ -1,42 +0,0 @@ -# Filtering backtrace. - -# Copyright (C) 2008, 2011 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import gdb -import itertools - -# Our only exports. -__all__ = ['push_frame_filter', 'create_frame_filter'] - -frame_filter = None - -def push_frame_filter (constructor): - """Register a new backtrace filter class with the 'backtrace' command. -The filter will be passed an iterator as an argument. The iterator -will return gdb.Frame-like objects. The filter should in turn act as -an iterator returning such objects.""" - global frame_filter - if frame_filter == None: - frame_filter = constructor - else: - frame_filter = lambda iterator, filter = frame_filter: constructor (filter (iterator)) - -def create_frame_filter (iter): - global frame_filter - if frame_filter is None: - return iter - return frame_filter (iter) - diff --git a/gdb/python/lib/gdb/command/backtrace.py b/gdb/python/lib/gdb/command/backtrace.py index 2aa5b90..f9b995e 100644 --- a/gdb/python/lib/gdb/command/backtrace.py +++ b/gdb/python/lib/gdb/command/backtrace.py @@ -1,6 +1,6 @@ # New backtrace command. -# Copyright (C) 2008, 2009, 2011 Free Software Foundation, Inc. +# Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,13 +16,11 @@ # along with this program. If not, see . import gdb -import gdb.backtrace +import gdb.frame import itertools -from gdb.FrameIterator import FrameIterator -from gdb.FrameWrapper import FrameWrapper import sys -class ReverseBacktraceParameter (gdb.Parameter): +class ReverseBacktraceParameter(gdb.Parameter): """The new-backtrace command can show backtraces in 'reverse' order. This means that the innermost frame will be printed last. Note that reverse backtraces are more expensive to compute.""" @@ -31,76 +29,119 @@ Note that reverse backtraces are more expensive to compute.""" show_doc = "Show whether backtraces will be printed in reverse order." def __init__(self): - gdb.Parameter.__init__ (self, "reverse-backtrace", - gdb.COMMAND_STACK, gdb.PARAM_BOOLEAN) + super(ReverseBacktraceParameter, self).__init__("reverse-backtrace", + gdb.COMMAND_STACK, + gdb.PARAM_BOOLEAN) # Default to compatibility with gdb. self.value = False -class FilteringBacktrace (gdb.Command): +class FilteringBacktrace(gdb.Command): """Print backtrace of all stack frames, or innermost COUNT frames. + +Usage: new-backtrace [COUNT|QUALIFIER]... + With a negative argument, print outermost -COUNT frames. -Use of the 'full' qualifier also prints the values of the local variables. -Use of the 'raw' qualifier avoids any filtering by loadable modules. + +Valid qualifiers are: + + full Also print the values of the local variables. + raw Avoid any filtering by loadable modules. + reverse Reverse the stack trace. If a reverse trace was + already selected by `set reverse-backtrace', then an + ordinary stack trace is done. Note that reverse + backtraces are more expensive to compute. + all Show frames that have been filtered out. """ - def __init__ (self): + def __init__(self): # FIXME: this is not working quite well enough to replace # "backtrace" yet. - gdb.Command.__init__ (self, "new-backtrace", gdb.COMMAND_STACK) + super(FilteringBacktrace, self).__init__("new-backtrace", + gdb.COMMAND_STACK) self.reverse = ReverseBacktraceParameter() - def reverse_iter (self, iter): - result = [] - for item in iter: - result.append (item) + def reverse_iter(self, iter): + result = list(iter) result.reverse() return result - def final_n (self, iter, x): - result = [] - for item in iter: - result.append (item) + def final_n(self, iter, x): + result = list(iter) return result[x:] - def invoke (self, arg, from_tty): + def invoke(self, arg, from_tty): i = 0 count = 0 filter = True full = False + reverse = self.reverse.value + showall = False - for word in arg.split (" "): + for word in arg.split(" "): if word == '': continue elif word == 'raw': filter = False elif word == 'full': full = True + elif word == 'reverse': + reverse = not reverse + elif word == 'all': + showall = True else: - count = int (word) + count = int(word) # FIXME: provide option to start at selected frame # However, should still number as if starting from newest - newest_frame = gdb.selected_thread ().newest_frame () - iter = itertools.imap (FrameWrapper, - FrameIterator (newest_frame)) + # FIXME: try/catch and wrap in gdb error + newest_frame = gdb.newest_frame() + iter = itertools.imap(gdb.frame.FrameWrapper, + gdb.frame.FrameIterator(newest_frame)) if filter: - iter = gdb.backtrace.create_frame_filter (iter) + iter = gdb.frame.create_frame_filter(iter) # Now wrap in an iterator that numbers the frames. - iter = itertools.izip (itertools.count (0), iter) + iter = itertools.izip(itertools.count(0), iter) # Reverse if the user wanted that. - if self.reverse.value: - iter = self.reverse_iter (iter) + if reverse: + iter = self.reverse_iter(iter) # Extract sub-range user wants. if count < 0: - iter = self.final_n (iter, count) + iter = self.final_n(iter, count) elif count > 0: - iter = itertools.islice (iter, 0, count) + iter = itertools.islice(iter, 0, count) for pair in iter: - sys.stdout.write ("#%-2d" % pair[0]) - pair[1].describe (sys.stdout, full) + sys.stdout.write("#%-2d" % pair[0]) + gdb.frame.print_frame(pair[1], sys.stdout, full) + if showall: + for f in pair[1].children(): + gdb.frame.print_frame(f, sys.stdout, full, ' ') FilteringBacktrace() + +def lame(): + class _Holder(gdb.Command): + def __init__(self, what): + super(_Holder, self).__init__(what + " backtrace filter", + gdb.COMMAND_STACK, + prefix = True) + + def invoke(self, arg, from_tty): + # FIXME + pass + + _Holder("set") + _Holder("show") + + class ShowFilter(gdb.Command): + def __init__(self): + super(ShowFilter, self).__init__("show backtrace filters", + gdb.COMMAND_STACK) + + def invoke(self, arg, from_tty): + gdb.frame.print_filters() + + ShowFilter() diff --git a/gdb/python/lib/gdb/command/upto.py b/gdb/python/lib/gdb/command/upto.py index faf54ed..5126531 100644 --- a/gdb/python/lib/gdb/command/upto.py +++ b/gdb/python/lib/gdb/command/upto.py @@ -1,6 +1,6 @@ # upto command. -# Copyright (C) 2009 Free Software Foundation, Inc. +# Copyright (C) 2009, 2010 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,9 +16,8 @@ # along with this program. If not, see . import gdb +import gdb.frame import re -from gdb.FrameIterator import FrameIterator -from gdb.FrameWrapper import FrameWrapper class UptoPrefix (gdb.Command): def __init__ (self): @@ -32,15 +31,15 @@ class UptoImplementation (gdb.Command): def search (self): saved = gdb.selected_frame () - iter = FrameIterator (saved) + iter = gdb.frame.FrameIterator (saved) found = False try: for frame in iter: frame.select () try: if self.filter (frame): - wrapper = FrameWrapper (frame) - wrapper.describe (sys.stdout, False) + wrapper = gdb.frame.FrameWrapper (frame) + gdb.frame.print_frame(wrapper, sys.stdout) return except: pass diff --git a/gdb/python/lib/gdb/frame.py b/gdb/python/lib/gdb/frame.py new file mode 100644 index 0000000..4743cd9 --- /dev/null +++ b/gdb/python/lib/gdb/frame.py @@ -0,0 +1,238 @@ +# Frame-related utilities. + +# Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gdb +import cStringIO + +class FrameIterator(object): + """An iterator that iterates over frames.""" + + def __init__ (self, frame): + "Initialize a FrameIterator. FRAME is the starting frame." + super(FrameIterator, self).__init__() + self.frame = frame + + def __iter__ (self): + return self + + def next (self): + result = self.frame + if result is None: + raise StopIteration + self.frame = result.older () + return result + +class _SymWrapper(object): + def __init__(self, frame, block, sym): + super(_SymWrapper, self).__init__() + self.frame = frame + self.block = block + if len(sym.linkage_name): + nsym, is_field_of_this = gdb.lookup_symbol (sym.linkage_name, block) + if nsym.addr_class != gdb.SYMBOL_LOC_REGISTER: + sym = nsym + self.sym = sym + + def name(self): + return self.sym.print_name + + def value(self): + try: + return self.frame.read_var(self.sym) + except RuntimeError, text: + return text + +class _BlockIterator(object): + def __init__(self, frame, block, just_args): + super(_BlockIterator, self).__init__() + self.frame = frame + self.block = block + self.just_args = just_args + self.iter = iter(self.block) + + def __iter__(self): + return self + + def next(self): + while True: + try: + result = self.iter.next() + if result.is_argument == self.just_args: + return _SymWrapper(self.frame, self.block, result) + except StopIteration: + if self.block.function is not None: + raise StopIteration + self.block = self.block.superblock + self.iter = iter(self.block) + +class FrameWrapper(object): + """A wrapper for a gdb.Frame object that presents a simpler interface. +A FrameWrapper forwards most methods to the underlying Frame. +It omits a few methods, and adds some others. +Any object conforming to this interface may be returned by a frame filter.""" + + def __init__(self, frame): + super(FrameWrapper, self).__init__() + self.frame = frame + + def name(self): + name = self.frame.name() + if name is None: + name = '??' + return name + + def type(self): + return self.frame.type() + + def older(self): + return self.frame.older() + + def arguments(self): + try: + block = self.frame.block() + return _BlockIterator(self.frame, block, True) + except RuntimeError: + # It is ok if block() fails. + return [] + + def locals(self): + try: + block = self.frame.block() + return _BlockIterator(self.frame, block, False) + except RuntimeError: + # It is ok if block() fails. + return [] + + def children(self): + if hasattr(self.frame, 'children'): + return self.frame.children() + return [] + + def file_and_line(self): + sal = self.frame.find_sal() + if sal.symtab and sal.symtab.filename: + return (sal.symtab.filename, sal.line) + return (None, None) + + def library(self): + pc = self.frame.pc() + return gdb.solib_name(pc) + +def _print_symbol(stream, sym, sep): + stream.write(sym.name()) + stream.write(sep) + val = sym.value() + if val is None: + stream.write('???') + else: + stream.write(str(val)) + +def _print_args(frame, stream): + stream.write(' (') + first = True + for arg in frame.arguments(): + if not first: + stream.write(', ') + first = False + _print_symbol(stream, arg, '=') + stream.write(')') + +def _print_locals(frame, stream): + for var in frame.locals(): + _print_symbol(stream, var, ' = ') + stream.write('\n') + +def print_frame(frame, stream, full = False, spaces = ''): + stream.write(spaces) + if frame.type() == gdb.DUMMY_FRAME: + stream.write(" \n") + elif frame.type() == gdb.SIGTRAMP_FRAME: + stream.write(" \n") + elif frame.type() == gdb.ARCH_FRAME: + stream.write(" \n") + else: + stream.write(' ') + stream.write(frame.name()) + _print_args(frame, stream) + (filename, line) = frame.file_and_line() + if filename is not None: + stream.write('\n') + stream.write(spaces) + stream.write(' at ') + stream.write(filename) + stream.write(':') + stream.write(str(line)) + else: + lib = frame.library() + if lib is not None: + stream.write(' from ') + stream.write(lib) + stream.write('\n') + if full: + nstr = cStringIO.StringIO() + _print_locals(frame, nstr) + for line in nstr.getvalue().splitlines(): + stream.write(spaces) + stream.write(' ') + stream.write(line) + stream.write('\n') + nstr.close() + +_frame_filters = {} + +class FrameFilter(gdb.Parameter): + def __init__(self, name): + super(FrameFilter, self).__init__('backtrace filter ' + name, + gdb.COMMAND_STACK, + gdb.PARAM_ZINTEGER) + self.name = name + self.value = 1 + global _frame_filters + _frame_filters[name] = self + + def filter(iter): + return iter + +def _get_value(elt): + return elt.value + +def _value_gt_0(elt): + return elt.value > 0 + +def create_frame_filter(iter): + global _frame_filters + elts = filter(_value_gt_0, _frame_filters.values()) + # FIXME to make it stable, should also sort by name as secondary key + elts.sort(key = _get_value, reverse = True) + for filt in elts: + iter = elts.filter(iter) + return iter + +def print_filters(): + global _frame_filters + elts = _frame_filters.values() + elts.sort(key = _get_value, reverse = True) + if not len(elts): + print 'No frame filters.' + else: + # It would be nice to print a short doc string here. + print 'Priority\tName' + for f in elts: + if f.value > 0: + print '%-8d\t%s' % (f.value, f.name) + else: + print 'Disabled\t%s' % f.name