This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB 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]

Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python


Alexander Smundak writes:
 > >  > diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
 > ...
 > > Also, we need another class: gdb.FrameID.
 > > I'm not sure whether to implement it in C or Python.
 > The code now accepts any object having 'sp' and possibly 'pc' and
 > 'special' attributes. IMHO there is no need to have a base class for this.
 > 
 > > There's some ambiguity on whether the Python error indicator will
 > > or will not be set on return.  Currently, sometimes it will and sometimes
 > > it won't.  I think the code is such that it won't matter today, but
 > > it would be good to either clear the error or at least document
 > > that the Python error indicator may or may not be set on return
 > > if the result is zero.
 > I have documented which internal functions are set Python error.
 > 
 > New revision:
 > 
 > gdb/ChangeLog
 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >     * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
 >     (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
 >     (py-unwind.o): New recipe.
 >     * NEWS: mention Python frame unwinding.
 >     * data-directory/Makefile.in (PYTHON_FILE_LIST): Add
 >     gdb/unwinder.py and gdb/command/unwinder.py
 >     * doc/python.texi (Writing a Frame Unwinder in Python): Add
 >     section.
 >     * python/lib/gdb/__init__.py (packages): Add frame_unwinders
 >     list.
 >     (execute_unwinders): New function.
 >     * python/lib/gdb/command/unwinders.py: New file.
 >     * python/lib/gdb/unwinder.py: New file.
 >     * python/py-objfile.c (objfile_object): Add frame_unwinders field.
 >     (objfpy_dealloc): Decrement frame_unwinders reference count.
 >     (objfpy_initialize): Create frame_unwinders list.
 >     (objfpy_get_frame_unwinders): New function.
 >     (objfpy_set_frame_unwinders): Ditto.
 >     (objfile_getset): Add frame_unwinders attribute to Objfile.
 >     * python/py-progspace.c (pspace_object): Add frame_unwinders field.
 >     (pspy_dealloc): Decrement frame_unwinders reference count.
 >     (pspy_initialize): Create frame_unwinders list.
 >     (pspy_get_frame_unwinders): New function.
 >     (pspy_set_frame_unwinders): Ditto.
 >     (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
 >     * python/py-unwind.c: New file.
 >     * python/python-internal.h (pspy_get_name_unwinders): New prototype.
 >     (objpy_get_frame_unwinders): New prototype.
 >     (gdbpy_initialize_unwind): New prototype.
 >     * python/python.c (gdbpy_apply_type_printers): Call
 >     gdbpy_initialize_unwind.
 > 
 > gdb/testsuite/ChangeLog
 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >     * gdb.python/py-unwind-maint.c: New file.
 >     * gdb.python/py-unwind-maint.exp: New test.
 >     * gdb.python/py-unwind-maint.py: New file.
 >     * gdb.python/py-unwind.c: New file.
 >     * gdb.python/py-unwind.exp: New test.
 >     * gdb.python/py-unwind.py: New test.

Hi. Just a few more nits.

 > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
 > index dbace2d..0bd3738 100644
 > --- a/gdb/Makefile.in
 > +++ b/gdb/Makefile.in
 > @@ -398,6 +398,7 @@ SUBDIR_PYTHON_OBS = \
 >  	py-symtab.o \
 >  	py-threadevent.o \
 >  	py-type.o \
 > +	py-unwind.o \
 >  	py-utils.o \
 >  	py-value.o \
 >  	py-varobj.o
 > @@ -437,6 +438,7 @@ SUBDIR_PYTHON_SRCS = \
 >  	python/py-symtab.c \
 >  	python/py-threadevent.c \
 >  	python/py-type.c \
 > +	python/py-unwind.c \
 >  	python/py-utils.c \
 >  	python/py-value.c \
 >  	python/py-varobj.c
 > @@ -2622,6 +2624,10 @@ py-type.o: $(srcdir)/python/py-type.c
 >  	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-type.c
 >  	$(POSTCOMPILE)
 >  
 > +py-unwind.o: $(srcdir)/python/py-unwind.c
 > +	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-unwind.c
 > +	$(POSTCOMPILE)
 > +
 >  py-utils.o: $(srcdir)/python/py-utils.c
 >  	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-utils.c
 >  	$(POSTCOMPILE)
 > diff --git a/gdb/NEWS b/gdb/NEWS
 > index bda4a35..ac994d9 100644
 > --- a/gdb/NEWS
 > +++ b/gdb/NEWS
 > @@ -12,6 +12,7 @@
 >    ** gdb.Objfile objects have a new attribute "username",
 >       which is the name of the objfile as specified by the user,
 >       without, for example, resolving symlinks.
 > +  ** You can now write frame unwinders in Python.
 >  
 >  * New commands
 >  
 > diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
 > index c01b86d..30cfd17 100644
 > --- a/gdb/data-directory/Makefile.in
 > +++ b/gdb/data-directory/Makefile.in
 > @@ -62,11 +62,13 @@ PYTHON_FILE_LIST = \
 >  	gdb/FrameDecorator.py \
 >  	gdb/types.py \
 >  	gdb/printing.py \
 > +	gdb/unwinder.py \
 >  	gdb/prompt.py \
 >  	gdb/xmethod.py \
 >  	gdb/command/__init__.py \
 >  	gdb/command/xmethods.py \
 >  	gdb/command/frame_filters.py \
 > +	gdb/command/unwinders.py \
 >  	gdb/command/type_printers.py \
 >  	gdb/command/pretty_printers.py \
 >  	gdb/command/prompt.py \
 > diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
 > index d725eb0..b837022 100644
 > --- a/gdb/doc/python.texi
 > +++ b/gdb/doc/python.texi
 > @@ -144,6 +144,7 @@ optional arguments while skipping others.  Example:
 >  * Frame Filter API::            Filtering Frames.
 >  * Frame Decorator API::         Decorating Frames.
 >  * Writing a Frame Filter::      Writing a Frame Filter.
 > +* Unwinding Frames in Python::  Writing frame unwinder.
 >  * Xmethods In Python::          Adding and replacing methods of C++ classes.
 >  * Xmethod API::                 Xmethod types.
 >  * Writing an Xmethod::          Writing an xmethod.
 > @@ -2178,6 +2179,141 @@ printed hierarchically.  Another approach would be to combine the
 >  marker in the inlined frame, and also show the hierarchical
 >  relationship.
 >  
 > +@node Unwinding Frames in Python
 > +@subsubsection Unwinding Frames in Python
 > +@cindex Unwinding frames in Python.
 > +
 > +In GDB terminology ``unwinding'' is the process of finding the
 > +previous frame (that is, caller's) from the current one. An unwinder
 > +has three methods. The first one checks if it can handle given frame
 > +(``sniff'' it). For the frames it can sniff an unwinder provides two
 > +additional methods: it can return frame's ID, and it can fetch
 > +registers from the previous frame. A running GDB mantains a list of
 > +the unwinders and calls each unwinder's sniffer in turn until it finds
 > +the one that recognizes the current frame. There is an API to register
 > +an unwinder.
 > +
 > +The unwinders that come with GDB handle standard frames for each
 > +platform where GDB is running. However, mixed language applications
 > +(for example, and application running Java Virtual Machine) sometimes
 > +use frame layouts that cannot be handled by the GDB unwinders. You can
 > +write Python code that can handle such custom frames.
 > +
 > +You implement a frame unwinder in Python as a class with which has two
 > +attributes, @code{name} and @code{enabled}, with obvious meanings, and
 > +a single method @code{__call__}, which examines a given frame and
 > +returns an object (an instance of gdb.UnwindInfo class) describing
 > +it. If an unwinder does not recognize a frame, it should return
 > +@code{None}. The code in GDB that enables writing unwinders in Python
 > +uses this object to return frame's ID and previous frame registers
 > +when GDB core asks for them.
 > +
 > +@subheading Unwinder Input
 > +
 > +An object passed to an unwinder (a @code{PendingFrame} instance)
 > +provides a method to read frame's registers:
 > +
 > +@defun PendingFrame.read_register (reg)
 > +This method returns the contents of the register @var{regn} in the
 > +frame as a @code{gdb.Value} object. @var{reg} can be either a register
 > +number or a register name; the values are platform-specific. They are
 > +usually found in the corresponding xxx-@code{tdep.h} file in the gdb
 > +source tree.
 > +@end defun
 > +
 > +It also provides a factory method to create a gdb.UnwindInfo instance
 > +to be returned to @value{GDBN}:
 > +
 > +@defun PendingFrame.create_unwind_info (frame_id)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{frame_id}.  The argument is used to build @value{GDBN}'s frame ID
 > +using one of the @code{frame_id_build_xxx} functions.  The exact
 > +function to be used is determined by @var{frame_id}'s attributes.  It
 > +should always have @code{sp} attribute. If it has @code{pc} and
 > +@code{special} attributes, the frame ID is built by invoking
 > +@code{frame_build_id_special (frame_id.sp, frame_id.pc,
 > +frame_id.special)}. If it has only @code{pc} attribute, the frame ID
 > +is built by invoking @code{frame_build_id (frame_id.sp, frame_id.pc)}
 > +(this is the most common case). Finally, if neither @code{pc} nor
 > +@code{special} are available, the frame ID is built by invoking
 > +@code{frame_build_id_wild (frame_id.sp)}. The attribute values should
 > +be @code{gdb.Value} objects.
 > +@end defun
 > +
 > +@subheading Unwinder Output: UnwindInfo
 > +
 > +A @code{gdb.UnwindInfo} object can be constructed by one of the
 > +methods described above. Use the following method to set the caller
 > +frame's registers:
 > +
 > +@defun gdb.UnwindInfo.add_saved_register (reg, value)
 > +@var{reg} identifies the register. It can be a number or a name, just
 > +as for the @code{PendingFrame.read_register} method above. @var{value}
 > +is a register value (a @code{gdb.Value} object).
 > +@end defun
 > +
 > +@subheading Unwinder Skeleton Code
 > +
 > +GDB comes with the module containing the base @code{Unwinder} class.
 > +Derive your unwinder class from it and structure the code as follows:
 > +
 > +@smallexample
 > +from gdb.unwinders import Unwinder
 > +
 > +class FrameId(object):
 > +    def __init__(self, sp, pc):
 > +        self.sp = sp
 > +        self.pc = pc
 > +
 > +
 > +class MyUnwinder(Unwinder):
 > +    def __init__(....):
 > +        supe(MyUnwinder, self).__init___(<expects unwinder name argument>)
 > +
 > +    def __call__(pending_frame):
 > +        if not <we recognize frame>:
 > +            return None
 > +        # Create UnwindInfo. Usually the frame is identified by stack pointer
 > +        # and program counter.
 > +        sp = pending_frame.read_register(<SP number>)
 > +        pc = pending_frame.read_register(<PC number>)
 > +        unwind_info = pending_frame.create_unwind_info(FrameId(sp, pc))
 > +
 > +        # Find the values of the registers in the caller's frame and 
 > +        # save them in the result:
 > +        unwind_info.add_saved_register(<register>, <value>)
 > +        ....
 > +
 > +        # Return the result:
 > +        return unwind_instance
 > +
 > +@end smallexample
 > +
 > +@subheading Registering a Unwinder
 > +
 > +An object file, a program space, and the @value{GDBN} proper can have
 > +unwinders registered with it.
 > +
 > +The @code{gdb.unwinders} module provides the function to register a
 > +unwinder:
 > +
 > +@defun gdb.unwinder.register_unwinder (locus, unwinder, replace=False)
 > +@var{locus} is specifies an object file or a program space to which
 > +@var{unwinder} is added. Passing @code{None} or @code{gdb} adds
 > +@var{unwinder} to the @value{GDBN}'s global unwinder list.  The newly
 > +added @var{unwinder} will be called before any other unwinder from the
 > +same locus.  Two unwinders in the same locus cannot have the same
 > +name. An attempt to add a unwinder with already existing name raises an
 > +exception unless @var{replace} is @code{True}, in which case the old
 > +unwinder is deleted.
 > +@end defun
 > +
 > +@subheading Unwinder Precedence
 > +
 > +@value{GDBN} first calls the unwinders from all the object files in no
 > +particular order, then the unwinders from the current program space,
 > +and finally the unwinders from @value{GDBN}.
 > +
 >  @node Xmethods In Python
 >  @subsubsection Xmethods In Python
 >  @cindex xmethods in Python
 > diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
 > index 92b06f2..fd2a215 100644
 > --- a/gdb/python/lib/gdb/__init__.py
 > +++ b/gdb/python/lib/gdb/__init__.py
 > @@ -71,6 +71,42 @@ type_printers = []
 >  xmethods = []
 >  # Initial frame filters.
 >  frame_filters = {}
 > +# Initial frame unwinders.
 > +frame_unwinders = []
 > +
 > +def execute_unwinders(pending_frame):
 > +    """Internal function called from GDB to execute all unwinders.
 > +
 > +    Runs each currently enabled unwinder until it finds the one that
 > +    can unwind given frame.
 > +
 > +    Arguments:
 > +        pending_frame: gdb.PendingFrame instance.
 > +    Returns:
 > +        UnwindInfo instance or None.

s/UnwindInfo/gdb.UnwindInfo/

 > +    """
 > +    for objfile in objfiles():

    for objfile in _gdb.objfiles():

 > +        for unwinder in objfile.frame_unwinders:
 > +            if unwinder.enabled:
 > +                unwind_info = unwinder(pending_frame)
 > +                if unwind_info is not None:
 > +                    return unwind_info
 > +
 > +    current_progspace = _gdb.current_progspace()
 > +    for unwinder in current_progspace.frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder(pending_frame)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    for unwinder in frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder(pending_frame)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    return None
 > +
 >  
 >  # Convenience variable to GDB's python directory
 >  PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
 > diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py
 > new file mode 100644
 > index 0000000..aa189fe
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/command/unwinders.py
 > @@ -0,0 +1,198 @@
 > +# Unwinder commands.
 > +# Copyright 2015 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 re
 > +
 > +
 > +def validate_regexp(exp, idstring):
 > +    try:
 > +        return re.compile(exp)
 > +    except SyntaxError:
 > +        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
 > +
 > +
 > +def parse_unwinder_command_args(arg):
 > +    """Internal utility to parse unwinder command argv.
 > +
 > +    Arguments:
 > +        arg: The arguments to the command. The format is:
 > +             [locus-regexp [name-regexp]]
 > +
 > +    Returns:
 > +        A 2-tuple of compiled regular expressions.
 > +
 > +    Raises:
 > +        SyntaxError: an error processing ARG
 > +    """
 > +
 > +    argv = gdb.string_to_argv(arg)
 > +    argc = len(argv)
 > +    if argc > 2:
 > +        raise SyntaxError("Too many arguments.")
 > +    locus_regexp = ""
 > +    name_regexp = ""
 > +    if argc >= 1:
 > +        locus_regexp = argv[0]
 > +        if argc >= 2:
 > +            name_regexp = argv[1]
 > +    return (validate_regexp(locus_regexp, "locus"),
 > +            validate_regexp(name_regexp, "unwinder"))
 > +
 > +
 > +class InfoUnwinder(gdb.Command):
 > +    """GDB command to list unwinders.
 > +
 > +    Usage: info unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression matching the location of the
 > +    unwinder.  If it is omitted, all registered unwinders from all
 > +    loci are listed.  A locus can be 'global', 'progspace' to list
 > +    the unwinders from the current progspace, or a regular expression
 > +    matching filenames of objfiles.
 > +
 > +    NAME-REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are listed.
 > +    """
 > +
 > +    def __init__(self):
 > +        super(InfoUnwinder, self).__init__("info unwinder",
 > +                                            gdb.COMMAND_DATA)

gdb.COMMAND_STACK

 > +
 > +    def list_unwinders(self, title, unwinders, name_re):
 > +        """Lists the unwinders whose name matches regexp.
 > +
 > +        Arguments:
 > +            title: The line to print before the list.
 > +            unwinders: The list of the unwinders.
 > +            name_re: unwinder name filter.
 > +        """
 > +        if not unwinders:
 > +            return
 > +        print title
 > +        for unwinder in unwinders:
 > +            if name_re.match(unwinder.name):
 > +                print("  %s%s" % ("" if unwinder.enabled else "[disabled] ",
 > +                                  unwinder.name))

The "info pretty-printer" command prints "[disabled]" after the name.
Do the same here.

 > +
 > +    def invoke(self, arg, from_tty):
 > +        locus_re, name_re = parse_unwinder_command_args(arg)
 > +        if locus_re.match("global"):
 > +            self.list_unwinders("Global:", gdb.frame_unwinders,
 > +                                name_re)
 > +        if locus_re.match("progspace"):
 > +            cp = gdb.current_progspace()
 > +            self.list_unwinders("Progspace %s:" % cp.filename,
 > +                                cp.frame_unwinders, name_re)
 > +        for objfile in gdb.objfiles():
 > +            if locus_re.match(objfile.filename):
 > +                self.list_unwinders("Objfile %s:" % objfile.filename,
 > +                                    objfile.frame_unwinders, name_re)
 > +
 > +
 > +def do_enable_unwinder1(unwinders, name_re, flag):
 > +    """Enable/disable unwinders whose names match given regex.
 > +
 > +    Arguments:
 > +        unwinders: The list of unwinders.
 > +        name_re: Unwinder name filter.
 > +        flag: Enable/disable.
 > +
 > +    Returns:
 > +        The number of unwinders affected.
 > +    """
 > +    total = 0
 > +    for unwinder in unwinders:
 > +        if name_re.match(unwinder.name):
 > +            unwinder.enabled = flag
 > +            total += 1
 > +    return total
 > +
 > +
 > +def do_enable_unwinder(arg, flag):
 > +    """Enable/disable unwinder(s)."""
 > +    (locus_re, name_re) = parse_unwinder_command_args(arg)
 > +    total = 0
 > +    if locus_re.match("global"):
 > +        total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag)
 > +    if locus_re.match("progspace"):
 > +        total += do_enable_unwinder1(gdb.current_progspace().frame_unwinders,
 > +                                     name_re, flag)
 > +    for objfile in gdb.objfiles():
 > +        if locus_re.match(objfile.filename):
 > +            total += do_enable_unwinder1(objfile.frame_unwinders, name_re,
 > +                                         flag)
 > +    print("%d unwinder%s %s" % (total, "" if total == 1 else "s",
 > +                                "enabled" if flag else "disabled"))
 > +
 > +
 > +class EnableUnwinder(gdb.Command):
 > +    """GDB command to enable unwinders.
 > +
 > +    Usage: enable unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression specifying the unwinders to
 > +    enable.  It can 'global', 'progspace', or the name of an objfile
 > +    within that progspace.
 > +
 > +    NAME_REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are affected.
 > +
 > +    """
 > +
 > +    def __init__(self):
 > +        super(EnableUnwinder, self).__init__("enable unwinder",
 > +                                             gdb.COMMAND_STACK)
 > +
 > +    def invoke(self, arg, from_tty):
 > +        """GDB calls this to perform the command."""
 > +        do_enable_unwinder(arg, True)
 > +
 > +
 > +class DisableUnwinder(gdb.Command):
 > +    """GDB command to disable the specified unwinder.
 > +
 > +    Usage: disable unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression specifying the unwinders to
 > +    disable.  It can 'global', 'progspace', or the name of an objfile
 > +    within that progspace.
 > +
 > +    NAME_REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are affected.
 > +
 > +    """
 > +
 > +    def __init__(self):
 > +        super(DisableUnwinder, self).__init__("disable unwinder",
 > +                                              gdb.COMMAND_STACK)
 > +
 > +    def invoke(self, arg, from_tty):
 > +        """GDB calls this to perform the command."""
 > +        do_enable_unwinder(arg, False)
 > +
 > +
 > +def register_unwinder_commands():
 > +    """Installs the unwinder commands."""
 > +    InfoUnwinder()
 > +    EnableUnwinder()
 > +    DisableUnwinder()
 > +
 > +
 > +register_unwinder_commands()
 > diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
 > new file mode 100644
 > index 0000000..0d15894
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/unwinder.py
 > @@ -0,0 +1,93 @@
 > +# Copyright (C) 2015 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/>.
 > +
 > +"""Unwinder class and register_unwinder function."""
 > +
 > +import gdb
 > +
 > +
 > +class Unwinder(object):
 > +    """Base class (or a template) for frame unwinders written in Python.
 > +
 > +    An unwinder has a single method __call__ and the attributes
 > +    described below.
 > +
 > +    Attributes:
 > +        name: The name of the unwinder.
 > +        enabled: A boolean indicating whether the unwinder is enabled.
 > +    """
 > +
 > +    def __init__(self, name):
 > +        """Constructor.
 > +
 > +        Args:
 > +            name: An identifying name for the unwinder.
 > +        """
 > +        self.name = name
 > +        self.enabled = True
 > +
 > +    def __call__(self, pending_frame):
 > +        """GDB calls this method to unwind a frame.
 > +
 > +        Arguments:
 > +            pending_frame: gdb.PendingFrame instance.
 > +
 > +        Returns:
 > +            gdb.UnwindInfo instance.
 > +        """
 > +        raise NotImplementedError("Unwinder __call__.")
 > +
 > +
 > +def register_unwinder(locus, unwinder, replace=False):
 > +    """Register unwinder in given locus.
 > +
 > +    The unwinder is prepended to the locus's unwinders list. Unwinder
 > +    name should be unique.
 > +
 > +    Arguments:
 > +        locus: Either an objfile, progspace, or None (in which case
 > +               the unwinder is registered globally).
 > +        unwinder: An object of a gdb.Unwinder subclass
 > +        replace: If True, replaces existing unwinder with the same name.
 > +                 Otherwise, raises exception if unwinder with the same
 > +                 name already exists.
 > +
 > +    Returns:
 > +        Nothing.
 > +
 > +    Raises:
 > +        RuntimeError: Unwinder name is not unique.

Also raises TypeError for bad locus.

 > +

remove blank line

 > +    """
 > +    if locus is None:
 > +        if gdb.parameter("verbose"):
 > +            gdb.write("Registering global %s unwinder ...\n" % unwinder.name)
 > +        locus = gdb
 > +    elif isinstance(locus, gdb.Objfile) or isinstance(locus, gdb.Progspace):
 > +        if gdb.parameter("verbose"):
 > +            gdb.write("Registering %s unwinder for %s ...\n" %
 > +                      (unwinder.name, locus.filename))
 > +    else:
 > +        raise TypeError("locus should be gdb.Ojbfile or gdb.Progspace")

gdb.Objfile
Plus should also mention locus can be None.

 > +
 > +    i = 0
 > +    for needle in locus.frame_unwinders:
 > +        if needle.name == unwinder.name:
 > +            if replace:
 > +                del locus.frame_unwinders[i]
 > +            else:
 > +                raise RuntimeError("Unwinder %s already exists." % unwinder.name)
 > +        i += 1
 > +    locus.frame_unwinders.insert(0, unwinder)
 > diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
 > index 157d200..c9528c3 100644
 > --- a/gdb/python/py-objfile.c
 > +++ b/gdb/python/py-objfile.c
 > @@ -42,6 +42,10 @@ typedef struct
 >  
 >    /* The frame filter list of functions.  */
 >    PyObject *frame_filters;
 > +
 > +  /* The list of frame unwinders.  */
 > +  PyObject *frame_unwinders;
 > +
 >    /* The type-printer list.  */
 >    PyObject *type_printers;
 >  
 > @@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o)
 >    Py_XDECREF (self->dict);
 >    Py_XDECREF (self->printers);
 >    Py_XDECREF (self->frame_filters);
 > +  Py_XDECREF (self->frame_unwinders);
 >    Py_XDECREF (self->type_printers);
 >    Py_XDECREF (self->xmethods);
 >    Py_TYPE (self)->tp_free (self);
 > @@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self)
 >    if (self->frame_filters == NULL)
 >      return 0;
 >  
 > +  self->frame_unwinders = PyList_New (0);
 > +  if (self->frame_unwinders == NULL)
 > +    return 0;
 > +
 >    self->type_printers = PyList_New (0);
 >    if (self->type_printers == NULL)
 >      return 0;
 > @@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
 >    return 0;
 >  }
 >  
 > +/* Return the frame unwinders attribute for this object file.  */
 > +
 > +PyObject *
 > +objfpy_get_frame_unwinders (PyObject *o, void *ignore)
 > +{
 > +  objfile_object *self = (objfile_object *) o;
 > +
 > +  Py_INCREF (self->frame_unwinders);
 > +  return self->frame_unwinders;
 > +}
 > +
 > +/* Set this object file's frame unwinders list to UNWINDERS.  */
 > +
 > +static int
 > +objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
 > +{
 > +  PyObject *tmp;
 > +  objfile_object *self = (objfile_object *) o;
 > +
 > +  if (!unwinders)
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       _("Cannot delete the frame unwinders attribute."));
 > +      return -1;
 > +    }
 > +
 > +  if (!PyList_Check (unwinders))
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       _("The frame_unwinders attribute must be a list."));
 > +      return -1;
 > +    }
 > +
 > +  /* Take care in case the LHS and RHS are related somehow.  */
 > +  tmp = self->frame_unwinders;
 > +  Py_INCREF (unwinders);
 > +  self->frame_unwinders = unwinders;
 > +  Py_XDECREF (tmp);
 > +
 > +  return 0;
 > +}
 > +
 >  /* Get the 'type_printers' attribute.  */
 >  
 >  static PyObject *
 > @@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] =
 >      "Pretty printers.", NULL },
 >    { "frame_filters", objfpy_get_frame_filters,
 >      objfpy_set_frame_filters, "Frame Filters.", NULL },
 > +  { "frame_unwinders", objfpy_get_frame_unwinders,
 > +    objfpy_set_frame_unwinders, "Frame Unwinders", NULL },
 >    { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
 >      "Type printers.", NULL },
 >    { "xmethods", objfpy_get_xmethods, NULL,
 > diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
 > index 93fbc14..17da3d1 100644
 > --- a/gdb/python/py-progspace.c
 > +++ b/gdb/python/py-progspace.c
 > @@ -41,6 +41,10 @@ typedef struct
 >  
 >    /* The frame filter list of functions.  */
 >    PyObject *frame_filters;
 > +
 > +  /* The frame unwinder list.  */
 > +  PyObject *frame_unwinders;
 > +
 >    /* The type-printer list.  */
 >    PyObject *type_printers;
 >  
 > @@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self)
 >    Py_XDECREF (ps_self->dict);
 >    Py_XDECREF (ps_self->printers);
 >    Py_XDECREF (ps_self->frame_filters);
 > +  Py_XDECREF (ps_self->frame_unwinders);
 >    Py_XDECREF (ps_self->type_printers);
 >    Py_XDECREF (ps_self->xmethods);
 >    Py_TYPE (self)->tp_free (self);
 > @@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self)
 >    if (self->frame_filters == NULL)
 >      return 0;
 >  
 > +  self->frame_unwinders = PyList_New (0);
 > +  if (self->frame_unwinders == NULL)
 > +    return 0;
 > +
 >    self->type_printers = PyList_New (0);
 >    if (self->type_printers == NULL)
 >      return 0;
 > @@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
 >    return 0;
 >  }
 >  
 > +/* Return the list of the frame unwinders for this program space.  */
 > +
 > +PyObject *
 > +pspy_get_frame_unwinders (PyObject *o, void *ignore)
 > +{
 > +  pspace_object *self = (pspace_object *) o;
 > +
 > +  Py_INCREF (self->frame_unwinders);
 > +  return self->frame_unwinders;
 > +}
 > +
 > +/* Set this program space's list of the unwinders to UNWINDERS.  */
 > +
 > +static int
 > +pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
 > +{
 > +  PyObject *tmp;
 > +  pspace_object *self = (pspace_object *) o;
 > +
 > +  if (!unwinders)
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       "cannot delete the frame unwinders list");
 > +      return -1;
 > +    }
 > +
 > +  if (!PyList_Check (unwinders))
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       "the frame unwinders attribute must be a list");
 > +      return -1;
 > +    }
 > +
 > +  /* Take care in case the LHS and RHS are related somehow.  */
 > +  tmp = self->frame_unwinders;
 > +  Py_INCREF (unwinders);
 > +  self->frame_unwinders = unwinders;
 > +  Py_XDECREF (tmp);
 > +
 > +  return 0;
 > +}
 > +
 >  /* Get the 'type_printers' attribute.  */
 >  
 >  static PyObject *
 > @@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] =
 >      "Pretty printers.", NULL },
 >    { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
 >      "Frame filters.", NULL },
 > +  { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders,
 > +    "Frame unwinders.", NULL },
 >    { "type_printers", pspy_get_type_printers, pspy_set_type_printers,
 >      "Type printers.", NULL },
 >    { "xmethods", pspy_get_xmethods, NULL,
 > diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
 > new file mode 100644
 > index 0000000..3610871
 > --- /dev/null
 > +++ b/gdb/python/py-unwind.c
 > @@ -0,0 +1,782 @@
 > +/* Python frame unwinder interface.
 > +
 > +   Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +   This file is part of GDB.
 > +
 > +   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/>.  */
 > +
 > +#include "defs.h"
 > +#include "arch-utils.h"
 > +#include "frame-unwind.h"
 > +#include "gdb_obstack.h"
 > +#include "gdbcmd.h"
 > +#include "language.h"
 > +#include "observer.h"
 > +#include "python-internal.h"
 > +#include "regcache.h"
 > +#include "valprint.h"
 > +#include "user-regs.h"
 > +
 > +#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
 > +  { fprintf_unfiltered (gdb_stdlog, args); }
 > +
 > +typedef struct
 > +{
 > +  PyObject_HEAD
 > +
 > +  /* Frame we are unwinding.  */
 > +  struct frame_info *frame_info;
 > +
 > +  /* Its architecture, passed by the sniffer caller.  */
 > +  struct gdbarch *gdbarch;
 > +} pending_frame_object;
 > +
 > +/* Saved registers array item.  */

blank line

 > +typedef struct
 > +{
 > +  int number;
 > +  PyObject *value;
 > +} saved_reg;
 > +DEF_VEC_O (saved_reg);
 > +
 > +/* The data we keep for the PyUnwindInfo: pending_frame, saved registers
 > +   and frame ID.  */
 > +
 > +typedef struct
 > +{
 > +  PyObject_HEAD
 > +
 > +  /* gdb.PendingFrame for the frame we are unwinding.  */
 > +  PyObject *pending_frame;
 > +
 > +  /* Its ID.  */
 > +  struct frame_id frame_id;
 > +
 > +  /* Saved registers array.  */
 > +  VEC (saved_reg) *saved_regs;
 > +} unwind_info_object;
 > +
 > +/* The data we keep for a frame we can unwind: frame ID and an array of
 > +   (register_number, register_value) pairs.  */
 > +
 > +typedef struct
 > +{
 > +  /* Frame ID.  */
 > +  struct frame_id frame_id;
 > +
 > +  /* GDB Architecture.  */
 > +  struct gdbarch *gdbarch;
 > +
 > +  /* Length of the `reg' array below.  */
 > +  int reg_count;
 > +
 > +  struct reg_info
 > +  {
 > +    /* Register number.  */
 > +    int number;
 > +
 > +    /* Register data bytes pointer.  */
 > +    gdb_byte data[MAX_REGISTER_SIZE];
 > +  } reg[];
 > +} cached_frame_info;
 > +
 > +static PyTypeObject pending_frame_object_type
 > +    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("pending_frame_object");
 > +
 > +static PyTypeObject unwind_info_object_type
 > +    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
 > +
 > +static unsigned int pyuw_debug = 0;
 > +
 > +static struct gdbarch_data *pyuw_gdbarch_data;
 > +
 > +/* Parses register id, which can be either a number or a name.
 > +   Returns 1 on success, 0 otherwise.  */
 > +
 > +static int
 > +pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
 > +                        int *reg_num)
 > +{
 > +  if (pyo_reg_id == NULL)
 > +    return 0;
 > +  if (PyString_Check (pyo_reg_id))
 > +    {
 > +      const char *reg_name = PyString_AS_STRING (pyo_reg_id);

There are some unicode and python-2-vs-3 issues with strings.
I suggest using gdbpy_is_string and gdbpy_obj_to_string
from gdb/python/py-utils.c here.
[Sorry for not catching this earlier.]

 > +
 > +      if (reg_name == NULL)
 > +        return 0;
 > +      *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name,
 > +                                              strlen (reg_name));
 > +      return *reg_num >= 0;
 > +    }
 > +  else if (PyInt_Check (pyo_reg_id))
 > +    {
 > +      long value;
 > +      if (gdb_py_int_as_long (pyo_reg_id, &value) && (int) value == value)
 > +        {
 > +          *reg_num = (int) value;
 > +          return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
 > +        }
 > +    }
 > +  return 0;
 > +}
 > +
 > +/* Convert gdb.Value instance to inferior's pointer.  Return 1 on success,
 > +   0 on failure.  */
 > +
 > +static int
 > +pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
 > +{
 > +  int rc = 0;
 > +  volatile struct gdb_exception except;

Delete definition of except.
With the addition of END_CATCH it's no longer needed.

 > +  struct value *value;
 > +
 > +  TRY
 > +    {
 > +      if ((value = value_object_to_value (pyo_value)) != NULL)
 > +        {
 > +          *addr = unpack_pointer (value_type (value),
 > +                                  value_contents (value));
 > +          rc = 1;
 > +        }
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      if (except.reason < 0)

There's no longer a need to test except.reason < 0.
Just call gdbpy_convert_exception unconditionally here.

 > +        gdbpy_convert_exception (except);
 > +    }
 > +  END_CATCH
 > +  return rc;
 > +}
 > +
 > +/* Get attribute from an object and convert it to the inferior's
 > +   pointer value.  Return 1 if attribute exists and its value can be
 > +   converted.  Otherwise, if attribute does not exist or its value is
 > +   None, return 0.  In all other cases set Python error and return
 > +   0.  */
 > +
 > +static int
 > +pyuw_object_attribute_to_pointer (PyObject *pyo, const char *attr_name,
 > +                                  CORE_ADDR *addr)
 > +{
 > +  int rc = 0;
 > +
 > +  if (PyObject_HasAttrString (pyo, attr_name))
 > +    {
 > +      PyObject *pyo_value = PyObject_GetAttrString (pyo, attr_name);
 > +      struct value *value;
 > +
 > +      if (pyo_value != NULL && pyo_value != Py_None)
 > +        {
 > +          rc = pyuw_value_obj_to_pointer (pyo_value, addr);
 > +          if (!rc)
 > +            PyErr_Format (
 > +                PyExc_ValueError,
 > +                _("The value of the '%s' attribute is not a pointer."),
 > +                attr_name);
 > +        }
 > +      Py_XDECREF (pyo_value);
 > +    }
 > +  return rc;
 > +}
 > +
 > +/* Called by the Python interpreter to obtain string representation
 > +   of the UnwindInfo object.  */
 > +
 > +static PyObject *
 > +unwind_infopy_str (PyObject *self)
 > +{
 > +  struct ui_file *strfile = mem_fileopen ();
 > +  unwind_info_object *unwind_info = (unwind_info_object *) self;
 > +  pending_frame_object *pending_frame
 > +      = (pending_frame_object *) (unwind_info->pending_frame);
 > +  PyObject *result;
 > +
 > +  fprintf_unfiltered (strfile, "Frame ID: ");
 > +  fprint_frame_id (strfile, unwind_info->frame_id);
 > +  {
 > +    char *sep = "";
 > +    int i;
 > +    struct value_print_options opts;
 > +    saved_reg *reg;
 > +    volatile struct gdb_exception except;
 > +
 > +    get_user_print_options (&opts);
 > +    fprintf_unfiltered (strfile, "\nSaved registers: (");
 > +    for (i = 0;
 > +         i < VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg);
 > +         i++)
 > +      {
 > +        struct value *value = value_object_to_value (reg->value);
 > +
 > +        fprintf_unfiltered (strfile, "%s(%d, ", sep, reg->number);
 > +        if (value != NULL)
 > +          {
 > +            TRY
 > +              {
 > +                value_print (value, strfile, &opts);
 > +                fprintf_unfiltered (strfile, ")");
 > +              }
 > +            CATCH (except, RETURN_MASK_ALL)
 > +              {
 > +                GDB_PY_HANDLE_EXCEPTION (except);
 > +              }
 > +            END_CATCH
 > +          }
 > +        else
 > +          fprintf_unfiltered (strfile, "<BAD>)");
 > +        sep = ", ";
 > +      }
 > +    fprintf_unfiltered (strfile, ")");
 > +  }
 > +  {
 > +    char *s = ui_file_xstrdup (strfile, NULL);
 > +
 > +    result = PyString_FromString (s);
 > +    xfree (s);
 > +  }
 > +  ui_file_delete (strfile);
 > +  return result;
 > +}
 > +
 > +/* Create UnwindInfo instance for given PendingFrame and frame ID.
 > +   Sets Python error and returns NULL on error.  */
 > +
 > +static PyObject *
 > +pyuw_create_unwind_info (PyObject *pyo_pending_frame,
 > +                         struct frame_id frame_id)
 > +{
 > +  unwind_info_object *unwind_info
 > +      = PyObject_New (unwind_info_object, &unwind_info_object_type);
 > +
 > +  if (((pending_frame_object *) pyo_pending_frame)->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "Attempting to use stale PendingFrame");
 > +      return NULL;
 > +    }
 > +  unwind_info->frame_id = frame_id;
 > +  Py_INCREF (pyo_pending_frame);
 > +  unwind_info->pending_frame = pyo_pending_frame;
 > +  unwind_info->saved_regs = VEC_alloc (saved_reg, 4);
 > +  return (PyObject *) unwind_info;
 > +}
 > +
 > +/* The implementation of
 > +   gdb.UnwindInfo.add_saved_register (REG, VALUE) -> None.  */
 > +
 > +static PyObject *
 > +unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
 > +{
 > +  unwind_info_object *unwind_info = (unwind_info_object *) self;
 > +  pending_frame_object *pending_frame
 > +      = (pending_frame_object *) (unwind_info->pending_frame);
 > +  PyObject *pyo_reg_id;
 > +  PyObject *pyo_reg_value;
 > +  int regnum;
 > +
 > +  if (pending_frame->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "UnwindInfo instance refers to a stale PendingFrame");
 > +      return NULL;
 > +    }
 > +  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
 > +                          &pyo_reg_id, &pyo_reg_value))
 > +    return NULL;
 > +  if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
 > +    {
 > +      PyErr_SetString (PyExc_ValueError, "Bad register");
 > +      return NULL;
 > +    }
 > +  {
 > +    struct value *value;
 > +    size_t data_size;
 > +
 > +    if (pyo_reg_value == NULL
 > +      || (value = value_object_to_value (pyo_reg_value)) == NULL)
 > +      {
 > +        PyErr_SetString (PyExc_ValueError, "Bad register value");
 > +        return NULL;
 > +      }
 > +    data_size = register_size (pending_frame->gdbarch, regnum);
 > +    if (data_size != TYPE_LENGTH (value_type (value)))
 > +      {
 > +        PyErr_Format (
 > +            PyExc_ValueError,
 > +            "The value of the register returned by the Python "
 > +            "sniffer has unexpected size: %u instead of %u.",
 > +            (unsigned) (TYPE_LENGTH (value_type (value))),

unnecessary parens around TYPE_LENGTH().

 > +            (unsigned) data_size);
 > +        return NULL;
 > +      }
 > +  }
 > +  {
 > +    int i;
 > +    saved_reg *reg;
 > +
 > +    for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
 > +      {
 > +        if (regnum == reg->number)
 > +          {
 > +            Py_DECREF (reg->value);
 > +            break;
 > +          }
 > +      }
 > +    if (reg == NULL)
 > +      {
 > +        reg = VEC_safe_push (saved_reg, unwind_info->saved_regs, NULL);
 > +        reg->number = regnum;
 > +      }
 > +    Py_INCREF (pyo_reg_value);
 > +    reg->value = pyo_reg_value;
 > +  }
 > +  Py_RETURN_NONE;
 > +}
 > +
 > +/* UnwindInfo cleanup.  */
 > +
 > +static void
 > +unwind_infopy_dealloc (PyObject *self)
 > +{
 > +  unwind_info_object *unwind_info = (unwind_info_object *) self;
 > +  int i;
 > +  saved_reg *reg;
 > +
 > +  Py_XDECREF (unwind_info->pending_frame);
 > +  for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
 > +      Py_DECREF (reg->value);
 > +  VEC_free (saved_reg, unwind_info->saved_regs);
 > +  Py_TYPE (self)->tp_free (self);
 > +}
 > +
 > +/* Called by the Python interpreter to obtain string representation
 > +   of the PendingFrame object.  */
 > +
 > +static PyObject *
 > +pending_framepy_str (PyObject *self)
 > +{
 > +  volatile struct gdb_exception except;

Delete definition of except.

 > +  struct frame_info *frame = ((pending_frame_object *) self)->frame_info;
 > +  const char *sp_str = NULL;
 > +  const char *pc_str = NULL;
 > +
 > +  if (frame == NULL)
 > +    return PyString_FromString ("Stale PendingFrame instance");
 > +  TRY
 > +    {
 > +      sp_str = core_addr_to_string_nz (get_frame_sp (frame));
 > +      pc_str = core_addr_to_string_nz (get_frame_pc (frame));
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      GDB_PY_HANDLE_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  return PyString_FromFormat ("SP=%s,PC=%s", sp_str, pc_str);
 > +}
 > +
 > +/* Implementation of gdb.PendingFrame.read_register (self, reg) -> gdb.Value.
 > +   Returns the value of register REG as gdb.Value instance.  */
 > +
 > +static PyObject *
 > +pending_framepy_read_register (PyObject *self, PyObject *args)
 > +{
 > +  pending_frame_object *pending_frame = (pending_frame_object *) self;
 > +  volatile struct gdb_exception except;

Delete definition of except.

 > +  struct value *val = NULL;
 > +  int regnum;
 > +  PyObject *pyo_reg_id;
 > +
 > +  if (pending_frame->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "Attempting to read register from stale PendingFrame");
 > +      return NULL;
 > +    }
 > +  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
 > +    return NULL;
 > +  if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
 > +    {
 > +      PyErr_SetString (PyExc_ValueError, "Bad register");
 > +      return NULL;
 > +    }
 > +
 > +  TRY
 > +    {
 > +      val = get_frame_register_value (pending_frame->frame_info, regnum);
 > +      if (val == NULL)
 > +        PyErr_Format (PyExc_ValueError,
 > +                      "Cannot read register %d from frame.",
 > +                      regnum);
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      GDB_PY_HANDLE_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  return val == NULL ? NULL : value_to_value_object (val);
 > +}
 > +
 > +/* Implementation of
 > +   PendingFrame.create_unwind_info (self, frameId) -> UnwindInfo.  */
 > +
 > +static PyObject *
 > +pending_framepy_create_unwind_info (PyObject *self, PyObject *args)
 > +{
 > +  PyObject *pyo_frame_id;
 > +  CORE_ADDR sp;
 > +  CORE_ADDR pc;
 > +  CORE_ADDR special;
 > +
 > +  if (!PyArg_ParseTuple (args, "O:create_unwind_info", &pyo_frame_id))
 > +      return NULL;
 > +  if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "sp", &sp))
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       _("frame_id should have 'sp' attribute."));
 > +      return NULL;
 > +    }
 > +
 > +  /* The logic of building frame_id depending on the attributes of
 > +     the frame_id object:
 > +     Has     Has    Has           Function to call
 > +     'sp'?   'pc'?  'special'?
 > +     ------|------|--------------|-------------------------
 > +     Y       N      *             frame_id_build_wild (sp)
 > +     Y       Y      N             frame_id_build (sp, pc)
 > +     Y       Y      Y             frame_build_id_special (sp, pc, special)
 > +  */
 > +  if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "pc", &pc))
 > +    return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
 > +  if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "special", &special))
 > +    return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
 > +  else
 > +    return pyuw_create_unwind_info (self,
 > +                                    frame_id_build_special (sp, pc, special));
 > +}
 > +

