diff mbox

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

Message ID CAHQ51u75+9HYAVJXYNQa0gTnQtYKEgmSkyAhAPYp-y4HGtXssg@mail.gmail.com
State New
Headers show

Commit Message

Alexander Smundak Feb. 26, 2015, 3:09 a.m. UTC
Here's another round.
I am not sure who will be the reviewer this time.

>>> 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.
>
> I agree.
>
>> 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 am going to publish it on openjdk.java.net site once the site's admin updates
my credentials.

> You could expand the testcase a little?
I have revised it really 'a little', i.e., there is more than one frame with
broken previous frame link, causing standard unwinders to fail, but I am
not sure it's enough. On the other hand, this is a test, making it too
complex would make it harder to debug.

> Please make errors a complete sentence with capitalization and
> punctuation.
Done.

> +
> +    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)
>
> I think parse_sniffer_command_args should error if the loci is not
> global, progspace or objectfile. Either that or the default to object
> file should be documented here, and in the parse_sniffer_command_args
> function.
Not sure what you mean -- locus can be either a literal "global",
a literal "progspace", or a regex to match object file name.

> +        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():
>
> There seems no way *not* to parse object files for sniffers? Say I set
> the loci to "global", that's what I want. Given there could be 1000s
> of object files, I think this default is a bit odd? I might be
> misunderstanding though.
From the point of view of the output, issuing `info sniffer global'
will yield confusing result if the current progspace happens to contain
an object file with filename 'global' exactly. IMHO it's not a big deal,
especially taking into account that object file names tend to be absolute
paths (the only one I've seen so far not having an absolute path has
filename "system-supplied DSO at 0x7ffff7ffa000".

As to the concerns about the performance: frankly, I've patterned
the code after pretty_printers.py in the same directory, thinking that
if the performance of the 'info sniffer global' will be on par with that
of 'info pretty-printer global'.

> +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():
>
> Again, in an environment where there may be  many object files this seems
> a rather wasteful search if I set the loci to "global", and then we
> search all object files pointlessly?
Judging by the experiments dje@ asked me to conduct, the performance
impact is fairly small, while the effort to maintain the exact list of
the curently
enabled sniffers is non-trivial. I can conduct additional experiments with
thousands of object files to see if it's worth it.

> +        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"))
>
>
> +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
>
> I think we may need a priority system. We ran into this with frame
> filters. Allow me to explain a scenario, and how a user might solve
> it.
I don't think there is much common between frame filters and frame
sniffers. The output of a filter is passed to the next filter, whereas
a sniffer just returns the previous frame, I am not sure why the
output of one sniffer may be handed down to the next one.

> Say in the future I am debugging openjdk, and the RPM on my Fedora
> system installs a bunch of frame sniffers in the GDB auto-loading
> path. So they get auto-loaded when the openjdk inferior is
> loaded. Good, that's what I want. But what happens if I want to
> register my own frame sniffer that does additional stuff?
If a sniffer already knows how to unwind a frame, there is nothing
more to do, and there is no need to override this (unless you are
debugging the sniffer proper, but this IMHO is a corner case).
This is quite different from the frame filters behavior.

> --- /dev/null
> +++ b/gdb/python/lib/gdb/sniffer.py
> @@ -0,0 +1,113 @@
> +# Copyright (C) 2013-2015
>
> This and others, 2015 only.
Done.

> +
> +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.
>
>      priority: The priority order of the sniffers
> (If you go with the idea of a priority based system)
>
>
> +        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).
>
> See below
>
> +            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)
>
> This seems like an internal detail exposed a little too much? Why not
> return an object with the attributes SP, PC and SPECIAL? These could
> be mapped from the Python API register methods talked of earlier in
> the previous emails.  These three registers are either returned (SP
> looks to be the mandatory one), or not. No additional registers other
> than the three listed will be included in FRAME_ID_REGNUMS?
>
> Does the actual number of the register matter here to the
> user/implementer?  You tell the user the register number for SP, PC
> and SPECIAL as you populate them in tuple order. Why make the user
> jump through hops to map values to those three register numbers from
> REG_DATA when you could do it for them? You could still return an
> attribute containing the tuple REG_DATA if they wanted to poke around
> the other registers. It seem like, though, we don't tell them what the
> other registers are, so only the three named are important.
>
> There could be a very good reason for this, but I cannot see it
> myself. What do you think?
>
I am struggling to describe platform-specific stuff in a platform-independent
manner. Perhaps a better approach would be to spell what a sniffer is
expected to return for each platform (... denotes optional (regnum,
regvalue) 2-tuples):

x86_64:
(((#RBP, $RBP), (#RSP, $RSP), (#RIP, $RIP), ...),
 (#RSP, #RIP))
where #RBP=6, #RSP=7, #RIP=16

x86:
(((#EBP, $EBP), (#ESP, $ESP), (#EIP, $EIP), ...),
 (#ESP, #EIP))
where #EBP=5, #ESP=4, #EIP=8

PPC64:
(((#R1, $R1), (#PC, $PC), ...),
 (#R1, #PC))
where #R1=1, #PC=64)

I don't think it's too much to ask unwinder's author to return such
tuples (BTW, it
also shows the for x86 the sniffer is expected to return xBP in
addition to xSP and xIP).
Do you think it will be better if the expected values were documented
per platform?

>
> +            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__")
>
> There might be a super-class provided somewhere the implements the
> comments above. So comments  might apply there too.
Not sure what you mean here, please elaborate.

> +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;
>
> Each field in a struct needs to be documented.
Commented cached_frame_info fields.

>
> +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.  */
>
> Can you explain this comment in more detail?

>
> +      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);
>
> I believe in this case you are overwriting the GDB generated exception
> with a more generic error message. Don't do it.
I have changed the code to validate `regnum' by calling
`user_ret_map_regnum_to_name', but I am not sure which exception
you mean.

> +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)
>
> This is a new object so you should unconditionally write the
> attribute?
Done.

> +static int
> +pyuw_parse_int (PyObject *pyo_int, int *valuep)
> +{
>
> There's an issue with Python 2.x and Python 3.x compatibility with
> longs. Please see py-value.c:1447 comment and resulting logic to
> ensure this works in both cases.
Done.

> +static int
> +pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
> +              void **cache_ptr)
> +...
> +  gdb_assert (*cache_ptr == NULL);
>
> Can you please document the assert, and the reason for it.
Deleted this assert.

>
> +  gdbarch = (void *)(self->unwind_data);
>
> This cast seems odd to me.
Indeed.

> +          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);
> +            }
>
> Can you please document this assert reason.
I assume you mean the one on the line below:
> +          gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
It's an overflow check -- is it redundant?

The ChangeLog files remain unchanged since last round, and the new
patch is attached.

Comments

Alexander Smundak March 2, 2015, 10:56 p.m. UTC | #1
Responding on this thread to Andy Wingo's comments
(see https://sourceware.org/ml/gdb-patches/2015-03/msg00037.html):
Doug & Phil, please comment.

On Mon, Mar 2, 2015 at 5:28 AM, Andy Wingo <wingo@igalia.com> wrote:
> Hi,
>
> I have a local patch that allows me to unwind frames in Guile, loosely
> modelled off of Alexander's patch that does the same in Python.  I've
> been working on getting an unwinder for V8 that uses this interface, but
> it is challenging for a number of reasons that seem more to do with GDB
> than with the extension interface.
>
> First I have some comments about the Python patch; apologies for not
> threading, as I wasn't on the list before.  I think the interface is a
> bit strange.  To sum up, the interface looks like this:
>
>     # private helper
>     def execute_sniffers(sniffer_info):
>         for sniffer in enabled_sniffers:
>             unwind_info = sniffer(sniffer_info)
>             if unwind_info:
>                 return unwind_info
>
> "sniffer_info" is a new data type, a sniffer info object, which exposes
> only a read_register() API.  The return is a (REGISTERS, FRAME_ID)
> tuple, or None if the sniffer didn't handle the frame.  REGISTERS and
> FRAME_ID are both built out of tuples and lists.
>
> To me it seems that this interface does not lend itself to good error
> reporting.  An API that instead built a specialized unwind_info object
> and called set_frame_id(), set_register() etc on it would simplify many
> things.
>
> The return value type is also not very extensible.  If it were an
> object, it would be more future-proof.  For example, what about the
> stop_reason ?

The rationale for the current interface is that the object returned
by a Python sniffer is immediately consumed, so it's not worth
providing any complicated machinery for it. I wasn't thinking about
future-proofing much.

So here's the new proposal for the Python API, hopefully in
line with what you have in mind for Guile:

If a sniffer is able to unwind a frame, it should return an instance of
gdb.sniffer.UnwindInfo class, which has the following methods:
* UnwindInfo(registers)
  Constructor. `registers' is a tuple of (register_number, register_value)
  2-tuples for the registers that can be unwound.
