diff mbox

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

Message ID CAHQ51u6+sCwSi3dYQXsz7orjq-yGjX6PayQduwVURx3DxpK=oA@mail.gmail.com
State New
Headers show

Commit Message

Alexander Smundak March 18, 2015, 10:57 p.m. UTC
> Nit: we are setting the register here.
Fixed.

>> +        _("When non-zero, Pythin unwinder debugging is enabled."),
>
> "Python"
Fixed.

> Still no support for register names.
Registers can be retrieved by name, please see
gdb/testsuite/gdb.python/py-unwind.py as an example.

>> +import gdb
>> +from gdb.sniffer import Sniffer
>> +
>> +class TestSniffer(Sniffer):
>
> I still think it's much better to call these "unwinders".  You say that
> it's the terminology that GDB uses but that's not really the case --
> "sniffer" names part of the unwinder interface, which Python and Guile
> implement the whole of.  You chose "unwinder" as the documentation
> heading and the file name; why introduce a new term to the user?
Renamed all but SnifferInfo.

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 unwinders.py.
        * doc/python.texi (Writing a Frame Unwinder in Python): Add
        section.
        * python/lib/gdb/__init__.py (packages): Add frame_unwinders list.
        * python/lib/gdb/command/unwinders.py: New file, implements GDB
        commands to list/enable/disable Python unwinders.
        * python/lib/gdb/function/unwinders.py: New file, implements
        execute_unwinders function.
        * python/lib/gdb/unwinder.py: New file, contains Unwinder class and
        register_unwinder function.
        * 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): Implement Objfile.frame_unwinders
        getter.
        (objfpy_set_frame_unwinders): Implement Objfile.frame_unwinders
        setter.
        (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): Implement gdb.Progspace.frame_unwinders
        getter.
        (pspy_set_frame_unwinders): Implement gdb.Progspace.frame_unwinders
        setter.
        (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
        * python/py-unwind.c: New file, implements Python frame unwinders
        interface.
        * 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.

2015-03-28  Sasha Smundak  <asmundak@google.com>

        * gdb.python/py-unwind-maint.c: Test program for py-unwind-maint.
        * gdb.python/py-unwind-maint.exp: Tests unwinder-related GDB
        commands.
        * gdb.python/py-unwind-maint.py: Pythons frame unwinders for the test.
        * gdb.python/py-unwind.c: Test program for the py-unwind test.
        * gdb.python/py-unwind.exp: Python frame unwinders test.
        * gdb.python/py-unwind.py: Python frame unwinder tested by
        py-unwind test.

Comments

Doug Evans March 23, 2015, 7:58 p.m. UTC | #1
Alexander Smundak writes:
 > > Nit: we are setting the register here.
 > Fixed.
 > 
 > >> +        _("When non-zero, Pythin unwinder debugging is enabled."),
 > >
 > > "Python"
 > Fixed.
 > 
 > > Still no support for register names.
 > Registers can be retrieved by name, please see
 > gdb/testsuite/gdb.python/py-unwind.py as an example.
 > 
 > >> +import gdb
 > >> +from gdb.sniffer import Sniffer
 > >> +
 > >> +class TestSniffer(Sniffer):
 > >
 > > I still think it's much better to call these "unwinders".  You say that
 > > it's the terminology that GDB uses but that's not really the case --
 > > "sniffer" names part of the unwinder interface, which Python and Guile
 > > implement the whole of.  You chose "unwinder" as the documentation
 > > heading and the file name; why introduce a new term to the user?
 > Renamed all but SnifferInfo.

Hi.

One high level issue we still need to sort out is priorities.
The scheme version allows unwinders to have priorities,
the python version does not.  I suppose we can live with the
difference, but IWBN to not do something different in python later.

Andy: Can we forgo priorities for now until we have a use-case and thus
something concrete to base them on?

 > 
 > 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 unwinders.py.
 >         * doc/python.texi (Writing a Frame Unwinder in Python): Add
 >         section.
 >         * python/lib/gdb/__init__.py (packages): Add frame_unwinders list.
 >         * python/lib/gdb/command/unwinders.py: New file, implements GDB
 >         commands to list/enable/disable Python unwinders.
 >         * python/lib/gdb/function/unwinders.py: New file, implements
 >         execute_unwinders function.
 >         * python/lib/gdb/unwinder.py: New file, contains Unwinder class and
 >         register_unwinder function.
 >         * 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): Implement Objfile.frame_unwinders
 >         getter.

For any new function, just say "New function."

 >         (objfpy_set_frame_unwinders): Implement Objfile.frame_unwinders
 >         setter.
 >         (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): Implement gdb.Progspace.frame_unwinders
 >         getter.
 >         (pspy_set_frame_unwinders): Implement gdb.Progspace.frame_unwinders
 >         setter.
 >         (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
 >         * python/py-unwind.c: New file, implements Python frame unwinders
 >         interface.
 >         * 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.
 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >         * gdb.python/py-unwind-maint.c: Test program for py-unwind-maint.
 >         * gdb.python/py-unwind-maint.exp: Tests unwinder-related GDB
 >         commands.
 >         * gdb.python/py-unwind-maint.py: Pythons frame unwinders for the test.
 >         * gdb.python/py-unwind.c: Test program for the py-unwind test.
 >         * gdb.python/py-unwind.exp: Python frame unwinders test.
 >         * gdb.python/py-unwind.py: Python frame unwinder tested by
 >         py-unwind test.

Similarly, for any new file just say "New file."

 > 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..049aa05 100644
 > --- a/gdb/data-directory/Makefile.in
 > +++ b/gdb/data-directory/Makefile.in
 > @@ -62,17 +62,20 @@ 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 \
 >  	gdb/command/explore.py \
 >  	gdb/function/__init__.py \
 >  	gdb/function/caller_is.py \
 > +        gdb/function/unwinders.py \

Use tab here instead of spaces for consistency.

 >  	gdb/function/strfns.py \
 >  	gdb/printer/__init__.py \
 >  	gdb/printer/bound_registers.py
 > diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
 > index d725eb0..a2685e8 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,132 @@ 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.

IWBN if the Scheme and Python sides had similar text,
but let's leave that until both patches are in.
Then someone (maybe myself) can go back and edit them.

 > +
 > +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{SnifferInfo} instance) provides
 > +a method to read frame's registers:
 > +
 > +@defun SnifferInfo.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 several factory methods. If an unwinder recognizes
 > +the frame, it should invoke one of them to create a gdb.UnwindInfo
 > +instance to be returned to GDB:
 > +
 > +@defun SnifferInfo.unwind_info_with_id(sp, pc)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{sp} and @var{pc} values. This is the most common way of creating
 > +instance result.
 > +@end defun
 > +
 > +@defun SnifferInfo.frame_id_build_special(sp, pc, special)
 > +Returns a new @code{gdb.UnwindInfo} instance identitified by given
 > +@var{sp}, @var{pc}, and @var{special} values.
 > +@end defun
 > +
 > +@defun gdb.UnwindInfo.frame_id_build_wild(sp)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{sp} value.
 > +@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.set_previous_frame_register(reg, value)
 > +@var{reg} identifies the register. It can be a number or a name, just
 > +as for the @code{SnifferInfo.read_register} method above. @var{value}
 > +is a register value (a @code{gdb.Value} object).
 > +
 > +@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 MyUnwinder(Unwinder):
 > +    def __init__(....):
 > +        super(MyUnwinder, self).__init___(<expects unwinder name argument>)
 > +    def __call__(sniffer_info):
 > +        if not <we recognize frame>:
 > +            return None
 > +        # Create unwinder result. The most common way to achieve this is
 > +        # to find SP (stack pointer) and PC (program counter) values
 > +        # in the current frame and then call unwind_info_with_id method:
 > +        unwind_info = sniffer_info.unwind_info_with_id(sp, pc)
 > +
 > +        # Find the values of the registers in the caller's frame and 
 > +        # save them in the result:
 > +        unwind_info.set_previous_frame_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..734e5ca 100644
 > --- a/gdb/python/lib/gdb/__init__.py
 > +++ b/gdb/python/lib/gdb/__init__.py
 > @@ -71,6 +71,8 @@ type_printers = []
 >  xmethods = []
 >  # Initial frame filters.
 >  frame_filters = {}
 > +# Initial frame unwinders.
 > +frame_unwinders = []
 >  
 >  # 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..1f0c58b
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/command/unwinders.py
 > @@ -0,0 +1,199 @@
 > +# 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 could be 'global', a regular expression
 > +    matching the current program space's filename, or a regular
 > +    expression matching filenames of objfiles.  Locus could be
 > +    'progspace' to specify that only unwinders from the current
 > +    progspace should be listed.
 > +
 > +    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.
 > +

