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

Message ID CAHQ51u5=mNXcA_CY4hQuvVHSKuMbaVUYyHvhrgZEUJZdX2iL8Q@mail.gmail.com
State New, archived
Headers

Commit Message

Alexander Smundak March 28, 2015, 1:10 a.m. UTC
  Addressed eliz@ and dje@ comments.

gdb/ChangeLog

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

    * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
    (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
    (py-unwind.o): New recipe.
    * NEWS: mention Python frame unwinding.
    * data-directory/Makefile.in (PYTHON_FILE_LIST): Add
    gdb/unwinder.py and gdb/command/unwinder.py
    * doc/python.texi (Writing a Frame Unwinder in Python): Add
    section.
    * python/lib/gdb/__init__.py (packages): Add frame_unwinders
    list.
    (execute_unwinders): New function.
    * python/lib/gdb/command/unwinders.py: New file.
    * python/lib/gdb/unwinder.py: New file.
    * python/py-objfile.c (objfile_object): Add frame_unwinders field.
    (objfpy_dealloc): Decrement frame_unwinders reference count.
    (objfpy_initialize): Create frame_unwinders list.
    (objfpy_get_frame_unwinders): New function.
    (objfpy_set_frame_unwinders): Ditto.
    (objfile_getset): Add frame_unwinders attribute to Objfile.
    * python/py-progspace.c (pspace_object): Add frame_unwinders field.
    (pspy_dealloc): Decrement frame_unwinders reference count.
    (pspy_initialize): Create frame_unwinders list.
    (pspy_get_frame_unwinders): New function.
    (pspy_set_frame_unwinders): Ditto.
    (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
    * python/py-unwind.c: New file.
    * python/python-internal.h (pspy_get_name_unwinders): New prototype.
    (objpy_get_frame_unwinders): New prototype.
    (gdbpy_initialize_unwind): New prototype.
    * python/python.c (gdbpy_apply_type_printers): Call
    gdbpy_initialize_unwind.

gdb/testsuite/ChangeLog

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

    * gdb.python/py-unwind-maint.c: New file.
    * gdb.python/py-unwind-maint.exp: New test.
    * gdb.python/py-unwind-maint.py: New file.
    * gdb.python/py-unwind.c: New file.
    * gdb.python/py-unwind.exp: New test.
    * gdb.python/py-unwind.py: New test.
  

Comments

Doug Evans March 30, 2015, 5:45 p.m. UTC | #1
Alexander Smundak writes:
 > Addressed eliz@ and dje@ comments.
 > 
 > gdb/ChangeLog
 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >     * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
 >     (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
 >     (py-unwind.o): New recipe.
 >     * NEWS: mention Python frame unwinding.
 >     * data-directory/Makefile.in (PYTHON_FILE_LIST): Add
 >     gdb/unwinder.py and gdb/command/unwinder.py
 >     * doc/python.texi (Writing a Frame Unwinder in Python): Add
 >     section.
 >     * python/lib/gdb/__init__.py (packages): Add frame_unwinders
 >     list.
 >     (execute_unwinders): New function.
 >     * python/lib/gdb/command/unwinders.py: New file.
 >     * python/lib/gdb/unwinder.py: New file.
 >     * python/py-objfile.c (objfile_object): Add frame_unwinders field.
 >     (objfpy_dealloc): Decrement frame_unwinders reference count.
 >     (objfpy_initialize): Create frame_unwinders list.
 >     (objfpy_get_frame_unwinders): New function.
 >     (objfpy_set_frame_unwinders): Ditto.
 >     (objfile_getset): Add frame_unwinders attribute to Objfile.
 >     * python/py-progspace.c (pspace_object): Add frame_unwinders field.
 >     (pspy_dealloc): Decrement frame_unwinders reference count.
 >     (pspy_initialize): Create frame_unwinders list.
 >     (pspy_get_frame_unwinders): New function.
 >     (pspy_set_frame_unwinders): Ditto.
 >     (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
 >     * python/py-unwind.c: New file.
 >     * python/python-internal.h (pspy_get_name_unwinders): New prototype.
 >     (objpy_get_frame_unwinders): New prototype.
 >     (gdbpy_initialize_unwind): New prototype.
 >     * python/python.c (gdbpy_apply_type_printers): Call
 >     gdbpy_initialize_unwind.
 > 
 > gdb/testsuite/ChangeLog
 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >     * gdb.python/py-unwind-maint.c: New file.
 >     * gdb.python/py-unwind-maint.exp: New test.
 >     * gdb.python/py-unwind-maint.py: New file.
 >     * gdb.python/py-unwind.c: New file.
 >     * gdb.python/py-unwind.exp: New test.
 >     * gdb.python/py-unwind.py: New test.

Hi.  Just a few more nits.

 > diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
 > index d725eb0..6b1878e 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,148 @@ 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 @value{GDBN} 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 @value{GDBN}
 > +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 @value{GDBN} handle standard frames.
 > +However, mixed language applications (for example, an application
 > +running Java Virtual Machine) sometimes use frame layouts that cannot
 > +be handled by the @value{GDBN} 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 @code{gdb.UnwindInfo class)}
 > +describing it.  If an unwinder does not recognize a frame, it should
 > +return @code{None}.  The code in @value{GDBN} that enables writing
 > +unwinders in Python uses this object to return frame's ID and previous
 > +frame registers when @value{GDBN} core asks for them.
 > +
 > +@subheading Unwinder Input
 > +
 > +An object passed to an unwinder (a @code{gdb.PendingFrame} instance)
 > +provides a method to read frame's registers:
 > +
 > +@defun PendingFrame.read_register (reg)
 > +This method returns the contents of the register @var{regn} in the
 > +frame as a @code{gdb.Value} object.  @var{reg} can be either a
 > +register number or a register name; the values are platform-specific.
 > +They are usually found in the corresponding
 > +@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree.
 > +@end defun
 > +
 > +It also provides a factory method to create a @code{gdb.UnwindInfo}
 > +instance to be returned to @value{GDBN}:
 > +
 > +@defun PendingFrame.create_unwind_info (frame_id)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{frame_id}.  The argument is used to build @value{GDBN}'s frame ID
 > +using one of functions provided by @value{GDBN}.  @var{frame_id}'s attributes
 > +determine which function will be used, as follows:
 > +
 > +@table @code
 > +@item sp, pc, special
 > +@code{frame_id_build_special (@var{frame_id}.sp, @var{frame_id}.pc, @var{frame_id}.special)}
 > +
 > +@item sp, pc
 > +@code{frame_id_build (@var{frame_id}.sp, @var{frame_id}.pc)}
 > +
 > +This is the most common case.
 > +
 > +@item sp
 > +@code{frame_id_build_wild (@var{frame_id}.sp)}
 > +@end table
 > +The attribute values should be @code{gdb.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

I'd replace "can be constructed by one of the methods describe above." with
"is constructed with the @code{PendingFrame.create_unwind_info}
method described above."

 > +frame's registers:
 > +
 > +@defun gdb.UnwindInfo.add_saved_register (reg, value)
 > +@var{reg} identifies the register.  It can be a number or a name, just
 > +as for the @code{PendingFrame.read_register} method above.
 > +@var{value} is a register value (a @code{gdb.Value} object).
 > +@end defun
 > +
 > +@subheading Unwinder Skeleton Code
 > +
 > +@value{GDBN} comes with the module containing the base @code{Unwinder}
 > +class.  Derive your unwinder class from it and structure the code as
 > +follows:
 > +
 > +@smallexample
 > +from gdb.unwinders import Unwinder
 > +
 > +class FrameId(object):
 > +    def __init__(self, sp, pc):
 > +        self.sp = sp
 > +        self.pc = pc
 > +
 > +
 > +class MyUnwinder(Unwinder):
 > +    def __init__(....):
 > +        supe(MyUnwinder, self).__init___(<expects unwinder name argument>)
 > +
 > +    def __call__(pending_frame):
 > +        if not <we recognize frame>:
 > +            return None
 > +        # Create UnwindInfo.  Usually the frame is identified by the stack 
 > +        # pointer and the program counter.
 > +        sp = pending_frame.read_register(<SP number>)
 > +        pc = pending_frame.read_register(<PC number>)
 > +        unwind_info = pending_frame.create_unwind_info(FrameId(sp, pc))
 > +
 > +        # Find the values of the registers in the caller's frame and 
 > +        # save them in the result:
 > +        unwind_info.add_saved_register(<register>, <value>)
 > +        ....
 > +
 > +        # Return the result:
 > +        return unwind_instance

s/unwind_instance/unwind_info/

 > diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
 > index 92b06f2..0494959 100644
 > --- a/gdb/python/lib/gdb/__init__.py
 > +++ b/gdb/python/lib/gdb/__init__.py
 > @@ -71,6 +71,42 @@ type_printers = []
 >  xmethods = []
 >  # Initial frame filters.
 >  frame_filters = {}
 > +# Initial frame unwinders.
 > +frame_unwinders = []
 > +
 > +def execute_unwinders(pending_frame):
 > +    """Internal function called from GDB to execute all unwinders.
 > +
 > +    Runs each currently enabled unwinder until it finds the one that
 > +    can unwind given frame.
 > +
 > +    Arguments:
 > +        pending_frame: gdb.PendingFrame instance.
 > +    Returns:
 > +        gdb.UnwindInfo instance or None.
 > +    """
 > +    for objfile in objfiles():

It's odd to call objfiles() here and _gdb.current_progspace() below.
Either the _gdb. prefix is necessary or it is not.
At the least let's be consistent here.
How about instead of adding _gdb. prefix here ...

 > +        for unwinder in objfile.frame_unwinders:
 > +            if unwinder.enabled:
 > +                unwind_info = unwinder(pending_frame)
 > +                if unwind_info is not None:
 > +                    return unwind_info
 > +
 > +    current_progspace = _gdb.current_progspace()

.... remove it here.
[and rerun the testsuite to verify]

 > +    for unwinder in current_progspace.frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder(pending_frame)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    for unwinder in frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder(pending_frame)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    return None
 > +
 >  
 >  # Convenience variable to GDB's python directory
 >  PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
  

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..30cfd17 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,11 +62,13 @@  PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/types.py \
 	gdb/printing.py \
+	gdb/unwinder.py \
 	gdb/prompt.py \
 	gdb/xmethod.py \
 	gdb/command/__init__.py \
 	gdb/command/xmethods.py \
 	gdb/command/frame_filters.py \
+	gdb/command/unwinders.py \
 	gdb/command/type_printers.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index d725eb0..6b1878e 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,148 @@  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 @value{GDBN} 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 @value{GDBN}
+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 @value{GDBN} handle standard frames.
+However, mixed language applications (for example, an application
+running Java Virtual Machine) sometimes use frame layouts that cannot
+be handled by the @value{GDBN} 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 @code{gdb.UnwindInfo class)}
+describing it.  If an unwinder does not recognize a frame, it should
+return @code{None}.  The code in @value{GDBN} that enables writing
+unwinders in Python uses this object to return frame's ID and previous
+frame registers when @value{GDBN} core asks for them.
+
+@subheading Unwinder Input
+
+An object passed to an unwinder (a @code{gdb.PendingFrame} instance)
+provides a method to read frame's registers:
+
+@defun PendingFrame.read_register (reg)
+This method returns the contents of the register @var{regn} in the
+frame as a @code{gdb.Value} object.  @var{reg} can be either a
+register number or a register name; the values are platform-specific.
+They are usually found in the corresponding
+@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree.
+@end defun
+
+It also provides a factory method to create a @code{gdb.UnwindInfo}
+instance to be returned to @value{GDBN}:
+
+@defun PendingFrame.create_unwind_info (frame_id)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{frame_id}.  The argument is used to build @value{GDBN}'s frame ID
+using one of functions provided by @value{GDBN}.  @var{frame_id}'s attributes
+determine which function will be used, as follows:
+
+@table @code
+@item sp, pc, special
+@code{frame_id_build_special (@var{frame_id}.sp, @var{frame_id}.pc, @var{frame_id}.special)}
+
+@item sp, pc
+@code{frame_id_build (@var{frame_id}.sp, @var{frame_id}.pc)}
+
+This is the most common case.
+
+@item sp
+@code{frame_id_build_wild (@var{frame_id}.sp)}
+@end table
+The attribute values should be @code{gdb.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.add_saved_register (reg, value)
+@var{reg} identifies the register.  It can be a number or a name, just
+as for the @code{PendingFrame.read_register} method above.
+@var{value} is a register value (a @code{gdb.Value} object).
+@end defun
+
+@subheading Unwinder Skeleton Code
+
+@value{GDBN} comes with the module containing the base @code{Unwinder}
+class.  Derive your unwinder class from it and structure the code as
+follows:
+
+@smallexample
+from gdb.unwinders import Unwinder
+
+class FrameId(object):
+    def __init__(self, sp, pc):
+        self.sp = sp
+        self.pc = pc
+
+
+class MyUnwinder(Unwinder):
+    def __init__(....):
+        supe(MyUnwinder, self).__init___(<expects unwinder name argument>)
+
+    def __call__(pending_frame):
+        if not <we recognize frame>:
+            return None
+        # Create UnwindInfo.  Usually the frame is identified by the stack 
+        # pointer and the program counter.
+        sp = pending_frame.read_register(<SP number>)
+        pc = pending_frame.read_register(<PC number>)
+        unwind_info = pending_frame.create_unwind_info(FrameId(sp, pc))
+
+        # Find the values of the registers in the caller's frame and 
+        # save them in the result:
+        unwind_info.add_saved_register(<register>, <value>)
+        ....
+
+        # Return the result:
+        return unwind_instance
+
+@end smallexample
+
+@subheading Registering a Unwinder
+
+An object file, a program space, and the @value{GDBN} proper can have
+unwinders registered with it.
+
+The @code{gdb.unwinders} module provides the function to register a
+unwinder:
+
+@defun gdb.unwinder.register_unwinder (locus, unwinder, replace=False)
+@var{locus} is specifies an object file or a program space to which
+@var{unwinder} is added.  Passing @code{None} or @code{gdb} adds
+@var{unwinder} to the @value{GDBN}'s global unwinder list.  The newly
+added @var{unwinder} will be called before any other unwinder from the
+same locus.  Two unwinders in the same locus cannot have the same
+name.  An attempt to add a unwinder with already existing name raises
+an exception unless @var{replace} is @code{True}, in which case the
+old unwinder is deleted.
+@end defun
+
+@subheading Unwinder Precedence
+
+@value{GDBN} first calls the unwinders from all the object files in no
+particular order, then the unwinders from the current program space,
+and finally the unwinders from @value{GDBN}.
+
 @node Xmethods In Python
 @subsubsection Xmethods In Python
 @cindex xmethods in Python
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 92b06f2..0494959 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -71,6 +71,42 @@  type_printers = []
 xmethods = []
 # Initial frame filters.
 frame_filters = {}
+# Initial frame unwinders.
+frame_unwinders = []
+
+def execute_unwinders(pending_frame):
+    """Internal function called from GDB to execute all unwinders.
+
+    Runs each currently enabled unwinder until it finds the one that
+    can unwind given frame.
+
+    Arguments:
+        pending_frame: gdb.PendingFrame instance.
+    Returns:
+        gdb.UnwindInfo instance or None.
+    """
+    for objfile in objfiles():
+        for unwinder in objfile.frame_unwinders:
+            if unwinder.enabled:
+                unwind_info = unwinder(pending_frame)
+                if unwind_info is not None:
+                    return unwind_info
+
+    current_progspace = _gdb.current_progspace()
+    for unwinder in current_progspace.frame_unwinders:
+        if unwinder.enabled:
+            unwind_info = unwinder(pending_frame)
+            if unwind_info is not None:
+                return unwind_info
+
+    for unwinder in frame_unwinders:
+        if unwinder.enabled:
+            unwind_info = unwinder(pending_frame)
+            if unwind_info is not None:
+                return unwind_info
+
+    return None
+
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py
new file mode 100644
index 0000000..8530b35
--- /dev/null
+++ b/gdb/python/lib/gdb/command/unwinders.py
@@ -0,0 +1,198 @@ 
+# Unwinder commands.
+# Copyright 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+import re
+
+
+def validate_regexp(exp, idstring):
+    try:
+        return re.compile(exp)
+    except SyntaxError:
+        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
+
+
+def parse_unwinder_command_args(arg):
+    """Internal utility to parse unwinder command argv.
+
+    Arguments:
+        arg: The arguments to the command. The format is:
+             [locus-regexp [name-regexp]]
+
+    Returns:
+        A 2-tuple of compiled regular expressions.
+
+    Raises:
+        SyntaxError: an error processing ARG
+    """
+
+    argv = gdb.string_to_argv(arg)
+    argc = len(argv)
+    if argc > 2:
+        raise SyntaxError("Too many arguments.")
+    locus_regexp = ""
+    name_regexp = ""
+    if argc >= 1:
+        locus_regexp = argv[0]
+        if argc >= 2:
+            name_regexp = argv[1]
+    return (validate_regexp(locus_regexp, "locus"),
+            validate_regexp(name_regexp, "unwinder"))
+
+
+class InfoUnwinder(gdb.Command):
+    """GDB command to list unwinders.
+
+    Usage: info unwinder [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the location of the
+    unwinder.  If it is omitted, all registered unwinders from all
+    loci are listed.  A locus can be 'global', 'progspace' to list
+    the unwinders from the current progspace, or a regular expression
+    matching filenames of objfiles.
+
+    NAME-REGEXP is a regular expression to filter unwinder names.  If
+    this omitted for a specified locus, then all registered unwinders
+    in the locus are listed.
+    """
+
+    def __init__(self):
+        super(InfoUnwinder, self).__init__("info unwinder",
+                                            gdb.COMMAND_STACK)
+
+    def list_unwinders(self, title, unwinders, name_re):
+        """Lists the unwinders whose name matches regexp.
+
+        Arguments:
+            title: The line to print before the list.
+            unwinders: The list of the unwinders.
+            name_re: unwinder name filter.
+        """
+        if not unwinders:
+            return
+        print title
+        for unwinder in unwinders:
+            if name_re.match(unwinder.name):
+                print("  %s%s" % (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:", gdb.frame_unwinders,
+                                name_re)
+        if locus_re.match("progspace"):
+            cp = gdb.current_progspace()
+            self.list_unwinders("Progspace %s:" % cp.filename,
+                                cp.frame_unwinders, name_re)
+        for objfile in gdb.objfiles():
+            if locus_re.match(objfile.filename):
+                self.list_unwinders("Objfile %s:" % objfile.filename,
+                                    objfile.frame_unwinders, name_re)
+
+
+def do_enable_unwinder1(unwinders, name_re, flag):
+    """Enable/disable unwinders whose names match given regex.
+
+    Arguments:
+        unwinders: The list of unwinders.
+        name_re: Unwinder name filter.
+        flag: Enable/disable.
+
+    Returns:
+        The number of unwinders affected.
+    """
+    total = 0
+    for unwinder in unwinders:
+        if name_re.match(unwinder.name):
+            unwinder.enabled = flag
+            total += 1
+    return total
+
+
+def do_enable_unwinder(arg, flag):
+    """Enable/disable unwinder(s)."""
+    (locus_re, name_re) = parse_unwinder_command_args(arg)
+    total = 0
+    if locus_re.match("global"):
+        total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag)
+    if locus_re.match("progspace"):
+        total += do_enable_unwinder1(gdb.current_progspace().frame_unwinders,
+                                     name_re, flag)
+    for objfile in gdb.objfiles():
+        if locus_re.match(objfile.filename):
+            total += do_enable_unwinder1(objfile.frame_unwinders, name_re,
+                                         flag)
+    print("%d unwinder%s %s" % (total, "" if total == 1 else "s",
+                                "enabled" if flag else "disabled"))
+
+
+class EnableUnwinder(gdb.Command):
+    """GDB command to enable unwinders.
+
+    Usage: enable unwinder [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression specifying the unwinders to
+    enable.  It can 'global', 'progspace', or the name of an objfile
+    within that progspace.
+
+    NAME_REGEXP is a regular expression to filter unwinder names.  If
+    this omitted for a specified locus, then all registered unwinders
+    in the locus are affected.
+
+    """
+
+    def __init__(self):
+        super(EnableUnwinder, self).__init__("enable unwinder",
+                                             gdb.COMMAND_STACK)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_unwinder(arg, True)
+
+
+class DisableUnwinder(gdb.Command):
+    """GDB command to disable the specified unwinder.
+
+    Usage: disable unwinder [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression specifying the unwinders to
+    disable.  It can 'global', 'progspace', or the name of an objfile
+    within that progspace.
+
+    NAME_REGEXP is a regular expression to filter unwinder names.  If
+    this omitted for a specified locus, then all registered unwinders
+    in the locus are affected.
+
+    """
+
+    def __init__(self):
+        super(DisableUnwinder, self).__init__("disable unwinder",
+                                              gdb.COMMAND_STACK)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_unwinder(arg, False)
+
+
+def register_unwinder_commands():
+    """Installs the unwinder commands."""
+    InfoUnwinder()
+    EnableUnwinder()
+    DisableUnwinder()
+
+
+register_unwinder_commands()
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
new file mode 100644
index 0000000..3554e9c
--- /dev/null
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -0,0 +1,94 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Unwinder class and register_unwinder function."""
+
+import gdb
+
+
+class Unwinder(object):
+    """Base class (or a template) for frame unwinders written in Python.
+
+    An unwinder has a single method __call__ and the attributes
+    described below.
+
+    Attributes:
+        name: The name of the unwinder.
+        enabled: A boolean indicating whether the unwinder is enabled.
+    """
+
+    def __init__(self, name):
+        """Constructor.
+
+        Args:
+            name: An identifying name for the unwinder.
+        """
+        self.name = name
+        self.enabled = True
+
+    def __call__(self, pending_frame):
+        """GDB calls this method to unwind a frame.
+
+        Arguments:
+            pending_frame: gdb.PendingFrame instance.
+
+        Returns:
+            gdb.UnwindInfo instance.
+        """
+        raise NotImplementedError("Unwinder __call__.")
+
+
+def register_unwinder(locus, unwinder, replace=False):
+    """Register unwinder in given locus.
+
+    The unwinder is prepended to the locus's unwinders list. Unwinder
+    name should be unique.
+
+    Arguments:
+        locus: Either an objfile, progspace, or None (in which case
+               the unwinder is registered globally).
+        unwinder: An object of a gdb.Unwinder subclass
+        replace: If True, replaces existing unwinder with the same name.
+                 Otherwise, raises exception if unwinder with the same
+                 name already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        RuntimeError: Unwinder name is not unique
+        TypeError: Bad locus type
+    """
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s unwinder ...\n" % unwinder.name)
+        locus = gdb
+    elif isinstance(locus, gdb.Objfile) or isinstance(locus, gdb.Progspace):
+        if gdb.parameter("verbose"):
+            gdb.write("Registering %s unwinder for %s ...\n" %
+                      (unwinder.name, locus.filename))
+    else:
+        raise TypeError("locus should be gdb.Objfile or gdb.Progspace or None")
+
+    i = 0
+    for needle in locus.frame_unwinders:
+        if needle.name == unwinder.name:
+            if replace:
+                del locus.frame_unwinders[i]
+            else:
+                raise RuntimeError("Unwinder %s already exists." %
+                                   unwinder.name)
+        i += 1
+    locus.frame_unwinders.insert(0, unwinder)
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 157d200..c9528c3 100644
--- a/gdb/python/py-objfile.c
+++ b/gdb/python/py-objfile.c
@@ -42,6 +42,10 @@  typedef struct
 
   /* The frame filter list of functions.  */
   PyObject *frame_filters;
+
+  /* The list of frame unwinders.  */
+  PyObject *frame_unwinders;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -184,6 +188,7 @@  objfpy_dealloc (PyObject *o)
   Py_XDECREF (self->dict);
   Py_XDECREF (self->printers);
   Py_XDECREF (self->frame_filters);
+  Py_XDECREF (self->frame_unwinders);
   Py_XDECREF (self->type_printers);
   Py_XDECREF (self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -206,6 +211,10 @@  objfpy_initialize (objfile_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_unwinders = PyList_New (0);
+  if (self->frame_unwinders == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -313,6 +322,48 @@  objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
   return 0;
 }
 
+/* Return the frame unwinders attribute for this object file.  */
+
+PyObject *
+objfpy_get_frame_unwinders (PyObject *o, void *ignore)
+{
+  objfile_object *self = (objfile_object *) o;
+
+  Py_INCREF (self->frame_unwinders);
+  return self->frame_unwinders;
+}
+
+/* Set this object file's frame unwinders list to UNWINDERS.  */
+
+static int
+objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
+{
+  PyObject *tmp;
+  objfile_object *self = (objfile_object *) o;
+
+  if (!unwinders)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete the frame unwinders attribute."));
+      return -1;
+    }
+
+  if (!PyList_Check (unwinders))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The frame_unwinders attribute must be a list."));
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_unwinders;
+  Py_INCREF (unwinders);
+  self->frame_unwinders = unwinders;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -651,6 +702,8 @@  static PyGetSetDef objfile_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", objfpy_get_frame_filters,
     objfpy_set_frame_filters, "Frame Filters.", NULL },
+  { "frame_unwinders", objfpy_get_frame_unwinders,
+    objfpy_set_frame_unwinders, "Frame Unwinders", NULL },
   { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
     "Type printers.", NULL },
   { "xmethods", objfpy_get_xmethods, NULL,
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index 93fbc14..17da3d1 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -41,6 +41,10 @@  typedef struct
 
   /* The frame filter list of functions.  */
   PyObject *frame_filters;
+
+  /* The frame unwinder list.  */
+  PyObject *frame_unwinders;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -82,6 +86,7 @@  pspy_dealloc (PyObject *self)
   Py_XDECREF (ps_self->dict);
   Py_XDECREF (ps_self->printers);
   Py_XDECREF (ps_self->frame_filters);
+  Py_XDECREF (ps_self->frame_unwinders);
   Py_XDECREF (ps_self->type_printers);
   Py_XDECREF (ps_self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -104,6 +109,10 @@  pspy_initialize (pspace_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_unwinders = PyList_New (0);
+  if (self->frame_unwinders == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -211,6 +220,48 @@  pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
   return 0;
 }
 
+/* Return the list of the frame unwinders for this program space.  */
+
+PyObject *
+pspy_get_frame_unwinders (PyObject *o, void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  Py_INCREF (self->frame_unwinders);
+  return self->frame_unwinders;
+}
+
+/* Set this program space's list of the unwinders to UNWINDERS.  */
+
+static int
+pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
+{
+  PyObject *tmp;
+  pspace_object *self = (pspace_object *) o;
+
+  if (!unwinders)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "cannot delete the frame unwinders list");
+      return -1;
+    }
+
+  if (!PyList_Check (unwinders))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "the frame unwinders attribute must be a list");
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_unwinders;
+  Py_INCREF (unwinders);
+  self->frame_unwinders = unwinders;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -345,6 +396,8 @@  static PyGetSetDef pspace_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
     "Frame filters.", NULL },
+  { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders,
+    "Frame unwinders.", NULL },
   { "type_printers", pspy_get_type_printers, pspy_set_type_printers,
     "Type printers.", NULL },
   { "xmethods", pspy_get_xmethods, NULL,
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
new file mode 100644
index 0000000..bcfea4b
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,788 @@ 
+/* Python frame unwinder interface.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "valprint.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* Frame we are unwinding.  */
+  struct frame_info *frame_info;
+
+  /* Its architecture, passed by the sniffer caller.  */
+  struct gdbarch *gdbarch;
+} pending_frame_object;
+
+/* Saved registers array item.  */
+
+typedef struct
+{
+  int number;
+  PyObject *value;
+} saved_reg;
+DEF_VEC_O (saved_reg);
+
+/* The data we keep for the PyUnwindInfo: pending_frame, saved registers
+   and frame ID.  */
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* gdb.PendingFrame for the frame we are unwinding.  */
+  PyObject *pending_frame;
+
+  /* Its ID.  */
+  struct frame_id frame_id;
+
+  /* Saved registers array.  */
+  VEC (saved_reg) *saved_regs;
+} unwind_info_object;
+
+/* The data we keep for a frame we can unwind: frame ID and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  /* Frame ID.  */
+  struct frame_id frame_id;
+
+  /* GDB Architecture.  */
+  struct gdbarch *gdbarch;
+
+  /* Length of the `reg' array below.  */
+  int reg_count;
+
+  struct reg_info
+  {
+    /* Register number.  */
+    int number;
+
+    /* Register data bytes pointer.  */
+    gdb_byte data[MAX_REGISTER_SIZE];
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject pending_frame_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("pending_frame_object");
+
+static PyTypeObject unwind_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Parses register id, which can be either a number or a name.
+   Returns 1 on success, 0 otherwise.  */
+
+static int
+pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
+                        int *reg_num)
+{
+  if (pyo_reg_id == NULL)
+    return 0;
+  if (gdbpy_is_string (pyo_reg_id))
+    {
+      const char *reg_name = gdbpy_obj_to_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 (PyInt_Check (pyo_reg_id))
+    {
+      long value;
+      if (gdb_py_int_as_long (pyo_reg_id, &value) && (int) value == value)
+        {
+          *reg_num = (int) value;
+          return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
+        }
+    }
+  return 0;
+}
+
+/* Convert gdb.Value instance to inferior's pointer.  Return 1 on success,
+   0 on failure.  */
+
+static int
+pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
+{
+  int rc = 0;
+  struct value *value;
+
+  TRY
+    {
+      if ((value = value_object_to_value (pyo_value)) != NULL)
+        {
+          *addr = unpack_pointer (value_type (value),
+                                  value_contents (value));
+          rc = 1;
+        }
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      gdbpy_convert_exception (except);
+    }
+  END_CATCH
+  return rc;
+}
+
+/* Get attribute from an object and convert it to the inferior's
+   pointer value.  Return 1 if attribute exists and its value can be
+   converted.  Otherwise, if attribute does not exist or its value is
+   None, return 0.  In all other cases set Python error and return
+   0.  */
+
+static int
+pyuw_object_attribute_to_pointer (PyObject *pyo, const char *attr_name,
+                                  CORE_ADDR *addr)
+{
+  int rc = 0;
+
+  if (PyObject_HasAttrString (pyo, attr_name))
+    {
+      PyObject *pyo_value = PyObject_GetAttrString (pyo, attr_name);
+      struct value *value;
+
+      if (pyo_value != NULL && pyo_value != Py_None)
+        {
+          rc = pyuw_value_obj_to_pointer (pyo_value, addr);
+          if (!rc)
+            PyErr_Format (
+                PyExc_ValueError,
+                _("The value of the '%s' attribute is not a pointer."),
+                attr_name);
+        }
+      Py_XDECREF (pyo_value);
+    }
+  return rc;
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the UnwindInfo object.  */
+
+static PyObject *
+unwind_infopy_str (PyObject *self)
+{
+  struct ui_file *strfile = mem_fileopen ();
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  pending_frame_object *pending_frame
+      = (pending_frame_object *) (unwind_info->pending_frame);
+  PyObject *result;
+
+  fprintf_unfiltered (strfile, "Frame ID: ");
+  fprint_frame_id (strfile, unwind_info->frame_id);
+  {
+    char *sep = "";
+    int i;
+    struct value_print_options opts;
+    saved_reg *reg;
+
+    get_user_print_options (&opts);
+    fprintf_unfiltered (strfile, "\nSaved registers: (");
+    for (i = 0;
+         i < VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg);
+         i++)
+      {
+        struct value *value = value_object_to_value (reg->value);
+
+        fprintf_unfiltered (strfile, "%s(%d, ", sep, reg->number);
+        if (value != NULL)
+          {
+            TRY
+              {
+                value_print (value, strfile, &opts);
+                fprintf_unfiltered (strfile, ")");
+              }
+            CATCH (except, RETURN_MASK_ALL)
+              {
+                GDB_PY_HANDLE_EXCEPTION (except);
+              }
+            END_CATCH
+          }
+        else
+          fprintf_unfiltered (strfile, "<BAD>)");
+        sep = ", ";
+      }
+    fprintf_unfiltered (strfile, ")");
+  }
+  {
+    char *s = ui_file_xstrdup (strfile, NULL);
+
+    result = PyString_FromString (s);
+    xfree (s);
+  }
+  ui_file_delete (strfile);
+  return result;
+}
+
+/* Create UnwindInfo instance for given PendingFrame and frame ID.
+   Sets Python error and returns NULL on error.  */
+
+static PyObject *
+pyuw_create_unwind_info (PyObject *pyo_pending_frame,
+                         struct frame_id frame_id)
+{
+  unwind_info_object *unwind_info
+      = PyObject_New (unwind_info_object, &unwind_info_object_type);
+
+  if (((pending_frame_object *) pyo_pending_frame)->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to use stale PendingFrame");
+      return NULL;
+    }
+  unwind_info->frame_id = frame_id;
+  Py_INCREF (pyo_pending_frame);
+  unwind_info->pending_frame = pyo_pending_frame;
+  unwind_info->saved_regs = VEC_alloc (saved_reg, 4);
+  return (PyObject *) unwind_info;
+}
+
+/* The implementation of
+   gdb.UnwindInfo.add_saved_register (REG, VALUE) -> None.  */
+
+static PyObject *
+unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  pending_frame_object *pending_frame
+      = (pending_frame_object *) (unwind_info->pending_frame);
+  PyObject *pyo_reg_id;
+  PyObject *pyo_reg_value;
+  int regnum;
+
+  if (pending_frame->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "UnwindInfo instance refers to a stale PendingFrame");
+      return NULL;
+    }
+  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
+                          &pyo_reg_id, &pyo_reg_value))
+    return NULL;
+  if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+  {
+    struct value *value;
+    size_t data_size;
+
+    if (pyo_reg_value == NULL
+      || (value = value_object_to_value (pyo_reg_value)) == NULL)
+      {
+        PyErr_SetString (PyExc_ValueError, "Bad register value");
+        return NULL;
+      }
+    data_size = register_size (pending_frame->gdbarch, regnum);
+    if (data_size != TYPE_LENGTH (value_type (value)))
+      {
+        PyErr_Format (
+            PyExc_ValueError,
+            "The value of the register returned by the Python "
+            "sniffer has unexpected size: %u instead of %u.",
+            (unsigned) TYPE_LENGTH (value_type (value)),
+            (unsigned) data_size);
+        return NULL;
+      }
+  }
+  {
+    int i;
+    saved_reg *reg;
+
+    for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
+      {
+        if (regnum == reg->number)
+          {
+            Py_DECREF (reg->value);
+            break;
+          }
+      }
+    if (reg == NULL)
+      {
+        reg = VEC_safe_push (saved_reg, unwind_info->saved_regs, NULL);
+        reg->number = regnum;
+      }
+    Py_INCREF (pyo_reg_value);
+    reg->value = pyo_reg_value;
+  }
+  Py_RETURN_NONE;
+}
+
+/* UnwindInfo cleanup.  */
+
+static void
+unwind_infopy_dealloc (PyObject *self)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  int i;
+  saved_reg *reg;
+
+  Py_XDECREF (unwind_info->pending_frame);
+  for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
+      Py_DECREF (reg->value);
+  VEC_free (saved_reg, unwind_info->saved_regs);
+  Py_TYPE (self)->tp_free (self);
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the PendingFrame object.  */
+
+static PyObject *
+pending_framepy_str (PyObject *self)
+{
+  struct frame_info *frame = ((pending_frame_object *) self)->frame_info;
+  const char *sp_str = NULL;
+  const char *pc_str = NULL;
+
+  if (frame == NULL)
+    return PyString_FromString ("Stale PendingFrame instance");
+  TRY
+    {
+      sp_str = core_addr_to_string_nz (get_frame_sp (frame));
+      pc_str = core_addr_to_string_nz (get_frame_pc (frame));
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+  END_CATCH
+
+  return PyString_FromFormat ("SP=%s,PC=%s", sp_str, pc_str);
+}
+
+/* Implementation of gdb.PendingFrame.read_register (self, reg) -> gdb.Value.
+   Returns the value of register REG as gdb.Value instance.  */
+
+static PyObject *
+pending_framepy_read_register (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+  struct value *val = NULL;
+  int regnum;
+  PyObject *pyo_reg_id;
+
+  if (pending_frame->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to read register from stale PendingFrame");
+      return NULL;
+    }
+  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
+    return NULL;
+  if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+
+  TRY
+    {
+      val = get_frame_register_value (pending_frame->frame_info, regnum);
+      if (val == NULL)
+        PyErr_Format (PyExc_ValueError,
+                      "Cannot read register %d from frame.",
+                      regnum);
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+  END_CATCH
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Implementation of
+   PendingFrame.create_unwind_info (self, frameId) -> UnwindInfo.  */
+
+static PyObject *
+pending_framepy_create_unwind_info (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_frame_id;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+  CORE_ADDR special;
+
+  if (!PyArg_ParseTuple (args, "O:create_unwind_info", &pyo_frame_id))
+      return NULL;
+  if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "sp", &sp))
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       _("frame_id should have 'sp' attribute."));
+      return NULL;
+    }
+
+  /* The logic of building frame_id depending on the attributes of
+     the frame_id object:
+     Has     Has    Has           Function to call
+     'sp'?   'pc'?  'special'?
+     ------|------|--------------|-------------------------
+     Y       N      *             frame_id_build_wild (sp)
+     Y       Y      N             frame_id_build (sp, pc)
+     Y       Y      Y             frame_id_build_special (sp, pc, special)
+  */
+  if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "pc", &pc))
+    return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
+  if (!pyuw_object_attribute_to_pointer (pyo_frame_id, "special", &special))
+    return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
+  else
+    return pyuw_create_unwind_info (self,
+                                    frame_id_build_special (sp, pc, special));
+}
+
+/* Invalidate PendingFrame instance.  */
+
+static void
+pending_frame_invalidate (void *pyo_pending_frame)
+{
+  if (pyo_pending_frame != NULL)
+    ((pending_frame_object *) pyo_pending_frame)->frame_info = NULL;
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
+              struct frame_id *this_id)
+{
+  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
+  if (pyuw_debug >= 1)
+    {
+      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+      fprint_frame_id (gdb_stdlog, *this_id);
+      fprintf_unfiltered (gdb_stdlog, "\n");
+    }
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
+                    int regnum)
+{
+  cached_frame_info *cached_frame = *cache_ptr;
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  TRACE_PY_UNWIND (1, "%s (frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
+                   regnum);
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (regnum == reg_info->number)
+        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
+    }
+
+  return frame_unwind_got_optimized (this_frame, regnum);
+}
+
+/* Frame sniffer dispatch.  */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch = (struct gdbarch *) (self->unwind_data);
+  struct cleanup *cleanups = ensure_python_env (gdbarch, current_language);
+  PyObject *pyo_execute;
+  PyObject *pyo_pending_frame;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame;
+
+  TRACE_PY_UNWIND (3, "%s (SP=%s, PC=%s)\n", __FUNCTION__,
+                   paddress (gdbarch, get_frame_sp (this_frame)),
+                   paddress (gdbarch, get_frame_pc (this_frame)));
+
+  /* Create PendingFrame instance to pass to sniffers.  */
+  pyo_pending_frame  = (PyObject *) PyObject_New (pending_frame_object,
+                                                  &pending_frame_object_type);
+  if (pyo_pending_frame == NULL)
+    goto error;
+  ((pending_frame_object *) pyo_pending_frame)->gdbarch = gdbarch;
+  ((pending_frame_object *) pyo_pending_frame)->frame_info = this_frame;
+  make_cleanup (pending_frame_invalidate, (void *) pyo_pending_frame);
+  make_cleanup_py_decref (pyo_pending_frame);
+
+  /* Run unwinders.  */
+  if (gdb_python_module == NULL
+      || ! PyObject_HasAttrString (gdb_python_module, "execute_unwinders"))
+    {
+      PyErr_SetString (PyExc_NameError,
+                       "Installation error: gdb.execute_unwinders function "
+                       "is missing");
+      goto error;
+    }
+  pyo_execute = PyObject_GetAttrString (gdb_python_module, "execute_unwinders");
+  if (pyo_execute == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_execute);
+  pyo_unwind_info
+      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_pending_frame, NULL);
+  if (pyo_unwind_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_unwind_info);
+  if (pyo_unwind_info == Py_None)
+    goto cannot_unwind;
+
+  /* Received UnwindInfo, cache data.  */
+  if (PyObject_IsInstance (pyo_unwind_info,
+                           (PyObject *) &unwind_info_object_type) <= 0)
+    error (_("A Unwinder should return gdb.UnwindInfo instance."));
+
+  {
+    unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
+    int reg_count = VEC_length (saved_reg, unwind_info->saved_regs);
+    saved_reg *reg;
+    int i;
+
+    cached_frame = xmalloc (sizeof (*cached_frame) +
+                            reg_count * sizeof (cached_frame->reg[0]));
+    cached_frame->gdbarch = gdbarch;
+    cached_frame->frame_id = unwind_info->frame_id;
+    cached_frame->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; VEC_iterate (saved_reg, unwind_info->saved_regs, i, reg); i++)
+      {
+        struct value *value = value_object_to_value (reg->value);
+        size_t data_size = register_size (gdbarch, reg->number);
+
+        cached_frame->reg[i].number = reg->number;
+
+        /* `value' validation was done before, just assert.  */
+        gdb_assert (value != NULL);
+        gdb_assert (data_size == TYPE_LENGTH (value_type (value)));
+        gdb_assert (data_size <= MAX_REGISTER_SIZE);
+
+        memcpy (cached_frame->reg[i].data, value_contents (value), data_size);
+      }
+  }
+
+  *cache_ptr = cached_frame;
+  do_cleanups (cleanups);
+  return 1;
+
+ error:
+  gdbpy_print_stack ();
+  /* Fallthrough.  */
+ cannot_unwind:
+  do_cleanups (cleanups);
+  return 0;
+}
+
+/* Frame cache release shim.  */
+
+static void
+pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
+  xfree (cache);
+}
+
+struct pyuw_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Python unwinders
+   intermediary.  */
+
+static void
+pyuw_on_new_gdbarch (struct gdbarch *newarch)
+{
+  struct pyuw_gdbarch_data_type *data =
+      gdbarch_data (newarch, pyuw_gdbarch_data);
+
+  if (!data->unwinder_registered)
+    {
+      struct frame_unwind *unwinder
+          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
+
+      unwinder->type = NORMAL_FRAME;
+      unwinder->stop_reason = default_frame_unwind_stop_reason;
+      unwinder->this_id = pyuw_this_id;
+      unwinder->prev_register = pyuw_prev_register;
+      unwinder->unwind_data = (void *) newarch;
+      unwinder->sniffer = pyuw_sniffer;
+      unwinder->dealloc_cache = pyuw_dealloc_cache;
+      frame_unwind_prepend_unwinder (newarch, unwinder);
+      data->unwinder_registered = 1;
+    }
+}
+
+/* Initialize unwind machinery.  */
+
+int
+gdbpy_initialize_unwind (void)
+{
+  int rc;
+  add_setshow_zuinteger_cmd
+      ("py-unwind", class_maintenance, &pyuw_debug,
+        _("Set Python unwinder debugging."),
+        _("Show Python unwinder debugging."),
+        _("When non-zero, Python unwinder debugging is enabled."),
+        NULL,
+        NULL,
+        &setdebuglist, &showdebuglist);
+  pyuw_gdbarch_data
+      = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
+  observer_attach_architecture_changed (pyuw_on_new_gdbarch);
+
+  if (PyType_Ready (&pending_frame_object_type) < 0)
+    return -1;
+  rc = gdb_pymodule_addobject (gdb_module, "PendingFrame",
+      (PyObject *) &pending_frame_object_type);
+  if (rc)
+    return rc;
+
+  if (PyType_Ready (&unwind_info_object_type) < 0)
+    return -1;
+  return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
+      (PyObject *) &unwind_info_object_type);
+}
+
+static PyMethodDef pending_frame_object_methods[] =
+{
+  { "read_register", pending_framepy_read_register, METH_VARARGS,
+    "read_register (REG) -> gdb.Value\n"
+    "Return the value of the REG in the frame." },
+  { "create_unwind_info",
+    pending_framepy_create_unwind_info, METH_VARARGS,
+    "create_unwind_info (FRAME_ID) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this PendingFrame, using FRAME_ID\n"
+    "to identify it." },
+  {NULL}  /* Sentinel */
+};
+
+static PyTypeObject pending_frame_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.PendingFrame",             /* tp_name */
+  sizeof (pending_frame_object),  /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  0,                              /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  pending_framepy_str,            /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT,             /* tp_flags */
+  "GDB PendingFrame object",      /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  pending_frame_object_methods,   /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
+
+static PyMethodDef unwind_info_object_methods[] =
+{
+  { "add_saved_register",
+    unwind_infopy_add_saved_register, METH_VARARGS,
+    "add_saved_register (REG, VALUE) -> None\n"
+    "Set the value of the REG in the previous frame to VALUE." },
+  { NULL }  /* Sentinel */
+};
+
+static PyTypeObject unwind_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.UnwindInfo",               /* tp_name */
+  sizeof (unwind_info_object),    /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  unwind_infopy_dealloc,          /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  unwind_infopy_str,              /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
+  "GDB UnwindInfo object",        /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  unwind_info_object_methods,     /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 4c4d32a..0581b33 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -391,12 +391,14 @@  PyObject *pspace_to_pspace_object (struct program_space *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *pspy_get_printers (PyObject *, void *);
 PyObject *pspy_get_frame_filters (PyObject *, void *);
+PyObject *pspy_get_frame_unwinders (PyObject *, void *);
 PyObject *pspy_get_xmethods (PyObject *, void *);
 
 PyObject *objfile_to_objfile_object (struct objfile *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *objfpy_get_printers (PyObject *, void *);
 PyObject *objfpy_get_frame_filters (PyObject *, void *);
+PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
 PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
@@ -491,6 +493,8 @@  int gdbpy_initialize_arch (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_xmethods (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_unwind (void)
+  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 
 struct cleanup *make_cleanup_py_decref (PyObject *py);
 struct cleanup *make_cleanup_py_xdecref (PyObject *py);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 58c7c92..1da63fd 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1821,7 +1821,8 @@  message == an error message without a stack will be printed."),
       || gdbpy_initialize_new_objfile_event ()  < 0
       || gdbpy_initialize_clear_objfiles_event ()  < 0
       || gdbpy_initialize_arch () < 0
-      || gdbpy_initialize_xmethods () < 0)
+      || gdbpy_initialize_xmethods () < 0
+      || gdbpy_initialize_unwind () < 0)
     goto fail;
 
   gdbpy_to_string_cst = PyString_FromString ("to_string");
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
new file mode 100644
index 0000000..8c1d935
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
@@ -0,0 +1,24 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main (void)
+{
+  int i = 0;
+
+  return i; /* next-line */
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
new file mode 100644
index 0000000..3b0e021
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
@@ -0,0 +1,64 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests Python-based
+# unwinding CLI.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+if ![runto_main ] then {
+    fail "Can't run to main"
+    return -1
+}
+
+gdb_test "source ${pyfile}" "Python script imported" "import python scripts"
+
+gdb_test_sequence "info unwinder" "Show all unwinders" {
+    "Global:"
+    "  global_unwinder"
+    "Progspace .*py-unwind-maint:"
+    "py_unwind_maint_ps_unwinder"
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
+
+gdb_test_sequence "continue" "Unwinders called" {
+    "py_unwind_maint_ps_unwinder called"
+    "global_unwinder called"
+}
+
+gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled"
+
+gdb_test_sequence "info unwinder" "Show with global unwinder disabled" {
+    "Global:"
+    "  global_unwinder \\[disabled\\]"
+    "Progspace .*py-unwind-maint:"
+    "  py_unwind_maint_ps_unwinder"
+}
+
+gdb_test_sequence "where" "Global unwinder disabled" {
+    "py_unwind_maint_ps_unwinder called\r\n#0  main"
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py
new file mode 100644
index 0000000..f8c6277
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
@@ -0,0 +1,59 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests python unwinders.
+
+import re
+import gdb.types
+from gdb.unwinder import Unwinder, register_unwinder
+
+class TestGlobalUnwinder(Unwinder):
+    def __init__(self):
+        super(TestGlobalUnwinder, self).__init__("global_unwinder")
+
+    def __call__(self, unwinder_info):
+        print "%s called" % self.name
+        return None
+
+class TestProgspaceUnwinder(Unwinder):
+    def __init__(self, name):
+        super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name)
+
+    def __call__(self, unwinder_info):
+        print "%s called" % self.name
+        return None
+
+class TestObjfileUnwinder(Unwinder):
+    def __init__(self, name):
+        super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name)
+
+    def __call__(self, unwinder_info):
+        print "%s called" % self.name
+        return None
+
+
+
+gdb.unwinder.register_unwinder(None, TestGlobalUnwinder())
+saw_runtime_error = False
+try:
+    gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=False)
+except RuntimeError:
+    saw_runtime_error = True
+if not saw_runtime_error:
+    raise RuntimeError("Missing runtime error from register_unwinder.")
+gdb.unwinder.register_unwinder(None, TestGlobalUnwinder(), replace=True)
+gdb.unwinder.register_unwinder(gdb.current_progspace(),
+                               TestProgspaceUnwinder("py_unwind_maint"))
+print "Python script imported"
diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c
new file mode 100644
index 0000000..cf41d78
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,81 @@ 
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This is the test program loaded into GDB by the py-unwind test.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void *
+swap_value (void **location, void *new_value)
+{
+  void *old_value = *location;
+  *location = new_value;
+  return old_value;
+}
+
+static void
+bad_layout(void **variable_ptr, void *fp)
+{
+  fprintf (stderr, "First variable should be allocated one word below "
+           "the frame.  Got variable's address %p, frame at %p instead.\n",
+           variable_ptr, fp);
+  abort();
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+corrupt_frame_inner (void)
+{
+  /* Save outer frame address, then corrupt the unwind chain by
+     setting the outer frame address in it to self.  This is
+     ABI-specific: the first word of the frame contains previous frame
+     address in amd64.  */
+  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+
+  /* Verify the compiler allocates the first local variable one word
+     below frame.  This is where the test unwinder expects to find the
+     correct outer frame address.  */
+  if (&previous_fp + 1 != (void **) MY_FRAME)
+    bad_layout (&previous_fp + 1, MY_FRAME);
+
+  /* Now restore it so that we can return.  The test sets the
+     breakpoint just before this happens, and GDB will not be able to
+     show the backtrace without JIT reader.  */
+  swap_value ((void **) MY_FRAME, previous_fp); /* break backtrace-broken */
+}
+
+static void
+corrupt_frame_outer (void)
+{
+  /* See above for the explanation of the code here.  This function
+     corrupts its frame, too, and then calls the inner one.  */
+  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+  if (&previous_fp + 1 != (void **) MY_FRAME)
+    bad_layout (&previous_fp, MY_FRAME);
+  corrupt_frame_inner ();
+  swap_value ((void **) MY_FRAME, previous_fp);
+}
+
+int
+main ()
+{
+  corrupt_frame_outer ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..53d6746
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It verifies that frame
+# unwinders can be implemented in Python.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+# This test runs on a specific platform.
+if { ! [istarget x86_64-*]} { continue }
+
+# The following tests require execution.
+
+if ![runto_main] then {
+    fail "Can't run to main"
+    return 0
+}
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
+
+gdb_test "source ${pyfile}" "Python script imported" \
+         "import python scripts"
+
+gdb_continue_to_breakpoint "break backtrace-broken"
+gdb_test_sequence "where"  "Backtrace restored by unwinder" {
+    "\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
+    "\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
+    "\\r\\n#2 .* main \\(.*\\) at"
+}
+
+
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
new file mode 100644
index 0000000..6257fd7
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,99 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+from gdb.unwinder import Unwinder
+
+class FrameId(object):
+
+    def __init__(self, sp, pc):
+        self._sp = sp
+        self._pc = pc
+
+    @property
+    def sp(self):
+        return self._sp
+
+    @property
+    def pc(self):
+        return self._pc
+
+
+class TestUnwinder(Unwinder):
+    AMD64_RBP = 6
+    AMD64_RSP = 7
+    AMD64_RIP = 16
+
+    def __init__(self):
+        Unwinder.__init__(self, "test unwinder")
+        self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
+        self.char_ptr_ptr_t = self.char_ptr_t.pointer()
+
+    def _read_word(self, address):
+        return address.cast(self.char_ptr_ptr_t).dereference()
+
+    def __call__(self, pending_frame):
+        """Test unwinder written in Python.
+
+        This unwinder can unwind the frames that have been deliberately
+        corrupted in a specific way (functions in the accompanying
+        py-unwind.c file do that.)
+        This code is only on AMD64.
+        On AMD64 $RBP points to the innermost frame (unless the code
+        was compiled with -fomit-frame-pointer), which contains the
+        address of the previous frame at offset 0. The functions
+        deliberately corrupt their frames as follows:
+                     Before                 After
+                   Corruption:           Corruption:
+                +--------------+       +--------------+
+        RBP-8   |              |       | Previous RBP |
+                +--------------+       +--------------+
+        RBP     + Previous RBP |       |    RBP       |
+                +--------------+       +--------------+
+        RBP+8   | Return RIP   |       | Return  RIP  |
+                +--------------+       +--------------+
+        Old SP  |              |       |              |
+
+        This unwinder recognizes the corrupt frames by checking that
+        *RBP == RBP, and restores previous RBP from the word above it.
+        """
+        try:
+            # NOTE: the registers in Unwinder API can be referenced
+            # either by name or by number. The code below uses both
+            # to achieve more coverage.
+            bp = pending_frame.read_register("rbp").cast(self.char_ptr_t)
+            if self._read_word(bp) != bp:
+                return None
+            # Found the frame that the test program has corrupted for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places.
+            previous_bp = self._read_word(bp - 8)
+            previous_ip = self._read_word(bp + 8)
+            previous_sp = bp + 16
+
+            frame_id = FrameId(
+                pending_frame.read_register(TestUnwinder.AMD64_RSP),
+                pending_frame.read_register(TestUnwinder.AMD64_RIP))
+            unwind_info = pending_frame.create_unwind_info(frame_id)
+            unwind_info.add_saved_register(TestUnwinder.AMD64_RBP,
+                                           previous_bp)
+            unwind_info.add_saved_register("rip", previous_ip)
+            unwind_info.add_saved_register("rsp", previous_sp)
+            return unwind_info
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
+print("Python script imported")