* frame_id_build_wild(SP)
  frame_id_build(SP, PC)
  frame_id_build_special(SP, PC, SPECIAL)
  Sets frame ID by calling the corresponding GDB function. It is an error
  to return UnwindInfo object before one of these methods is called (a
  sniffer should return None if it cannot unwind a frame)
* set_register(register_number, register_value)
  Adds a 2-tuple to the list of unwound registers. Not sure this is needed.

Do we agree on this?

> Exposing register numbers in the API does not makes sense to me, as most
> users won't know those register numbers.  It shouldn't be necessary to
> open up GDB's C source to implement an unwinder.  Instead why not
> specify registers as strings, as elsewhere
> (e.g. gdb.Frame.read_register)?
My concern is that name lookups are expensive, and 'SnifferInfo.read_register'
will be called by each Python sniffer at least once. Then it makes sense to
have UnwindInfo use register numbers, too, for consistency.
In https://sourceware.org/ml/gdb-patches/2015-02/msg00329.html
I am proposing a tradeoff: add
`gdb.Architecture.register_name_to_number' method.
On the Python side, register number values can then be initialized
during architecture-specific sniffer state initialization.

> The sniffer_info object is unfortunate -- it's a frame, but without
> frame methods.  You can't get its architecture from python, for
> example, or get the next frame.  More about that later.
I guess you know by now that it is not a frame. The interface
reflects that.

> In your patch, the sniffer_info object could outlive the call to the
> sniffer, and so AFAICT it needs to be nulled out afterwards, as its
> internal frame_info pointer will become invalid.
Yes, Doug has mentioned that already, and I forgot to do that. Will do
in the next revision.

> In the read_register() function, I believe you can use
> get_frame_register_value instead of deprecated_frame_register_read.
You can't, get frame_register_value wiil assert because the frame
has no frame ID yet.

>                           *   *   *
> As a general comment, separation of frame decorators from unwinders
> causes duplicate work.  For example in V8 I will need to find the
> v8::internal::Code* object that corresponds to the frame in order to
> unwind the frame, but I will also need it for getting the line number.
> I can't cache it in a sensible way because GC could free or move the
> code.  (I suppose I could attach to GC-related breakpoints and
> invalidate a GDB-side cache, but that makes the GDB extension more
> brittle.)

> I don't know if there is a good solution here, though.
>                           *   *   *
>
> I have a similar interface in Scheme.  The Scheme unwinder constructs an
> "ephemeral frame object", which wraps this data:
>
>     /* Data associated with a ephemeral frame.  */
>     struct uwscm_ephemeral_frame
>     {
>       /* The frame being unwound, used for the read-register interface.  */
>       struct frame_info *this_frame;
>
>       /* The architecture of the frame, here for convenience.  */
>       struct gdbarch *gdbarch;
>
>       /* The frame_id for the ephemeral frame; initially unset.  */
>       struct frame_id frame_id;
>
>       /* Nonzero if the frame_id has been set.  */
>       int has_frame_id;
>
>       /* A list of (REGNUM . VALUE) pairs, indicating register values for the
>          ephemeral frame.  */
>       SCM registers;
>     };
>
> (Why a new object type?  Because all <gdb:frame> objects have a
> frame_id, and this one does not, and it turns out it's a deep invariant
> that frames have identifiers.)
>
> The list of unwinders is run in order over the ephemeral frame, and the
> first one that calls (set-ephemeral-frame-id! frame sp [ip [special]])
> on the frame takes responsibility of unwinding the frame.  You can read
> registers with ephemeral-frame-read-register, and set their unwound
> values with ephemeral-frame-write-register!.  Simple stuff, and I'll
> post later once I get the V8 unwinder working.
>
> Unfortunately, the unwind callback is really squirrely -- you can't do
> much there.
>
>   * You can't call the Guile lookup-symbol function within an unwind
>     handler, because the Guile wrapper wants to default the "block"
>     argument from the selected frame, and there is no selected frame.
>
>   * You can't value-call, which is not unexpected in general, but the
>     reason is unexpected: because call_function_by_hand calls
>     get_current_arch and that doesn't work
>
>   * You can't call get_current_arch, directly or indirectly, because it
>     causes unbounded recursion:
>
>       #3  0x00000000006a65f2 in frame_unwind_try_unwinder (this_frame=this_frame@entry=0x5f6af10, this_cache=this_cache@entry=0x5f6af28, unwinder=0x409fe30) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame-unwind.c:126
>       #4  0x00000000006a696f in frame_unwind_find_by_frame (this_frame=this_frame@entry=0x5f6af10, this_cache=this_cache@entry=0x5f6af28) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame-unwind.c:157
>       #5  0x00000000006a32cb in get_prev_frame_if_no_cycle (fi=0x5f6af10) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:454
>       #6  0x00000000006a32cb in get_prev_frame_if_no_cycle (this_frame=this_frame@entry=0x5f6ae40) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1780
>       #7  0x00000000006a53a9 in get_prev_frame_always (this_frame=0x5f6ae40) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1954
>       #8  0x00000000006a53a9 in get_prev_frame_always (this_frame=this_frame@entry=0x5f6ae40) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1971
>       #9  0x00000000006a5ab1 in get_prev_frame (this_frame=this_frame@entry=0x5f6ae40) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:2212
>       #10 0x00000000006a5d8c in unwind_to_current_frame (ui_out=<optimized out>, args=args@entry=0x5f6ae40) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1447
>       #11 0x00000000005cf63c in catch_exceptions_with_msg (func_uiout=<optimized out>, func=func@entry=0x6a5d80 <unwind_to_current_frame>, func_args=func_args@entry=0x5f6ae40, gdberrmsg=gdberrmsg@entry=0x0, mask=mask@entry=RETURN_MASK_ERROR)
>           at /home/wingo/src/binutils-gdb/+2.2/../gdb/exceptions.c:189
>       #12 0x00000000005cf75a in catch_exceptions (uiout=<optimized out>, func=func@entry=0x6a5d80 <unwind_to_current_frame>, func_args=func_args@entry=0x5f6ae40, mask=mask@entry=RETURN_MASK_ERROR) at /home/wingo/src/binutils-gdb/+2.2/../gdb/exceptions.c:169
>       #13 0x00000000006a33e0 in get_current_frame () at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1486
>       #14 0x00000000006a3fe7 in get_selected_frame (message=message@entry=0x0) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1541
>       #15 0x00000000005e7a27 in get_current_arch () at /home/wingo/src/binutils-gdb/+2.2/../gdb/arch-utils.c:784
>
>     Perhaps this is only the case for the most inner frame?  Anyway this
>     is the reason that many other things fail.
>
>   * You can't read user regs from an ephemeral frame for some reason:
>
>       /home/wingo/src/binutils-gdb/+2.2/../gdb/regcache.c:779: internal-error: regcache_cooked_read_value: Assertion `regnum < regcache->descr->nr_cooked_registers' failed.
>       #9  0x00000000005732b5 in regcache_cooked_read_value (regcache=0xd25cc0, regnum=221) at /home/wingo/src/binutils-gdb/+2.2/../gdb/regcache.c:779
>       #10 0x0000000000684c28 in sentinel_frame_prev_register (this_frame=0x6c7c350, this_prologue_cache=<optimized out>, regnum=<optimized out>) at /home/wingo/src/binutils-gdb/+2.2/../gdb/sentinel-frame.c:52
>       #11 0x00000000006a4408 in frame_unwind_register_value (frame=0x6c7c350, regnum=221) at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1111
>       #12 0x00000000006a468f in frame_register_unwind (frame=<optimized out>, regnum=<optimized out>, optimizedp=0x7fffffffcde8, unavailablep=0x7fffffffcdec, lvalp=0x7fffffffcdf0, addrp=0x7fffffffcdf8, realnump=0x7fffffffcdf4, bufferp=0x7fffffffce40 "l\305\001\367\377\177") at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1016
>       #13 0x00000000006a4892 in frame_register (frame=<optimized out>, regnum=<optimized out>, optimizedp=<optimized out>, unavailablep=<optimized out>, lvalp=<optimized out>, addrp=<optimized out>, realnump=<optimized out>, bufferp=<optimized out>)
>           at /home/wingo/src/binutils-gdb/+2.2/../gdb/frame.c:1057
>
> And so on.  From what I can tell, all of this is because there is no
> selected frame.  I recognize that this situation reflects reality in
> some way -- we're still building the selected frame -- but is there any
> way that we could have GDB be in a more "normal" state while the unwind
> callback is running?
Alexander Smundak March 3, 2015, 12:49 a.m. UTC | #2
On Wed, Feb 25, 2015 at 7:09 PM, Alexander Smundak <asmundak@google.com> wrote:
>>>> 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.
>>
>> I agree.
>>
>>> 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 am going to publish it on openjdk.java.net site once the site's admin updates
> my credentials.
I've posted it at the OpenJDK revisions site:
http://cr.openjdk.java.net/~asmundak/gdbunwind/hotspot/webrev.00/agent/src/os/linux/gdb/libjvm.so-gdb.py.html

