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

Message ID CAHQ51u5_ViLaEmv9e43R-wzuWw8dwNkb-2XgCRy5ELQq5FUAWg@mail.gmail.com
State New, archived
Headers

Commit Message

Alexander Smundak Feb. 12, 2015, 5:58 p.m. UTC
  On Wed, Feb 4, 2015 at 2:35 PM, Doug Evans <dje@google.com> wrote:
> High level comments:
>
> Is it possible to see the code, and example usage, of a real-life use-case
> of this? That will help folks not familiar with this project to understand
> the problem we are trying to solve.
The case in question is Java Virtual Machine. It is a JIT compiler for Java,
and it is part of the OpenJDK (Java Development Kit). It compiles Java
methods on the fly, and the emitted code omits frame pointers (e.g., on
x86_64 platform RBP is used as a general purpose register rather than
as frame pointer). And, the emitted code does not have unwind info
expected by GDB, so standard sniffers fail and the traceback stops when
it encounters a frame for the JIT-compiled code.
If we know how JVM works, we know where to find the descriptors of the
currently compiled code, and once we locate the descriptor for a given PC,
we can extract the frame size, and unwind the frame.
It's easier to have a custom sniffer than to make JVM maintain DWARF
unwind info. Besides, most of the code for the sniffer is reused by the
corresponding frame decorator.
The full implementation of the combined sniffer/frame filter for OpenJDK
is about 2500 lines and will eventually become part of it. I am waiting for
this GDB patch to be reviewed before I can present it to be reviewed by
the JDK community :-)

> I'm still not sure what kind of performance cost we're looking at here as
> it scales up, I can imagine most times there'll be no Python sniffers,
> or at most one or two.  But it would be good to collect some perf data
> (e.g., install 1,10,100 no-op sniffers and see if there's any measurable
> difference in backtrace performance).
I ran a test that walks a 100-frame stack calling 100 Python sniffers
per frame, and it executes (throught the dejagnu checker) in 500ms on
Xeon E5-1650 0 @ 3.20GHz.
Please let me know if you would like it to be added to gdb/testsuite.

> Exposing frame id implementation details (sp,pc,special), and the
> form of how to do that, is something the community needs to decide on.
> I think we can come up with something suitable, though perhaps not
> the current form.

The revised patch is attached. The important differences are as follows:
* A sniffer is now an object, using the pattern similar to xmethods
and pretty printers.
* Register values are properly types (that is, based on a register type)

I am still not certain whether it's worth changing SnifferInfo.read_register to
have registers retrieved by name rather than by its number. Perhaps
adding gdb.Architecture.register_name_to_number method will be
a reasonable tradeoff?

The documentation is obviously unfinished (to be done once the design
issues are resolved), and what exists needs to be put into proper
English.

Here's take two:

gdb/ChangeLog:
2015-02-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 sniffers.py.
        * doc/python.texi (Writing a Frame Unwinder in Python): Add
        section.
        * python/lib/gdb/__init__.py (packages): Add frame_sniffers list.
        * python/lib/gdb/command/sniffers.py: New file, implements GDB
        commands to list/enable/disable Python sniffers.
        * python/lib/gdb/function/sniffers.py: New file, implements
        execute_sniffers function.
        * python/lib/gdb/sniffer.py: New file, contains Sniffer class and
        register_sniffer function.
        * python/py-objfile.c (objfile_object): Add frame_sniffers field.
        (objfpy_dealloc): Decrement frame_sniffers reference count.
        (objfpy_initialize): Create frame_sniffers list.
        (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
        getter.
        (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
        setter.
        (objfile_getset): Add frame_sniffers attribute to Objfile.
        * python/py-progspace.c (pspace_object): Add frame_sniffers field.
        (pspy_dealloc): Decrement frame_sniffers reference count.
        (pspy_initialize): Create frame_sniffers list.
        (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        getter.
        (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        setter.
        (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
        * python/py-unwind.c: New file, implements Python frame sniffers
        interface.
        * python/python-internal.h (pspy_get_name_sniffers): New prototype.
        (objpy_get_frame_sniffers): New prototype.
        (gdbpy_initialize_unwind): New prototype.
        * python/python.c (gdbpy_apply_type_printers): Call
        gdbpy_initialize_unwind.

gdb/testsuite/ChangeLog:
2014-02-30  Sasha Smundak  <asmundak@google.com>

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

Comments

Alexander Smundak Feb. 19, 2015, 2:32 a.m. UTC | #1
Ping.

On Thu, Feb 12, 2015 at 9:58 AM, Alexander Smundak <asmundak@google.com> wrote:
> On Wed, Feb 4, 2015 at 2:35 PM, Doug Evans <dje@google.com> wrote:
>> High level comments:
>>
>> Is it possible to see the code, and example usage, of a real-life use-case
>> of this? That will help folks not familiar with this project to understand
>> the problem we are trying to solve.
> The case in question is Java Virtual Machine. It is a JIT compiler for Java,
> and it is part of the OpenJDK (Java Development Kit). It compiles Java
> methods on the fly, and the emitted code omits frame pointers (e.g., on
> x86_64 platform RBP is used as a general purpose register rather than
> as frame pointer). And, the emitted code does not have unwind info
> expected by GDB, so standard sniffers fail and the traceback stops when
> it encounters a frame for the JIT-compiled code.
> If we know how JVM works, we know where to find the descriptors of the
> currently compiled code, and once we locate the descriptor for a given PC,
> we can extract the frame size, and unwind the frame.
> It's easier to have a custom sniffer than to make JVM maintain DWARF
> unwind info. Besides, most of the code for the sniffer is reused by the
> corresponding frame decorator.
> The full implementation of the combined sniffer/frame filter for OpenJDK
> is about 2500 lines and will eventually become part of it. I am waiting for
> this GDB patch to be reviewed before I can present it to be reviewed by
> the JDK community :-)
>
>> I'm still not sure what kind of performance cost we're looking at here as
>> it scales up, I can imagine most times there'll be no Python sniffers,
>> or at most one or two.  But it would be good to collect some perf data
>> (e.g., install 1,10,100 no-op sniffers and see if there's any measurable
>> difference in backtrace performance).
> I ran a test that walks a 100-frame stack calling 100 Python sniffers
> per frame, and it executes (throught the dejagnu checker) in 500ms on
> Xeon E5-1650 0 @ 3.20GHz.
> Please let me know if you would like it to be added to gdb/testsuite.
>
>> Exposing frame id implementation details (sp,pc,special), and the
>> form of how to do that, is something the community needs to decide on.
>> I think we can come up with something suitable, though perhaps not
>> the current form.
>
> The revised patch is attached. The important differences are as follows:
> * A sniffer is now an object, using the pattern similar to xmethods
> and pretty printers.
> * Register values are properly types (that is, based on a register type)
>
> I am still not certain whether it's worth changing SnifferInfo.read_register to
> have registers retrieved by name rather than by its number. Perhaps
> adding gdb.Architecture.register_name_to_number method will be
> a reasonable tradeoff?
>
> The documentation is obviously unfinished (to be done once the design
> issues are resolved), and what exists needs to be put into proper
> English.
>
> Here's take two:
>
> gdb/ChangeLog:
> 2015-02-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 sniffers.py.
>         * doc/python.texi (Writing a Frame Unwinder in Python): Add
>         section.
>         * python/lib/gdb/__init__.py (packages): Add frame_sniffers list.
>         * python/lib/gdb/command/sniffers.py: New file, implements GDB
>         commands to list/enable/disable Python sniffers.
>         * python/lib/gdb/function/sniffers.py: New file, implements
>         execute_sniffers function.
>         * python/lib/gdb/sniffer.py: New file, contains Sniffer class and
>         register_sniffer function.
>         * python/py-objfile.c (objfile_object): Add frame_sniffers field.
>         (objfpy_dealloc): Decrement frame_sniffers reference count.
>         (objfpy_initialize): Create frame_sniffers list.
>         (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
>         getter.
>         (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
>         setter.
>         (objfile_getset): Add frame_sniffers attribute to Objfile.
>         * python/py-progspace.c (pspace_object): Add frame_sniffers field.
>         (pspy_dealloc): Decrement frame_sniffers reference count.
>         (pspy_initialize): Create frame_sniffers list.
>         (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
>         getter.
>         (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
>         setter.
>         (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
>         * python/py-unwind.c: New file, implements Python frame sniffers
>         interface.
>         * python/python-internal.h (pspy_get_name_sniffers): New prototype.
>         (objpy_get_frame_sniffers): New prototype.
>         (gdbpy_initialize_unwind): New prototype.
>         * python/python.c (gdbpy_apply_type_printers): Call
>         gdbpy_initialize_unwind.
>
> gdb/testsuite/ChangeLog:
> 2014-02-30  Sasha Smundak  <asmundak@google.com>
>
>         * gdb.python/py-unwind-maint.c: Test program for py-unwind-maint.
>         * gdb.python/py-unwind-maint.exp: Tests sniffer-related GDB
>         commands.
>         * gdb.python/py-unwind-maint.py: Pythons sniffers for the test.
>         * gdb.python/py-unwind.c: Test program for the py-unwind test.
>         * gdb.python/py-unwind.exp: Python frame sniffers test.
>         * gdb.python/py-unwind.py: Frame sniffer in Python tested by
>         py-unwind test.
  

Patch

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 8addef4..3773e2c 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -394,6 +394,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
@@ -433,6 +434,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
@@ -2608,6 +2610,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 f19577a..89157a4 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..47b4957 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/sniffer.py \
 	gdb/prompt.py \
 	gdb/xmethod.py \
 	gdb/command/__init__.py \
 	gdb/command/xmethods.py \
 	gdb/command/frame_filters.py \
+	gdb/command/sniffers.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/sniffers.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..62537d2 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 a frame unwinder in Python.
 * Xmethods In Python::          Adding and replacing methods of C++ classes.
 * Xmethod API::                 Xmethod types.
 * Writing an Xmethod::          Writing an xmethod.
@@ -2178,6 +2179,101 @@  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. 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 sniffer 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 the data
+describing it (that is, when it recognizes it). GDB comes with the module
+containing the base @code{Sniffer} class for that. The sniffer looks as
+follows:
+@smallexample
+from gdb.sniffers import Sniffer
+
+class MySniffer(Sniffer):
+    def __init__(....):
+        super(MySniffer, self).__init___(<expects sniffer name argument>)
+    def __call__(sniffer_info):
+        if not <we recognize frame>:
+            return None
+        <find previous frame registers>
+        return (<registers>, <frame ID registers>)
+@end smallexample
+
+@subheading Examining The Current Frame
+
+@value{GDBN} passes a @code{gdb.SnifferInfo} instance when it calls
+sniffer's @code{__call__} method. This class has a single method:
+
+@defun SnifferInfo.read_register (self, regnum)
+This method returns the contents of the register @var{regnum} in the
+frame as a @code{gdb.Value} object. @var{regnum} values are
+platform-specific. They are usually defined in the corresponding
+xxx-@code{tdep.h} file in the gdb source tree.
+@end defun
+
+@subheading Returning Previous Frame
+
+If sniffer's @code{__call__} method recognizes the frame, it should
+return a (@var{registers}, @var{frame_id_register_numbers}) tuple.
+
+@var{registers} describe the registers that can be unwound (i.e., the
+registers from the previous frame that have been saved in the current
+frame described by @var{sniffer_info}). It is a tuple where each
+element is a (@var{regnum}, @var{regdata}) 2-tuple.  @var{regnum} is
+a register number, and @var{regdata} is register contents (a
+@code{gdb.Value} object).
+
+@var{frame_id_register_numbers} is a tuple specifying the registers
+used to construct frame ID of the returned frame.  It is a (@var{sp}),
+(@var{sp}, @var{pc}) or (@var{sp}, @var{pc}, @var{special}) tuple,
+where @var{sp}, @var{pc}, @var{special} are register numbers. The
+referenced registers should be present in @var{registers} tuple. The
+frame ID is constructed by calling
+@code{frame_id_build_wild}(@var{value}(@var{sp})),
+@code{frame_id_build}(@var{value}(@var{sp}), @var{value}(@var{pc})),
+or @code{frame_id_build}(@var{value}(@var{sp}), @var{value}(@var{pc}),
+@var{value}(@var{special})) respectively.
+
+@subheading Registering a Sniffer
+
+An object file, a program space, and the @value{GDBN} proper can have
+sniffers registered with it.
+
+The @code{gdb.sniffers} module provides the function to register a
+sniffer:
+
+@defun gdb.sniffer.register_sniffer (locus, sniffer, replace=False)
+@var{locus} is specifies an object file or a program space to which
+@var{sniffer} is added. Passing @code{None} or @code{gdb} adds
+@var{sniffer} to the @value{GDBN}'s global sniffer list.  The newly
+added @var{sniffer} will be called before any other sniffer from the
+same locus.  Two sniffers in the same locus cannot have the same
+name. An attempt to add a sniffer with already existing name raises an
+exception unless @var{replace} is @code{True}, in which case the old
+sniffer is deleted.
+@end defun
+
+@subheading Sniffer Precedence
+
+@value{GDBN} first calls the sniffers from all the object files in no
+particular order, then the sniffers from the current program space,
+and finally the sniffers 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..8d7f651 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 sniffers.
+frame_sniffers = []
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/command/sniffers.py b/gdb/python/lib/gdb/command/sniffers.py
new file mode 100644
index 0000000..b340434
--- /dev/null
+++ b/gdb/python/lib/gdb/command/sniffers.py
@@ -0,0 +1,198 @@ 
+# Sniffer commands.
+# Copyright 2013-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_sniffer_command_args(arg):
+    """Internal utility to parse sniffer 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, "sniffer"))
+
+
+class InfoSniffer(gdb.Command):
+    """GDB command to list sniffers.
+
+    Usage: info sniffer [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the location of the
+    sniffer.  If it is omitted, all registered sniffers 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 sniffers from the current
+    progspace should be listed.
+
+    NAME-REGEXP is a regular expression to filter sniffer names.
+    If this omitted for a specified locus, then all registered
+    sniffers in the locus are listed.
+    """
+
+    def __init__(self):
+        super(InfoSniffer, self).__init__("info sniffer",
+                                          gdb.COMMAND_DATA)
+
+    def list_sniffers(self, title, sniffers, name_re):
+        """Lists the sniffers whose name matches regexp.
+
+        Arguments:
+            title: The line to print before the list.
+            sniffers: The list of the sniffers.
+            name_re: sniffer name filter.
+        """
+        if not sniffers:
+            return
+        print title
+        for sniffer in sniffers:
+            if name_re.match(sniffer.name):
+                print("  %s%s" % (sniffer.name,
+                                  "" if sniffer.enabled else "[disabled]"))
+
+    def invoke(self, arg, from_tty):
+        locus_re, name_re = parse_sniffer_command_args(arg)
+        if locus_re.match("global"):
+            self.list_sniffers("global sniffers:", gdb.frame_sniffers,
+                               name_re)
+        if locus_re.match("progspace"):
+            cp = gdb.current_progspace()
+            self.list_sniffers("progspace %s sniffers:" % cp.filename,
+                               cp.frame_sniffers, name_re)
+        for objfile in gdb.objfiles():
+            if locus_re.match(objfile.filename):
+                self.list_sniffers("objfile %s sniffers:" % objfile.filename,
+                                   objfile.frame_sniffers, name_re)
+
+
+def do_enable_sniffer1(sniffers, name_re, flag):
+    """Enable/disable sniffers whose names match given regex.
+
+    Arguments:
+        sniffers: The list of sniffers.
+        name_re: Sniffer name filter.
+        flag: Enable/disable.
+
+    Returns:
+        The number of sniffers affected.
+    """
+    total = 0
+    for sniffer in sniffers:
+        if name_re.match(sniffer.name):
+            sniffer.enabled = flag
+            total += 1
+    return total
+
+
+def do_enable_sniffer(arg, flag):
+    """Enable/disable sniffer(s)."""
+    (locus_re, name_re) = parse_sniffer_command_args(arg)
+    total = 0
+    if locus_re.match("global"):
+        total += do_enable_sniffer1(gdb.frame_sniffers, name_re, flag)
+    if locus_re.match("progspace"):
+        total += do_enable_sniffer1(gdb.current_progspace().frame_sniffers,
+                                    name_re, flag)
+    for objfile in gdb.objfiles():
+        if locus_re.match(objfile.filename):
+            total += do_enable_sniffer1(objfile.frame_sniffers, name_re,
+                                        flag)
+    print("%d sniffer%s %s" % (total, "" if total == 1 else "s",
+                               "enabled" if flag else "disabled"))
+
+
+class EnableSniffer(gdb.Command):
+    """GDB command to enable sniffers.
+
+    Usage: enable sniffer [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 sniffer names.
+    If this omitted for a specified locus, then all registered
+    sniffers in the locus are affected.
+    """
+
+    def __init__(self):
+        super(EnableSniffer, self).__init__("enable sniffer",
+                                            gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_sniffer(arg, True)
+
+
+class DisableSniffer(gdb.Command):
+    """GDB command to disable the specified sniffer.
+
+    Usage: disable sniffer [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 sniffer names.
+    If this omitted for a specified locus, then all registered
+    sniffers in the locus are affected.
+    """
+
+    def __init__(self):
+        super(DisableSniffer, self).__init__("disable sniffer",
+                                             gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_sniffer(arg, False)
+
+
+def register_sniffer_commands():
+    """Installs the sniffer commands."""
+    InfoSniffer()
+    EnableSniffer()
+    DisableSniffer()
+
+
+register_sniffer_commands()
diff --git a/gdb/python/lib/gdb/function/sniffers.py b/gdb/python/lib/gdb/function/sniffers.py
new file mode 100644
index 0000000..6ea6604
--- /dev/null
+++ b/gdb/python/lib/gdb/function/sniffers.py
@@ -0,0 +1,53 @@ 
+# Copyright (C) 2013-2014 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 sniffers."""
+
+import gdb
+
+
+def execute_sniffers(sniffer_info):
+    """Internal function called from GDB to execute all sniffers.
+
+    Runs each currently enabled sniffer until it finds the one that can
+    unwind given frame.
+
+    Arguments:
+        sniffer_info: an instance of gdb.SnifferInfo.
+    Returns:
+        Unwind info or None. See the description of the value returned
+        by Sniffer.__call__ below.
+    """
+    for objfile in gdb.objfiles():
+        for sniffer in objfile.frame_sniffers:
+            if sniffer.enabled:
+                unwind_info = sniffer.__call__(sniffer_info)
+                if unwind_info is not None:
+                    return unwind_info
+
+    current_progspace = gdb.current_progspace()
+    for sniffer in current_progspace.frame_sniffers:
+        if sniffer.enabled:
+            unwind_info = sniffer.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    for sniffer in gdb.frame_sniffers:
+        if sniffer.enabled:
+            unwind_info = sniffer.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    return None
diff --git a/gdb/python/lib/gdb/sniffer.py b/gdb/python/lib/gdb/sniffer.py
new file mode 100644
index 0000000..6c9f327
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffer.py
@@ -0,0 +1,113 @@ 
+# Copyright (C) 2013-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/>.
+
+"""Sniffer class and register_sniffer function."""
+
+import gdb
+
+
+class Sniffer(object):
+    """Base class (or a template) for frame sniffers written in Python.
+
+    A sniffer has a single method __call__ and the attributes described below.
+
+    Attributes:
+        name: The name of the sniffer.
+        enabled: A boolean indicating whether the sniffer is enabled.
+    """
+
+    def __init__(self, name):
+        """Constructor.
+
+        Args:
+            name: An identifying name for the sniffer.
+        """
+        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:
+            (REG_DATA, FRAME_ID_REGNUMS) tuple, or None if this sniffer cannot
+            unwind the frame.
+            REG_DATA is a tuple containing the registers in the unwound frame.
+            Each element in thr REG_DATA is a (REG_NUM, REG_VALUE) tuple,
+            where REG_NUM is platform-specific register number, and REG_VALUE
+            is register value (a gdb.Value instance).
+            FRAME_ID_REGNUMS is a tuple specifying the registers used
+            to construct the frame ID. For instance, on x86_64,
+            FRAME_ID_REGNUMS is (7, 16), as 7 is the stack pointer
+            register (RSP), and 16 is the instruction pointer (RIP).
+            FRAME_ID_REGNUMS is a (SP), (SP, PC), or (SP, PC, SPECIAL)
+            tuple, where SP, PC, and SPECIAL are register numbers. Depending
+            on the tuple, Python sniffer support code uses internal GDB
+            functions to construct frame ID as follows:
+            (SP)                  frame_id_build_wild (Value(SP))
+            (SP, PC)              frame_id_build (Value(SP), Value(PC))
+            (SP, PC, SPECIAL)     frame_id_build_special (Value(SP),
+                                   Value(PC), Value(SPECIAL)
+            The registers in the FRAME_ID_REGNUMS tuple should be among those
+            returned by REG_DATA.
+            The chapter "Stack Frames" in the GDB Internals Guide describes
+            frame ID.
+        """
+        raise NotImplementedError("Sniffer __call__")
+
+
+def register_sniffer(locus, sniffer, replace=False):
+    """Register sniffer in given locus.
+
+    The sniffer is prepended to the locus's sniffers list. Sniffer
+    name should be unique.
+
+    Arguments:
+        locus: Either an objfile, progspace, or None (in which case
+               the sniffer is registered globally).
+        sniffer: An object of a gdb.Sniffer subclass
+        replace: If True, replaces existing sniffer with the same name.
+                 Otherwise, raises exception if sniffer with the same
+                 ame already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        ValueError: sniffer name contains ';'.
+        RuntimeError: Sniffer name is not unique.
+
+    """
+    if sniffer.name.find(";") >= 0:
+        raise ValueError("semicolon ';' in printer name")
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s sniffer ...\n" % sniffer.name)
+        locus = gdb
+    else:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering %s sniffer for %s ...\n" %
+                      (sniffer.name, locus.filename))
+    i = 0
+    for needle in locus.frame_sniffers:
+        if needle.name == sniffer.name:
+            if replace:
+                del locus.frame_sniffers[i]
+            else:
+                raise RuntimeError("sniffer %s already exists" % sniffer.name)
+        i += 1
+    locus.frame_sniffers.insert(0, sniffer)
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 0aecaf6..a7bb7cd 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 frame sniffers list of functions.  */
+  PyObject *frame_sniffers;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -181,6 +185,7 @@  objfpy_dealloc (PyObject *o)
   Py_XDECREF (self->dict);
   Py_XDECREF (self->printers);
   Py_XDECREF (self->frame_filters);
+  Py_XDECREF (self->frame_sniffers);
   Py_XDECREF (self->type_printers);
   Py_XDECREF (self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -203,6 +208,10 @@  objfpy_initialize (objfile_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_sniffers = PyList_New (0);
+  if (self->frame_sniffers == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -310,6 +319,48 @@  objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
   return 0;
 }
 
+/* Return the frame sniffers attribute for this object file.  */
+
+PyObject *
+objfpy_get_frame_sniffers (PyObject *o, void *ignore)
+{
+  objfile_object *self = (objfile_object *) o;
+
+  Py_INCREF (self->frame_sniffers);
+  return self->frame_sniffers;
+}
+
+/* Set this object file's frame sniffers list to SNIFFERS.  */
+
+static int
+objfpy_set_frame_sniffers (PyObject *o, PyObject *sniffers, void *ignore)
+{
+  PyObject *tmp;
+  objfile_object *self = (objfile_object *) o;
+
+  if (!sniffers)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete the frame sniffers attribute."));
+      return -1;
+    }
+
+  if (!PyList_Check (sniffers))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The frame_sniffers attribute must be a list."));
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_sniffers;
+  Py_INCREF (sniffers);
+  self->frame_sniffers = sniffers;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -645,6 +696,8 @@  static PyGetSetDef objfile_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", objfpy_get_frame_filters,
     objfpy_set_frame_filters, "Frame Filters.", NULL },
+  { "frame_sniffers", objfpy_get_frame_sniffers,
+    objfpy_set_frame_sniffers, "Frame Sniffers", 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 29b9f96..ce85b1a 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 sniffer list.  */
+  PyObject *frame_sniffers;
+
   /* 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_sniffers);
   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_sniffers = PyList_New (0);
+  if (self->frame_sniffers == 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 sniffers for this program space.  */
+
+PyObject *
+pspy_get_frame_sniffers (PyObject *o, void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  Py_INCREF (self->frame_sniffers);
+  return self->frame_sniffers;
+}
+
+/* Set this program space's list of the sniffers to SNIFFERS.  */
+
+static int
+pspy_set_frame_sniffers (PyObject *o, PyObject *sniffers, void *ignore)
+{
+  PyObject *tmp;
+  pspace_object *self = (pspace_object *) o;
+
+  if (!sniffers)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "cannot delete the frame sniffers list");
+      return -1;
+    }
+
+  if (!PyList_Check (sniffers))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "the frame sniffers attribute must be a list");
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_sniffers;
+  Py_INCREF (sniffers);
+  self->frame_sniffers = sniffers;
+  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_sniffers", pspy_get_frame_sniffers, pspy_set_frame_sniffers,
+    "Frame sniffers.", 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..4c935aa
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,539 @@ 
+/* Python frame unwinder interface
+
+   Copyright (C) 2013-2014 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 "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+  struct frame_info *frame_info;
+} sniffer_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
+{
+  struct frame_id frame_id;
+  struct gdbarch *gdbarch;
+  int reg_count;
+  struct reg_info
+  {
+    int number;
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  char *s;
+  PyObject *result;
+  struct frame_info *frame = ((sniffer_info_object *)self)->frame_info;
+
+  s = xstrprintf ("SP=%s,PC=%s", core_addr_to_string_nz (get_frame_sp (frame)),
+        core_addr_to_string_nz (get_frame_pc (frame)));
+  result = PyString_FromString (s);
+  xfree (s);
+
+  return result;
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of register REGNUM as pointer.  */
+
+static PyObject *
+sniffer_infopy_read_register (PyObject *self, PyObject *args)
+{
+  volatile struct gdb_exception except;
+  int regnum;
+  struct value *val = NULL;
+
+  if (!PyArg_ParseTuple (args, "i", &regnum))
+    return NULL;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      /* Calling `value_of_register' will result in infinite recursion.
+         Call `deprecated_frame_register_read' instead.  */
+      struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
+      struct gdbarch *gdbarch = get_frame_arch (frame);
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      if (deprecated_frame_register_read (frame, regnum, buffer))
+        val = value_from_contents (register_type (gdbarch, regnum), buffer);
+      if (val == NULL)
+        {
+          char *error_text
+              = xstrprintf (_("Cannot read register %d from frame"), regnum);
+          PyErr_SetString (PyExc_ValueError, error_text);
+          xfree (error_text);
+        }
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  if (sniffer_info != NULL)
+    sniffer_info->frame_info = frame;
+
+  return (PyObject *) sniffer_info;
+}
+
+/* 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 || !PyInt_Check (pyo_int))
+    return 0;
+  long_value = PyInt_AsLong (pyo_int);
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* Parse given tuple of Python Ints into an array. Returns the number
+   of items in the tuple, or -1 on error (bad tuple element type,
+   array too small).  */
+
+static Py_ssize_t
+pyuw_parse_ints (PyObject *pyo_sequence, int *values, Py_ssize_t max_values)
+{
+  Py_ssize_t size;
+  Py_ssize_t i;
+
+  if (! PyTuple_Check (pyo_sequence))
+    return -1;
+  size = PyTuple_Size (pyo_sequence);
+  if (size < 0 || size > max_values)
+    return -1;
+  for (i = 0; i < size; ++i)
+    {
+      if (!pyuw_parse_int (PyTuple_GetItem (pyo_sequence, i), &values[i]))
+        return -1;
+    }
+  return i;
+}
+
+/* Retrieve register value for the cached unwind info as target pointer.
+   Return 1 on success, 0 on failure.  */
+
+static int
+pyuw_reg_value (cached_frame_info *cached_frame, int regnum, CORE_ADDR *value)
+{
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (reg_info->number == regnum)
+        {
+          *value = unpack_pointer
+              (register_type (cached_frame->gdbarch, regnum), reg_info->data);
+          return 1;
+        }
+    }
+
+  error (_("Python sniffer uses register #%d for this_id, "
+           "but this register is not available"), regnum);
+}
+
+/* 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);
+}
+
+/* Parse frame ID tuple returned by the sniffer info GDB's frame_id and
+   save it in the cached frame.  */
+
+static void
+pyuw_parse_frame_id (cached_frame_info *cached_frame,
+                     PyObject *pyo_frame_id_regs)
+{
+  int regno[3];
+  CORE_ADDR sp, pc, special;
+
+  if (!PyTuple_Check (pyo_frame_id_regs))
+    error (_("The second element of the pair returned by a Python "
+             "sniffer should be a tuple"));
+
+  switch (pyuw_parse_ints (pyo_frame_id_regs, regno, ARRAY_SIZE (regno)))
+    {
+    case 1:
+      if (pyuw_reg_value (cached_frame, regno[0], &sp))
+      {
+        cached_frame->frame_id = frame_id_build_wild (sp);
+        return;
+      }
+      break;
+    case 2:
+      if (pyuw_reg_value (cached_frame, regno[0], &sp)
+          || pyuw_reg_value (cached_frame, regno[1], &pc))
+      {
+        cached_frame->frame_id = frame_id_build (sp, pc);
+        return;
+      }
+      break;
+    case 3:
+      if (pyuw_reg_value (cached_frame, regno[0], &sp)
+          || pyuw_reg_value (cached_frame, regno[1], &pc)
+        || pyuw_reg_value (cached_frame, regno[2], &special))
+      {
+        cached_frame->frame_id = frame_id_build_special (sp, pc, special);
+        return;
+      }
+      break;
+    }
+  error (_("Unwinder should return a tuple of 1 to 3 ints in the second item"));
+}
+
+/* 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;
+
+  gdb_assert (*cache_ptr == NULL);
+  gdbarch = (void *)(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 (this_frame);
+  if (pyo_sniffer_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_sniffer_info);
+
+  if ((pyo_module = PyImport_ImportModule ("gdb.function.sniffers")) == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_module);
+
+  pyo_execute = PyObject_GetAttrString (pyo_module, "execute_sniffers");
+  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;
+
+  /* Unwind_info is a (REGISTERS, FRAME_ID_REGNUMS) tuple.  REGISTERS
+   * is a tuple of the (REG_NR, REG_VALUE) tuples. FRAME_ID_REGNUMS is
+   * the tuple of REGNO values.  */
+  if (!PyTuple_Check (pyo_unwind_info)
+      || PyTuple_Size (pyo_unwind_info) != 2)
+    error (_("Sniffer should return a pair (REGISTERS, FRAME_ID_REGNUMS)"));
+
+  {
+    PyObject *pyo_registers = PyTuple_GetItem (pyo_unwind_info, 0);
+    int i;
+    int reg_count;
+    size_t cached_frame_size;
+    size_t gdb_bytes_count;
+    gdb_byte *gdb_data_free, *gdb_data_end;
+
+    if (pyo_registers == NULL)
+      goto error;
+    if (!PyTuple_Check (pyo_registers))
+      error (_("The first element of the returned pair should be a tuple"));
+
+    /* Figure out how much space we need to allocate.  */
+    reg_count = PyTuple_Size (pyo_registers);
+    if (reg_count <= 0)
+      error (_("Register list should not be empty"));
+    /* We might overestimate `gdb_bytes_count', but it's not worth
+       parsing register numbers twice to calculate the exact number of
+       bytes needed.  */
+    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->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; i < reg_count; i++)
+      {
+        PyObject *pyo_reg = PyTuple_GetItem (pyo_registers, i);
+        struct reg_info *reg = &(cached_frame->reg[i]);
+
+        if (pyo_reg == NULL)
+          goto error;
+
+        if (!PyTuple_Check (pyo_reg) || PyTuple_Size (pyo_reg) != 2
+            || !pyuw_parse_int (PyTuple_GetItem (pyo_reg, 0),
+                                &reg->number))
+          {
+            error (_("Python sniffer returned bad register tuple: "
+                     "item #%d is not a (reg_no, reg_data) tuple "
+                     "with integer reg_no and reg_data"), i);
+          }
+
+        {
+          PyObject *pyo_reg_value = PyTuple_GetItem (pyo_reg, 1);
+          struct value *value;
+          size_t data_size;
+
+          if (pyo_reg_value == NULL)
+            goto error;
+
+          if ((value = value_object_to_value (pyo_reg_value)) == NULL)
+            error (_("Python sniffer returned bad register tuple: "
+                     "item #%d, register value should have type "
+                     "gdb.Value type"), i);
+          data_size = register_size (gdbarch, reg->number);
+          if (data_size != TYPE_LENGTH (value_enclosing_type (value)))
+            {
+              error (_("The value of the register #%d returned by the "
+                       "Python sniffer has unexpected size: %u instead "
+                       "of %u"), reg->number,
+                     (unsigned)(TYPE_LENGTH (value_enclosing_type (value))),
+                     (unsigned)data_size);
+            }
+          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;
+        }
+      }
+  }
+
+  {
+    PyObject *pyo_frame_id_regs = PyTuple_GetItem (pyo_unwind_info, 1);
+    if (pyo_frame_id_regs == NULL)
+      goto error;
+    pyuw_parse_frame_id (cached_frame, pyo_frame_id_regs);
+  }
+
+  *cache_ptr = cached_frame;
+  discard_cleanups (cached_frame_cleanups);
+  do_cleanups (cleanups);
+  return 1;
+
+error:
+  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 sniffers
+   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)
+{
+  add_setshow_zuinteger_cmd
+      ("py-unwind", class_maintenance, &pyuw_debug,
+        _("Set Python unwinder debugging."),
+        _("Show Python unwinder debugging."),
+        _("When non-zero, Pythin 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;
+  return gdb_pymodule_addobject (gdb_module, "SnifferInfo",
+      (PyObject *) &sniffer_info_object_type);
+}
+
+static PyMethodDef sniffer_info_object_methods[] =
+{
+  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
+    "read_register (register_name) -> gdb.Value\n\
+Return the value of the register in 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 */
+};
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index a77f5a6..34179de 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -390,12 +390,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_sniffers (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_sniffers (PyObject *, void *);
 PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
@@ -490,6 +492,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 344d8d2..3e079b5 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1790,7 +1790,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..55db4dc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
@@ -0,0 +1,64 @@ 
+# Copyright (C) 2010-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
+# pretty-printing for the 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 sniffer" "Show all sniffers" {
+    "global sniffers:"
+    "  global_sniffer"
+    "progspace.*sniffers:"
+    "py_unwind_maint_ps_sniffer"
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
+
+gdb_test_sequence "continue" "Sniffers called" {
+    "py_unwind_maint_ps_sniffer called"
+    "global_sniffer called"
+}
+
+gdb_test "disable sniffer global .*" "1 sniffer disabled" "Sniffer disabled"
+
+gdb_test_sequence "info sniffer" "Show with global sniffer disabled" {
+    "global sniffers:"
+    "  global_sniffer\\[disabled\\]"
+    "progspace.*sniffers:"
+    "  py_unwind_maint_ps_sniffer"
+}
+
+gdb_test_sequence "where" "Global sniffer disabled" {
+    "py_unwind_maint_ps_sniffer 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..f978df4
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
@@ -0,0 +1,59 @@ 
+# Copyright (C) 2010-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 sniffers.
+
+import re
+import gdb.types
+from gdb.sniffer import Sniffer, register_sniffer
+
+class TestGlobalSniffer(Sniffer):
+    def __init__(self):
+        super(TestGlobalSniffer, self).__init__("global_sniffer")
+
+    def __call__(self, sniffer_info):
+        print "%s called" % self.name
+        return None
+
+class TestProgspaceSniffer(Sniffer):
+    def __init__(self, name):
+        super(TestProgspaceSniffer, self).__init__("%s_ps_sniffer" % name)
+
+    def __call__(self, sniffer_info):
+        print "%s called" % self.name
+        return None
+
+class TestObjfileSniffer(Sniffer):
+    def __init__(self, name):
+        super(TestObjfileSniffer, self).__init__("%s_obj_sniffer" % name)
+
+    def __call__(self, sniffer_info):
+        print "%s called" % self.name
+        return None
+
+
+
+gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer())
+saw_runtime_error = False
+try:
+    gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer(), replace=False)
+except RuntimeError:
+    saw_runtime_error = True
+if not saw_runtime_error:
+    raise RuntimeError("Missing runtime error from register_sniffer")
+gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer(), replace=True)
+gdb.sniffer.register_sniffer(gdb.current_progspace(),
+                              TestProgspaceSniffer("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..84349d8
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,70 @@ 
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2011-2014 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;
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+break_backtrace (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 *outer_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+
+  /* Verify the compiler allocates the first local variable one word
+     below frame.  This is where test JIT reader expects to find the
+     correct outer frame address.  */
+  if (&outer_fp + 1 != (void **) MY_FRAME)
+    {
+      fprintf (stderr, "First variable should be allocated one word below "
+               "the frame, got variable's address %p, frame at %p instead\n",
+               &outer_fp, MY_FRAME);
+      abort ();
+    }
+
+  /* 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, outer_fp); /* break backtrace-broken */
+}
+
+static void
+break_backtrace_caller (void)
+{
+  break_backtrace ();
+}
+
+int
+main ()
+{
+  break_backtrace_caller ();
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..a52d06f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@ 
+# Copyright (C) 2009-2014 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
+# sniffers 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 sniffer" {
+    "\\r\\n#0 .* break_backtrace \\(\\) at "
+    "\\r\\n#1 .* break_backtrace_caller \\(\\) 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..8fa7fdc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,51 @@ 
+# Copyright (C) 2013-2014 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.sniffer import Sniffer
+
+class TestSniffer(Sniffer):
+    AMD64_RBP = 6
+    AMD64_RSP = 7
+    AMD64_RIP = 16
+
+    def __init__(self):
+        Sniffer.__init__(self, "test sniffer")
+        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):
+        "Sniffer written in Python."
+        bp = sniffer_info.read_register(
+            TestSniffer.AMD64_RBP).cast(self.char_ptr_t)
+        try:
+            if self._read_word(bp) != bp:
+                return None
+            # Found the frame that the test program fudged 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_sp = bp + 16
+            return (((TestSniffer.AMD64_RBP, self._read_word(bp - 8)),
+                     (TestSniffer.AMD64_RIP, self._read_word(bp + 8)),
+                     (TestSniffer.AMD64_RSP, bp + 16)),
+                    (TestSniffer.AMD64_RSP, TestSniffer.AMD64_RIP))
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.sniffer.register_sniffer(None, TestSniffer())
+print("Python script imported")