Remove blank line.

 > +    """
 > +
 > +    def __init__(self):
 > +        super(InfoUnwinder, self).__init__("info unwinder",
 > +                                            gdb.COMMAND_DATA)
 > +
 > +    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" % (unwinder.name,
 > +                                  "" if unwinder.enabled else "[disabled]"))
 > +
 > +    def invoke(self, arg, from_tty):
 > +        locus_re, name_re = parse_unwinder_command_args(arg)
 > +        if locus_re.match("global"):
 > +            self.list_unwinders("global unwinders:", gdb.frame_unwinders,
 > +                                name_re)
 > +        if locus_re.match("progspace"):
 > +            cp = gdb.current_progspace()
 > +            self.list_unwinders("progspace %s unwinders:" % cp.filename,
 > +                                cp.frame_unwinders, name_re)
 > +        for objfile in gdb.objfiles():
 > +            if locus_re.match(objfile.filename):
 > +                self.list_unwinders("objfile %s unwinders:" % objfile.filename,
 > +                                    objfile.frame_unwinders, name_re)

The file names here can be really long and thus having text after them
will be less readable. Plus the user typed "info unwinders" so there's
no need to print "unwinder" in the title. I realize even "info pretty-printer"
doesn't follow this - but I want to fix this.
This is what I'd like to see:

Global:
  ...
Progspace filename:
  ...
Objfile filename:
  ...
Objfile filename:
  ...

I sometimes wonder if we could have all of these (pretty-printers,
type-printers, xmethods, unwinders, etc.) use the same code, instead
of all of this duplication to implement info/enable/disable.
I wouldn't impose that on this patch of course, that's a future cleanup
exercise.

 > +
 > +
 > +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"))

One mistake I made with pretty-printers is that enable/disable command should
be silent by default. For consistency let's leave this as is.
Another future cleanup exercise.

 > +
 > +
 > +class EnableUnwinder(gdb.Command):
 > +    """GDB command to enable unwinders.
 > +
 > +    Usage: enable unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression matching the objects to examine.
 > +    Loci are "global", the program space's file, and the objfiles within
 > +    that program space.

"the program space's file" should be "progspace".
[I'm guessing this is just cut-n-paste but let's fix this one now.]

 > +
 > +    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_DATA)

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 matching the objects to examine.
 > +    Loci are "global", the program space's file, and the objfiles within
 > +    that program space.

"the program space's file" should be "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_DATA)

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/function/unwinders.py b/gdb/python/lib/gdb/function/unwinders.py
 > new file mode 100644
 > index 0000000..9be8b8b
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/function/unwinders.py
 > @@ -0,0 +1,52 @@
 > +# 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/>.
 > +
 > +"""Internal functions for working with frame unwinders."""
 > +
 > +import gdb
 > +
 > +
 > +def execute_unwinders(sniffer_info):

This function needs to go elsewhere as this directory is for
python convenience functions, e.g. $_streq. We should have better
documentation on conventions used in python/lib, sorry!

We haven't had a need for something more elaborate
so I suggest moving this function to python/lib/gdb/__init__.py.

 > +    """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:
 > +        sniffer_info: an instance of gdb.SnifferInfo.
 > +    Returns:
 > +        UnwindInfo instance or None.
 > +    """
 > +    for objfile in gdb.objfiles():
 > +        for unwinder in objfile.frame_unwinders:
 > +            if unwinder.enabled:
 > +                unwind_info = unwinder.__call__(sniffer_info)

Write this as:

                unwind_info = unwinder(sniffer_info)

and similarly throughout.

 > +                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.__call__(sniffer_info)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    for unwinder in gdb.frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder.__call__(sniffer_info)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    return None
 > diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
 > new file mode 100644
 > index 0000000..2d65a5e
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/unwinder.py
 > @@ -0,0 +1,89 @@
 > +# 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.
 > +
 > +    A unwinder has a single method __call__ and the attributes described below.