Please bear in mind it's very preliminary (see TODO) and it does not reflect
the most recent proposal to have sniffer return UnwindInfo object instead of
a tuple containing registers and frame ID.
Andy Wingo March 3, 2015, 8:46 a.m. UTC | #3
Hi Alexander,

Thanks for the reply!

On Mon 02 Mar 2015 23:56, Alexander Smundak <asmundak@google.com> writes:

> So here's the new proposal for the Python API, hopefully in
> line with what you have in mind for Guile:
>
> If a sniffer is able to unwind a frame, it should return an instance of
> gdb.sniffer.UnwindInfo class, which has the following methods:
> * UnwindInfo(registers)
>   Constructor. `registers' is a tuple of (register_number, register_value)
>   2-tuples for the registers that can be unwound.
> * frame_id_build_wild(SP)
>   frame_id_build(SP, PC)
>   frame_id_build_special(SP, PC, SPECIAL)
>   Sets frame ID by calling the corresponding GDB function. It is an error
>   to return UnwindInfo object before one of these methods is called (a
>   sniffer should return None if it cannot unwind a frame)
> * set_register(register_number, register_value)
>   Adds a 2-tuple to the list of unwound registers. Not sure this is needed.

You'll need a link to the sniffer_info in order to be able to give good
errors for set_register, to check that the register exists and that the
value is of the correct type and size.  For that reason, in my first
draft of a Guile interface, the "ephemeral frame" is like your
sniffer_info and unwind_info together.  Perhaps this is a bad idea
though.

I would note as a meta-point that there are going to be some differences
between a Python and a Scheme interface, just for linguistic reasons.
Please consider my feedback as merely a friendly review and not an
obligation in any way :)  In particular, I'm not a GDB developer and
don't have a finely tuned nose for the tao of GDB :)

>> [W]hy not specify registers as strings, as elsewhere
>> (e.g. gdb.Frame.read_register)?
> My concern is that name lookups are expensive

Are they?  I wouldn't think so, no more than anything that happens in
Python.

> I am proposing a tradeoff: add
> `gdb.Architecture.register_name_to_number' method.
> On the Python side, register number values can then be initialized
> during architecture-specific sniffer state initialization.

