Tom Tromey
2011-01-19 19:07:01 UTC
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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
-
-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 (" <function called from gdb>\n")
- elif self.type () == gdb.SIGTRAMP_FRAME:
- stream.write (" <signal handler called>\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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
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 <http://www.gnu.org/licenses/>.
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 <http://www.gnu.org/licenses/>.
+
+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(" <function called from gdb>\n")
+ elif frame.type() == gdb.SIGTRAMP_FRAME:
+ stream.write(" <signal handler called>\n")
+ elif frame.type() == gdb.ARCH_FRAME:
+ stream.write(" <cross-architecture call>\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
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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
-
-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 (" <function called from gdb>\n")
- elif self.type () == gdb.SIGTRAMP_FRAME:
- stream.write (" <signal handler called>\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 <http://www.gnu.org/licenses/>.
-
-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 <http://www.gnu.org/licenses/>.
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 <http://www.gnu.org/licenses/>.
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 <http://www.gnu.org/licenses/>.
+
+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(" <function called from gdb>\n")
+ elif frame.type() == gdb.SIGTRAMP_FRAME:
+ stream.write(" <signal handler called>\n")
+ elif frame.type() == gdb.ARCH_FRAME:
+ stream.write(" <cross-architecture call>\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