An unwinder ...

 > +
 > +    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, sniffer_info):
 > +        """GDB calls this method to unwind a frame.

s/sniffer_info/pending_frame/

 > +
 > +        Arguments:
 > +            sniffer_info: An instance of gdb.SnifferInfo describing the frame.
 > +
 > +        Returns:
 > +	    gdb.UnwindInfo instance.

There's a tab character in the previous line, change to spaces.
(no tabs in python).

 > +        """
 > +        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.
 > +
 > +    """
 > +    if locus is None:
 > +        if gdb.parameter("verbose"):
 > +            gdb.write("Registering global %s unwinder ...\n" % unwinder.name)
 > +        locus = gdb
 > +    else:

Probably should verify locus is an instance of gdb.Objfile or gdb.Progspace.
Something like

  if isinstance(locus, gdb.Objfile) or isinstance(locus, gdb.Progspace):
    ...
  else
    raise ...

 > +        if gdb.parameter("verbose"):
 > +            gdb.write("Registering %s unwinder for %s ...\n" %
 > +                      (unwinder.name, locus.filename))
 > +    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..128d710
 > --- /dev/null
 > +++ b/gdb/python/py-unwind.c
 > @@ -0,0 +1,831 @@
 > +/* 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;
 > +} sniffer_info_object;

s/sniffer_info/pending_frame/
[and similarly throughout]

Also, we need another class: gdb.FrameID.
I'm not sure whether to implement it in C or Python.

 > +
 > +/* The data we keep for the PyUnwindInfo: sniffer_info, previous
 > + * frame's register set and frame ID.  */
 > +
 > +typedef struct
 > +{
 > +  PyObject_HEAD
 > +
 > +  /* gdb.SnifferInfo for the frame we are unwinding.  */
 > +  PyObject *sniffer_info;
 > +
 > +  /* Its ID.  */
 > +  struct frame_id frame_id;
 > +
 > +  /* Previous frame registers array.  */
 > +  struct reg_pydata
 > +  {
 > +    int number;
 > +    PyObject *value;
 > +  } *prev_frame_regs;

s/prev_frame_regs/saved_regs/

 > +
 > +  /* The current size of the array above.  */
 > +  int prev_frame_regs_size;
 > +
 > +  /* And its capacity.  */
 > +  int prev_frame_regs_capacity;

While C doesn't have nice things like stl::vector, and while
we'll eventually have stl::vector, for now we have common/vec.h.
It'd be best to have saved_regs use it.
grep for "VEC (" and "VEC_" in *.c */*.c for examples.

 > +
 > +} 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;

It would be simpler to have data be: gdb_byte data[MAX_REGISTER_SIZE];

 > +  } reg[];
 > +} cached_frame_info;
 > +
 > +static PyTypeObject sniffer_info_object_type
 > +    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_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;
 > +
 > +/* Parse Python Int, saving it at the given address. Returns 1 on success,
 > +   0 otherwise.  */

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.

 > +
 > +static int
 > +pyuw_parse_int (PyObject *pyo_int, int *valuep)
 > +{
 > +  long long_value;
 > +
 > +  if (pyo_int == NULL)
 > +    return 0;
 > +
 > +  /* Make a long logic check first.  In Python 3.x, internally, all
 > +     integers are represented as longs.  In Python 2.x, there is still
 > +     a differentiation internally between a PyInt and a PyLong.
 > +     Explicitly do this long check conversion first. In GDB, for
 > +     Python 3.x, we #ifdef PyInt = PyLong.  This check has to be done

Since we're just checking for register numbers here,
let's simplify this and only call PyInt_Check.
I see we do this elsewhere in py-*.c (e.g., py-breakpoint.c).
Plus there is gdb_py_int_as_long to simplify the conversion.

 > +     first to ensure we do not lose information in the conversion
 > +     process.  */
 > +  else if (PyLong_Check (pyo_int))
 > +    {
 > +      LONGEST l = PyLong_AsLongLong (pyo_int);
 > +
 > +      if (PyErr_Occurred ())
 > +        return 0;
 > +      long_value = (long)l;
 > +      if (l != long_value)
 > +        return 0;
 > +    }
 > +  else if (PyInt_Check (pyo_int))
 > +    {
 > +      long_value = PyInt_AsLong (pyo_int);
 > +      if (PyErr_Occurred ())
 > +        return 0;
 > +    }
 > +  else
 > +    return 0;
 > +  if (long_value != (int) long_value)
 > +    return 0;
 > +  *valuep = (int) long_value;
 > +  return 1;
 > +}
 > +
 > +/* Parses register id, which can be either a number or a name.
 > +   Returns 1 on success, 0 otherwise.  */

Similarly, either clear any Python error indicator before returning
or document that the Python error indicator may or may not be set
on return if the result is zero.

 > +
 > +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);
 > +      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 (pyuw_parse_int (pyo_reg_id, reg_num))
 > +    return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;

There's a convention for gdb/python/*.c to consider here and I'm
not sure it applies here. Basically, any time python/*.c calls back
to gdb it has to be wrapped in a TRY/CATCH. user_reg_map_regnum_to_name
won't throw a gdb error today, but is there a sufficient worry to wrap
the call in a TRY/CATCH in case is does "tomorrow"?
Dunno. IOW, someone may wish you to wrap the call to
user_reg_map_regnum_to_name in a TRY/CATCH, but for now I'm going
to say leave it as is.

 > +  else
 > +    return 0;
 > +}
 > +
 > +/* Convert gdb.Value object to COREADDR.  */
 > +
 > +static int
 > +pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
 > +{
 > +  struct value *value = value_object_to_value (pyo_value);
 > +
 > +  if (value == NULL)
 > +    return 0;
 > +  *addr = unpack_pointer (value_type (value), value_contents (value));

This one does need to be wrapped in a TRY/CATCH/END_CATCH
in case any of the callbacks to gdb throws a gdb error.

 > +  return 1;
 > +}
 > +
 > +/* Called by the Python interpreter to obtain string representation
 > +   of the UnwindInfo object.  */
 > +
 > +static PyObject *
 > +unwind_infopy_str (PyObject *self)
 > +{
 > +  PyObject *result;
 > +  struct ui_file *strfile = mem_fileopen ();
 > +  unwind_info_object *unwind_info = (unwind_info_object *) self;
 > +  sniffer_info_object *sniffer_info
 > +      = (sniffer_info_object *) (unwind_info->sniffer_info);
 > +
 > +  fprintf_unfiltered (strfile, "Frame ID: ");

If someone else wants to require text here to be wrapped in _()
I'll let them, but I'm going to let this go.

 > +  fprint_frame_id (strfile, unwind_info->frame_id);
 > +  {
 > +    int i;
 > +    char *sep = "";
 > +    struct value_print_options opts;
 > +
 > +    get_user_print_options (&opts);
 > +    fprintf_unfiltered (strfile, "\nPrevious frame registers: (");

s/Previous frame/Saved/

I checked, and "info frame" prints "Saved registers:"
so there is precedent here.

 > +    for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
 > +      {
 > +        struct value *value
 > +            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
 > +
 > +        fprintf_unfiltered (strfile, "%s(%d, ", sep,
 > +                            unwind_info->prev_frame_regs[i].number);
 > +        if (value != NULL)
 > +          {
 > +            value_print (value, strfile, &opts);

Wrap call to value_print in TRY/CATCH/END_CATCH.

 > +            fprintf_unfiltered (strfile, ")");
 > +          }
 > +        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 SnifferInfo and frame ID.  */
 > +
 > +static PyObject *
 > +pyuw_create_unwind_info (PyObject *pyo_sniffer_info,
 > +                         struct frame_id frame_id)
 > +{
 > +  unwind_info_object *unwind_info
 > +      = PyObject_New (unwind_info_object, &unwind_info_object_type);
 > +
 > +  if (((sniffer_info_object *) pyo_sniffer_info)->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "Attempting to use stale SnifferInfo");
 > +      return NULL;
 > +    }
 > +  unwind_info->frame_id = frame_id;
 > +  Py_INCREF (pyo_sniffer_info);
 > +  unwind_info->sniffer_info = pyo_sniffer_info;
 > +  unwind_info->prev_frame_regs_size = 0;
 > +  unwind_info->prev_frame_regs_capacity = 4;
 > +  unwind_info->prev_frame_regs =
 > +      xmalloc (unwind_info->prev_frame_regs_capacity *
 > +               sizeof (unwind_info->prev_frame_regs[0]));
 > +  return (PyObject *) unwind_info;
 > +}
 > +
 > +/* The implementation of
 > +   gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None.  */

s/set_previous_frame_register/add_saved_register/

 > +
 > +static PyObject *
 > +unwind_infopy_set_previous_frame_register (PyObject *self, PyObject *args)
 > +{
 > +  unwind_info_object *unwind_info = (unwind_info_object *) self;
 > +  sniffer_info_object *sniffer_info
 > +      = (sniffer_info_object *) (unwind_info->sniffer_info);
 > +  PyObject *pyo_reg_id;
 > +  PyObject *pyo_reg_value;
 > +  int regnum;
 > +
 > +  if (sniffer_info->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "UnwindInfo instance refers to a stale SnifferInfo");
 > +      return NULL;
 > +    }
 > +  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
 > +                          &pyo_reg_id, &pyo_reg_value))
 > +    return NULL;
 > +  if (!pyuw_parse_register_id (sniffer_info->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 (sniffer_info->gdbarch, regnum);
 > +    if (data_size != TYPE_LENGTH (value_enclosing_type (value)))

Using value_enclosing_type here instead of value_type feels odd.
Let's go with value_type unless someone can think of a reason
to use value_enclosing_type.

 > +      {
 > +        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_enclosing_type (value))),

value_type

 > +            (unsigned) data_size);
 > +        return NULL;
 > +      }
 > +  }
 > +  {
 > +    int i;
 > +
 > +    for (i = 0;
 > +         (i < unwind_info->prev_frame_regs_size)
 > +             && regnum != unwind_info->prev_frame_regs[i].number; i++)
 > +      ;
 > +    if (i < unwind_info->prev_frame_regs_size)
 > +      Py_DECREF (unwind_info->prev_frame_regs[i].value);
 > +    else
 > +      {
 > +        if (i >= unwind_info->prev_frame_regs_capacity)
 > +          {
 > +            unwind_info->prev_frame_regs_capacity *= 2;
 > +            unwind_info->prev_frame_regs = xrealloc
 > +                (unwind_info->prev_frame_regs,
 > +                 unwind_info->prev_frame_regs_capacity
 > +                 * sizeof (unwind_info->prev_frame_regs[0]));
 > +          }
 > +        unwind_info->prev_frame_regs_size++;
 > +        unwind_info->prev_frame_regs[i].number = regnum;
 > +      }

This code will reduce to VEC_safe_push with vec.h.

 > +    Py_INCREF (pyo_reg_value);
 > +    unwind_info->prev_frame_regs[i].value = pyo_reg_value;
 > +  }
 > +  Py_INCREF (Py_None);
 > +  return Py_None;

Use Py_RETURN_NONE.

 > +}
 > +
 > +/* UnwindInfo cleanup.  */
 > +
 > +static void
 > +unwind_infopy_dealloc (PyObject *self)
 > +{
 > +  unwind_info_object *unwind_info = (unwind_info_object *) self;
 > +  int i;
 > +
 > +  Py_XDECREF (unwind_info->sniffer_info);
 > +  for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
 > +      Py_DECREF (unwind_info->prev_frame_regs[i].value);
 > +  xfree (unwind_info->prev_frame_regs);
 > +  Py_TYPE (self)->tp_free (self);
 > +}
 > +
 > +/* Called by the Python interpreter to obtain string representation
 > +   of the SnifferInfo object.  */
 > +
 > +static PyObject *
 > +sniffer_infopy_str (PyObject *self)
 > +{
 > +  struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
 > +
 > +  if (frame == NULL)
 > +    return PyString_FromString ("Stale SnifferInfo instance");
 > +  return PyString_FromFormat ("SP=%s,PC=%s",
 > +                              core_addr_to_string_nz (get_frame_sp (frame)),
 > +                              core_addr_to_string_nz (get_frame_pc (frame)));

Wrap the callbacks to gdb (get_frame_*) in TRY/CATCH/END_CATCH.

 > +}
 > +
 > +/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
 > +   Returns the value of register REGNUM as gdb.Value instance.  */

REGNUM can also be a name.
How about using "reg" instead of "regnum" and pointing out that it
can be an int or a string in the text.

 > +
 > +static PyObject *
 > +sniffer_infopy_read_register (PyObject *self, PyObject *args)
 > +{
 > +  volatile struct gdb_exception except;
 > +  int regnum;
 > +  struct value *val = NULL;
 > +  sniffer_info_object *sniffer_info = (sniffer_info_object *) self;
 > +  PyObject *pyo_reg_id;
 > +
 > +  if (sniffer_info->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "Attempting to read register from stale SnifferInfo");
 > +      return NULL;
 > +    }
 > +
 > +  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
 > +    return NULL;
 > +  if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, &regnum))
 > +    {
 > +      PyErr_SetString (PyExc_ValueError, "Bad register");
 > +      return NULL;
 > +    }
 > +  TRY
 > +    {
 > +      gdb_byte buffer[MAX_REGISTER_SIZE];

buffer is unused

 > +
 > +      val = get_frame_register_value (
 > +          ((sniffer_info_object *) self)->frame_info, regnum);

Cast is unnecessary, use sniffer_info local
(renamed to pending_frame).

 > +      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
 > +   gdb.SnifferInfo.unwind_info_with_id (self, SP, PC) -> None.  */

Rewrite to take a gdb.FrameID argument.

One might want to allow the frame_id arg to be duck-typed.
E.g., just fetch attributes sp,pc,special from the object
and if not present then assume not provided.
sp is required of course.

 > +
 > +static PyObject *
 > +sniffer_infopy_unwind_info_with_id (PyObject *self, PyObject *args)
 > +{
 > +  PyObject *pyo_sp;
 > +  PyObject *pyo_pc;
 > +  CORE_ADDR sp;
 > +  CORE_ADDR pc;
 > +
 > +  if (!PyArg_ParseTuple (args, "OO:unwind_info_with_id", &pyo_sp, &pyo_pc)
 > +       || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
 > +       || !pyuw_value_obj_to_pointer (pyo_pc, &pc))
 > +    return NULL;
 > +
 > +  return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
 > +}
 > +
 > +/* Implementation of
 > +   gdb.SnifferInfo.unwind_info_with_id_special (self, SP, PC, SPECIAL) -> None.  */

Delete.

 > +
 > +static PyObject *
 > +sniffer_infopy_unwind_info_with_id_special (PyObject *self, PyObject *args)
 > +{
 > +  PyObject *pyo_sp;
 > +  PyObject *pyo_pc;
 > +  PyObject *pyo_special;
 > +  CORE_ADDR sp;
 > +  CORE_ADDR pc;
 > +  CORE_ADDR special;
 > +
 > +  if (!PyArg_ParseTuple (args, "OOO:unwind_info_with_id_special",
 > +                          &pyo_sp, &pyo_pc, &pyo_special)
 > +      || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
 > +      || !pyuw_value_obj_to_pointer (pyo_pc, &pc)
 > +      || !pyuw_value_obj_to_pointer (pyo_special, &special))
 > +    return NULL;
 > +
 > +  return pyuw_create_unwind_info (self,
 > +                                  frame_id_build_special (sp, pc, special));
 > +}
 > +
 > +/* Implementation of
 > +   gdb.SnifferInfo.unwind_info_with_id_wild (self, SP) -> None.  */

Delete.

 > +
 > +static PyObject *
 > +sniffer_infopy_unwind_info_with_id_wild (PyObject *self, PyObject *args)
 > +{
 > +  PyObject *pyo_sp;
 > +  CORE_ADDR sp;
 > +
 > +  if (!PyArg_ParseTuple (args, "O:unwind_info_with_id_wild", &pyo_sp)
 > +      || !pyuw_value_obj_to_pointer (pyo_sp, &sp))
 > +    return NULL;
 > +
 > +  return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
 > +}
 > +
 > +/* Create Python SnifferInfo object.  */
 > +
 > +static PyObject *
 > +frame_info_to_sniffer_info_object (struct gdbarch *gdbarch,
 > +                                   struct frame_info *frame)
 > +{
 > +  sniffer_info_object *sniffer_info
 > +      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
 > +
 > +  sniffer_info->gdbarch = gdbarch;
 > +  sniffer_info->frame_info = frame;
 > +  return (PyObject *) sniffer_info;
 > +}
 > +
 > +/* Invalidate SnifferInfo object.  */

insert blank line

 > +static void
 > +sniffer_info_invalidate (PyObject *pyo_sniffer_info)
 > +{
 > +  if (pyo_sniffer_info == NULL)
 > +    return;
 > +  ((sniffer_info_object *) pyo_sniffer_info)->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 cleanup *cleanups;
 > +  struct cleanup *cached_frame_cleanups;
 > +  PyObject *pyo_module;
 > +  PyObject *pyo_execute;
 > +  PyObject *pyo_sniffer_info;
 > +  PyObject *pyo_unwind_info;
 > +  cached_frame_info *cached_frame = NULL;
 > +
 > +  gdbarch = (struct gdbarch *) (self->unwind_data);
 > +  cleanups = ensure_python_env (gdbarch, current_language);
 > +  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)));
 > +  pyo_sniffer_info = frame_info_to_sniffer_info_object (gdbarch, this_frame);
 > +  if (pyo_sniffer_info == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_sniffer_info);
 > +
 > +  if ((pyo_module = PyImport_ImportModule ("gdb.function.unwinders")) == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_module);
 > +
 > +  pyo_execute = PyObject_GetAttrString (pyo_module, "execute_unwinders");
 > +  if (pyo_execute == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_execute);
 > +
 > +  pyo_unwind_info
 > +      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_sniffer_info, NULL);
 > +  if (pyo_unwind_info == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_unwind_info);
 > +  if (pyo_unwind_info == Py_None)
 > +    goto error;
 > +  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 i;
 > +    int reg_count;
 > +    size_t cached_frame_size;
 > +    size_t gdb_bytes_count;
 > +    gdb_byte *gdb_data_free, *gdb_data_end;
 > +
 > +    /* Figure out how much space we need to allocate.  */
 > +    reg_count = unwind_info->prev_frame_regs_size;
 > +    gdb_bytes_count = reg_count * MAX_REGISTER_SIZE;
 > +    cached_frame_size = sizeof (*cached_frame) +
 > +        reg_count * sizeof (cached_frame->reg[0]) +
 > +        gdb_bytes_count * sizeof (gdb_byte);
 > +
 > +    cached_frame = xmalloc (cached_frame_size);
 > +    cached_frame_cleanups = make_cleanup (xfree, cached_frame);
 > +    /* Allocations after this point will be discarded!  */
 > +
 > +    gdb_data_end = (gdb_byte *) ((char *) cached_frame + cached_frame_size);
 > +    gdb_data_free = gdb_data_end - gdb_bytes_count;
 > +
 > +    cached_frame->gdbarch = gdbarch;
 > +    cached_frame->frame_id = unwind_info->frame_id;
 > +    cached_frame->reg_count = reg_count;
 > +
 > +    /* Populate registers array.  */
 > +    for (i = 0; i < reg_count; i++)
 > +      {
 > +        struct reg_info *reg = &(cached_frame->reg[i]);
 > +        struct value *value
 > +            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
 > +        size_t data_size;
 > +
 > +        reg->number = unwind_info->prev_frame_regs[i].number;
 > +        /* `value' validation was done before, just assert.  */
 > +        gdb_assert (value != NULL);
 > +        data_size = register_size (gdbarch, reg->number);
 > +        gdb_assert (data_size == TYPE_LENGTH (value_enclosing_type (value)));
 > +        /* Should not overflow  the buffer.  */
 > +        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
 > +        memcpy (gdb_data_free, value_contents (value), data_size);
 > +        reg->data = gdb_data_free;
 > +        gdb_data_free += data_size;
 > +      }
 > +  }
 > +
 > +  *cache_ptr = cached_frame;
 > +  discard_cleanups (cached_frame_cleanups);
 > +  do_cleanups (cleanups);
 > +  sniffer_info_invalidate (pyo_sniffer_info);

Make sniffer_info_invalidate (renamed to pending_frame_invalidate)
called via a cleanup.

 > +  return 1;
 > +
 > +error:
 > +  do_cleanups (cleanups);
 > +  sniffer_info_invalidate (pyo_sniffer_info);
 > +  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);
 > +      TRACE_PY_UNWIND (1, "%s: registered unwinder for %s\n", __FUNCTION__,
 > +                       gdbarch_bfd_arch_info (newarch)->printable_name);

I suspect this TRACE call can never print anything as all arches
are registered before the user could do "set debug ...".

 > +      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 (&sniffer_info_object_type) < 0)
 > +    return -1;
 > +  rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo",
 > +      (PyObject *) &sniffer_info_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 sniffer_info_object_methods[] =
 > +{
 > +  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
 > +    "read_register (REG) -> gdb.Value\n"
 > +    "Return the value of the REG in the frame." },
 > +  { "unwind_info_with_id",
 > +    sniffer_infopy_unwind_info_with_id, METH_VARARGS,
 > +    "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this FrameData, using given SP and PC registers \n"
 > +    "to identify the frame." },
 > +  { "unwind_info_with_id_special",
 > +    sniffer_infopy_unwind_info_with_id_special, METH_VARARGS,
 > +    "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL "
 > +    "registers to identify the frame." },
 > +  { "unwind_info_with_id_wild",
 > +    sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS,
 > +    "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this FrameData, using given SP register to \n"
 > +    "identify the frame." },
 > +  {NULL}  /* Sentinel */
 > +};
 > +
 > +static PyTypeObject sniffer_info_object_type =
 > +{
 > +  PyVarObject_HEAD_INIT (NULL, 0)
 > +  "gdb.SnifferInfo",              /* tp_name */
 > +  sizeof (sniffer_info_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 */
 > +  sniffer_infopy_str,             /* tp_str */
 > +  0,                              /* tp_getattro */
 > +  0,                              /* tp_setattro */
 > +  0,                              /* tp_as_buffer */
 > +  Py_TPFLAGS_DEFAULT,             /* tp_flags */
 > +  "GDB SnifferInfo object",       /* tp_doc */
 > +  0,                              /* tp_traverse */
 > +  0,                              /* tp_clear */
 > +  0,                              /* tp_richcompare */
 > +  0,                              /* tp_weaklistoffset */
 > +  0,                              /* tp_iter */
 > +  0,                              /* tp_iternext */
 > +  sniffer_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 */
 > +};
 > +

extra blank line

 > +
 > +static PyMethodDef unwind_info_object_methods[] =
 > +{
 > +  { "set_previous_frame_register",
 > +    unwind_infopy_set_previous_frame_register, METH_VARARGS,
 > +    "set_previous_frame_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..df2168b
 > --- /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 unwinders:"
 > +    "  global_unwinder"
 > +    "progspace.*unwinders:"
 > +    "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 unwinders:"
 > +    "  global_unwinder\\[disabled\\]"
 > +    "progspace.*unwinders:"
 > +    "  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..35d5313
 > --- /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(gdb, TestGlobalUnwinder())
 > +saw_runtime_error = False
 > +try:
 > +    gdb.unwinder.register_unwinder(gdb, 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(gdb, 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..8770578
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.py
 > @@ -0,0 +1,83 @@
 > +# 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 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, sniffer_info):
 > +        """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 = sniffer_info.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
 > +
 > +            sp = sniffer_info.read_register(TestUnwinder.AMD64_RSP)
 > +            ip = sniffer_info.read_register(TestUnwinder.AMD64_RIP)
 > +            unwind_info = sniffer_info.unwind_info_with_id(sp, ip)
 > +            unwind_info.set_previous_frame_register(TestUnwinder.AMD64_RBP,
 > +                                                    previous_bp)
 > +            unwind_info.set_previous_frame_register("rip", previous_ip)
 > +            unwind_info.set_previous_frame_register("rsp", previous_sp)
 > +            return unwind_info
 > +        except (gdb.error, RuntimeError):
 > +            return None
 > +
 > +gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
 > +print("Python script imported")