If it were Guile I would leave off the numbers, but hey that's me :)
I'll leave this one to Doug.

>> The sniffer_info object is unfortunate -- it's a frame, but without
>> frame methods.  You can't get its architecture from python, for
>> example, or get the next frame.  More about that later.
> I guess you know by now that it is not a frame. The interface
> reflects that.

Well.  I mean, it's not a frame to Python, but its only state is a
"struct frame_info" pointer, and its only method is also present on
gdb.Frame, so it looks a lot like a frame to me :)

>> In the read_register() function, I believe you can use
>> get_frame_register_value instead of deprecated_frame_register_read.
> You can't, get frame_register_value wiil assert because the frame
> has no frame ID yet.

The comment in the source says:

          /* Call `deprecated_frame_register_read' -- calling
             `value_of_register' would an assert in `get_frame_id'
             because our frame is incomplete.  */

Whereas get_frame_register_value looks something like this:

  struct value *
  frame_unwind_register_value (struct frame_info *frame, int regnum)
  {
    /* Find the unwinder.  */
    if (frame->unwind == NULL)
      frame_unwind_find_by_frame (frame, &frame->prologue_cache);
  
    /* Ask this frame to unwind its register.  */
    return frame->unwind->prev_register (frame, &frame->prologue_cache, regnum);
  }
  
  struct value *
  get_frame_register_value (struct frame_info *frame, int regnum)
  {
    return frame_unwind_register_value (frame->next, regnum);
  }