Missing function comment.

 > +static void
 > +pending_frame_invalidate (void *pyo_pending_frame)
 > +{
 > +  if (pyo_pending_frame != NULL)
 > +    ((pending_frame_object *) pyo_pending_frame)->frame_info = NULL;
 > +}
 > +
 > +/* frame_unwind.this_id method.  */
 > +
 > +static void
 > +pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
 > +              struct frame_id *this_id)
 > +{
 > +  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
 > +  if (pyuw_debug >= 1)
 > +    {
 > +      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
 > +      fprint_frame_id (gdb_stdlog, *this_id);
 > +      fprintf_unfiltered (gdb_stdlog, "\n");
 > +    }
 > +}
 > +
 > +/* frame_unwind.prev_register.  */
 > +
 > +static struct value *
 > +pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
 > +                    int regnum)
 > +{
 > +  cached_frame_info *cached_frame = *cache_ptr;
 > +  struct reg_info *reg_info = cached_frame->reg;
 > +  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
 > +
 > +  TRACE_PY_UNWIND (1, "%s (frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
 > +                   regnum);
 > +  for (; reg_info < reg_info_end; ++reg_info)
 > +    {
 > +      if (regnum == reg_info->number)
 > +        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
 > +    }
 > +
 > +  return frame_unwind_got_optimized (this_frame, regnum);
 > +}
 > +
 > +/* Frame sniffer dispatch.  */
 > +
 > +static int
 > +pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
 > +              void **cache_ptr)
 > +{
 > +  struct gdbarch *gdbarch = (struct gdbarch *) (self->unwind_data);
 > +  struct cleanup *cleanups = ensure_python_env (gdbarch, current_language);
 > +  PyObject *pyo_execute;
 > +  PyObject *pyo_pending_frame;
 > +  PyObject *pyo_unwind_info;
 > +  cached_frame_info *cached_frame;
 > +
 > +  TRACE_PY_UNWIND (3, "%s (SP=%s, PC=%s)\n", __FUNCTION__,
 > +                   paddress (gdbarch, get_frame_sp (this_frame)),
 > +                   paddress (gdbarch, get_frame_pc (this_frame)));
 > +
 > +  /* Create PendingFrame instance to pass to sniffers.  */
 > +  pyo_pending_frame  = (PyObject *) PyObject_New (pending_frame_object,
 > +                                                  &pending_frame_object_type);
 > +  if (pyo_pending_frame == NULL)
 > +    goto error;
 > +  ((pending_frame_object *) pyo_pending_frame)->gdbarch = gdbarch;
 > +  ((pending_frame_object *) pyo_pending_frame)->frame_info = this_frame;
 > +  make_cleanup (pending_frame_invalidate, (void *) pyo_pending_frame);
 > +  make_cleanup_py_decref (pyo_pending_frame);
 > +
 > +  /* Run unwinders.  */
 > +  if (gdb_python_module == NULL
 > +      || ! PyObject_HasAttrString (gdb_python_module, "execute_unwinders"))
 > +    goto error;

If we get an error here something's wrong with the gdb installation (right?).
We should print an error message to notify the user,
but we should also allow the user to turn it off.
How about marking this as some kind of python error and leave it to
gdbpy_print_stack?

 > +  pyo_execute = PyObject_GetAttrString (gdb_python_module, "execute_unwinders");
 > +  if (pyo_execute == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_execute);
 > +  pyo_unwind_info
 > +      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_pending_frame, NULL);
 > +  if (pyo_unwind_info == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_unwind_info);
 > +  if (pyo_unwind_info == Py_None)
 > +    goto error;