Andy Wingo March 24, 2015, 9:05 a.m. UTC | #2
Hi,

Replying for comments that affect the Guile patch.

On Mon 23 Mar 2015 20:58, Doug Evans <dje@google.com> writes:

> Andy: Can we forgo priorities for now until we have a use-case and thus
> something concrete to base them on?

Sure.

>  > +@node Unwinding Frames in Python
>  > +@subsubsection Unwinding Frames in Python
>  > +@cindex Unwinding frames in Python.
>
> IWBN if the Scheme and Python sides had similar text,
> but let's leave that until both patches are in.
> Then someone (maybe myself) can go back and edit them.

ACK

Andy
diff mbox

Patch

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..049aa05 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,17 +62,20 @@  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 \
 	gdb/command/explore.py \
 	gdb/function/__init__.py \
 	gdb/function/caller_is.py \
+        gdb/function/unwinders.py \
 	gdb/function/strfns.py \
 	gdb/printer/__init__.py \
 	gdb/printer/bound_registers.py
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index d725eb0..a2685e8 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,132 @@  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{SnifferInfo} instance) provides
+a method to read frame's registers:
+
+@defun SnifferInfo.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 several factory methods. If an unwinder recognizes
+the frame, it should invoke one of them to create a gdb.UnwindInfo
+instance to be returned to GDB:
+
+@defun SnifferInfo.unwind_info_with_id(sp, pc)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{sp} and @var{pc} values. This is the most common way of creating
+instance result.
+@end defun
+
+@defun SnifferInfo.frame_id_build_special(sp, pc, special)
+Returns a new @code{gdb.UnwindInfo} instance identitified by given
+@var{sp}, @var{pc}, and @var{special} values.
+@end defun
+
+@defun gdb.UnwindInfo.frame_id_build_wild(sp)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{sp} value.
+@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.set_previous_frame_register(reg, value)
+@var{reg} identifies the register. It can be a number or a name, just
+as for the @code{SnifferInfo.read_register} method above. @var{value}
+is a register value (a @code{gdb.Value} object).
+
+@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 MyUnwinder(Unwinder):
+    def __init__(....):
+        super(MyUnwinder, self).__init___(<expects unwinder name argument>)
+    def __call__(sniffer_info):
+        if not <we recognize frame>:
+            return None
+        # Create unwinder result. The most common way to achieve this is
+        # to find SP (stack pointer) and PC (program counter) values
+        # in the current frame and then call unwind_info_with_id method:
+        unwind_info = sniffer_info.unwind_info_with_id(sp, pc)
+
+        # Find the values of the registers in the caller's frame and 
+        # save them in the result:
+        unwind_info.set_previous_frame_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..734e5ca 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -71,6 +71,8 @@  type_printers = []
 xmethods = []
 # Initial frame filters.
 frame_filters = {}