So it doesn't touch THIS_FRAME.

Alexander, did you not run into nasty crashes while doing random Python
things inside your unwind handler?

For completeness, here's a draft of the unwinder I was working on, with
a bunch of helpers elided:

  (define (unwind-v8-frame frame)
    (let* ((isolate (cached-current-isolate))
           (prev-pc (ephemeral-frame-read-register frame "rip"))
           (code (and isolate
                      (lookup-code-for-pc prev-pc isolate))))
      (when code
        (let* ((fp (ephemeral-frame-read-register frame "rbp"))
               (type (if (code-optimized? code)
                         (v8-constant "StackFrame::OPTIMIZED")
                         (v8-constant "StackFrame::JAVA_SCRIPT")))
               (pc-address (compute-standard-frame-pc-address fp))
               (pc (value-dereference pc-address))
               (start-pc (code-instruction-start code))
               (sp (compute-frame-older-sp fp type))
               (fp (compute-standard-frame-older-fp fp)))
          (set-ephemeral-frame-id! frame fp start-pc)
          (ephemeral-frame-write-register! frame "rsp" sp)
          (ephemeral-frame-write-register! frame "rbp" fp)
          (ephemeral-frame-write-register! frame "rip" pc)))))

As you can see it's the set-ephemeral-frame-id! that marks the frame as
unwound.  A pretty weird interface, maybe I'd do better to separate them
again.

Andy
Andy Wingo March 3, 2015, 2:38 p.m. UTC | #4
On Tue 03 Mar 2015 01:49, Alexander Smundak <asmundak@google.com> writes:

> I've posted it at the OpenJDK revisions site:
> http://cr.openjdk.java.net/~asmundak/gdbunwind/hotspot/webrev.00/agent/src/os/linux/gdb/libjvm.so-gdb.py.html

This file would appear to be incompatible with current GDB, as it would
seem to be GPLv2 only.

Andy
Alexander Smundak March 4, 2015, 2:36 a.m. UTC | #5
>> So here's the new proposal for the Python API, hopefully in
>> line with what you have in mind for Guile:
>>
>> If a sniffer is able to unwind a frame, it should return an instance of
>> gdb.sniffer.UnwindInfo class, which has the following methods:
>> * UnwindInfo(registers)
>>   Constructor. `registers' is a tuple of (register_number, register_value)
>>   2-tuples for the registers that can be unwound.
>> * frame_id_build_wild(SP)
>>   frame_id_build(SP, PC)
>>   frame_id_build_special(SP, PC, SPECIAL)
>>   Sets frame ID by calling the corresponding GDB function. It is an error
>>   to return UnwindInfo object before one of these methods is called (a
>>   sniffer should return None if it cannot unwind a frame)
>> * set_register(register_number, register_value)
>>   Adds a 2-tuple to the list of unwound registers. Not sure this is needed.
>
> You'll need a link to the sniffer_info in order to be able to give good
> errors for set_register, to check that the register exists and that the
> value is of the correct type and size.  For that reason, in my first
> draft of a Guile interface, the "ephemeral frame" is like your
> sniffer_info and unwind_info together.  Perhaps this is a bad idea
> though.
I am somewhat confused, so maybe my proposal wasn't clear, let
me try again.
In the current implementation for Python a sniffer return something
similar to this (x86_64 architecture):
    return (((AMD64_RBP_NUM, fp), (AMD64_RIP_NUM, pc), (AMD64_RSP_NUM, sp)),
                (AMD64_RSP_NUM, AMD64_RIP_NUM))
(the variables `fp', 'pc', 'sp' contain the values of the respective
variables in the returned frame).
I am proposing to return an object as follows:
    previous_frame = UnwindInfo(((AMD64_RBP_NUM, fp), (AMD64_RIP_NUM,
pc), (AMD64_RSP_NUM, sp)))
    previous_frame.frame_id_build(sp, pc)
    return previous_frame
or
    previous_frame = UnwindInfo(((AMD64_RBP_NUM, fp), (AMD64_RIP_NUM, pc)))
    previous_frame.set_register(AMD64_RSP_NUM, sp)
    previous_frame.frame_id_build(sp, pc)
    return previous_frame