It's confusing to branch to error here as this isn't an error.
How about branching to return_zero here? [see below]

 > +
 > +  /* Received UnwindInfo, cache data.  */
 > +  if (PyObject_IsInstance (pyo_unwind_info,
 > +                           (PyObject *) &unwind_info_object_type) <= 0)
 > +    error (_("A Unwinder should return gdb.UnwindInfo instance."));
 > +
 > +  {
 > +    unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
 > +    int reg_count = VEC_length (saved_reg, unwind_info->saved_regs);
 > +    saved_reg *reg;
 > +    int i;
 > +
 > +    cached_frame = xmalloc (sizeof (*cached_frame) +
 > +                            reg_count * sizeof (cached_frame->reg[0]));
 > +    cached_frame->gdbarch = gdbarch;
 > +    cached_frame->frame_id = unwind_info->frame_id;
 > +    cached_frame->reg_count = reg_count;
 > +
 > +    /* Populate registers array.  */
 > +    for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
 > +      {
 > +        struct value *value = value_object_to_value (reg->value);
 > +        size_t data_size = register_size (gdbarch, reg->number);
 > +
 > +        cached_frame->reg[i].number = reg->number;
 > +
 > +        /* `value' validation was done before, just assert.  */
 > +        gdb_assert (value != NULL);
 > +        gdb_assert (data_size == TYPE_LENGTH (value_type (value)));
 > +        gdb_assert (data_size <= MAX_REGISTER_SIZE);
 > +
 > +        memcpy (cached_frame->reg[i].data, value_contents (value), data_size);
 > +      }
 > +  }
 > +
 > +  *cache_ptr = cached_frame;
 > +  do_cleanups (cleanups);
 > +  return 1;
 > +
 > +error:

Indent "error" by one space: improves diff -p output.

We should only get here for Python errors,
and this should call gdbpy_print_stack.
Suggest writing this as (untested):

 error:
  gdbpy_print_stack ();
 return_zero:
  do_cleanups (cleanups);
  return 0;

 > +  do_cleanups (cleanups);
 > +  return 0;
 > +}
 > +
 > +/* Frame cache release shim.  */
 > +
 > +static void
 > +pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
 > +{
 > +  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
 > +  xfree (cache);
 > +}
 > +
 > +struct pyuw_gdbarch_data_type
 > +{
 > +  /* Has the unwinder shim been prepended? */
 > +  int unwinder_registered;
 > +};
 > +
 > +static void *
 > +pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
 > +{
 > +  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
 > +}
 > +
 > +/* New inferior architecture callback: register the Python unwinders
 > +   intermediary.  */
 > +
 > +static void
 > +pyuw_on_new_gdbarch (struct gdbarch *newarch)
 > +{
 > +  struct pyuw_gdbarch_data_type *data =
 > +      gdbarch_data (newarch, pyuw_gdbarch_data);
 > +
 > +  if (!data->unwinder_registered)
 > +    {
 > +      struct frame_unwind *unwinder
 > +          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
 > +
 > +      unwinder->type = NORMAL_FRAME;
 > +      unwinder->stop_reason = default_frame_unwind_stop_reason;
 > +      unwinder->this_id = pyuw_this_id;
 > +      unwinder->prev_register = pyuw_prev_register;
 > +      unwinder->unwind_data = (void *) newarch;
 > +      unwinder->sniffer = pyuw_sniffer;
 > +      unwinder->dealloc_cache = pyuw_dealloc_cache;
 > +      frame_unwind_prepend_unwinder (newarch, unwinder);
 > +      data->unwinder_registered = 1;
 > +    }
 > +}
 > +
 > +/* Initialize unwind machinery.  */
 > +
 > +int
 > +gdbpy_initialize_unwind (void)
 > +{
 > +  int rc;
 > +  add_setshow_zuinteger_cmd
 > +      ("py-unwind", class_maintenance, &pyuw_debug,
 > +        _("Set Python unwinder debugging."),
 > +        _("Show Python unwinder debugging."),
 > +        _("When non-zero, Python unwinder debugging is enabled."),
 > +        NULL,
 > +        NULL,
 > +        &setdebuglist, &showdebuglist);
 > +  pyuw_gdbarch_data
 > +      = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
 > +  observer_attach_architecture_changed (pyuw_on_new_gdbarch);
 > +
 > +  if (PyType_Ready (&pending_frame_object_type) < 0)
 > +    return -1;
 > +  rc = gdb_pymodule_addobject (gdb_module, "PendingFrame",
 > +      (PyObject *) &pending_frame_object_type);
 > +  if (rc)
 > +    return rc;
 > +
 > +  if (PyType_Ready (&unwind_info_object_type) < 0)
 > +    return -1;
 > +  return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
 > +      (PyObject *) &unwind_info_object_type);
 > +}
 > +
 > +static PyMethodDef pending_frame_object_methods[] =
 > +{
 > +  { "read_register", pending_framepy_read_register, METH_VARARGS,
 > +    "read_register (REG) -> gdb.Value\n"
 > +    "Return the value of the REG in the frame." },
 > +  { "create_unwind_info",
 > +    pending_framepy_create_unwind_info, METH_VARARGS,
 > +    "create_unwind_info (FRAME_ID) -> gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this PendingFrame, using FRAME_ID\n"
 > +    "to identify it." },
 > +  {NULL}  /* Sentinel */
 > +};
 > +
 > +static PyTypeObject pending_frame_object_type =
 > +{
 > +  PyVarObject_HEAD_INIT (NULL, 0)
 > +  "gdb.PendingFrame",             /* tp_name */
 > +  sizeof (pending_frame_object),  /* tp_basicsize */
 > +  0,                              /* tp_itemsize */
 > +  0,                              /* tp_dealloc */
 > +  0,                              /* tp_print */
 > +  0,                              /* tp_getattr */
 > +  0,                              /* tp_setattr */
 > +  0,                              /* tp_compare */
 > +  0,                              /* tp_repr */
 > +  0,                              /* tp_as_number */
 > +  0,                              /* tp_as_sequence */
 > +  0,                              /* tp_as_mapping */
 > +  0,                              /* tp_hash  */
 > +  0,                              /* tp_call */
 > +  pending_framepy_str,            /* tp_str */
 > +  0,                              /* tp_getattro */
 > +  0,                              /* tp_setattro */
 > +  0,                              /* tp_as_buffer */
 > +  Py_TPFLAGS_DEFAULT,             /* tp_flags */
 > +  "GDB PendingFrame object",      /* tp_doc */
 > +  0,                              /* tp_traverse */
 > +  0,                              /* tp_clear */
 > +  0,                              /* tp_richcompare */
 > +  0,                              /* tp_weaklistoffset */
 > +  0,                              /* tp_iter */
 > +  0,                              /* tp_iternext */
 > +  pending_frame_object_methods,   /* tp_methods */
 > +  0,                              /* tp_members */
 > +  0,                              /* tp_getset */
 > +  0,                              /* tp_base */
 > +  0,                              /* tp_dict */
 > +  0,                              /* tp_descr_get */
 > +  0,                              /* tp_descr_set */
 > +  0,                              /* tp_dictoffset */
 > +  0,                              /* tp_init */
 > +  0,                              /* tp_alloc */
 > +};
 > +
 > +static PyMethodDef unwind_info_object_methods[] =
 > +{
 > +  { "add_saved_register",
 > +    unwind_infopy_add_saved_register, METH_VARARGS,
 > +    "add_saved_register (REG, VALUE) -> None\n"
 > +    "Set the value of the REG in the previous frame to VALUE." },
 > +  { NULL }  /* Sentinel */
 > +};
 > +
 > +static PyTypeObject unwind_info_object_type =
 > +{
 > +  PyVarObject_HEAD_INIT (NULL, 0)
 > +  "gdb.UnwindInfo",               /* tp_name */
 > +  sizeof (unwind_info_object),    /* tp_basicsize */
 > +  0,                              /* tp_itemsize */
 > +  unwind_infopy_dealloc,          /* tp_dealloc */
 > +  0,                              /* tp_print */
 > +  0,                              /* tp_getattr */
 > +  0,                              /* tp_setattr */
 > +  0,                              /* tp_compare */
 > +  0,                              /* tp_repr */
 > +  0,                              /* tp_as_number */
 > +  0,                              /* tp_as_sequence */
 > +  0,                              /* tp_as_mapping */
 > +  0,                              /* tp_hash  */
 > +  0,                              /* tp_call */
 > +  unwind_infopy_str,              /* tp_str */
 > +  0,                              /* tp_getattro */
 > +  0,                              /* tp_setattro */
 > +  0,                              /* tp_as_buffer */
 > +  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
 > +  "GDB UnwindInfo object",        /* tp_doc */
 > +  0,                              /* tp_traverse */
 > +  0,                              /* tp_clear */
 > +  0,                              /* tp_richcompare */
 > +  0,                              /* tp_weaklistoffset */
 > +  0,                              /* tp_iter */
 > +  0,                              /* tp_iternext */
 > +  unwind_info_object_methods,     /* tp_methods */
 > +  0,                              /* tp_members */
 > +  0,                              /* tp_getset */
 > +  0,                              /* tp_base */
 > +  0,                              /* tp_dict */
 > +  0,                              /* tp_descr_get */
 > +  0,                              /* tp_descr_set */
 > +  0,                              /* tp_dictoffset */
 > +  0,                              /* tp_init */
 > +  0,                              /* tp_alloc */
 > +};
 > diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
 > index 4c4d32a..0581b33 100644
 > --- a/gdb/python/python-internal.h
 > +++ b/gdb/python/python-internal.h
 > @@ -391,12 +391,14 @@ PyObject *pspace_to_pspace_object (struct program_space *)
 >      CPYCHECKER_RETURNS_BORROWED_REF;
 >  PyObject *pspy_get_printers (PyObject *, void *);
 >  PyObject *pspy_get_frame_filters (PyObject *, void *);
 > +PyObject *pspy_get_frame_unwinders (PyObject *, void *);
 >  PyObject *pspy_get_xmethods (PyObject *, void *);
 >  
 >  PyObject *objfile_to_objfile_object (struct objfile *)
 >      CPYCHECKER_RETURNS_BORROWED_REF;
 >  PyObject *objfpy_get_printers (PyObject *, void *);
 >  PyObject *objfpy_get_frame_filters (PyObject *, void *);
 > +PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
 >  PyObject *objfpy_get_xmethods (PyObject *, void *);
 >  PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 >  
 > @@ -491,6 +493,8 @@ int gdbpy_initialize_arch (void)
 >    CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 >  int gdbpy_initialize_xmethods (void)
 >    CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 > +int gdbpy_initialize_unwind (void)
 > +  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 >  
 >  struct cleanup *make_cleanup_py_decref (PyObject *py);
 >  struct cleanup *make_cleanup_py_xdecref (PyObject *py);
 > diff --git a/gdb/python/python.c b/gdb/python/python.c
 > index 58c7c92..1da63fd 100644
 > --- a/gdb/python/python.c
 > +++ b/gdb/python/python.c
 > @@ -1821,7 +1821,8 @@ message == an error message without a stack will be printed."),
 >        || gdbpy_initialize_new_objfile_event ()  < 0
 >        || gdbpy_initialize_clear_objfiles_event ()  < 0
 >        || gdbpy_initialize_arch () < 0
 > -      || gdbpy_initialize_xmethods () < 0)
 > +      || gdbpy_initialize_xmethods () < 0
 > +      || gdbpy_initialize_unwind () < 0)
 >      goto fail;
 >  
 >    gdbpy_to_string_cst = PyString_FromString ("to_string");
 > diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
 > new file mode 100644
 > index 0000000..8c1d935
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
 > @@ -0,0 +1,24 @@
 > +/* This testcase is part of GDB, the GNU debugger.
 > +
 > +   Copyright 2015 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/>.  */
 > +
 > +int
 > +main (void)
 > +{
 > +  int i = 0;
 > +
 > +  return i; /* next-line */
 > +}
 > diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
 > new file mode 100644
 > index 0000000..b287501
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
 > @@ -0,0 +1,64 @@
 > +# Copyright (C) 2015 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/>.
 > +
 > +# This file is part of the GDB testsuite.  It tests Python-based
 > +# unwinding CLI.
 > +
 > +load_lib gdb-python.exp
 > +
 > +standard_testfile
 > +
 > +if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
 > +    return -1
 > +}
 > +
 > +# Skip all tests if Python scripting is not enabled.
 > +if { [skip_python_tests] } { continue }
 > +
 > +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
 > +
 > +if ![runto_main ] then {
 > +    fail "Can't run to main"
 > +    return -1
 > +}
 > +
 > +gdb_test "source ${pyfile}" "Python script imported" "import python scripts"
 > +
 > +gdb_test_sequence "info unwinder" "Show all unwinders" {
 > +    "Global:"
 > +    "  global_unwinder"
 > +    "Progspace .*py-unwind-maint:"
 > +    "py_unwind_maint_ps_unwinder"
 > +}
 > +
 > +gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
 > +
 > +gdb_test_sequence "continue" "Unwinders called" {
 > +    "py_unwind_maint_ps_unwinder called"
 > +    "global_unwinder called"
 > +}
 > +
 > +gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled"
 > +
 > +gdb_test_sequence "info unwinder" "Show with global unwinder disabled" {
 > +    "Global:"
 > +    "  \\[disabled\\] global_unwinder"
 > +    "Progspace .*py-unwind-maint:"
 > +    "  py_unwind_maint_ps_unwinder"
 > +}
 > +
 > +gdb_test_sequence "where" "Global unwinder disabled" {
 > +    "py_unwind_maint_ps_unwinder called\r\n#0  main"
 > +}
 > diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py
 > new file mode 100644
 > index 0000000..f8c6277
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
 > @@ -0,0 +1,59 @@
 > +# Copyright (C) 2015 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/>.
 > +
 > +# This file is part of the GDB testsuite.  It tests python unwinders.
 > +
 > +import re
 > +import gdb.types
 > +from gdb.unwinder import Unwinder, register_unwinder
 > +
 > +class TestGlobalUnwinder(Unwinder):
 > +    def __init__(self):
 > +        super(TestGlobalUnwinder, self).__init__("global_unwinder")
 > +
 > +    def __call__(self, unwinder_info):
 > +        print "%s called" % self.name
 > +        return None
 > +
 > +class TestProgspaceUnwinder(Unwinder):
 > +    def __init__(self, name):
 > +        super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name)
 > +
 > +    def __call__(self, unwinder_info):
 > +        print "%s called" % self.name
 > +        return None
 > +
 > +class TestObjfileUnwinder(Unwinder):
 > +    def __init__(self, name):
 > +        super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name)
 > +
 > +    def __call__(self, unwinder_info):
 > +        print "%s called" % self.name
 > +        return None
 > +
 > +
 > +
 > +gdb.unwinder.register_unwinder(None, TestGlobalUnwinder())
 > +saw_runtime_error = False
 > +try:
 > +    gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=False)
 > +except RuntimeError:
 > +    saw_runtime_error = True
 > +if not saw_runtime_error:
 > +    raise RuntimeError("Missing runtime error from register_unwinder.")
 > +gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=True)
 > +gdb.unwinder.register_unwinder(gdb.current_progspace(),
 > +                               TestProgspaceUnwinder("py_unwind_maint"))
 > +print "Python script imported"
 > diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c
 > new file mode 100644
 > index 0000000..cf41d78
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.c
 > @@ -0,0 +1,81 @@
 > +/* This test program is part of GDB, the GNU debugger.
 > +
 > +   Copyright 2015 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/>.  */
 > +
 > +/* This is the test program loaded into GDB by the py-unwind test.  */
 > +
 > +#include <stdint.h>
 > +#include <stdio.h>
 > +#include <stdlib.h>
 > +
 > +static void *
 > +swap_value (void **location, void *new_value)
 > +{
 > +  void *old_value = *location;
 > +  *location = new_value;
 > +  return old_value;
 > +}
 > +
 > +static void
 > +bad_layout(void **variable_ptr, void *fp)
 > +{
 > +  fprintf (stderr, "First variable should be allocated one word below "
 > +           "the frame.  Got variable's address %p, frame at %p instead.\n",
 > +           variable_ptr, fp);
 > +  abort();
 > +}
 > +
 > +#define MY_FRAME (__builtin_frame_address (0))
 > +
 > +static void
 > +corrupt_frame_inner (void)
 > +{
 > +  /* Save outer frame address, then corrupt the unwind chain by
 > +     setting the outer frame address in it to self.  This is
 > +     ABI-specific: the first word of the frame contains previous frame
 > +     address in amd64.  */
 > +  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
 > +
 > +  /* Verify the compiler allocates the first local variable one word
 > +     below frame.  This is where the test unwinder expects to find the
 > +     correct outer frame address.  */
 > +  if (&previous_fp + 1 != (void **) MY_FRAME)
 > +    bad_layout (&previous_fp + 1, MY_FRAME);
 > +
 > +  /* Now restore it so that we can return.  The test sets the
 > +     breakpoint just before this happens, and GDB will not be able to
 > +     show the backtrace without JIT reader.  */
 > +  swap_value ((void **) MY_FRAME, previous_fp); /* break backtrace-broken */
 > +}
 > +
 > +static void
 > +corrupt_frame_outer (void)
 > +{
 > +  /* See above for the explanation of the code here.  This function
 > +     corrupts its frame, too, and then calls the inner one.  */
 > +  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
 > +  if (&previous_fp + 1 != (void **) MY_FRAME)
 > +    bad_layout (&previous_fp, MY_FRAME);
 > +  corrupt_frame_inner ();
 > +  swap_value ((void **) MY_FRAME, previous_fp);
 > +}
 > +
 > +int
 > +main ()
 > +{
 > +  corrupt_frame_outer ();
 > +  return 0;
 > +}
 > diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
 > new file mode 100644
 > index 0000000..53d6746
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.exp
 > @@ -0,0 +1,54 @@
 > +# Copyright (C) 2015 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/>.
 > +
 > +# This file is part of the GDB testsuite.  It verifies that frame
 > +# unwinders can be implemented in Python.
 > +
 > +load_lib gdb-python.exp
 > +
 > +standard_testfile
 > +
 > +if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
 > +    return -1
 > +}
 > +
 > +# Skip all tests if Python scripting is not enabled.
 > +if { [skip_python_tests] } { continue }
 > +
 > +# This test runs on a specific platform.
 > +if { ! [istarget x86_64-*]} { continue }
 > +
 > +# The following tests require execution.
 > +
 > +if ![runto_main] then {
 > +    fail "Can't run to main"
 > +    return 0
 > +}
 > +
 > +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
 > +
 > +gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
 > +
 > +gdb_test "source ${pyfile}" "Python script imported" \
 > +         "import python scripts"
 > +
 > +gdb_continue_to_breakpoint "break backtrace-broken"
 > +gdb_test_sequence "where"  "Backtrace restored by unwinder" {
 > +    "\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
 > +    "\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
 > +    "\\r\\n#2 .* main \\(.*\\) at"
 > +}
 > +
 > +
 > diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
 > new file mode 100644
 > index 0000000..6257fd7
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.py
 > @@ -0,0 +1,99 @@
 > +# Copyright (C) 2015 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
 > +from gdb.unwinder import Unwinder
 > +
 > +class FrameId(object):
 > +
 > +    def __init__(self, sp, pc):
 > +        self._sp = sp
 > +        self._pc = pc
 > +
 > +    @property
 > +    def sp(self):
 > +        return self._sp
 > +
 > +    @property
 > +    def pc(self):
 > +        return self._pc
 > +
 > +
 > +class TestUnwinder(Unwinder):
 > +    AMD64_RBP = 6
 > +    AMD64_RSP = 7
 > +    AMD64_RIP = 16
 > +
 > +    def __init__(self):
 > +        Unwinder.__init__(self, "test unwinder")
 > +        self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
 > +        self.char_ptr_ptr_t = self.char_ptr_t.pointer()
 > +
 > +    def _read_word(self, address):
 > +        return address.cast(self.char_ptr_ptr_t).dereference()
 > +
 > +    def __call__(self, pending_frame):
 > +        """Test unwinder written in Python.
 > +
 > +        This unwinder can unwind the frames that have been deliberately
 > +        corrupted in a specific way (functions in the accompanying
 > +        py-unwind.c file do that.)
 > +        This code is only on AMD64.
 > +        On AMD64 $RBP points to the innermost frame (unless the code
 > +        was compiled with -fomit-frame-pointer), which contains the
 > +        address of the previous frame at offset 0. The functions
 > +        deliberately corrupt their frames as follows:
 > +                     Before                 After
 > +                   Corruption:           Corruption:
 > +                +--------------+       +--------------+
 > +        RBP-8   |              |       | Previous RBP |
 > +                +--------------+       +--------------+
 > +        RBP     + Previous RBP |       |    RBP       |
 > +                +--------------+       +--------------+
 > +        RBP+8   | Return RIP   |       | Return  RIP  |
 > +                +--------------+       +--------------+
 > +        Old SP  |              |       |              |
 > +
 > +        This unwinder recognizes the corrupt frames by checking that
 > +        *RBP == RBP, and restores previous RBP from the word above it.
 > +        """
 > +        try:
 > +            # NOTE: the registers in Unwinder API can be referenced
 > +            # either by name or by number. The code below uses both
 > +            # to achieve more coverage.
 > +            bp = pending_frame.read_register("rbp").cast(self.char_ptr_t)
 > +            if self._read_word(bp) != bp:
 > +                return None
 > +            # Found the frame that the test program has corrupted for us.
 > +            # The correct BP for the outer frame has been saved one word
 > +            # above, previous IP and SP are at the expected places.
 > +            previous_bp = self._read_word(bp - 8)
 > +            previous_ip = self._read_word(bp + 8)
 > +            previous_sp = bp + 16
 > +
 > +            frame_id = FrameId(
 > +                pending_frame.read_register(TestUnwinder.AMD64_RSP),
 > +                pending_frame.read_register(TestUnwinder.AMD64_RIP))
 > +            unwind_info = pending_frame.create_unwind_info(frame_id)
 > +            unwind_info.add_saved_register(TestUnwinder.AMD64_RBP,
 > +                                           previous_bp)
 > +            unwind_info.add_saved_register("rip", previous_ip)
 > +            unwind_info.add_saved_register("rsp", previous_sp)
 > +            return unwind_info
 > +        except (gdb.error, RuntimeError):
 > +            return None
 > +
 > +gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
 > +print("Python script imported")

-- 
/dje


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