+# Initial frame unwinders.
+frame_unwinders = []
 
 # 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..1f0c58b
--- /dev/null
+++ b/gdb/python/lib/gdb/command/unwinders.py
@@ -0,0 +1,199 @@ 
+# 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 could be 'global', a regular expression
+    matching the current program space's filename, or a regular
+    expression matching filenames of objfiles.  Locus could be
+    'progspace' to specify that only unwinders from the current
+    progspace should be listed.
+
+    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)
+
+    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" % (unwinder.name,
+                                  "" if unwinder.enabled else "[disabled]"))
+
+    def invoke(self, arg, from_tty):
+        locus_re, name_re = parse_unwinder_command_args(arg)
+        if locus_re.match("global"):
+            self.list_unwinders("global unwinders:", gdb.frame_unwinders,
+                                name_re)
+        if locus_re.match("progspace"):
+            cp = gdb.current_progspace()
+            self.list_unwinders("progspace %s unwinders:" % cp.filename,
+                                cp.frame_unwinders, name_re)
+        for objfile in gdb.objfiles():
+            if locus_re.match(objfile.filename):
+                self.list_unwinders("objfile %s unwinders:" % 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 matching the objects to examine.
+    Loci are "global", the program space's file, and the objfiles within
+    that program space.
+
+    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_DATA)
+
+    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 matching the objects to examine.
+    Loci are "global", the program space's file, and the objfiles within
+    that program space.
+
+    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_DATA)
+
+    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/function/unwinders.py b/gdb/python/lib/gdb/function/unwinders.py
new file mode 100644
index 0000000..9be8b8b
--- /dev/null
+++ b/gdb/python/lib/gdb/function/unwinders.py
@@ -0,0 +1,52 @@ 
+# 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/>.
+
+"""Internal functions for working with frame unwinders."""
+
+import gdb
+
+
+def execute_unwinders(sniffer_info):
+    """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:
+        sniffer_info: an instance of gdb.SnifferInfo.
+    Returns:
+        UnwindInfo instance or None.
+    """
+    for objfile in gdb.objfiles():
+        for unwinder in objfile.frame_unwinders:
+            if unwinder.enabled:
+                unwind_info = unwinder.__call__(sniffer_info)
+                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.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    for unwinder in gdb.frame_unwinders:
+        if unwinder.enabled:
+            unwind_info = unwinder.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    return None
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
new file mode 100644
index 0000000..2d65a5e
--- /dev/null
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -0,0 +1,89 @@ 
+# 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.
+
+    A 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, sniffer_info):
+        """GDB calls this method to unwind a frame.
+
+        Arguments:
+            sniffer_info: An instance of gdb.SnifferInfo describing the frame.
+
+        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.
+
+    """
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s unwinder ...\n" % unwinder.name)
+        locus = gdb
+    else:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering %s unwinder for %s ...\n" %
+                      (unwinder.name, locus.filename))
+    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..128d710
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,831 @@ 
+/* 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;
+} sniffer_info_object;
+
+/* The data we keep for the PyUnwindInfo: sniffer_info, previous
+ * frame's register set and frame ID.  */
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* gdb.SnifferInfo for the frame we are unwinding.  */
+  PyObject *sniffer_info;
+
+  /* Its ID.  */
+  struct frame_id frame_id;
+
+  /* Previous frame registers array.  */
+  struct reg_pydata
+  {
+    int number;
+    PyObject *value;
+  } *prev_frame_regs;
+
+  /* The current size of the array above.  */
+  int prev_frame_regs_size;
+
+  /* And its capacity.  */
+  int prev_frame_regs_capacity;
+
+} 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;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_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;
+
+/* Parse Python Int, saving it at the given address. Returns 1 on success,
+   0 otherwise.  */
+
+static int
+pyuw_parse_int (PyObject *pyo_int, int *valuep)
+{
+  long long_value;
+
+  if (pyo_int == NULL)
+    return 0;
+
+  /* Make a long logic check first.  In Python 3.x, internally, all
+     integers are represented as longs.  In Python 2.x, there is still
+     a differentiation internally between a PyInt and a PyLong.
+     Explicitly do this long check conversion first. In GDB, for
+     Python 3.x, we #ifdef PyInt = PyLong.  This check has to be done
+     first to ensure we do not lose information in the conversion
+     process.  */
+  else if (PyLong_Check (pyo_int))
+    {
+      LONGEST l = PyLong_AsLongLong (pyo_int);
+
+      if (PyErr_Occurred ())
+        return 0;
+      long_value = (long)l;
+      if (l != long_value)
+        return 0;
+    }
+  else if (PyInt_Check (pyo_int))
+    {
+      long_value = PyInt_AsLong (pyo_int);
+      if (PyErr_Occurred ())
+        return 0;
+    }
+  else
+    return 0;
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* 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);
+      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 (pyuw_parse_int (pyo_reg_id, reg_num))
+    return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
+  else
+    return 0;
+}
+
+/* Convert gdb.Value object to COREADDR.  */
+
+static int
+pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
+{
+  struct value *value = value_object_to_value (pyo_value);
+
+  if (value == NULL)
+    return 0;
+  *addr = unpack_pointer (value_type (value), value_contents (value));
+  return 1;
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the UnwindInfo object.  */
+
+static PyObject *
+unwind_infopy_str (PyObject *self)
+{
+  PyObject *result;
+  struct ui_file *strfile = mem_fileopen ();
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  sniffer_info_object *sniffer_info
+      = (sniffer_info_object *) (unwind_info->sniffer_info);
+
+  fprintf_unfiltered (strfile, "Frame ID: ");
+  fprint_frame_id (strfile, unwind_info->frame_id);
+  {
+    int i;
+    char *sep = "";
+    struct value_print_options opts;
+
+    get_user_print_options (&opts);
+    fprintf_unfiltered (strfile, "\nPrevious frame registers: (");
+    for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
+      {
+        struct value *value
+            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
+
+        fprintf_unfiltered (strfile, "%s(%d, ", sep,
+                            unwind_info->prev_frame_regs[i].number);
+        if (value != NULL)
+          {
+            value_print (value, strfile, &opts);
+            fprintf_unfiltered (strfile, ")");
+          }
+        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 SnifferInfo and frame ID.  */
+
+static PyObject *
+pyuw_create_unwind_info (PyObject *pyo_sniffer_info,
+                         struct frame_id frame_id)
+{
+  unwind_info_object *unwind_info
+      = PyObject_New (unwind_info_object, &unwind_info_object_type);
+
+  if (((sniffer_info_object *) pyo_sniffer_info)->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to use stale SnifferInfo");
+      return NULL;
+    }
+  unwind_info->frame_id = frame_id;
+  Py_INCREF (pyo_sniffer_info);
+  unwind_info->sniffer_info = pyo_sniffer_info;
+  unwind_info->prev_frame_regs_size = 0;
+  unwind_info->prev_frame_regs_capacity = 4;
+  unwind_info->prev_frame_regs =
+      xmalloc (unwind_info->prev_frame_regs_capacity *
+               sizeof (unwind_info->prev_frame_regs[0]));
+  return (PyObject *) unwind_info;
+}
+
+/* The implementation of
+   gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None.  */
+
+static PyObject *
+unwind_infopy_set_previous_frame_register (PyObject *self, PyObject *args)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  sniffer_info_object *sniffer_info
+      = (sniffer_info_object *) (unwind_info->sniffer_info);
+  PyObject *pyo_reg_id;
+  PyObject *pyo_reg_value;
+  int regnum;
+
+  if (sniffer_info->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "UnwindInfo instance refers to a stale SnifferInfo");
+      return NULL;
+    }
+  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
+                          &pyo_reg_id, &pyo_reg_value))
+    return NULL;
+  if (!pyuw_parse_register_id (sniffer_info->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 (sniffer_info->gdbarch, regnum);
+    if (data_size != TYPE_LENGTH (value_enclosing_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_enclosing_type (value))),
+            (unsigned) data_size);
+        return NULL;
+      }
+  }
+  {
+    int i;
+
+    for (i = 0;
+         (i < unwind_info->prev_frame_regs_size)
+             && regnum != unwind_info->prev_frame_regs[i].number; i++)
+      ;
+    if (i < unwind_info->prev_frame_regs_size)
+      Py_DECREF (unwind_info->prev_frame_regs[i].value);
+    else
+      {
+        if (i >= unwind_info->prev_frame_regs_capacity)
+          {
+            unwind_info->prev_frame_regs_capacity *= 2;
+            unwind_info->prev_frame_regs = xrealloc
+                (unwind_info->prev_frame_regs,
+                 unwind_info->prev_frame_regs_capacity
+                 * sizeof (unwind_info->prev_frame_regs[0]));
+          }
+        unwind_info->prev_frame_regs_size++;
+        unwind_info->prev_frame_regs[i].number = regnum;
+      }
+    Py_INCREF (pyo_reg_value);
+    unwind_info->prev_frame_regs[i].value = pyo_reg_value;
+  }
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+/* UnwindInfo cleanup.  */
+
+static void
+unwind_infopy_dealloc (PyObject *self)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  int i;
+
+  Py_XDECREF (unwind_info->sniffer_info);
+  for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
+      Py_DECREF (unwind_info->prev_frame_regs[i].value);
+  xfree (unwind_info->prev_frame_regs);
+  Py_TYPE (self)->tp_free (self);
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
+
+  if (frame == NULL)
+    return PyString_FromString ("Stale SnifferInfo instance");
+  return PyString_FromFormat ("SP=%s,PC=%s",
+                              core_addr_to_string_nz (get_frame_sp (frame)),
+                              core_addr_to_string_nz (get_frame_pc (frame)));
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of register REGNUM as gdb.Value instance.  */
+
+static PyObject *
+sniffer_infopy_read_register (PyObject *self, PyObject *args)
+{
+  volatile struct gdb_exception except;
+  int regnum;
+  struct value *val = NULL;
+  sniffer_info_object *sniffer_info = (sniffer_info_object *) self;
+  PyObject *pyo_reg_id;
+
+  if (sniffer_info->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to read register from stale SnifferInfo");
+      return NULL;
+    }
+
+  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
+    return NULL;
+  if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+  TRY
+    {
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      val = get_frame_register_value (
+          ((sniffer_info_object *) self)->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
+   gdb.SnifferInfo.unwind_info_with_id (self, SP, PC) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+
+  if (!PyArg_ParseTuple (args, "OO:unwind_info_with_id", &pyo_sp, &pyo_pc)
+       || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+       || !pyuw_value_obj_to_pointer (pyo_pc, &pc))
+    return NULL;
+
+  return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id_special (self, SP, PC, SPECIAL) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id_special (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  PyObject *pyo_special;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+  CORE_ADDR special;
+
+  if (!PyArg_ParseTuple (args, "OOO:unwind_info_with_id_special",
+                          &pyo_sp, &pyo_pc, &pyo_special)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+      || !pyuw_value_obj_to_pointer (pyo_pc, &pc)
+      || !pyuw_value_obj_to_pointer (pyo_special, &special))
+    return NULL;
+
+  return pyuw_create_unwind_info (self,
+                                  frame_id_build_special (sp, pc, special));
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id_wild (self, SP) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id_wild (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  CORE_ADDR sp;
+
+  if (!PyArg_ParseTuple (args, "O:unwind_info_with_id_wild", &pyo_sp)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp))
+    return NULL;
+
+  return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct gdbarch *gdbarch,
+                                   struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  sniffer_info->gdbarch = gdbarch;
+  sniffer_info->frame_info = frame;
+  return (PyObject *) sniffer_info;
+}
+
+/* Invalidate SnifferInfo object.  */
+static void
+sniffer_info_invalidate (PyObject *pyo_sniffer_info)
+{
+  if (pyo_sniffer_info == NULL)
+    return;
+  ((sniffer_info_object *) pyo_sniffer_info)->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 cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdbarch = (struct gdbarch *) (self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  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)));
+  pyo_sniffer_info = frame_info_to_sniffer_info_object (gdbarch, this_frame);
+  if (pyo_sniffer_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_sniffer_info);
+
+  if ((pyo_module = PyImport_ImportModule ("gdb.function.unwinders")) == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_module);
+
+  pyo_execute = PyObject_GetAttrString (pyo_module, "execute_unwinders");
+  if (pyo_execute == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_execute);
+
+  pyo_unwind_info
+      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_sniffer_info, NULL);
+  if (pyo_unwind_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_unwind_info);
+  if (pyo_unwind_info == Py_None)
+    goto error;
+  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 i;
+    int reg_count;
+    size_t cached_frame_size;
+    size_t gdb_bytes_count;
+    gdb_byte *gdb_data_free, *gdb_data_end;
+
+    /* Figure out how much space we need to allocate.  */
+    reg_count = unwind_info->prev_frame_regs_size;
+    gdb_bytes_count = reg_count * MAX_REGISTER_SIZE;
+    cached_frame_size = sizeof (*cached_frame) +
+        reg_count * sizeof (cached_frame->reg[0]) +
+        gdb_bytes_count * sizeof (gdb_byte);
+
+    cached_frame = xmalloc (cached_frame_size);
+    cached_frame_cleanups = make_cleanup (xfree, cached_frame);
+    /* Allocations after this point will be discarded!  */
+
+    gdb_data_end = (gdb_byte *) ((char *) cached_frame + cached_frame_size);
+    gdb_data_free = gdb_data_end - gdb_bytes_count;
+
+    cached_frame->gdbarch = gdbarch;
+    cached_frame->frame_id = unwind_info->frame_id;
+    cached_frame->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; i < reg_count; i++)
+      {
+        struct reg_info *reg = &(cached_frame->reg[i]);
+        struct value *value
+            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
+        size_t data_size;
+
+        reg->number = unwind_info->prev_frame_regs[i].number;
+        /* `value' validation was done before, just assert.  */
+        gdb_assert (value != NULL);
+        data_size = register_size (gdbarch, reg->number);
+        gdb_assert (data_size == TYPE_LENGTH (value_enclosing_type (value)));
+        /* Should not overflow  the buffer.  */
+        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+        memcpy (gdb_data_free, value_contents (value), data_size);
+        reg->data = gdb_data_free;
+        gdb_data_free += data_size;
+      }
+  }
+
+  *cache_ptr = cached_frame;
+  discard_cleanups (cached_frame_cleanups);
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 1;
+
+error:
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  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);
+      TRACE_PY_UNWIND (1, "%s: registered unwinder for %s\n", __FUNCTION__,
+                       gdbarch_bfd_arch_info (newarch)->printable_name);
+      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 (&sniffer_info_object_type) < 0)
+    return -1;
+  rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo",
+      (PyObject *) &sniffer_info_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 sniffer_info_object_methods[] =
+{
+  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
+    "read_register (REG) -> gdb.Value\n"
+    "Return the value of the REG in the frame." },
+  { "unwind_info_with_id",
+    sniffer_infopy_unwind_info_with_id, METH_VARARGS,
+    "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP and PC registers \n"
+    "to identify the frame." },
+  { "unwind_info_with_id_special",
+    sniffer_infopy_unwind_info_with_id_special, METH_VARARGS,
+    "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL "
+    "registers to identify the frame." },
+  { "unwind_info_with_id_wild",
+    sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS,
+    "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP register to \n"
+    "identify the frame." },
+  {NULL}  /* Sentinel */
+};
+
+static PyTypeObject sniffer_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.SnifferInfo",              /* tp_name */
+  sizeof (sniffer_info_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 */
+  sniffer_infopy_str,             /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT,             /* tp_flags */
+  "GDB SnifferInfo object",       /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  sniffer_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 */
+};
+
+
+static PyMethodDef unwind_info_object_methods[] =
+{
+  { "set_previous_frame_register",
+    unwind_infopy_set_previous_frame_register, METH_VARARGS,
+    "set_previous_frame_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..df2168b
--- /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 unwinders:"
+    "  global_unwinder"
+    "progspace.*unwinders:"
+    "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 unwinders:"
+    "  global_unwinder\\[disabled\\]"
+    "progspace.*unwinders:"
+    "  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..35d5313
--- /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(gdb, TestGlobalUnwinder())
+saw_runtime_error = False
+try:
+    gdb.unwinder.register_unwinder(gdb, 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(gdb, 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..8770578
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,83 @@ 
+# 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 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, sniffer_info):
+        """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 = sniffer_info.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
+
+            sp = sniffer_info.read_register(TestUnwinder.AMD64_RSP)
+            ip = sniffer_info.read_register(TestUnwinder.AMD64_RIP)
+            unwind_info = sniffer_info.unwind_info_with_id(sp, ip)
+            unwind_info.set_previous_frame_register(TestUnwinder.AMD64_RBP,
+                                                    previous_bp)
+            unwind_info.set_previous_frame_register("rip", previous_ip)
+            unwind_info.set_previous_frame_register("rsp", previous_sp)
+            return unwind_info
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
+print("Python script imported")