Checking the type and size of the a register in the implementation of
the `set_register' method depends only on the current architecture,
not on what's available in the sniffer_info.

>>> [W]hy not specify registers as strings, as elsewhere
>>> (e.g. gdb.Frame.read_register)?
>> My concern is that name lookups are expensive
>
> Are they?  I wouldn't think so, no more than anything that happens in
> Python.
The function mapping a register name to the number,
`user_reg_map_name_to_regnum', compares given register name
with all known register names, one at a time. I thought Python interpreter
is somewhat smarter than that and use dictionaries for lookups. Even if
it is not, there is no need to pile up inefficiency.
Actually, it's not that difficult to be able to pass either a register number,
or register name.

>>> The sniffer_info object is unfortunate -- it's a frame, but without
>>> frame methods.  You can't get its architecture from python, for
>>> example, or get the next frame.  More about that later.
>> I guess you know by now that it is not a frame. The interface
>> reflects that.
>
> Well.  I mean, it's not a frame to Python, but its only state is a
> "struct frame_info" pointer, and its only method is also present on
> gdb.Frame, so it looks a lot like a frame to me :)
>
>>> In the read_register() function, I believe you can use
>>> get_frame_register_value instead of deprecated_frame_register_read.
>> You can't, get frame_register_value wiil assert because the frame
>> has no frame ID yet.
>
> The comment in the source says:
>
>           /* Call `deprecated_frame_register_read' -- calling
>              `value_of_register' would an assert in `get_frame_id'
>              because our frame is incomplete.  */
>
> Whereas get_frame_register_value looks something like this:
>
>   struct value *
>   frame_unwind_register_value (struct frame_info *frame, int regnum)
>   {
>     /* Find the unwinder.  */
>     if (frame->unwind == NULL)
>       frame_unwind_find_by_frame (frame, &frame->prologue_cache);
>
>     /* Ask this frame to unwind its register.  */
>     return frame->unwind->prev_register (frame, &frame->prologue_cache, regnum);
>   }
>
>   struct value *
>   get_frame_register_value (struct frame_info *frame, int regnum)
>   {
>     return frame_unwind_register_value (frame->next, regnum);
>   }
>
> So it doesn't touch THIS_FRAME.

Here's traceback I get when I try to call value_of_register:
#5  0x00000000006b5212 in internal_error (file=file@entry=0x838760
"<...>/gdb/frame.c", line=line@entry=478, fmt=<optimized out>)
    at gdb/common/errors.c:55
#6  0x0000000000688796 in get_frame_id (fi=fi@entry=0x24b63f0) at
gdb/frame.c:478
#7  0x0000000000551ea3 in value_of_register_lazy
(frame=frame@entry=0x24b63f0, regnum=regnum@entry=6) at
gdb/findvar.c:290
#8  0x0000000000551fef in value_of_register (regnum=6,
frame=0x24b63f0) at gdb/findvar.c:271

I am not sure why this particular comment I've added attracts so much
attention :-)

> Alexander, did you not run into nasty crashes while doing random Python
> things inside your unwind handler?
I used to have random crashes before I learned to call `ensure_python_env'
to establish Python runtime environment before suing Python API, but after
that, no. Do you have an example that you can share?
Alexander Smundak March 4, 2015, 2:52 a.m. UTC | #6
>> I've posted it at the OpenJDK revisions site:
>> http://cr.openjdk.java.net/~asmundak/gdbunwind/hotspot/webrev.00/agent/src/os/linux/gdb/libjvm.so-gdb.py.html
>
> This file would appear to be incompatible with current GDB, as it would
> seem to be GPLv2 only.

It's not part of GDB. Apparently it need not be licensed under GPLv3.
Andy Wingo March 4, 2015, 7:49 a.m. UTC | #7
Howdy :)

Reordering some of the replies.

On Wed 04 Mar 2015 03:36, Alexander Smundak <asmundak@google.com> writes:

>> You'll need a link to the sniffer_info in order to be able to give good
>> errors for set_register, to check that the register exists and that the
>> value is of the correct type and size.
>
> Checking the type and size of the a register in the implementation of
> the `set_register' method depends only on the current architecture,
> not on what's available in the sniffer_info.

There is no current architecture because there may be no selected frame.

In any case the issue is the arch of the frame being unwound, which the
API implies might be different from the target architecture.  I am not
sure how this can be the case; can a GDB person chime in?

So when your user does:

>     previous_frame = UnwindInfo(((AMD64_RBP_NUM, fp), (AMD64_RIP_NUM,
> pc), (AMD64_RSP_NUM, sp)))

you can't correctly detect errors because you don't know the
architecture.

Incidentally if you're doing this from python then what I would want as
a user is a traceback for the error when it occurs.  That's easier to do
if you don't lump multiple things in one call.  For that reason in the
Guile interface you would have, for your example:

  (ephemeral-frame-set-id! frame sp pc)
  (ephemeral-frame-set-register! frame "rbp" fp)
  (ephemeral-frame-set-register! frame "rip" ip)
  (ephemeral-frame-set-register! frame "rsp" sp)

But, to each folk their stroke.

