This is the mail archive of the archer@sourceware.org mailing list for the Archer project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[python] a few commands in python


This adds a few new gdb commands written in Python.

The "require" command can be used to load other commands written in
Python.  I figured for the time being I'd make most of the new
commands (except "require") not be loaded by default.

The "alias" command can be used to make aliases.  This is better than
using the gdb "define" command because it handle multiple arguments
properly.

The "new-backtrace" command is a replacement for "backtrace".  It
allows reverse backtraces and also it allows for backtrace filtering
via Python plugins.

The filtering code is intended for use by interpreters (and other
things like Alexander's Gnome signal-emission filter) to wash frames
before the user sees them.  E.g., a filter for Python itself could
turn a C frame representing the Python interpreter loop into a new
frame that shows the state of the interpreted code.

Filters register themselves with gdb.backtrace.push_frame_filter.  My
intent here is to register such filters when an interpreter's objfile
is opened, just as we do with pretty-printers.

Eventually I'd like this new backtrace to replace the built-in
backtrace, when loaded.  I don't think it is mature enough for that
yet.

Thiago -- you could put your in_scope function in
python/lib/gdb/function/ and then change require.py to add:

    RequireSubcommand("function")

That would add a "require function" command and give folks a way to
easily use this.


I'd like to reiterate that the Python library code is still an
experimental area.  For all I know we won't want any of these in the
final product.  I'm definitely interested in feedback.

Tom

2008-11-23  Tom Tromey  <tromey@redhat.com>

	* Makefile.in (PY_DIRS): Update.
	(PY_FILES): List new files.
	* python/lib/gdb/FrameIterator.py: New file.
	* python/lib/gdb/backtrace.py: New file.
	* python/lib/gdb/command/backtrace.py: New file.
	* python/lib/gdb/command/require.py: New file.
	* python/lib/gdb/__init__.py: New file.

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 79a9d1c..26e4aaf 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1917,11 +1917,13 @@ python-value.o: $(srcdir)/python/python-value.c
 # first.  These are maintained by hand because that is simpler than
 # writing portable sh to make the __init__.py files, and the result is
 # faster.
-PY_DIRS = gdb gdb/libstdcxx gdb/libstdcxx/v6
+PY_DIRS = gdb gdb/command gdb/libstdcxx gdb/libstdcxx/v6
 
 # All python library files, with the "python/lib" stripped off.
 # Note that we should only install files in the "gdb" module.
-PY_FILES = gdb/libstdcxx/v6/printers.py
+PY_FILES = gdb/backtrace.py gdb/command/alias.py \
+    gdb/command/backtrace.py gdb/command/require.py gdb/FrameIterator.py \
+    gdb/__init__.py gdb/libstdcxx/v6/printers.py
 
 # Install the Python library.  Python library files go under
 # $(GDB_DATADIR_PATH)/python.  We create a dummy __init__.py for
diff --git a/gdb/python/lib/gdb/FrameIterator.py b/gdb/python/lib/gdb/FrameIterator.py
new file mode 100644
index 0000000..c842504
--- /dev/null
+++ b/gdb/python/lib/gdb/FrameIterator.py
@@ -0,0 +1,33 @@
+# Iterator over frames.
+
+# Copyright (C) 2008 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 == None:
+            raise StopIteration
+        self.frame = result.get_prev()
+        return result
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
new file mode 100644
index 0000000..b375c68
--- /dev/null
+++ b/gdb/python/lib/gdb/__init__.py
@@ -0,0 +1,19 @@
+# Startup code.
+
+# Copyright (C) 2008 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/>.
+
+# Load the require command by default.
+import gdb.command.require
diff --git a/gdb/python/lib/gdb/backtrace.py b/gdb/python/lib/gdb/backtrace.py
new file mode 100644
index 0000000..317c6d8
--- /dev/null
+++ b/gdb/python/lib/gdb/backtrace.py
@@ -0,0 +1,40 @@
+# Filtering backtrace.
+
+# Copyright (C) 2008 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."""
+    if frame_filter == None:
+        frame_filter = constructor
+    else:
+        frame_filter = lambda iterator: constructor (frame_filter (iterator))
+
+def create_frame_filter (iter):
+    if frame_filter is None:
+        return iter
+    return frame_filter (iter)
+
diff --git a/gdb/python/lib/gdb/command/alias.py b/gdb/python/lib/gdb/command/alias.py
new file mode 100644
index 0000000..8443d67
--- /dev/null
+++ b/gdb/python/lib/gdb/command/alias.py
@@ -0,0 +1,59 @@
+# Alias command.
+
+# Copyright (C) 2008 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
+
+class AliasImplementation (gdb.Command):
+    def __init__ (self, name, real, doc):
+        # Have to set __doc__ before the super init call.
+        # It would be nice if gdb's help looked up __doc__ dynamically.
+        self.__doc__ = doc
+        # Note: no good way to complete :(
+        super (AliasImplementation, self).__init__ (name, gdb.COMMAND_NONE)
+        self.real = real
+
+    def invoke(self, arg, from_tty):
+        if arg == None:
+            gdb.execute (self.real, from_tty)
+        else:
+            gdb.execute (self.real + ' ' + arg, from_tty)
+
+class AliasCommand (gdb.Command):
+    """Alias one command to another.
+In the simplest form, the first word is the name of the alias, and
+the remaining words are the the expansion.
+An '=' by itself can be used to define a multi-word alias; words
+before the '=' are the name of the new command."""
+
+    def __init__ (self):
+        # Completion is not quite right here.
+        super (AliasCommand, self).__init__ ("alias", gdb.COMMAND_NONE,
+                                             gdb.COMPLETE_COMMAND)
+
+    def invoke (self, arg, from_tty):
+        # Without some form of quoting we can't alias a multi-word
+        # command to another command.
+        args = arg.split()
+        try:
+            i = args.index ('=')
+        except ValueError:
+            i = 0
+        target = " ".join(args[(i+1):])
+        AliasImplementation (" ".join (args[0:i]), target,
+                             "This command is an alias for '%s'." % target)
+
+AliasCommand()
diff --git a/gdb/python/lib/gdb/command/backtrace.py b/gdb/python/lib/gdb/command/backtrace.py
new file mode 100644
index 0000000..2884b72
--- /dev/null
+++ b/gdb/python/lib/gdb/command/backtrace.py
@@ -0,0 +1,199 @@
+# New backtrace command.
+
+# Copyright (C) 2008 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 gdb.backtrace
+import itertools
+from gdb.FrameIterator import FrameIterator
+import sys
+
+class FrameWrapper:
+    def __init__ (self, frame):
+        self.frame = frame;
+
+    def write_symbol (self, stream, sym, block):
+        if len (sym.get_linkage_name ()):
+            nsym, is_field_of_this = gdb.lookup_symbol (sym.get_linkage_name (), block, gdb.SYMBOL_VAR_DOMAIN)
+            if nsym.get_class () != gdb.SYMBOL_LOC_REGISTER:
+                sym = nsym
+
+        stream.write (sym.get_print_name () + "=")
+        try:
+            val = self.frame.read_var_value (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.get_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.get_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.frame.get_type () == gdb.DUMMY_FRAME:
+            stream.write (" <function called from gdb>\n")
+        elif self.frame.get_type () == gdb.SIGTRAMP_FRAME:
+            stream.write (" <signal handler called>\n")
+        else:
+            sal = self.frame.find_sal ()
+            pc = self.frame.get_pc ()
+            name = self.frame.get_name ()
+            if not name:
+                name = "??"
+            if pc != sal.get_pc () or not sal.symtab:
+                stream.write (" 0x%08x in" % pc)
+            stream.write (" " + name + " (")
+
+            func = gdb.find_pc_function (self.frame.get_address_in_block ())
+            self.print_frame_args (stream, func)
+
+            stream.write (")")
+
+            if sal.symtab and sal.symtab.get_filename ():
+                stream.write (" at " + sal.symtab.get_filename ())
+                stream.write (":" + str (sal.get_line ()))
+
+            if not self.frame.get_name () or (not sal.symtab or not sal.symtab.get_filename ()):
+                lib = gdb.solib_address (pc)
+                if lib:
+                    stream.write (" from " + lib)
+
+            stream.write ("\n")
+
+            if full:
+                self.print_frame_locals (stream, func)
+
+    def __getattribute__ (self, name):
+        return getattr (self.frame, name)
+
+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."""
+
+    set_doc = "Enable or disable reverse backtraces."
+    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)
+        # Default to compatibility with gdb.
+        self.value = False
+
+class FilteringBacktrace (gdb.Command):
+    """Print backtrace of all stack frames, or innermost COUNT frames.
+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.
+"""
+
+    def __init__ (self):
+        # FIXME: this is not working quite well enough to replace
+        # "backtrace" yet.
+        gdb.Command.__init__ (self, "new-backtrace", gdb.COMMAND_STACK)
+        self.reverse = ReverseBacktraceParameter()
+
+    def reverse_iter (self, iter):
+        result = []
+        for item in iter:
+            result.append (item)
+        result.reverse()
+        return result
+
+    def final_n (self, iter, x):
+        result = []
+        for item in iter:
+            result.append (item)
+        return result[x:]
+
+    def invoke (self, arg, from_tty):
+        i = 0
+        count = 0
+        filter = True
+        full = False
+
+        if arg == None:
+            arg = ''
+        for word in arg.split (" "):
+            if word == '':
+                continue
+            elif word == 'raw':
+                filter = False
+            elif word == 'full':
+                full = True
+            else:
+                count = int (word)
+
+        # FIXME: provide option to start at selected frame
+        # However, should still number as if starting from current
+        iter = itertools.imap (FrameWrapper,
+                               FrameIterator (gdb.get_current_frame ()))
+        if filter:
+            iter = gdb.backtrace.create_frame_filter (iter)
+
+        # Now wrap in an iterator that numbers the frames.
+        iter = itertools.izip (itertools.count (0), iter)
+
+        # Reverse if the user wanted that.
+        if self.reverse.value:
+            iter = self.reverse_iter (iter)
+
+        # Extract sub-range user wants.
+        if count < 0:
+            iter = self.final_n (iter, count)
+        elif count > 0:
+            iter = itertools.islice (iter, 0, count)
+
+        for pair in iter:
+            sys.stdout.write ("#%-2d" % pair[0])
+            pair[1].describe (sys.stdout, full)
+
+FilteringBacktrace()
diff --git a/gdb/python/lib/gdb/command/require.py b/gdb/python/lib/gdb/command/require.py
new file mode 100644
index 0000000..2112b9a
--- /dev/null
+++ b/gdb/python/lib/gdb/command/require.py
@@ -0,0 +1,57 @@
+# Demand-loading commands.
+
+# Copyright (C) 2008 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 os
+
+class RequireCommand (gdb.Command):
+    """Prefix command for requiring features."""
+
+    def __init__ (self):
+        super (RequireCommand, self).__init__ ("require",
+                                               gdb.COMMAND_SUPPORT,
+                                               gdb.COMPLETE_NONE,
+                                               True)
+
+class RequireSubcommand (gdb.Command):
+    """Demand-load a command by name."""
+
+    def __init__ (self, name):
+        self.__doc__ = "Demand-load a %s by name." % name
+        super (RequireSubcommand, self).__init__ ("require %s" % name,
+                                                  gdb.COMMAND_SUPPORT)
+        self.name = name
+
+    def invoke (self, arg, from_tty):
+        for cmd in arg.split():
+            exec ('import gdb.' + self.name + '.' + cmd, globals ())
+
+    def complete (self, text, word):
+        dir = gdb.datadir + '/python/gdb/' + self.name
+        result = []
+        for file in os.listdir(dir):
+            if not file.startswith (word) or not file.endswith ('.py'):
+                continue
+            feature = file[0:-3]
+            if feature == 'require' or feature == '__init__':
+                continue
+            result.append (feature)
+        return result
+
+RequireCommand()
+RequireSubcommand("command")
+# Don't install a "require function" command until we have one.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]