>>>> [W]hy not specify registers as strings, as elsewhere
>>>> (e.g. gdb.Frame.read_register)?
>
> The function mapping a register name to the number,
> `user_reg_map_name_to_regnum', compares given register name
> with all known register names, one at a time. I thought Python interpreter
> is somewhat smarter than that and use dictionaries for lookups. Even if
> it is not, there is no need to pile up inefficiency.
> Actually, it's not that difficult to be able to pass either a register number,
> or register name.

The one-at-a-time nature of user_reg_map_name_to_regnum is a detail and
not essential.

However I think we have argued this enough and I won't say any more
about it, being as I don't have LGTM power over these things :-))

>>>> In the read_register() function, I believe you can use
>>>> get_frame_register_value instead of deprecated_frame_register_read.
>
> Here's traceback I get when I try to call value_of_register:

I suggested "get_frame_register_value", not "value_of_register".
Confusion is understandable though with all these similarly-named,
similarly-purposed functions...

>> Alexander, did you not run into nasty crashes while doing random Python
>> things inside your unwind handler?
> I used to have random crashes before I learned to call `ensure_python_env'
> to establish Python runtime environment before suing Python API, but after
> that, no. Do you have an example that you can share?

Sure; I figured out what was going on yesterday.  With your test cases,
is the innermost frame handled by the unwinder?  I ran into lots of
problems there, because -- and I'm going to have to bullet-point this as
it gets complicated --

  * An unwinder is called on the innermost frame.

  * Many actions, for example looking up a symbol without specifying a
    block. will request the selected frame.

  * get_selected_frame() sees there is no selected frame, and goes to
    get_current_frame() and will select that.

  * get_current_frame creates a sentinel frame and unwinds that to
    produce the innermost frame.

  * After unwinding saved registers from the sentinel, frame.c finishes
    constructing the innermost frame by doing a compute_frame_id() on
    the frame.

  * compute_frame_id() goes to compute the unwinder for the innermost
    frame, in order to call the this_id() method, which leads us back to
    the beginning.

You may not have seen this if your test cases do not have an
"interesting" frame as the innermost frame.

I have a fix for this, but it's a bit deep.  I'll post my patch today.
Happily, with a combination of unwinders and frame filters, I am able to
get a correct, pretty backtrace:

  #0  0x00000d3c5b0661a1 in TestCase () at /hack/v8/test/mjsunit/debug-step-4-in-frame.js:94
  #1  0x00000d3c5b06a3d3 in  () at /hack/v8/test/mjsunit/debug-step-4-in-frame.js:112
  #2  0x00000d3c5b02c620 in [internal frame] ()
  #3  0x00000d3c5b014d31 in [entry frame] ()
  #4  0x0000000000b4e949 in v8::internal::Invoke(...) at ../src/execution.cc:128
  #5  0x0000000000b4ed23 in v8::internal::Execution::Call(...) at ../src/execution.cc:179
  #6  0x0000000000a3f813 in v8::Script::Run() at ../src/api.cc:1514
  #7  0x0000000000a149fa in v8::Shell::ExecuteString(...) at ../src/d8.cc:281
  #8  0x0000000000a194eb in v8::SourceGroup::Execute(...) at ../src/d8.cc:1213
  #9  0x0000000000a1a128 in v8::Shell::RunMain(...) at ../src/d8.cc:1448
  #10 0x0000000000a1efdc in v8::Shell::Main(...) at ../src/d8.cc:1721
  #11 0x0000000000a1f143 in main(...) at ../src/d8.cc:1757

Before, the backtrace was garbled, incorrect, without names, and wasn't
able to unwind out of the compiled JS code.  Wheeee :-))

Andy
Andy Wingo March 9, 2015, 9:42 a.m. UTC | #8
Greets Alexander,

One thing I found in your docs:

> +@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.

The frame_id that the unwinder computes is for THIS, not for PREVIOUS,
so the interface is incorrect if it works as documented.

Here is a section of the docs that I wrote for the Guile side.

   Before getting into the API, we should discuss how unwinders work in
   @value{GDBN}.

   As an example, consider a stack in which we have already computed
   frame 0 and we want to compute frame 1.  We say that frame 0 is the
   ``inner'' frame, and frame 1 will be the ``outer'' frame.

   Unwinding starts with a model of the state of all registers in an
   inner, already unwound frame.  In our case, we start with frame 0.
   @value{GDBN} then constructs a ephemeral frame object for the outer
   frame that is being built (frame 1) and links it to the inner frame
   (frame 0).  @value{GDBN} then goes through its list of registered
   unwinders, searching for one that knows how to unwind the frame.  When
   it finds one, @value{GDBN} will ask the unwinder to compute a frame
   identifier for the outer frame.  Once the unwinder has done so, the
   frame is marked as ``valid'' and can be accessed using the normal
   frame API.

   A frame identifier (frame ID) consists of code and data pointers
   associated with a frame which will remain valid as long as the frame
   is still alive.  Usually a frame ID is a pair of the code and stack
   pointers as they were when control entered the function associated
   with the frame, though as described below there are other ways to
   build a frame ID@.  However as you can see, computing the frame ID
   requires some input from the unwinder to determine the start code
   address (PC) and the frame pointer (FP), especially on platforms that
   don't dedicate a register to the FP.

   (Given this description, you might wonder how the frame ID for the
   innermost frame (frame 0) is unwound, given that unwinding requires an
   inner frame.  The answer is that internally, @value{GDBN} always has a
   ``sentinel'' frame that is inner to the innermost frame, and which has
   a pre-computed unwinder that just returns the registers as they are,
   without unwinding.)

   The Guile frame unwinder API loosely follows this protocol as
   described above.  Guile will build a special ``ephemeral frame
   object'' corresponding the frame being unwound (in our example,
   frame 1).  It allows the user to read registers from that ephemeral
   frame, which in reality are unwound from the already-existing frame
   0.  If the unwinder decides that it can handle the frame in question,
   it then sets the frame ID on the ephemeral frame.  It also records the
   values of any registers saved in the frame, for use when unwinding
   its outer frame (frame 2).

Regards,

Andy
Phil Muldoon March 9, 2015, 11:02 a.m. UTC | #9
On 04/03/15 07:49, Andy Wingo wrote:
> Howdy :)
>
> Reordering some of the replies.
>
> On Wed 04 Mar 2015 03:36, Alexander Smundak <asmundak@google.com> writes:

Just a note. I am generally satisfied with the patch and your answers
to my questions. Just now need to get the nod off Doug, and coordinate
with Andy and/or other guile people to harmonious the design.

Cheers

Phil
diff mbox

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..ad7f693
--- /dev/null
+++ b/gdb/python/lib/gdb/command/sniffers.py
@@ -0,0 +1,198 @@ 
+# Sniffer 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_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..9bdca62
--- /dev/null
+++ b/gdb/python/lib/gdb/function/sniffers.py
@@ -0,0 +1,53 @@ 
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Internal functions for working with frame 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..650ad3b
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffer.py
@@ -0,0 +1,110 @@ 
+# 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/>.
+
+"""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
+                 name already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        RuntimeError: Sniffer name is not unique.
+
+    """
+    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..94b2c57
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,582 @@ 
+/* 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 "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
+{
+  /* Frame ID.  */
+  struct frame_id frame_id;
+
+  /* GDB Architecture.  */
+  struct gdbarch *gdbarch;
+
+  /* Length of the `reg' array below.  */
+  int reg_count;
+
+  struct reg_info
+  {
+    /* Register number.  */
+    int number;
+
+    /* Register data bytes pointer.  */
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static 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 gdb.Value instance.  */
+
+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)
+    {
+      struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
+      struct gdbarch *gdbarch = get_frame_arch (frame);
+      char *error_text = NULL;
+
+      /* Validate regnum to prevent assert in `regcache_cooked_read_value'.  */
+      if (user_reg_map_regnum_to_name (gdbarch, regnum) == NULL)
+        error_text = xstrprintf (_("Bad register number: %d."), regnum);
+      else
+        {
+          gdb_byte buffer[MAX_REGISTER_SIZE];
+
+          /* Call `deprecated_frame_register_read' -- calling
+             `value_of_register' would an assert in `get_frame_id'
+             because our frame is incomplete.  */
+          if (deprecated_frame_register_read (frame, regnum, buffer))
+            val = value_from_contents (register_type (gdbarch, regnum),
+                                       buffer);
+          if (val == NULL)
+            error_text = xstrprintf (_("Cannot read register %d from frame."),
+                                     regnum);
+        }
+      if (error_text != NULL)
+        {
+          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);
+
+  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)
+    return 0;
+  /* Make a long logic check first.  In Python 3.x, internally, all
+     integers are represented as longs.  In Python 2.x, there is still
+     a differentiation internally between a PyInt and a PyLong.
+     Explicitly do this long check conversion first. In GDB, for
+     Python 3.x, we #ifdef PyInt = PyLong.  This check has to be done
+     first to ensure we do not lose information in the conversion
+     process.  */
+  else if (PyLong_Check (pyo_int))
+    {
+      LONGEST l = PyLong_AsLongLong (pyo_int);
+
+      if (PyErr_Occurred ())
+        return 0;
+      long_value = (long)l;
+      if (l != long_value)
+        return 0;
+    }
+  else if (PyInt_Check (pyo_int))
+    {
+      long_value = PyInt_AsLong (pyo_int);
+      if (PyErr_Occurred ())
+        return 0;
+    }
+  else
+    return 0;
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* 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;
+
+  gdbarch = (struct gdbarch *) (self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  TRACE_PY_UNWIND (3, "%s(SP=%s, PC=%s)\n", __FUNCTION__,
+                   paddress (gdbarch, get_frame_sp (this_frame)),
+                   paddress (gdbarch, get_frame_pc (this_frame)));
+  pyo_sniffer_info = frame_info_to_sniffer_info_object (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."), 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);
+            }
+          /* Should not overflow  the buffer.  */
+          gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+          memcpy (gdb_data_free, value_contents (value), data_size);
+          reg->data = gdb_data_free;
+          gdb_data_free += data_size;
+        }
+      }
+  }
+
+  {
+    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..a10e1aa
--- /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 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..74b702c
--- /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 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..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..4bf09b3
--- /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
+# 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 .* 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..866d882
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,74 @@ 
+# 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.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):
+        """Test sniffer written in Python.
+
+        This sniffer 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 sniffer recognizes the corrupt frames by checking that
+        *RBP == RBP, and restores previous RBP from the word above it.
+        """
+        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 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_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(), True)
+print("Python script imported")