public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
@ 2014-12-15 18:14 Alexander Smundak
  2014-12-22 19:24 ` Alexander Smundak
                   ` (3 more replies)
  0 siblings, 4 replies; 60+ messages in thread
From: Alexander Smundak @ 2014-12-15 18:14 UTC (permalink / raw)
  To: gdb-patches

[-- Attachment #1: Type: text/plain, Size: 2368 bytes --]

Python frame filters provide the ability to display non-native frames in
a mixed-language application (say, a backtrace of an application written
in C and embedding Java Virtual Machine can be displayed as a mix of
native frames and Java frames). However, GDB cannot always unwind
non-native frames. The proposed patch adds the ability to write frame
unwinders in Python.

2014-12-12  Sasha Smundak  <asmundak@google.com>

        * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
        (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
        (py-unwind.o): New recipe.
        * NEWS: mention Python frame unwinding.
        * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
        * doc/python.texi (Writing a Frame Unwinder in Python): Add
        section.
        * python/py-objfile.c (objfile_object): Add frame_sniffers field.
        (objfpy_dealloc): Decrement frame_sniffers reference count.
        (objfpy_initialize): Create frame_sniffers list.
        (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
        getter.
        (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
        setter.
        (objfile_getset): Add frame_sniffers attribute to Objfile.
        * python/py-progspace.c (pspace_object): Add frame_sniffers field.
        (pspy_dealloc): Decrement frame_sniffers reference count.
        (pspy_initialize): Create frame_sniffers list.
        (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        getter.
        (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        setter.
        (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
        * python/py-unwind.c: New file, implements Python frame sniffers
        interface.
        * python/python-internal.h (pspy_get_name_sniffers): New prototype.
        (objpy_get_frame_sniffers): New prototype.
        (gdbpy_initialize_unwind): New prototype.
        * python/python.c (gdbpy_apply_type_printers): Call
        gdbpy_initialize_unwind.
        * python/lib/gdb/__init__.py (packages): add frame_sniffers.

2014-12-12  Sasha Smundak  <asmundak@google.com>

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

[-- Attachment #2: patch2 --]
[-- Type: application/octet-stream, Size: 38742 bytes --]

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 2f69eb2..ba396ea 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
@@ -2593,6 +2595,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 a6789bd..cfced73 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -29,6 +29,7 @@
      selecting a new file to debug.
   ** You can now add attributes to gdb.Objfile and gdb.Progspace objects.
   ** New function gdb.lookup_objfile.
+  ** You can now write frame unwinders in Python.
 
 * New Python-based convenience functions:
 
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 00c70bb..ff6e8d2 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,6 +62,7 @@ PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/types.py \
 	gdb/printing.py \
+	gdb/sniffers.py \
 	gdb/prompt.py \
 	gdb/xmethod.py \
 	gdb/command/__init__.py \
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 234ce5c..45b38d9 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.
+* Writing a Frame Unwinder::    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,57 @@ printed hierarchically.  Another approach would be to combine the
 marker in the inlined frame, and also show the hierarchical
 relationship.
 
+@node Writing a Frame Unwinder
+@subsubsection Writing a Frame Unwinder in Python
+@cindex Writing a frame unwinder in Python.
+
+You can tell GDB how to unwind certain types of frames by writing a
+sniffer function and adding it to the list of the sniffers. 
+
+@subheading Frame Sniffer API
+
+A sniffer function receives a single argument describing the frame to
+be examined (a @code{gdb.SnifferInfo} object). It returns frame unwind
+information described below if it knows how to unwind the frame, or
+@code{None} otherwise.
+
+
+The @code{gdb.SnifferInfo} 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.
+@end defun
+
+The frame unwind information returned by your sniffer should be a pair
+(@var{registers}, @var{id_register_numbers}), where
+@var{registers} describe the registers that can be unwound (i.e.,
+the registers from the previous frame that have been saved in this
+frame), and @var{id_register_numbers} provides the data for the
+construction of the frame ID of the previous frame.
+
+The @var{registers} is a list of (@var{regnum}, @var{regdata})
+pairs, where @var{regnum} is a (platform-specific) register number,
+and @var{regdata} is register contents (a @code{gdb.Value} object).
+
+The @var{id_register_numbers} is either (@var{sp}), (@var{sp}, @var{pc}),
+or (@var{sp}, @var{pc}, @var{special}) tuple, where @var{sp},
+@var{pc}, @var{special} are register numbers, and the referenced
+registers should be present in @var{registers}. The frame ID is
+constructed by calling
+@code{make_id_build_wild}(@var{ValueOf}(@var{sp}),
+@code{make_id_build}(@var{ValueOf}(@var{sp}), @var{ValueOf}(@var{pc}),
+or @code{make_id_build}(@var{ValueOf}(@var{sp}),
+@var{ValueOf}(@var{pc}, @var{ValueOf}(@var{special}) respectively.
+
+@subheading Registering a Sniffer
+
+@code{gdb.Objfile}, @code{gdb.Progspace}, and @code{gdb} itself have
+@code{frame_sniffers} attribute containing a list of the
+sniffers. Register your sniffer by adding it to one of them.
+
 @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 8c6eee2..ffc0d1d 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -28,7 +28,7 @@ class _GdbFile (object):
     # These two are needed in Python 3
     encoding = "UTF-8"
     errors = "strict"
-    
+
     def close(self):
         # Do nothing.
         return None
@@ -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/sniffers.py b/gdb/python/lib/gdb/sniffers.py
new file mode 100644
index 0000000..0533642
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffers.py
@@ -0,0 +1,64 @@
+# Frame unwinding support.
+# Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Internal functions for working with frame sniffers."""
+
+import gdb
+import collections
+
+
+def execute_sniffers(sniffer_info):
+    """Internal function called from GDB that executes sniffers
+    implemented in Python. A sniffer able to unwind the frame returns
+    a tuple containing unwind information.
+
+    Arguments:
+        sniffer_info: an instance of gdb.SnifferInfo.
+
+    Returns:
+        unwind_info: a pair (REG_DATA, FRAME_ID_REGNUMS). REG_DATA is
+        tuple of (REG_NUM, REG_VALUE) pairs, where REG_NUM is
+        (platform-specific) register number, and REG_VALUE is Value
+        object with register value. FRAME_ID_REGNUM can be a (SP,),
+        (SP, PC), or (SP, PC, SPECIAL) tuple, where SP, PC, and
+        SPECIAL are (platform specific) register numbers.
+        The frame ID is built in each case as follows:
+          (SP,)                 make_id_build_wild (Value(SP))
+          (SP, PC)              make_id_build (Value(SP), Value(PC))
+          (SP, PC, SPECIAL)     make_id_build_special (Value(SP),
+                                   Value(PC), Value(SPECIAL)
+        The registers present in FRAME_ID_REGNUM should be among those
+        returned by REG_DATA.
+    """
+
+    current_progspace = gdb.current_progspace()
+    for objfile in gdb.objfiles():
+        for sniffer in objfile.frame_sniffers:
+            unwind_info = sniffer(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    for sniffer in current_progspace.frame_sniffers:
+        unwind_info = sniffer(sniffer_info)
+        if unwind_info is not None:
+            return unwind_info
+
+    for sniffer in gdb.frame_sniffers:
+        unwind_info = sniffer(sniffer_info)
+        if unwind_info is not None:
+            return unwind_info
+
+    return None
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index e78ceba..bbe68f8 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;
 
@@ -162,6 +166,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);
@@ -184,6 +189,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;
@@ -291,6 +300,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 *
@@ -618,6 +669,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 96339b1..0972498 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..47a99f7
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,514 @@
+/* Python frame unwinder interface
+
+   Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+  struct frame_info *frame_info;
+} sniffer_info_object;
+
+/* The data we keep for a frame we can unwind: frame_id and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  struct frame_id frame_id;
+  struct gdbarch *gdbarch;
+  int reg_count;
+  struct reg_info
+  {
+    int number;
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  char *s;
+  PyObject *result;
+  struct frame_info *frame = ((sniffer_info_object *)self)->frame_info;
+
+  s = xstrprintf ("SP=%s,PC=%s", core_addr_to_string_nz (get_frame_sp (frame)),
+        core_addr_to_string_nz (get_frame_pc (frame)));
+  result = PyString_FromString (s);
+  xfree (s);
+
+  return result;
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of a register as pointer.  */
+
+static PyObject *
+sniffer_infopy_read_register (PyObject *self, PyObject *args)
+{
+  volatile struct gdb_exception except;
+  int regnum;
+  struct value *val = NULL;
+
+  if (!PyArg_ParseTuple (args, "i", &regnum))
+    return NULL;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      /* Cannot call `value_of_register' as frame_info is not ready yet, so
+         use deprecated call instead.  */
+      struct frame_info *frame = ((sniffer_info_object *)self)->frame_info;
+      struct gdbarch *gdbarch = get_frame_arch (frame);
+      gdb_byte buffer[sizeof (CORE_ADDR)];
+
+      gdb_assert (register_size (gdbarch, regnum) <= ARRAY_SIZE (buffer));
+      if (deprecated_frame_register_read (frame, regnum, buffer))
+        {
+          struct type *ptr_type = builtin_type (gdbarch)->builtin_data_ptr;
+
+          val = value_from_pointer (ptr_type, unpack_pointer (ptr_type, buffer));
+        }
+
+      if (val == NULL)
+        PyErr_SetString (PyExc_ValueError, _("Unknown register."));
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  if (sniffer_info != NULL)
+    sniffer_info->frame_info = frame;
+
+  return (PyObject *) sniffer_info;
+}
+
+/* Parse given tuple of Python Ints into an array. Returns the number of
+   items in the tuple, or -1 if it is not a tuple. If tuple has
+   more elements than array size, these elements are ignored.  */
+
+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)
+    return -1;
+  if (size < max_values)
+    max_values = size;
+  for (i = 0; i < max_values; ++i)
+    {
+      PyObject *pyo_item = PyTuple_GetItem (pyo_sequence, i);
+
+      if (pyo_item == NULL || !PyInt_Check (pyo_item))
+        return -1;
+      values[i] = (int)PyInt_AsLong (pyo_item);
+    }
+  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");
+    }
+}
+
+/* Register unwind shim.  */
+
+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
+   saved 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;
+      }
+  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;
+      }
+  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;
+      }
+  }
+  error (_("Unwinder should return a tuple of ints in the second item"));
+}
+
+/* Frame sniffer dispatch.  */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch;
+  struct cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdb_assert (*cache_ptr == NULL);
+  gdbarch = (void *)(self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  TRACE_PY_UNWIND (3, "%s(SP=%lx, PC=%lx)\n", __FUNCTION__,
+      get_frame_sp (this_frame), 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.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;
+  if (pyo_unwind_info == Py_None)
+    goto error;
+  make_cleanup_py_decref (pyo_unwind_info);
+
+  /* Unwind_info is a pair (REGISTERS, FRAME_ID_REGNUMS).  REGISTERS
+   * is a list of the (REG_NR, REG_VALUE) pairs. FRAME_ID_REGNUMS is
+   * the list 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"));
+    gdb_bytes_count = reg_count * sizeof (CORE_ADDR);
+    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);
+    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))
+        error (_("Python sniffer returned bad register list: "
+                 "item #%d is not a (reg_no, reg_data) pair"), i);
+
+      {
+        PyObject *pyo_reg_number =  PyTuple_GetItem (pyo_reg, 0);
+
+        if (pyo_reg_number == NULL)
+          goto error;
+        if (!PyInt_Check (pyo_reg_number))
+          error (_("Python sniffer returned bad register list: "
+                   "item #%d contains non-integer register number"), i);
+        reg->number = (int)PyInt_AsLong (pyo_reg_number);
+      }
+
+      {
+        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 list: item #%d, "
+                   "register value should have type gdb.Value type"), i);
+        data_size = register_size (gdbarch, reg->number);
+        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+        memcpy (gdb_data_free, value_contents (value), data_size);
+        cached_frame->reg[i].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);
+  xfree (cached_frame);
+  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);
+  sniffer_info_object_type.tp_new = PyType_GenericNew;
+  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 544fe93..c83830f 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -386,12 +386,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);
 
@@ -486,6 +488,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 b1d8283..12e4d56 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1762,7 +1762,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.c b/gdb/testsuite/gdb.python/py-unwind.c
new file mode 100644
index 0000000..bb267d7
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,70 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2011-2014 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This is the test program loaded into GDB by the py-unwind test.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void *
+swap_value (void **location, void *new_value)
+{
+  void *old_value = *location;
+  *location = new_value;
+  return old_value;
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+break_backtrace ()
+{
+  /* Save outer frame address, then corrupt the unwind chain by
+     setting the outer frame address in it to self.  This is
+     ABI-specific: the first word of the frame contains previous frame
+     address in amd64.  */
+  void *outer_fp = swap_value ((void **)MY_FRAME, MY_FRAME);
+
+  /* Verify the compiler allocates the first local variable one word
+     below frame.  This is where test JIT reader expects to find the
+     correct outer frame address.  */
+  if (&outer_fp + 1 != (void **)MY_FRAME)
+    {
+      fprintf (stderr, "First variable should be allocated one word below "
+               "the frame, got variable's address %p, frame at %p instead\n",
+               &outer_fp, MY_FRAME);
+      abort();
+    }
+
+  /* Now restore it so that we can return.  The test sets the
+     breakpoint just before this happens, and GDB will not be able to
+     show the backtrace without JIT reader.  */
+  swap_value (MY_FRAME, outer_fp); /* break backtrace-broken */
+}
+
+static void
+break_backtrace_caller ()
+{
+  break_backtrace ();
+}
+
+int
+main (int argc, char *argv[])
+{
+  break_backtrace_caller ();
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..2fc5284
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@
+# Copyright (C) 2009-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It verifies that frame
+# sniffers can be implemented in Python.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+# This test runs on a specific platform.
+if { ! [istarget x86_64-*]} { continue }
+
+# The following tests require execution.
+
+if ![runto_main] then {
+    fail "Can't run to main"
+    return 0
+}
+
+set pyfile [gdb_remote_download host \
+		${srcdir}/${subdir}/${testfile}.py]
+
+gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
+
+gdb_test "source ${pyfile}" ".*Python script imported.*" \
+         "import python scripts"
+
+gdb_continue_to_breakpoint "break backtrace-broken"
+gdb_test_sequence "where"  "Bad backtrace" {
+    "\[\r\n\]+#0 .* break_backtrace \\(\\) at "
+    "\[\r\n\]+#1 .* break_backtrace_caller \\(\\) at "
+    "\[\r\n\]+#2 .* main \\(.*\\) at"
+}
+
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
new file mode 100644
index 0000000..8976e55
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+
+def _read_word(address):
+    return address.cast(_char_ptr_ptr_t).dereference()
+
+
+def sniff (sniffer_info):
+    "Sniffer written in Python."
+    bp = sniffer_info.read_register(_amd64_rbp).cast(_char_ptr_t)
+    try:
+        if (_read_word(bp) == bp):
+            # Found the frame that the test program fudged for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places
+            previous_bp = _read_word(bp - 8)
+            previous_ip = _read_word(bp + 8)
+            previous_sp = bp + 16
+            return (((_amd64_rbp, previous_bp),
+                     (_amd64_rip, previous_ip),
+                     (_amd64_rsp, previous_sp)),
+                    (_amd64_rsp, _amd64_rip))
+
+    except (gdb.error, RuntimeError):
+        return None
+
+
+_char_ptr_t = gdb.lookup_type("unsigned char").pointer()
+_char_ptr_ptr_t = _char_ptr_t.pointer()
+_uint_ptr_t = gdb.lookup_type("unsigned long long")
+_amd64_rbp = 6
+_amd64_rsp = 7
+_amd64_rip = 16
+gdb.frame_sniffers=[sniff]
+print("Python script imported")

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2014-12-15 18:14 [RFC] [PATCH] Provide the ability to write the frame unwinder in Python Alexander Smundak
@ 2014-12-22 19:24 ` Alexander Smundak
  2014-12-29 18:02   ` Alexander Smundak
  2015-01-12 21:00 ` Simon Marchi
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2014-12-22 19:24 UTC (permalink / raw)
  To: gdb-patches

Ping.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2014-12-22 19:24 ` Alexander Smundak
@ 2014-12-29 18:02   ` Alexander Smundak
  2015-01-05 17:53     ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2014-12-29 18:02 UTC (permalink / raw)
  To: gdb-patches

Ping.

On Mon, Dec 22, 2014 at 11:24 AM, Alexander Smundak <asmundak@google.com> wrote:
> Ping.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2014-12-29 18:02   ` Alexander Smundak
@ 2015-01-05 17:53     ` Alexander Smundak
  2015-01-12 20:03       ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-01-05 17:53 UTC (permalink / raw)
  To: gdb-patches

Ping.

On Mon, Dec 29, 2014 at 10:02 AM, Alexander Smundak <asmundak@google.com> wrote:
> Ping.
>
> On Mon, Dec 22, 2014 at 11:24 AM, Alexander Smundak <asmundak@google.com> wrote:
>> Ping.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-01-05 17:53     ` Alexander Smundak
@ 2015-01-12 20:03       ` Alexander Smundak
  2015-01-22  3:31         ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-01-12 20:03 UTC (permalink / raw)
  To: gdb-patches

Ping.

On Mon, Jan 5, 2015 at 9:53 AM, Alexander Smundak <asmundak@google.com> wrote:
> Ping.
>
> On Mon, Dec 29, 2014 at 10:02 AM, Alexander Smundak <asmundak@google.com> wrote:
>> Ping.
>>
>> On Mon, Dec 22, 2014 at 11:24 AM, Alexander Smundak <asmundak@google.com> wrote:
>>> Ping.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2014-12-15 18:14 [RFC] [PATCH] Provide the ability to write the frame unwinder in Python Alexander Smundak
  2014-12-22 19:24 ` Alexander Smundak
@ 2015-01-12 21:00 ` Simon Marchi
  2015-01-12 21:22   ` Doug Evans
  2015-02-04 22:36 ` Doug Evans
  2015-02-20  9:42 ` Phil Muldoon
  3 siblings, 1 reply; 60+ messages in thread
From: Simon Marchi @ 2015-01-12 21:00 UTC (permalink / raw)
  To: gdb-patches

On 14-12-15 01:13 PM, Alexander Smundak wrote:
> Python frame filters provide the ability to display non-native frames in
> a mixed-language application (say, a backtrace of an application written
> in C and embedding Java Virtual Machine can be displayed as a mix of
> native frames and Java frames). However, GDB cannot always unwind
> non-native frames. The proposed patch adds the ability to write frame
> unwinders in Python.
> 
> 2014-12-12  Sasha Smundak  <asmundak@google.com>
> 
>         * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
>         (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
>         (py-unwind.o): New recipe.
>         * NEWS: mention Python frame unwinding.
>         * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
>         * doc/python.texi (Writing a Frame Unwinder in Python): Add
>         section.
>         * python/py-objfile.c (objfile_object): Add frame_sniffers field.
>         (objfpy_dealloc): Decrement frame_sniffers reference count.
>         (objfpy_initialize): Create frame_sniffers list.
>         (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
>         getter.
>         (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
>         setter.
>         (objfile_getset): Add frame_sniffers attribute to Objfile.
>         * python/py-progspace.c (pspace_object): Add frame_sniffers field.
>         (pspy_dealloc): Decrement frame_sniffers reference count.
>         (pspy_initialize): Create frame_sniffers list.
>         (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
>         getter.
>         (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
>         setter.
>         (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
>         * python/py-unwind.c: New file, implements Python frame sniffers
>         interface.
>         * python/python-internal.h (pspy_get_name_sniffers): New prototype.
>         (objpy_get_frame_sniffers): New prototype.
>         (gdbpy_initialize_unwind): New prototype.
>         * python/python.c (gdbpy_apply_type_printers): Call
>         gdbpy_initialize_unwind.
>         * python/lib/gdb/__init__.py (packages): add frame_sniffers.
> 
> 2014-12-12  Sasha Smundak  <asmundak@google.com>
> 
>         * gdb.python/py-unwind.c: Test program for the py-unwind test.
>         * gdb.python/py-unwind.exp: Python frame sniffers test.
>         * gdb.python/py-unwind.py: Frame sniffer in Python tested by
>         py-unwind test.
> 

Hi Alexander,

I don't have much experience in this field, so I fail to see what problem it would
help to solve.

From what I understand, with CPython, each PyEval_EvalFrameEx frame maps to one
non-native (Python) frame. So it's easy to just filter out the others and tweak
the output with a frame filter. Are you talking about other languages/VM where it's
not as simple as this? Could you provide a more concrete example to illustrate the
usefulness of the feature?

Thanks!

Simon

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-01-12 21:00 ` Simon Marchi
@ 2015-01-12 21:22   ` Doug Evans
  0 siblings, 0 replies; 60+ messages in thread
From: Doug Evans @ 2015-01-12 21:22 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches

On Mon, Jan 12, 2015 at 1:00 PM, Simon Marchi <simon.marchi@ericsson.com> wrote:
> On 14-12-15 01:13 PM, Alexander Smundak wrote:
>> Python frame filters provide the ability to display non-native frames in
>> a mixed-language application (say, a backtrace of an application written
>> in C and embedding Java Virtual Machine can be displayed as a mix of
>> native frames and Java frames). However, GDB cannot always unwind
>> non-native frames. The proposed patch adds the ability to write frame
>> unwinders in Python.
>>
>> 2014-12-12  Sasha Smundak  <asmundak@google.com>
>>
>>         * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
>>         (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
>>         (py-unwind.o): New recipe.
>>         * NEWS: mention Python frame unwinding.
>>         * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
>>         * doc/python.texi (Writing a Frame Unwinder in Python): Add
>>         section.
>>         * python/py-objfile.c (objfile_object): Add frame_sniffers field.
>>         (objfpy_dealloc): Decrement frame_sniffers reference count.
>>         (objfpy_initialize): Create frame_sniffers list.
>>         (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
>>         getter.
>>         (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
>>         setter.
>>         (objfile_getset): Add frame_sniffers attribute to Objfile.
>>         * python/py-progspace.c (pspace_object): Add frame_sniffers field.
>>         (pspy_dealloc): Decrement frame_sniffers reference count.
>>         (pspy_initialize): Create frame_sniffers list.
>>         (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
>>         getter.
>>         (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
>>         setter.
>>         (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
>>         * python/py-unwind.c: New file, implements Python frame sniffers
>>         interface.
>>         * python/python-internal.h (pspy_get_name_sniffers): New prototype.
>>         (objpy_get_frame_sniffers): New prototype.
>>         (gdbpy_initialize_unwind): New prototype.
>>         * python/python.c (gdbpy_apply_type_printers): Call
>>         gdbpy_initialize_unwind.
>>         * python/lib/gdb/__init__.py (packages): add frame_sniffers.
>>
>> 2014-12-12  Sasha Smundak  <asmundak@google.com>
>>
>>         * gdb.python/py-unwind.c: Test program for the py-unwind test.
>>         * gdb.python/py-unwind.exp: Python frame sniffers test.
>>         * gdb.python/py-unwind.py: Frame sniffer in Python tested by
>>         py-unwind test.
>>
>
> Hi Alexander,
>
> I don't have much experience in this field, so I fail to see what problem it would
> help to solve.
>
> From what I understand, with CPython, each PyEval_EvalFrameEx frame maps to one
> non-native (Python) frame. So it's easy to just filter out the others and tweak
> the output with a frame filter. Are you talking about other languages/VM where it's
> not as simple as this? Could you provide a more concrete example to illustrate the
> usefulness of the feature?

That's exactly it: "other languages/VM", and in particular Java.

I plan to get to this this week, but more eyes can certainly help.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-01-12 20:03       ` Alexander Smundak
@ 2015-01-22  3:31         ` Alexander Smundak
  2015-01-29  1:36           ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-01-22  3:31 UTC (permalink / raw)
  To: gdb-patches

Ping

On Mon, Jan 12, 2015 at 12:03 PM, Alexander Smundak <asmundak@google.com> wrote:
> Ping.
>
> On Mon, Jan 5, 2015 at 9:53 AM, Alexander Smundak <asmundak@google.com> wrote:
>> Ping.
>>
>> On Mon, Dec 29, 2014 at 10:02 AM, Alexander Smundak <asmundak@google.com> wrote:
>>> Ping.
>>>
>>> On Mon, Dec 22, 2014 at 11:24 AM, Alexander Smundak <asmundak@google.com> wrote:
>>>> Ping.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-01-22  3:31         ` Alexander Smundak
@ 2015-01-29  1:36           ` Alexander Smundak
  0 siblings, 0 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-01-29  1:36 UTC (permalink / raw)
  To: gdb-patches

On Wed, Jan 21, 2015 at 7:31 PM, Alexander Smundak <asmundak@google.com> wrote:
> Ping
>
> On Mon, Jan 12, 2015 at 12:03 PM, Alexander Smundak <asmundak@google.com> wrote:
>> Ping.
>>
>> On Mon, Jan 5, 2015 at 9:53 AM, Alexander Smundak <asmundak@google.com> wrote:
>>> Ping.
>>>
>>> On Mon, Dec 29, 2014 at 10:02 AM, Alexander Smundak <asmundak@google.com> wrote:
>>>> Ping.
>>>>
>>>> On Mon, Dec 22, 2014 at 11:24 AM, Alexander Smundak <asmundak@google.com> wrote:
>>>>> Ping.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2014-12-15 18:14 [RFC] [PATCH] Provide the ability to write the frame unwinder in Python Alexander Smundak
  2014-12-22 19:24 ` Alexander Smundak
  2015-01-12 21:00 ` Simon Marchi
@ 2015-02-04 22:36 ` Doug Evans
  2015-02-12 17:58   ` Alexander Smundak
  2015-02-20  9:42 ` Phil Muldoon
  3 siblings, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-02-04 22:36 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: gdb-patches

Alexander Smundak writes:
 > Python frame filters provide the ability to display non-native frames in
 > a mixed-language application (say, a backtrace of an application written
 > in C and embedding Java Virtual Machine can be displayed as a mix of
 > native frames and Java frames). However, GDB cannot always unwind
 > non-native frames. The proposed patch adds the ability to write frame
 > unwinders in Python.
 > 
 > 2014-12-12  Sasha Smundak  <asmundak@google.com>
 > 
 >         * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
 >         (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
 >         (py-unwind.o): New recipe.
 >         * NEWS: mention Python frame unwinding.
 >         * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
 >         * doc/python.texi (Writing a Frame Unwinder in Python): Add
 >         section.
 >         * python/py-objfile.c (objfile_object): Add frame_sniffers field.
 >         (objfpy_dealloc): Decrement frame_sniffers reference count.
 >         (objfpy_initialize): Create frame_sniffers list.
 >         (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
 >         getter.
 >         (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
 >         setter.
 >         (objfile_getset): Add frame_sniffers attribute to Objfile.
 >         * python/py-progspace.c (pspace_object): Add frame_sniffers field.
 >         (pspy_dealloc): Decrement frame_sniffers reference count.
 >         (pspy_initialize): Create frame_sniffers list.
 >         (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
 >         getter.
 >         (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
 >         setter.
 >         (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
 >         * python/py-unwind.c: New file, implements Python frame sniffers
 >         interface.
 >         * python/python-internal.h (pspy_get_name_sniffers): New prototype.
 >         (objpy_get_frame_sniffers): New prototype.
 >         (gdbpy_initialize_unwind): New prototype.
 >         * python/python.c (gdbpy_apply_type_printers): Call
 >         gdbpy_initialize_unwind.
 >         * python/lib/gdb/__init__.py (packages): add frame_sniffers.
 > 
 > 2014-12-12  Sasha Smundak  <asmundak@google.com>
 > 
 >         * gdb.python/py-unwind.c: Test program for the py-unwind test.
 >         * gdb.python/py-unwind.exp: Python frame sniffers test.
 >         * gdb.python/py-unwind.py: Frame sniffer in Python tested by
 >         py-unwind test.

Hi.
Sorry for the delay, and THANKS for the patience.

grep for >>>> to find comments

High level comments:

Is it possible to see the code, and example usage, of a real-life use-case
of this? That will help folks not familiar with this project to understand
the problem we are trying to solve.

I'm still not sure what kind of performance cost we're looking at here as
it scales up, I can imagine most times there'll be no Python sniffers,
or at most one or two.  But it would be good to collect some perf data
(e.g., install 1,10,100 no-op sniffers and see if there's any measurable
difference in backtrace performance).

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

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 2f69eb2..ba396ea 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
@@ -2593,6 +2595,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 a6789bd..cfced73 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -29,6 +29,7 @@
      selecting a new file to debug.
   ** You can now add attributes to gdb.Objfile and gdb.Progspace objects.
   ** New function gdb.lookup_objfile.
+  ** You can now write frame unwinders in Python.
 
 * New Python-based convenience functions:
 

>>>>
NEWS file entry will need to be updated now that we've branched 7.9.

diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 00c70bb..ff6e8d2 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,6 +62,7 @@ PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/types.py \
 	gdb/printing.py \
+	gdb/sniffers.py \
 	gdb/prompt.py \
 	gdb/xmethod.py \
 	gdb/command/__init__.py \
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 234ce5c..45b38d9 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.
+* Writing a Frame Unwinder::    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.

>>>>
We may want a couple of sections, akin to pretty-printers
and frame-filters.  E.g., one section describing the API,
and another section being a tutorial on how to write an unwinder,
akin to "Frame Filter API" vs "Writing a Frame Filter".
I don't have a strong opinion on whether to combine them,
other than if there's no compelling reason to be different
than how things are currently done then don't be.

and one section
@@ -2178,6 +2179,57 @@ printed hierarchically.  Another approach would be to combine the
 marker in the inlined frame, and also show the hierarchical
 relationship.
 
+@node Writing a Frame Unwinder
+@subsubsection Writing a Frame Unwinder in Python
+@cindex Writing a frame unwinder in Python.
+
+You can tell GDB how to unwind certain types of frames by writing a
+sniffer function and adding it to the list of the sniffers. 

>>>>
.... list of sniffers. ?

+
+@subheading Frame Sniffer API
+
+A sniffer function receives a single argument describing the frame to
+be examined (a @code{gdb.SnifferInfo} object). It returns frame unwind
+information described below if it knows how to unwind the frame, or
+@code{None} otherwise.
+

>>>>
extra blank line

+
+The @code{gdb.SnifferInfo} class has a single method:
+
+@defun SnifferInfo.read_register (self, regnum)
+

>>>>
no blank line here

+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.
+@end defun
+
+The frame unwind information returned by your sniffer should be a pair
+(@var{registers}, @var{id_register_numbers}), where
+@var{registers} describe the registers that can be unwound (i.e.,
+the registers from the previous frame that have been saved in this
+frame), and @var{id_register_numbers} provides the data for the
+construction of the frame ID of the previous frame.
+
+The @var{registers} is a list of (@var{regnum}, @var{regdata})

>>>>
This doesn't read well to me but I'm not sure how to change it.
The @var{registers} value is a ... ?

+pairs, where @var{regnum} is a (platform-specific) register number,

>>>>
Are "pairs" a common name for python tuples of two elements?
Alas, I'm not sure whether it's ok to use "pairs" here or not.
Let's leave it as is until someone else speaks up.

Plus I think you can remove the parens around "platform-specific".

+and @var{regdata} is register contents (a @code{gdb.Value} object).

>>>>
.... is the register's contents as a @code{gdb.Value} object.

+
+The @var{id_register_numbers} is either (@var{sp}), (@var{sp}, @var{pc}),

>>>>
The @var{id_register_numbers} value is either ... ?

+or (@var{sp}, @var{pc}, @var{special}) tuple, where @var{sp},
+@var{pc}, @var{special} are register numbers, and the referenced
+registers should be present in @var{registers}. The frame ID is
+constructed by calling
+@code{make_id_build_wild}(@var{ValueOf}(@var{sp}),
+@code{make_id_build}(@var{ValueOf}(@var{sp}), @var{ValueOf}(@var{pc}),
+or @code{make_id_build}(@var{ValueOf}(@var{sp}),
+@var{ValueOf}(@var{pc}, @var{ValueOf}(@var{special}) respectively.

>>>>
This paragraph will need elaboration.
E.g., "special" will be confusing to readers not familiar
with gdb implementation details.
Which of course raises the question: How much of gdb implementation
details do we want to expose here? And in what form?
I don't have a good answer to that yet, but hopefully the review process
will shake this out.

Where do make_id_build* come from? I can't find them anywhere.
Also, the names are too generic, there's nothing in the name that
suggests "frame id". Are these in fact frame_id_build*?
Those are internal gdb function names which we won't want to expose
in the documentation. One could just say something generic like
"The frame ID is an opaque object that is constructed from
@var{sp}, @var{pc}, and @var{special}." or some such.
[setting aside the issues raised in the previous paragraph]

+
+@subheading Registering a Sniffer
+
+@code{gdb.Objfile}, @code{gdb.Progspace}, and @code{gdb} itself have
+@code{frame_sniffers} attribute containing a list of the
+sniffers. Register your sniffer by adding it to one of them.
+

>>>>
This will need elaboration of course, e.g., mimicing what is done for
pretty-printers. E.g., grep for "Progspace.pretty_printers" and
"Objfile.pretty_printers" in python.texi. There is no specific entry for
"gdb.pretty_printers", that's probably an oversight.

OTOH I totally understand not wanting to invest too much time in
documentation until the details of what to document are worked out. :-)

We'll also want all of the machinery that pretty-printers, et.al.,
have for enabling, disabling, and listing them.

 @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 8c6eee2..ffc0d1d 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -28,7 +28,7 @@ class _GdbFile (object):
     # These two are needed in Python 3
     encoding = "UTF-8"
     errors = "strict"
-    
+

>>>>
unnecessary whitespace change

     def close(self):
         # Do nothing.
         return None
@@ -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/sniffers.py b/gdb/python/lib/gdb/sniffers.py
new file mode 100644
index 0000000..0533642
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffers.py
@@ -0,0 +1,64 @@
+# Frame unwinding support.
+# Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Internal functions for working with frame sniffers."""
+
+import gdb
+import collections

>>>>
do we need to import collections?

+
+
+def execute_sniffers(sniffer_info):
+    """Internal function called from GDB that executes sniffers
+    implemented in Python. A sniffer able to unwind the frame returns
+    a tuple containing unwind information.
+
+    Arguments:
+        sniffer_info: an instance of gdb.SnifferInfo.
+
+    Returns:
+        unwind_info: a pair (REG_DATA, FRAME_ID_REGNUMS). REG_DATA is
+        tuple of (REG_NUM, REG_VALUE) pairs, where REG_NUM is
+        (platform-specific) register number, and REG_VALUE is Value
+        object with register value. FRAME_ID_REGNUM can be a (SP,),
+        (SP, PC), or (SP, PC, SPECIAL) tuple, where SP, PC, and
+        SPECIAL are (platform specific) register numbers.
+        The frame ID is built in each case as follows:
+          (SP,)                 make_id_build_wild (Value(SP))
+          (SP, PC)              make_id_build (Value(SP), Value(PC))
+          (SP, PC, SPECIAL)     make_id_build_special (Value(SP),
+                                   Value(PC), Value(SPECIAL)
+        The registers present in FRAME_ID_REGNUM should be among those
+        returned by REG_DATA.
+    """
+
+    current_progspace = gdb.current_progspace()

>>>>
Move this assignment to current_progspace down to where it is used.

+    for objfile in gdb.objfiles():
+        for sniffer in objfile.frame_sniffers:
+            unwind_info = sniffer(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    for sniffer in current_progspace.frame_sniffers:
+        unwind_info = sniffer(sniffer_info)
+        if unwind_info is not None:
+            return unwind_info
+
+    for sniffer in gdb.frame_sniffers:
+        unwind_info = sniffer(sniffer_info)
+        if unwind_info is not None:
+            return unwind_info
+
+    return None

>>>>
I'm tempted to have this implemented this in C,
but let's leave it as is for now.

diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index e78ceba..bbe68f8 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;
 
@@ -162,6 +166,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);
@@ -184,6 +189,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;
@@ -291,6 +300,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 *
@@ -618,6 +669,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 96339b1..0972498 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..47a99f7
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,514 @@
+/* Python frame unwinder interface
+
+   Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+  struct frame_info *frame_info;
+} sniffer_info_object;
+
+/* The data we keep for a frame we can unwind: frame_id and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  struct frame_id frame_id;
+  struct gdbarch *gdbarch;
+  int reg_count;
+  struct reg_info
+  {
+    int number;
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  char *s;
+  PyObject *result;
+  struct frame_info *frame = ((sniffer_info_object *)self)->frame_info;
+
+  s = xstrprintf ("SP=%s,PC=%s", core_addr_to_string_nz (get_frame_sp (frame)),
+        core_addr_to_string_nz (get_frame_pc (frame)));
+  result = PyString_FromString (s);
+  xfree (s);
+
+  return result;
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of a register as pointer.  */

>>>>
Returns the value of register REGNUM (but see below: do we want to pass
register names here?).

+
+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)
+    {
+      /* Cannot call `value_of_register' as frame_info is not ready yet, so
+         use deprecated call instead.  */

>>>>
It would be good to expand on what "frame_info is not ready yet" means.
The reader sees it being used anyway so what about it isn't ready?

+      struct frame_info *frame = ((sniffer_info_object *)self)->frame_info;

>>>>
space before "self"

Question: What if a sniffer_info object is saved in python,
(e.g., a registered sniffer could save it in a global),
and is later used when frame_info is invalid?
It would be good to have a testcase exercising this.

+      struct gdbarch *gdbarch = get_frame_arch (frame);
+      gdb_byte buffer[sizeof (CORE_ADDR)];

>>>>
Some registers are bigger than CORE_ADDR.
Use MAX_REGISTER_SIZE (from defs.h) instead.

+
+      gdb_assert (register_size (gdbarch, regnum) <= ARRAY_SIZE (buffer));

>>>>
register_size has an assert that regnum is valid,
but we don't know that yet (the user could pass -1 or whatever).
Plus, I see that gdb.Frame.read_register takes register names
as arguments (and uses user_reg_map_name_to_regnum to validate its
argument). How about passing register names instead of numbers here too?

+      if (deprecated_frame_register_read (frame, regnum, buffer))
+        {
+          struct type *ptr_type = builtin_type (gdbarch)->builtin_data_ptr;
+
+          val = value_from_pointer (ptr_type, unpack_pointer (ptr_type, buffer));

>>>>
We're assuming all registers (that one would be interested in) are pointers
here. That may be ok for the task at hand, but we'll need to be clear about
that in the documentation. Plus, "read_register" doesn't really tell me, the
reader, that it can only return regs that can be used as pointers.
Can read_register just return the register in its normal type
(e.g., whatever gdb.Frame.read_register returns)?
I know we can't use value_of_register here because we don't have a
complete frame yet (that's what we're trying to build),
but can we still do the equivalent? One can get a register's type
with register_type (gdbarch, regnum).

+        }
+
+      if (val == NULL)
+        PyErr_SetString (PyExc_ValueError, _("Unknown register."));

>>>>
I think val can be NULL for other reasons (e.g.,
deprecated_frame_register_read checks !optimized && !unavailable).
We'll be handling "unknown register" earlier anyway so we'll need
to change this.

+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  if (sniffer_info != NULL)
+    sniffer_info->frame_info = frame;
+
+  return (PyObject *) sniffer_info;
+}
+
+/* Parse given tuple of Python Ints into an array. Returns the number of
+   items in the tuple, or -1 if it is not a tuple. If tuple has
+   more elements than array size, these elements are ignored.  */

>>>>
It's easier to relax restrictions than it is to impose them after the fact.
Thus I prefer being strict in what I accept as valid input here.
So instead of ignoring extra elements I would flag it as an error.

+
+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)
+    return -1;
+  if (size < max_values)
+    max_values = size;

>>>>
Instead of modifying max_values here, I'd set size and use that in the for()
loop.

+  for (i = 0; i < max_values; ++i)
+    {
+      PyObject *pyo_item = PyTuple_GetItem (pyo_sequence, i);
+
+      if (pyo_item == NULL || !PyInt_Check (pyo_item))
+        return -1;
+      values[i] = (int)PyInt_AsLong (pyo_item);

>>>>
It would be good to check that there's no loss of precision here.
I.e., first assign to a long and then check val == (int) val.

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

>>>>
space before *cache_ptr.

+  if (pyuw_debug >= 1)
+    {
+      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+      fprint_frame_id (gdb_stdlog, *this_id);
+      fprintf_unfiltered (gdb_stdlog, "\n");
+    }
+}
+
+/* Register unwind shim.  */

>>>>
Misplaced comment.
/* frame_unwind.prev_register method.  */ ?

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

>>>>
This for loop needs to be wrapped in {} (the body isn't more than one line).

+    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
+   saved 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))) {

>>>>
opening { goes on next line, and cases are indented.
grep for other uses of switch() in the sources.

+  case 1:
+    if (pyuw_reg_value (cached_frame, regno[0], &sp))
+      {
+        cached_frame->frame_id = frame_id_build_wild (sp);
+        return;
+      }
+  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;
+      }
+  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;
+      }
+  }
+  error (_("Unwinder should return a tuple of ints in the second item"));
+}
+
+/* Frame sniffer dispatch.  */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch;
+  struct cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdb_assert (*cache_ptr == NULL);
+  gdbarch = (void *)(self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  TRACE_PY_UNWIND (3, "%s(SP=%lx, PC=%lx)\n", __FUNCTION__,
+      get_frame_sp (this_frame), get_frame_pc (this_frame));

>>>>
Alas, one can't use %lx to print CORE_ADDR.
Use paddress().

+  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.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;
+  if (pyo_unwind_info == Py_None)
+    goto error;

>>>>
Technically, Py_None needs to be decref'd too.

+  make_cleanup_py_decref (pyo_unwind_info);
+
+  /* Unwind_info is a pair (REGISTERS, FRAME_ID_REGNUMS).  REGISTERS
+   * is a list of the (REG_NR, REG_VALUE) pairs. FRAME_ID_REGNUMS is
+   * the list 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"));

>>>>
Lists and tuples are different in Python.
The above comment says REGISTERS is a list but here we're checking for tuples.

+
+    /* 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"));
+    gdb_bytes_count = reg_count * sizeof (CORE_ADDR);

>>>>
s/sizeof (CORE_ADDR)/MAX_REGISTER_SIZE/
Or you could loop over all the registers first collecting their sizes.

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

>>>>
Best add a comment here saying no more cleanups may be added,
as they will be discarded along with cached_frame_cleanups.
A more robust way would be to allocate cached_frame and its cleanup
first, and discard it last.

+    gdb_data_end = (gdb_byte *)((char *)cached_frame + cached_frame_size);

>>>>
space between cast and value

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

>>>>
indent { two more spaces (grep for examples in the sources)

+      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))
+        error (_("Python sniffer returned bad register list: "
+                 "item #%d is not a (reg_no, reg_data) pair"), i);
+
+      {
+        PyObject *pyo_reg_number =  PyTuple_GetItem (pyo_reg, 0);
+
+        if (pyo_reg_number == NULL)
+          goto error;
+        if (!PyInt_Check (pyo_reg_number))
+          error (_("Python sniffer returned bad register list: "
+                   "item #%d contains non-integer register number"), i);
+        reg->number = (int)PyInt_AsLong (pyo_reg_number);
+      }
+
+      {
+        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 list: item #%d, "
+                   "register value should have type gdb.Value type"), i);
+        data_size = register_size (gdbarch, reg->number);
+        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+        memcpy (gdb_data_free, value_contents (value), data_size);

>>>>
value could have fewer bytes than the register's size,
will need to check for that.

+        cached_frame->reg[i].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);
+  xfree (cached_frame);

>>>>
Remove the call to xfree as do_cleanups (cleanups) will run all cleanups
created after it was created (unless you reorganize things and allocate
cached_frame and its cleanup first).

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

>>>>
extra space after =

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

>>>>
space before 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);

>>>>
"set/show debug py-unwind" needs to be documented in the manual.
There isn't a good place that stands out though.
[I can quibble with every choice I can think of.]
But I do like mentioning all the debug options in one place.
grep for "@node Debugging Output" in gdb.texinfo.
Some are documented in multiple places (there and elsewhere), but I think
we can just document this in one place.

+  pyuw_gdbarch_data
+      = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
+  observer_attach_architecture_changed (pyuw_on_new_gdbarch);
+  sniffer_info_object_type.tp_new = PyType_GenericNew;

>>>>
Delete the setting of tp_new.
ref: https://sourceware.org/ml/gdb-patches/2014-09/msg00718.html

+  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\

>>>>
Heh, we're using register names here so this part won't have to change. :-)

+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 544fe93..c83830f 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -386,12 +386,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);
 
@@ -486,6 +488,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 b1d8283..12e4d56 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1762,7 +1762,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.c b/gdb/testsuite/gdb.python/py-unwind.c
new file mode 100644
index 0000000..bb267d7
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,70 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2011-2014 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This is the test program loaded into GDB by the py-unwind test.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void *
+swap_value (void **location, void *new_value)
+{
+  void *old_value = *location;
+  *location = new_value;
+  return old_value;
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+break_backtrace ()

>>>>
break_backtrace (void)

+{
+  /* Save outer frame address, then corrupt the unwind chain by
+     setting the outer frame address in it to self.  This is
+     ABI-specific: the first word of the frame contains previous frame
+     address in amd64.  */
+  void *outer_fp = swap_value ((void **)MY_FRAME, MY_FRAME);

>>>>
I'm not sure what type __builtin_frame_address returns off hand,
but there's no cast in the call to swap_value below.
Either it's needed in both places or we can remove it here.

+
+  /* Verify the compiler allocates the first local variable one word
+     below frame.  This is where test JIT reader expects to find the
+     correct outer frame address.  */
+  if (&outer_fp + 1 != (void **)MY_FRAME)
+    {
+      fprintf (stderr, "First variable should be allocated one word below "
+               "the frame, got variable's address %p, frame at %p instead\n",
+               &outer_fp, MY_FRAME);
+      abort();
+    }
+
+  /* Now restore it so that we can return.  The test sets the
+     breakpoint just before this happens, and GDB will not be able to
+     show the backtrace without JIT reader.  */
+  swap_value (MY_FRAME, outer_fp); /* break backtrace-broken */
+}
+
+static void
+break_backtrace_caller ()

>>>>
break_backtrace_caller (void)

+{
+  break_backtrace ();
+}
+
+int
+main (int argc, char *argv[])

>>>>
For simplicity I'd leave out argc,argv here.

+{
+  break_backtrace_caller ();
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..2fc5284
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@
+# Copyright (C) 2009-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It verifies that frame
+# sniffers can be implemented in Python.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+# This test runs on a specific platform.
+if { ! [istarget x86_64-*]} { continue }
+
+# The following tests require execution.
+
+if ![runto_main] then {
+    fail "Can't run to main"
+    return 0
+}
+
+set pyfile [gdb_remote_download host \
+		${srcdir}/${subdir}/${testfile}.py]
+
+gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
+
+gdb_test "source ${pyfile}" ".*Python script imported.*" \

>>>>
Remove the trailing ".*" in the regexp.
ref: https://sourceware.org/gdb/wiki/GDBTestcaseCookbook
Could also remove the leading ".*".

+         "import python scripts"
+
+gdb_continue_to_breakpoint "break backtrace-broken"
+gdb_test_sequence "where"  "Bad backtrace" {
+    "\[\r\n\]+#0 .* break_backtrace \\(\\) at "
+    "\[\r\n\]+#1 .* break_backtrace_caller \\(\\) at "
+    "\[\r\n\]+#2 .* main \\(.*\\) at"
+}
+
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
new file mode 100644
index 0000000..8976e55
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+
+def _read_word(address):
+    return address.cast(_char_ptr_ptr_t).dereference()
+
+
+def sniff (sniffer_info):
+    "Sniffer written in Python."
+    bp = sniffer_info.read_register(_amd64_rbp).cast(_char_ptr_t)
+    try:
+        if (_read_word(bp) == bp):
+            # Found the frame that the test program fudged for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places
+            previous_bp = _read_word(bp - 8)
+            previous_ip = _read_word(bp + 8)
+            previous_sp = bp + 16
+            return (((_amd64_rbp, previous_bp),
+                     (_amd64_rip, previous_ip),
+                     (_amd64_rsp, previous_sp)),
+                    (_amd64_rsp, _amd64_rip))
+
+    except (gdb.error, RuntimeError):
+        return None
+
+
+_char_ptr_t = gdb.lookup_type("unsigned char").pointer()
+_char_ptr_ptr_t = _char_ptr_t.pointer()
+_uint_ptr_t = gdb.lookup_type("unsigned long long")
+_amd64_rbp = 6
+_amd64_rsp = 7
+_amd64_rip = 16
+gdb.frame_sniffers=[sniff]

>>>>
spaces around =

Looking at this I realize this is just copying from the early
pretty-printer implementation where they were just functions.
But then we needed names and an enabled flag, and then it
made more sense to record them as objects. And down the road,
who knows. Thus I'm thinking it'd be better to just start out
with recording these as objects.
I.e., create a Sniffer object akin to what gdb/python/lib/gdb/printing.py
does for class PrettyPrinter.  The object will have two attributes:
name and enabled, and one method: __call__.
I'd also copy gdb/python/lib/gdb/printing.py:register_pretty_printer,
and duplicate gdb/python/lib/gdb/command/pretty_printers.py though
you won't need all the "subprinter" complexity,
gdb/python/lib/gdb/command/xmethods.py will be easier to copy from
(similarly: gdb/python/lib/gdb/xmethod.py).

+print("Python script imported")

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-04 22:36 ` Doug Evans
@ 2015-02-12 17:58   ` Alexander Smundak
  2015-02-19  2:32     ` Alexander Smundak
  2015-02-20 11:12     ` Phil Muldoon
  0 siblings, 2 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-02-12 17:58 UTC (permalink / raw)
  To: Doug Evans; +Cc: gdb-patches

[-- Attachment #1: Type: text/plain, Size: 5429 bytes --]

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

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

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

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

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

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

Here's take two:

gdb/ChangeLog:
2015-02-28  Sasha Smundak  <asmundak@google.com>

        * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
        (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
        (py-unwind.o): New recipe.
        * NEWS: mention Python frame unwinding.
        * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
        * doc/python.texi (Writing a Frame Unwinder in Python): Add
        section.
        * python/lib/gdb/__init__.py (packages): Add frame_sniffers list.
        * python/lib/gdb/command/sniffers.py: New file, implements GDB
        commands to list/enable/disable Python sniffers.
        * python/lib/gdb/function/sniffers.py: New file, implements
        execute_sniffers function.
        * python/lib/gdb/sniffer.py: New file, contains Sniffer class and
        register_sniffer function.
        * python/py-objfile.c (objfile_object): Add frame_sniffers field.
        (objfpy_dealloc): Decrement frame_sniffers reference count.
        (objfpy_initialize): Create frame_sniffers list.
        (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
        getter.
        (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
        setter.
        (objfile_getset): Add frame_sniffers attribute to Objfile.
        * python/py-progspace.c (pspace_object): Add frame_sniffers field.
        (pspy_dealloc): Decrement frame_sniffers reference count.
        (pspy_initialize): Create frame_sniffers list.
        (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        getter.
        (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        setter.
        (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
        * python/py-unwind.c: New file, implements Python frame sniffers
        interface.
        * python/python-internal.h (pspy_get_name_sniffers): New prototype.
        (objpy_get_frame_sniffers): New prototype.
        (gdbpy_initialize_unwind): New prototype.
        * python/python.c (gdbpy_apply_type_printers): Call
        gdbpy_initialize_unwind.

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

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

[-- Attachment #2: patch3.diff --]
[-- Type: text/plain, Size: 58862 bytes --]

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 8addef4..3773e2c 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -394,6 +394,7 @@ SUBDIR_PYTHON_OBS = \
 	py-symtab.o \
 	py-threadevent.o \
 	py-type.o \
+	py-unwind.o \
 	py-utils.o \
 	py-value.o \
 	py-varobj.o
@@ -433,6 +434,7 @@ SUBDIR_PYTHON_SRCS = \
 	python/py-symtab.c \
 	python/py-threadevent.c \
 	python/py-type.c \
+	python/py-unwind.c \
 	python/py-utils.c \
 	python/py-value.c \
 	python/py-varobj.c
@@ -2608,6 +2610,10 @@ py-type.o: $(srcdir)/python/py-type.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-type.c
 	$(POSTCOMPILE)
 
+py-unwind.o: $(srcdir)/python/py-unwind.c
+	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-unwind.c
+	$(POSTCOMPILE)
+
 py-utils.o: $(srcdir)/python/py-utils.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-utils.c
 	$(POSTCOMPILE)
diff --git a/gdb/NEWS b/gdb/NEWS
index f19577a..89157a4 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -12,6 +12,7 @@
   ** gdb.Objfile objects have a new attribute "username",
      which is the name of the objfile as specified by the user,
      without, for example, resolving symlinks.
+  ** You can now write frame unwinders in Python.
 
 * New commands
 
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index c01b86d..47b4957 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,17 +62,20 @@ PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/types.py \
 	gdb/printing.py \
+	gdb/sniffer.py \
 	gdb/prompt.py \
 	gdb/xmethod.py \
 	gdb/command/__init__.py \
 	gdb/command/xmethods.py \
 	gdb/command/frame_filters.py \
+	gdb/command/sniffers.py \
 	gdb/command/type_printers.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/explore.py \
 	gdb/function/__init__.py \
 	gdb/function/caller_is.py \
+        gdb/function/sniffers.py \
 	gdb/function/strfns.py \
 	gdb/printer/__init__.py \
 	gdb/printer/bound_registers.py
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index d725eb0..62537d2 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -144,6 +144,7 @@ optional arguments while skipping others.  Example:
 * Frame Filter API::            Filtering Frames.
 * Frame Decorator API::         Decorating Frames.
 * Writing a Frame Filter::      Writing a Frame Filter.
+* Unwinding Frames in Python::  Writing a frame unwinder in Python.
 * Xmethods In Python::          Adding and replacing methods of C++ classes.
 * Xmethod API::                 Xmethod types.
 * Writing an Xmethod::          Writing an xmethod.
@@ -2178,6 +2179,101 @@ printed hierarchically.  Another approach would be to combine the
 marker in the inlined frame, and also show the hierarchical
 relationship.
 
+@node Unwinding Frames in Python
+@subsubsection Unwinding Frames in Python
+@cindex Unwinding frames in Python.
+
+In GDB terminology ``unwinding'' is the process of finding the
+previous frame (that is, caller's) from the current one. A running GDB
+mantains a list of the unwinders and calls each unwinder's sniffer in
+turn until it finds the one that recognizes the current frame. There
+is an API to register an unwinder.
+
+The unwinders that come with GDB handle standard frames for each
+platform where GDB is running. However, mixed language applications
+(for example, and application running Java Virtual Machine) sometimes
+use frame layouts that cannot be handled by the GDB unwinders. You
+can write Python code that can handle such custom frames.
+
+You implement a sniffer as a class with which has two attributes,
+@code{name} and @code{enabled}, with obvious meanings, and a single
+method @code{__call__}, which examines a given frame and returns the data
+describing it (that is, when it recognizes it). GDB comes with the module
+containing the base @code{Sniffer} class for that. The sniffer looks as
+follows:
+@smallexample
+from gdb.sniffers import Sniffer
+
+class MySniffer(Sniffer):
+    def __init__(....):
+        super(MySniffer, self).__init___(<expects sniffer name argument>)
+    def __call__(sniffer_info):
+        if not <we recognize frame>:
+            return None
+        <find previous frame registers>
+        return (<registers>, <frame ID registers>)
+@end smallexample
+
+@subheading Examining The Current Frame
+
+@value{GDBN} passes a @code{gdb.SnifferInfo} instance when it calls
+sniffer's @code{__call__} method. This class has a single method:
+
+@defun SnifferInfo.read_register (self, regnum)
+This method returns the contents of the register @var{regnum} in the
+frame as a @code{gdb.Value} object. @var{regnum} values are
+platform-specific. They are usually defined in the corresponding
+xxx-@code{tdep.h} file in the gdb source tree.
+@end defun
+
+@subheading Returning Previous Frame
+
+If sniffer's @code{__call__} method recognizes the frame, it should
+return a (@var{registers}, @var{frame_id_register_numbers}) tuple.
+
+@var{registers} describe the registers that can be unwound (i.e., the
+registers from the previous frame that have been saved in the current
+frame described by @var{sniffer_info}). It is a tuple where each
+element is a (@var{regnum}, @var{regdata}) 2-tuple.  @var{regnum} is
+a register number, and @var{regdata} is register contents (a
+@code{gdb.Value} object).
+
+@var{frame_id_register_numbers} is a tuple specifying the registers
+used to construct frame ID of the returned frame.  It is a (@var{sp}),
+(@var{sp}, @var{pc}) or (@var{sp}, @var{pc}, @var{special}) tuple,
+where @var{sp}, @var{pc}, @var{special} are register numbers. The
+referenced registers should be present in @var{registers} tuple. The
+frame ID is constructed by calling
+@code{frame_id_build_wild}(@var{value}(@var{sp})),
+@code{frame_id_build}(@var{value}(@var{sp}), @var{value}(@var{pc})),
+or @code{frame_id_build}(@var{value}(@var{sp}), @var{value}(@var{pc}),
+@var{value}(@var{special})) respectively.
+
+@subheading Registering a Sniffer
+
+An object file, a program space, and the @value{GDBN} proper can have
+sniffers registered with it.
+
+The @code{gdb.sniffers} module provides the function to register a
+sniffer:
+
+@defun gdb.sniffer.register_sniffer (locus, sniffer, replace=False)
+@var{locus} is specifies an object file or a program space to which
+@var{sniffer} is added. Passing @code{None} or @code{gdb} adds
+@var{sniffer} to the @value{GDBN}'s global sniffer list.  The newly
+added @var{sniffer} will be called before any other sniffer from the
+same locus.  Two sniffers in the same locus cannot have the same
+name. An attempt to add a sniffer with already existing name raises an
+exception unless @var{replace} is @code{True}, in which case the old
+sniffer is deleted.
+@end defun
+
+@subheading Sniffer Precedence
+
+@value{GDBN} first calls the sniffers from all the object files in no
+particular order, then the sniffers from the current program space,
+and finally the sniffers from @value{GDBN}.
+
 @node Xmethods In Python
 @subsubsection Xmethods In Python
 @cindex xmethods in Python
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 92b06f2..8d7f651 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -71,6 +71,8 @@ type_printers = []
 xmethods = []
 # Initial frame filters.
 frame_filters = {}
+# Initial frame sniffers.
+frame_sniffers = []
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/command/sniffers.py b/gdb/python/lib/gdb/command/sniffers.py
new file mode 100644
index 0000000..b340434
--- /dev/null
+++ b/gdb/python/lib/gdb/command/sniffers.py
@@ -0,0 +1,198 @@
+# Sniffer commands.
+# Copyright 2013-2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+import re
+
+
+def validate_regexp(exp, idstring):
+    try:
+        return re.compile(exp)
+    except SyntaxError:
+        raise SyntaxError("invalid %s regexp: %s" % (idstring, exp))
+
+
+def parse_sniffer_command_args(arg):
+    """Internal utility to parse sniffer command argv.
+
+    Arguments:
+        arg: The arguments to the command. The format is:
+             [locus-regexp [name-regexp]]
+
+    Returns:
+        A 2-tuple of compiled regular expressions.
+
+    Raises:
+        SyntaxError: an error processing ARG
+    """
+
+    argv = gdb.string_to_argv(arg)
+    argc = len(argv)
+    if argc > 2:
+        raise SyntaxError("too many arguments")
+    locus_regexp = ""
+    name_regexp = ""
+    if argc >= 1:
+        locus_regexp = argv[0]
+        if argc >= 2:
+            name_regexp = argv[1]
+    return (validate_regexp(locus_regexp, "locus"),
+            validate_regexp(name_regexp, "sniffer"))
+
+
+class InfoSniffer(gdb.Command):
+    """GDB command to list sniffers.
+
+    Usage: info sniffer [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the location of the
+    sniffer.  If it is omitted, all registered sniffers from all loci
+    are listed.  A locus could be 'global', a regular expression
+    matching the current program space's filename, or a regular
+    expression matching filenames of objfiles.  Locus could be
+    'progspace' to specify that only sniffers from the current
+    progspace should be listed.
+
+    NAME-REGEXP is a regular expression to filter sniffer names.
+    If this omitted for a specified locus, then all registered
+    sniffers in the locus are listed.
+    """
+
+    def __init__(self):
+        super(InfoSniffer, self).__init__("info sniffer",
+                                          gdb.COMMAND_DATA)
+
+    def list_sniffers(self, title, sniffers, name_re):
+        """Lists the sniffers whose name matches regexp.
+
+        Arguments:
+            title: The line to print before the list.
+            sniffers: The list of the sniffers.
+            name_re: sniffer name filter.
+        """
+        if not sniffers:
+            return
+        print title
+        for sniffer in sniffers:
+            if name_re.match(sniffer.name):
+                print("  %s%s" % (sniffer.name,
+                                  "" if sniffer.enabled else "[disabled]"))
+
+    def invoke(self, arg, from_tty):
+        locus_re, name_re = parse_sniffer_command_args(arg)
+        if locus_re.match("global"):
+            self.list_sniffers("global sniffers:", gdb.frame_sniffers,
+                               name_re)
+        if locus_re.match("progspace"):
+            cp = gdb.current_progspace()
+            self.list_sniffers("progspace %s sniffers:" % cp.filename,
+                               cp.frame_sniffers, name_re)
+        for objfile in gdb.objfiles():
+            if locus_re.match(objfile.filename):
+                self.list_sniffers("objfile %s sniffers:" % objfile.filename,
+                                   objfile.frame_sniffers, name_re)
+
+
+def do_enable_sniffer1(sniffers, name_re, flag):
+    """Enable/disable sniffers whose names match given regex.
+
+    Arguments:
+        sniffers: The list of sniffers.
+        name_re: Sniffer name filter.
+        flag: Enable/disable.
+
+    Returns:
+        The number of sniffers affected.
+    """
+    total = 0
+    for sniffer in sniffers:
+        if name_re.match(sniffer.name):
+            sniffer.enabled = flag
+            total += 1
+    return total
+
+
+def do_enable_sniffer(arg, flag):
+    """Enable/disable sniffer(s)."""
+    (locus_re, name_re) = parse_sniffer_command_args(arg)
+    total = 0
+    if locus_re.match("global"):
+        total += do_enable_sniffer1(gdb.frame_sniffers, name_re, flag)
+    if locus_re.match("progspace"):
+        total += do_enable_sniffer1(gdb.current_progspace().frame_sniffers,
+                                    name_re, flag)
+    for objfile in gdb.objfiles():
+        if locus_re.match(objfile.filename):
+            total += do_enable_sniffer1(objfile.frame_sniffers, name_re,
+                                        flag)
+    print("%d sniffer%s %s" % (total, "" if total == 1 else "s",
+                               "enabled" if flag else "disabled"))
+
+
+class EnableSniffer(gdb.Command):
+    """GDB command to enable sniffers.
+
+    Usage: enable sniffer [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the objects to examine.
+    Loci are "global", the program space's file, and the objfiles within
+    that program space.
+
+    NAME_REGEXP is a regular expression to filter sniffer names.
+    If this omitted for a specified locus, then all registered
+    sniffers in the locus are affected.
+    """
+
+    def __init__(self):
+        super(EnableSniffer, self).__init__("enable sniffer",
+                                            gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_sniffer(arg, True)
+
+
+class DisableSniffer(gdb.Command):
+    """GDB command to disable the specified sniffer.
+
+    Usage: disable sniffer [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the objects to examine.
+    Loci are "global", the program space's file, and the objfiles within
+    that program space.
+
+    NAME_REGEXP is a regular expression to filter sniffer names.
+    If this omitted for a specified locus, then all registered
+    sniffers in the locus are affected.
+    """
+
+    def __init__(self):
+        super(DisableSniffer, self).__init__("disable sniffer",
+                                             gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_sniffer(arg, False)
+
+
+def register_sniffer_commands():
+    """Installs the sniffer commands."""
+    InfoSniffer()
+    EnableSniffer()
+    DisableSniffer()
+
+
+register_sniffer_commands()
diff --git a/gdb/python/lib/gdb/function/sniffers.py b/gdb/python/lib/gdb/function/sniffers.py
new file mode 100644
index 0000000..6ea6604
--- /dev/null
+++ b/gdb/python/lib/gdb/function/sniffers.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Internal functions for working with frame sniffers."""
+
+import gdb
+
+
+def execute_sniffers(sniffer_info):
+    """Internal function called from GDB to execute all sniffers.
+
+    Runs each currently enabled sniffer until it finds the one that can
+    unwind given frame.
+
+    Arguments:
+        sniffer_info: an instance of gdb.SnifferInfo.
+    Returns:
+        Unwind info or None. See the description of the value returned
+        by Sniffer.__call__ below.
+    """
+    for objfile in gdb.objfiles():
+        for sniffer in objfile.frame_sniffers:
+            if sniffer.enabled:
+                unwind_info = sniffer.__call__(sniffer_info)
+                if unwind_info is not None:
+                    return unwind_info
+
+    current_progspace = gdb.current_progspace()
+    for sniffer in current_progspace.frame_sniffers:
+        if sniffer.enabled:
+            unwind_info = sniffer.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    for sniffer in gdb.frame_sniffers:
+        if sniffer.enabled:
+            unwind_info = sniffer.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    return None
diff --git a/gdb/python/lib/gdb/sniffer.py b/gdb/python/lib/gdb/sniffer.py
new file mode 100644
index 0000000..6c9f327
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffer.py
@@ -0,0 +1,113 @@
+# Copyright (C) 2013-2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Sniffer class and register_sniffer function."""
+
+import gdb
+
+
+class Sniffer(object):
+    """Base class (or a template) for frame sniffers written in Python.
+
+    A sniffer has a single method __call__ and the attributes described below.
+
+    Attributes:
+        name: The name of the sniffer.
+        enabled: A boolean indicating whether the sniffer is enabled.
+    """
+
+    def __init__(self, name):
+        """Constructor.
+
+        Args:
+            name: An identifying name for the sniffer.
+        """
+        self.name = name
+        self.enabled = True
+
+    def __call__(self, sniffer_info):
+        """GDB calls this method to unwind a frame.
+
+        Arguments:
+            sniffer_info: An instance of gdb.SnifferInfo describing the frame.
+
+        Returns:
+            (REG_DATA, FRAME_ID_REGNUMS) tuple, or None if this sniffer cannot
+            unwind the frame.
+            REG_DATA is a tuple containing the registers in the unwound frame.
+            Each element in thr REG_DATA is a (REG_NUM, REG_VALUE) tuple,
+            where REG_NUM is platform-specific register number, and REG_VALUE
+            is register value (a gdb.Value instance).
+            FRAME_ID_REGNUMS is a tuple specifying the registers used
+            to construct the frame ID. For instance, on x86_64,
+            FRAME_ID_REGNUMS is (7, 16), as 7 is the stack pointer
+            register (RSP), and 16 is the instruction pointer (RIP).
+            FRAME_ID_REGNUMS is a (SP), (SP, PC), or (SP, PC, SPECIAL)
+            tuple, where SP, PC, and SPECIAL are register numbers. Depending
+            on the tuple, Python sniffer support code uses internal GDB
+            functions to construct frame ID as follows:
+            (SP)                  frame_id_build_wild (Value(SP))
+            (SP, PC)              frame_id_build (Value(SP), Value(PC))
+            (SP, PC, SPECIAL)     frame_id_build_special (Value(SP),
+                                   Value(PC), Value(SPECIAL)
+            The registers in the FRAME_ID_REGNUMS tuple should be among those
+            returned by REG_DATA.
+            The chapter "Stack Frames" in the GDB Internals Guide describes
+            frame ID.
+        """
+        raise NotImplementedError("Sniffer __call__")
+
+
+def register_sniffer(locus, sniffer, replace=False):
+    """Register sniffer in given locus.
+
+    The sniffer is prepended to the locus's sniffers list. Sniffer
+    name should be unique.
+
+    Arguments:
+        locus: Either an objfile, progspace, or None (in which case
+               the sniffer is registered globally).
+        sniffer: An object of a gdb.Sniffer subclass
+        replace: If True, replaces existing sniffer with the same name.
+                 Otherwise, raises exception if sniffer with the same
+                 ame already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        ValueError: sniffer name contains ';'.
+        RuntimeError: Sniffer name is not unique.
+
+    """
+    if sniffer.name.find(";") >= 0:
+        raise ValueError("semicolon ';' in printer name")
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s sniffer ...\n" % sniffer.name)
+        locus = gdb
+    else:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering %s sniffer for %s ...\n" %
+                      (sniffer.name, locus.filename))
+    i = 0
+    for needle in locus.frame_sniffers:
+        if needle.name == sniffer.name:
+            if replace:
+                del locus.frame_sniffers[i]
+            else:
+                raise RuntimeError("sniffer %s already exists" % sniffer.name)
+        i += 1
+    locus.frame_sniffers.insert(0, sniffer)
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 0aecaf6..a7bb7cd 100644
--- a/gdb/python/py-objfile.c
+++ b/gdb/python/py-objfile.c
@@ -42,6 +42,10 @@ typedef struct
 
   /* The frame filter list of functions.  */
   PyObject *frame_filters;
+
+  /* The frame sniffers list of functions.  */
+  PyObject *frame_sniffers;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -181,6 +185,7 @@ objfpy_dealloc (PyObject *o)
   Py_XDECREF (self->dict);
   Py_XDECREF (self->printers);
   Py_XDECREF (self->frame_filters);
+  Py_XDECREF (self->frame_sniffers);
   Py_XDECREF (self->type_printers);
   Py_XDECREF (self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -203,6 +208,10 @@ objfpy_initialize (objfile_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_sniffers = PyList_New (0);
+  if (self->frame_sniffers == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -310,6 +319,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
   return 0;
 }
 
+/* Return the frame sniffers attribute for this object file.  */
+
+PyObject *
+objfpy_get_frame_sniffers (PyObject *o, void *ignore)
+{
+  objfile_object *self = (objfile_object *) o;
+
+  Py_INCREF (self->frame_sniffers);
+  return self->frame_sniffers;
+}
+
+/* Set this object file's frame sniffers list to SNIFFERS.  */
+
+static int
+objfpy_set_frame_sniffers (PyObject *o, PyObject *sniffers, void *ignore)
+{
+  PyObject *tmp;
+  objfile_object *self = (objfile_object *) o;
+
+  if (!sniffers)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete the frame sniffers attribute."));
+      return -1;
+    }
+
+  if (!PyList_Check (sniffers))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The frame_sniffers attribute must be a list."));
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_sniffers;
+  Py_INCREF (sniffers);
+  self->frame_sniffers = sniffers;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -645,6 +696,8 @@ static PyGetSetDef objfile_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", objfpy_get_frame_filters,
     objfpy_set_frame_filters, "Frame Filters.", NULL },
+  { "frame_sniffers", objfpy_get_frame_sniffers,
+    objfpy_set_frame_sniffers, "Frame Sniffers", NULL },
   { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
     "Type printers.", NULL },
   { "xmethods", objfpy_get_xmethods, NULL,
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index 29b9f96..ce85b1a 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -41,6 +41,10 @@ typedef struct
 
   /* The frame filter list of functions.  */
   PyObject *frame_filters;
+
+  /* The frame sniffer list.  */
+  PyObject *frame_sniffers;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self)
   Py_XDECREF (ps_self->dict);
   Py_XDECREF (ps_self->printers);
   Py_XDECREF (ps_self->frame_filters);
+  Py_XDECREF (ps_self->frame_sniffers);
   Py_XDECREF (ps_self->type_printers);
   Py_XDECREF (ps_self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_sniffers = PyList_New (0);
+  if (self->frame_sniffers == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
   return 0;
 }
 
+/* Return the list of the frame sniffers for this program space.  */
+
+PyObject *
+pspy_get_frame_sniffers (PyObject *o, void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  Py_INCREF (self->frame_sniffers);
+  return self->frame_sniffers;
+}
+
+/* Set this program space's list of the sniffers to SNIFFERS.  */
+
+static int
+pspy_set_frame_sniffers (PyObject *o, PyObject *sniffers, void *ignore)
+{
+  PyObject *tmp;
+  pspace_object *self = (pspace_object *) o;
+
+  if (!sniffers)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "cannot delete the frame sniffers list");
+      return -1;
+    }
+
+  if (!PyList_Check (sniffers))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "the frame sniffers attribute must be a list");
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_sniffers;
+  Py_INCREF (sniffers);
+  self->frame_sniffers = sniffers;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
     "Frame filters.", NULL },
+  { "frame_sniffers", pspy_get_frame_sniffers, pspy_set_frame_sniffers,
+    "Frame sniffers.", NULL },
   { "type_printers", pspy_get_type_printers, pspy_set_type_printers,
     "Type printers.", NULL },
   { "xmethods", pspy_get_xmethods, NULL,
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
new file mode 100644
index 0000000..4c935aa
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,539 @@
+/* Python frame unwinder interface
+
+   Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+  struct frame_info *frame_info;
+} sniffer_info_object;
+
+/* The data we keep for a frame we can unwind: frame_id and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  struct frame_id frame_id;
+  struct gdbarch *gdbarch;
+  int reg_count;
+  struct reg_info
+  {
+    int number;
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  char *s;
+  PyObject *result;
+  struct frame_info *frame = ((sniffer_info_object *)self)->frame_info;
+
+  s = xstrprintf ("SP=%s,PC=%s", core_addr_to_string_nz (get_frame_sp (frame)),
+        core_addr_to_string_nz (get_frame_pc (frame)));
+  result = PyString_FromString (s);
+  xfree (s);
+
+  return result;
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of register REGNUM as pointer.  */
+
+static PyObject *
+sniffer_infopy_read_register (PyObject *self, PyObject *args)
+{
+  volatile struct gdb_exception except;
+  int regnum;
+  struct value *val = NULL;
+
+  if (!PyArg_ParseTuple (args, "i", &regnum))
+    return NULL;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      /* Calling `value_of_register' will result in infinite recursion.
+         Call `deprecated_frame_register_read' instead.  */
+      struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
+      struct gdbarch *gdbarch = get_frame_arch (frame);
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      if (deprecated_frame_register_read (frame, regnum, buffer))
+        val = value_from_contents (register_type (gdbarch, regnum), buffer);
+      if (val == NULL)
+        {
+          char *error_text
+              = xstrprintf (_("Cannot read register %d from frame"), regnum);
+          PyErr_SetString (PyExc_ValueError, error_text);
+          xfree (error_text);
+        }
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  if (sniffer_info != NULL)
+    sniffer_info->frame_info = frame;
+
+  return (PyObject *) sniffer_info;
+}
+
+/* Parse Python Int, saving it at the given address. Returns 1 on success,
+   0 otherwise.  */
+
+static int
+pyuw_parse_int (PyObject *pyo_int, int *valuep)
+{
+  long long_value;
+  if (pyo_int == NULL || !PyInt_Check (pyo_int))
+    return 0;
+  long_value = PyInt_AsLong (pyo_int);
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* Parse given tuple of Python Ints into an array. Returns the number
+   of items in the tuple, or -1 on error (bad tuple element type,
+   array too small).  */
+
+static Py_ssize_t
+pyuw_parse_ints (PyObject *pyo_sequence, int *values, Py_ssize_t max_values)
+{
+  Py_ssize_t size;
+  Py_ssize_t i;
+
+  if (! PyTuple_Check (pyo_sequence))
+    return -1;
+  size = PyTuple_Size (pyo_sequence);
+  if (size < 0 || size > max_values)
+    return -1;
+  for (i = 0; i < size; ++i)
+    {
+      if (!pyuw_parse_int (PyTuple_GetItem (pyo_sequence, i), &values[i]))
+        return -1;
+    }
+  return i;
+}
+
+/* Retrieve register value for the cached unwind info as target pointer.
+   Return 1 on success, 0 on failure.  */
+
+static int
+pyuw_reg_value (cached_frame_info *cached_frame, int regnum, CORE_ADDR *value)
+{
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (reg_info->number == regnum)
+        {
+          *value = unpack_pointer
+              (register_type (cached_frame->gdbarch, regnum), reg_info->data);
+          return 1;
+        }
+    }
+
+  error (_("Python sniffer uses register #%d for this_id, "
+           "but this register is not available"), regnum);
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
+              struct frame_id *this_id)
+{
+  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
+  if (pyuw_debug >= 1)
+    {
+      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+      fprint_frame_id (gdb_stdlog, *this_id);
+      fprintf_unfiltered (gdb_stdlog, "\n");
+    }
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
+                    int regnum)
+{
+  cached_frame_info *cached_frame = *cache_ptr;
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  TRACE_PY_UNWIND (1, "%s(frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
+                   regnum);
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (regnum == reg_info->number)
+        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
+    }
+
+  return frame_unwind_got_optimized (this_frame, regnum);
+}
+
+/* Parse frame ID tuple returned by the sniffer info GDB's frame_id and
+   save it in the cached frame.  */
+
+static void
+pyuw_parse_frame_id (cached_frame_info *cached_frame,
+                     PyObject *pyo_frame_id_regs)
+{
+  int regno[3];
+  CORE_ADDR sp, pc, special;
+
+  if (!PyTuple_Check (pyo_frame_id_regs))
+    error (_("The second element of the pair returned by a Python "
+             "sniffer should be a tuple"));
+
+  switch (pyuw_parse_ints (pyo_frame_id_regs, regno, ARRAY_SIZE (regno)))
+    {
+    case 1:
+      if (pyuw_reg_value (cached_frame, regno[0], &sp))
+      {
+        cached_frame->frame_id = frame_id_build_wild (sp);
+        return;
+      }
+      break;
+    case 2:
+      if (pyuw_reg_value (cached_frame, regno[0], &sp)
+          || pyuw_reg_value (cached_frame, regno[1], &pc))
+      {
+        cached_frame->frame_id = frame_id_build (sp, pc);
+        return;
+      }
+      break;
+    case 3:
+      if (pyuw_reg_value (cached_frame, regno[0], &sp)
+          || pyuw_reg_value (cached_frame, regno[1], &pc)
+        || pyuw_reg_value (cached_frame, regno[2], &special))
+      {
+        cached_frame->frame_id = frame_id_build_special (sp, pc, special);
+        return;
+      }
+      break;
+    }
+  error (_("Unwinder should return a tuple of 1 to 3 ints in the second item"));
+}
+
+/* Frame sniffer dispatch.  */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch;
+  struct cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdb_assert (*cache_ptr == NULL);
+  gdbarch = (void *)(self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  TRACE_PY_UNWIND (3, "%s(SP=%s, PC=%s)\n", __FUNCTION__,
+                   paddress (gdbarch, get_frame_sp (this_frame)),
+                   paddress (gdbarch, get_frame_pc (this_frame)));
+  pyo_sniffer_info = frame_info_to_sniffer_info_object (this_frame);
+  if (pyo_sniffer_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_sniffer_info);
+
+  if ((pyo_module = PyImport_ImportModule ("gdb.function.sniffers")) == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_module);
+
+  pyo_execute = PyObject_GetAttrString (pyo_module, "execute_sniffers");
+  if (pyo_execute == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_execute);
+
+  pyo_unwind_info
+      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_sniffer_info, NULL);
+  if (pyo_unwind_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_unwind_info);
+  if (pyo_unwind_info == Py_None)
+    goto error;
+
+  /* Unwind_info is a (REGISTERS, FRAME_ID_REGNUMS) tuple.  REGISTERS
+   * is a tuple of the (REG_NR, REG_VALUE) tuples. FRAME_ID_REGNUMS is
+   * the tuple of REGNO values.  */
+  if (!PyTuple_Check (pyo_unwind_info)
+      || PyTuple_Size (pyo_unwind_info) != 2)
+    error (_("Sniffer should return a pair (REGISTERS, FRAME_ID_REGNUMS)"));
+
+  {
+    PyObject *pyo_registers = PyTuple_GetItem (pyo_unwind_info, 0);
+    int i;
+    int reg_count;
+    size_t cached_frame_size;
+    size_t gdb_bytes_count;
+    gdb_byte *gdb_data_free, *gdb_data_end;
+
+    if (pyo_registers == NULL)
+      goto error;
+    if (!PyTuple_Check (pyo_registers))
+      error (_("The first element of the returned pair should be a tuple"));
+
+    /* Figure out how much space we need to allocate.  */
+    reg_count = PyTuple_Size (pyo_registers);
+    if (reg_count <= 0)
+      error (_("Register list should not be empty"));
+    /* We might overestimate `gdb_bytes_count', but it's not worth
+       parsing register numbers twice to calculate the exact number of
+       bytes needed.  */
+    gdb_bytes_count = reg_count * MAX_REGISTER_SIZE;
+    cached_frame_size = sizeof (*cached_frame) +
+        reg_count * sizeof (cached_frame->reg[0]) +
+        gdb_bytes_count * sizeof (gdb_byte);
+
+    cached_frame = xmalloc (cached_frame_size);
+    cached_frame_cleanups = make_cleanup (xfree, cached_frame);
+    /* Allocations after this point will be discarded!  */
+
+    gdb_data_end = (gdb_byte *)((char *) cached_frame + cached_frame_size);
+    gdb_data_free = gdb_data_end - gdb_bytes_count;
+
+    cached_frame->gdbarch = gdbarch;
+    cached_frame->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; i < reg_count; i++)
+      {
+        PyObject *pyo_reg = PyTuple_GetItem (pyo_registers, i);
+        struct reg_info *reg = &(cached_frame->reg[i]);
+
+        if (pyo_reg == NULL)
+          goto error;
+
+        if (!PyTuple_Check (pyo_reg) || PyTuple_Size (pyo_reg) != 2
+            || !pyuw_parse_int (PyTuple_GetItem (pyo_reg, 0),
+                                &reg->number))
+          {
+            error (_("Python sniffer returned bad register tuple: "
+                     "item #%d is not a (reg_no, reg_data) tuple "
+                     "with integer reg_no and reg_data"), i);
+          }
+
+        {
+          PyObject *pyo_reg_value = PyTuple_GetItem (pyo_reg, 1);
+          struct value *value;
+          size_t data_size;
+
+          if (pyo_reg_value == NULL)
+            goto error;
+
+          if ((value = value_object_to_value (pyo_reg_value)) == NULL)
+            error (_("Python sniffer returned bad register tuple: "
+                     "item #%d, register value should have type "
+                     "gdb.Value type"), i);
+          data_size = register_size (gdbarch, reg->number);
+          if (data_size != TYPE_LENGTH (value_enclosing_type (value)))
+            {
+              error (_("The value of the register #%d returned by the "
+                       "Python sniffer has unexpected size: %u instead "
+                       "of %u"), reg->number,
+                     (unsigned)(TYPE_LENGTH (value_enclosing_type (value))),
+                     (unsigned)data_size);
+            }
+          gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+          memcpy (gdb_data_free, value_contents (value), data_size);
+          reg->data = gdb_data_free;
+          gdb_data_free += data_size;
+        }
+      }
+  }
+
+  {
+    PyObject *pyo_frame_id_regs = PyTuple_GetItem (pyo_unwind_info, 1);
+    if (pyo_frame_id_regs == NULL)
+      goto error;
+    pyuw_parse_frame_id (cached_frame, pyo_frame_id_regs);
+  }
+
+  *cache_ptr = cached_frame;
+  discard_cleanups (cached_frame_cleanups);
+  do_cleanups (cleanups);
+  return 1;
+
+error:
+  do_cleanups (cleanups);
+  return 0;
+}
+
+/* Frame cache release shim.  */
+
+static void
+pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
+  xfree (cache);
+}
+
+struct pyuw_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Python sniffers
+   intermediary.  */
+
+static void
+pyuw_on_new_gdbarch (struct gdbarch *newarch)
+{
+  struct pyuw_gdbarch_data_type *data =
+      gdbarch_data (newarch, pyuw_gdbarch_data);
+
+  if (!data->unwinder_registered)
+    {
+      struct frame_unwind *unwinder
+          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
+
+      unwinder->type = NORMAL_FRAME;
+      unwinder->stop_reason = default_frame_unwind_stop_reason;
+      unwinder->this_id = pyuw_this_id;
+      unwinder->prev_register = pyuw_prev_register;
+      unwinder->unwind_data = (void *) newarch;
+      unwinder->sniffer = pyuw_sniffer;
+      unwinder->dealloc_cache = pyuw_dealloc_cache;
+      frame_unwind_prepend_unwinder (newarch, unwinder);
+      TRACE_PY_UNWIND (1, "%s: registered unwinder for %s\n", __FUNCTION__,
+                       gdbarch_bfd_arch_info (newarch)->printable_name);
+      data->unwinder_registered = 1;
+    }
+}
+
+/* Initialize unwind machinery.  */
+
+int
+gdbpy_initialize_unwind (void)
+{
+  add_setshow_zuinteger_cmd
+      ("py-unwind", class_maintenance, &pyuw_debug,
+        _("Set Python unwinder debugging."),
+        _("Show Python unwinder debugging."),
+        _("When non-zero, Pythin unwinder debugging is enabled."),
+        NULL,
+        NULL,
+        &setdebuglist, &showdebuglist);
+  pyuw_gdbarch_data
+      = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
+  observer_attach_architecture_changed (pyuw_on_new_gdbarch);
+  if (PyType_Ready (&sniffer_info_object_type) < 0)
+    return -1;
+  return gdb_pymodule_addobject (gdb_module, "SnifferInfo",
+      (PyObject *) &sniffer_info_object_type);
+}
+
+static PyMethodDef sniffer_info_object_methods[] =
+{
+  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
+    "read_register (register_name) -> gdb.Value\n\
+Return the value of the register in the frame." },
+  {NULL}  /* Sentinel */
+};
+
+static PyTypeObject sniffer_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.SnifferInfo",              /* tp_name */
+  sizeof (sniffer_info_object),   /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  0,                              /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  sniffer_infopy_str,             /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT,             /* tp_flags */
+  "GDB snifferInfo object",       /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  sniffer_info_object_methods,    /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index a77f5a6..34179de 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -390,12 +390,14 @@ PyObject *pspace_to_pspace_object (struct program_space *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *pspy_get_printers (PyObject *, void *);
 PyObject *pspy_get_frame_filters (PyObject *, void *);
+PyObject *pspy_get_frame_sniffers (PyObject *, void *);
 PyObject *pspy_get_xmethods (PyObject *, void *);
 
 PyObject *objfile_to_objfile_object (struct objfile *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *objfpy_get_printers (PyObject *, void *);
 PyObject *objfpy_get_frame_filters (PyObject *, void *);
+PyObject *objfpy_get_frame_sniffers (PyObject *, void *);
 PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
@@ -490,6 +492,8 @@ int gdbpy_initialize_arch (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_xmethods (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_unwind (void)
+  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 
 struct cleanup *make_cleanup_py_decref (PyObject *py);
 struct cleanup *make_cleanup_py_xdecref (PyObject *py);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 344d8d2..3e079b5 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1790,7 +1790,8 @@ message == an error message without a stack will be printed."),
       || gdbpy_initialize_new_objfile_event ()  < 0
       || gdbpy_initialize_clear_objfiles_event ()  < 0
       || gdbpy_initialize_arch () < 0
-      || gdbpy_initialize_xmethods () < 0)
+      || gdbpy_initialize_xmethods () < 0
+      || gdbpy_initialize_unwind () < 0)
     goto fail;
 
   gdbpy_to_string_cst = PyString_FromString ("to_string");
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
new file mode 100644
index 0000000..8c1d935
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
@@ -0,0 +1,24 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main (void)
+{
+  int i = 0;
+
+  return i; /* next-line */
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
new file mode 100644
index 0000000..55db4dc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
@@ -0,0 +1,64 @@
+# Copyright (C) 2010-2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests Python-based
+# pretty-printing for the CLI.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+if ![runto_main ] then {
+    fail "Can't run to main"
+    return -1
+}
+
+gdb_test "source ${pyfile}" "Python script imported" "import python scripts"
+
+gdb_test_sequence "info sniffer" "Show all sniffers" {
+    "global sniffers:"
+    "  global_sniffer"
+    "progspace.*sniffers:"
+    "py_unwind_maint_ps_sniffer"
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
+
+gdb_test_sequence "continue" "Sniffers called" {
+    "py_unwind_maint_ps_sniffer called"
+    "global_sniffer called"
+}
+
+gdb_test "disable sniffer global .*" "1 sniffer disabled" "Sniffer disabled"
+
+gdb_test_sequence "info sniffer" "Show with global sniffer disabled" {
+    "global sniffers:"
+    "  global_sniffer\\[disabled\\]"
+    "progspace.*sniffers:"
+    "  py_unwind_maint_ps_sniffer"
+}
+
+gdb_test_sequence "where" "Global sniffer disabled" {
+    "py_unwind_maint_ps_sniffer called\r\n#0  main"
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py
new file mode 100644
index 0000000..f978df4
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2010-2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests python sniffers.
+
+import re
+import gdb.types
+from gdb.sniffer import Sniffer, register_sniffer
+
+class TestGlobalSniffer(Sniffer):
+    def __init__(self):
+        super(TestGlobalSniffer, self).__init__("global_sniffer")
+
+    def __call__(self, sniffer_info):
+        print "%s called" % self.name
+        return None
+
+class TestProgspaceSniffer(Sniffer):
+    def __init__(self, name):
+        super(TestProgspaceSniffer, self).__init__("%s_ps_sniffer" % name)
+
+    def __call__(self, sniffer_info):
+        print "%s called" % self.name
+        return None
+
+class TestObjfileSniffer(Sniffer):
+    def __init__(self, name):
+        super(TestObjfileSniffer, self).__init__("%s_obj_sniffer" % name)
+
+    def __call__(self, sniffer_info):
+        print "%s called" % self.name
+        return None
+
+
+
+gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer())
+saw_runtime_error = False
+try:
+    gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer(), replace=False)
+except RuntimeError:
+    saw_runtime_error = True
+if not saw_runtime_error:
+    raise RuntimeError("Missing runtime error from register_sniffer")
+gdb.sniffer.register_sniffer(gdb, TestGlobalSniffer(), replace=True)
+gdb.sniffer.register_sniffer(gdb.current_progspace(),
+                              TestProgspaceSniffer("py_unwind_maint"))
+print "Python script imported"
diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c
new file mode 100644
index 0000000..84349d8
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,70 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2011-2014 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This is the test program loaded into GDB by the py-unwind test.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void *
+swap_value (void **location, void *new_value)
+{
+  void *old_value = *location;
+  *location = new_value;
+  return old_value;
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+break_backtrace (void)
+{
+  /* Save outer frame address, then corrupt the unwind chain by
+     setting the outer frame address in it to self.  This is
+     ABI-specific: the first word of the frame contains previous frame
+     address in amd64.  */
+  void *outer_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+
+  /* Verify the compiler allocates the first local variable one word
+     below frame.  This is where test JIT reader expects to find the
+     correct outer frame address.  */
+  if (&outer_fp + 1 != (void **) MY_FRAME)
+    {
+      fprintf (stderr, "First variable should be allocated one word below "
+               "the frame, got variable's address %p, frame at %p instead\n",
+               &outer_fp, MY_FRAME);
+      abort ();
+    }
+
+  /* Now restore it so that we can return.  The test sets the
+     breakpoint just before this happens, and GDB will not be able to
+     show the backtrace without JIT reader.  */
+  swap_value ((void **) MY_FRAME, outer_fp); /* break backtrace-broken */
+}
+
+static void
+break_backtrace_caller (void)
+{
+  break_backtrace ();
+}
+
+int
+main ()
+{
+  break_backtrace_caller ();
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..a52d06f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@
+# Copyright (C) 2009-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It verifies that frame
+# sniffers can be implemented in Python.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+# This test runs on a specific platform.
+if { ! [istarget x86_64-*]} { continue }
+
+# The following tests require execution.
+
+if ![runto_main] then {
+    fail "Can't run to main"
+    return 0
+}
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
+
+gdb_test "source ${pyfile}" "Python script imported" \
+         "import python scripts"
+
+gdb_continue_to_breakpoint "break backtrace-broken"
+gdb_test_sequence "where"  "Backtrace restored by sniffer" {
+    "\\r\\n#0 .* break_backtrace \\(\\) at "
+    "\\r\\n#1 .* break_backtrace_caller \\(\\) at "
+    "\\r\\n#2 .* main \\(.*\\) at"
+}
+
+
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
new file mode 100644
index 0000000..8fa7fdc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+from gdb.sniffer import Sniffer
+
+class TestSniffer(Sniffer):
+    AMD64_RBP = 6
+    AMD64_RSP = 7
+    AMD64_RIP = 16
+
+    def __init__(self):
+        Sniffer.__init__(self, "test sniffer")
+        self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
+        self.char_ptr_ptr_t = self.char_ptr_t.pointer()
+
+    def _read_word(self, address):
+        return address.cast(self.char_ptr_ptr_t).dereference()
+
+    def __call__(self, sniffer_info):
+        "Sniffer written in Python."
+        bp = sniffer_info.read_register(
+            TestSniffer.AMD64_RBP).cast(self.char_ptr_t)
+        try:
+            if self._read_word(bp) != bp:
+                return None
+            # Found the frame that the test program fudged for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places
+            previous_sp = bp + 16
+            return (((TestSniffer.AMD64_RBP, self._read_word(bp - 8)),
+                     (TestSniffer.AMD64_RIP, self._read_word(bp + 8)),
+                     (TestSniffer.AMD64_RSP, bp + 16)),
+                    (TestSniffer.AMD64_RSP, TestSniffer.AMD64_RIP))
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.sniffer.register_sniffer(None, TestSniffer())
+print("Python script imported")

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-12 17:58   ` Alexander Smundak
@ 2015-02-19  2:32     ` Alexander Smundak
  2015-02-20 11:12     ` Phil Muldoon
  1 sibling, 0 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-02-19  2:32 UTC (permalink / raw)
  To: Doug Evans; +Cc: gdb-patches

Ping.

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2014-12-15 18:14 [RFC] [PATCH] Provide the ability to write the frame unwinder in Python Alexander Smundak
                   ` (2 preceding siblings ...)
  2015-02-04 22:36 ` Doug Evans
@ 2015-02-20  9:42 ` Phil Muldoon
  2015-02-20  9:59   ` Phil Muldoon
  3 siblings, 1 reply; 60+ messages in thread
From: Phil Muldoon @ 2015-02-20  9:42 UTC (permalink / raw)
  To: Alexander Smundak, gdb-patches, Doug Evans

On 15/12/14 18:13, Alexander Smundak wrote:
> Python frame filters provide the ability to display non-native frames in
> a mixed-language application (say, a backtrace of an application written
> in C and embedding Java Virtual Machine can be displayed as a mix of
> native frames and Java frames). However, GDB cannot always unwind
> non-native frames. The proposed patch adds the ability to write frame
> unwinders in Python.
>
> 2014-12-12  Sasha Smundak  <asmundak@google.com>
Sorry I missed this. It came in when I was on Christmas vacation. I'll take an additional look at it soon.

Doug did Alexander answer your questions in his reply?

Cheers

Phil

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-20  9:42 ` Phil Muldoon
@ 2015-02-20  9:59   ` Phil Muldoon
  0 siblings, 0 replies; 60+ messages in thread
From: Phil Muldoon @ 2015-02-20  9:59 UTC (permalink / raw)
  To: Alexander Smundak, gdb-patches, Doug Evans

On 20/02/15 09:42, Phil Muldoon wrote:
> On 15/12/14 18:13, Alexander Smundak wrote:
>> Python frame filters provide the ability to display non-native frames in
>> a mixed-language application (say, a backtrace of an application written
>> in C and embedding Java Virtual Machine can be displayed as a mix of
>> native frames and Java frames). However, GDB cannot always unwind
>> non-native frames. The proposed patch adds the ability to write frame
>> unwinders in Python.
>>
>> 2014-12-12  Sasha Smundak  <asmundak@google.com>
> Sorry I missed this. It came in when I was on Christmas vacation. I'll take an additional look at it soon.
>
> Doug did Alexander answer your questions in his reply?
>
Oops disregard. I forgot that mailing list replies don't cross monthly boundaries.

Cheers

Phil

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-12 17:58   ` Alexander Smundak
  2015-02-19  2:32     ` Alexander Smundak
@ 2015-02-20 11:12     ` Phil Muldoon
  2015-02-26  3:09       ` Alexander Smundak
  1 sibling, 1 reply; 60+ messages in thread
From: Phil Muldoon @ 2015-02-20 11:12 UTC (permalink / raw)
  To: Alexander Smundak, Doug Evans; +Cc: gdb-patches

On 12/02/15 17:58, Alexander Smundak wrote:
> On Wed, Feb 4, 2015 at 2:35 PM, Doug Evans <dje@google.com> wrote:
>> High level comments:
>>
>> Is it possible to see the code, and example usage, of a real-life use-case
>> of this? That will help folks not familiar with this project to understand
>> the problem we are trying to solve.

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 :-)

You could expand the testcase a little?

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

Comments inline. I looked at in detail. It seems a good idea. Some
things need a little work.

diff --git a/gdb/python/lib/gdb/command/sniffers.py b/gdb/python/lib/gdb/command/sniffers.py
new file mode 100644
index 0000000..b340434
--- /dev/null
+++ b/gdb/python/lib/gdb/command/sniffers.py
@@ -0,0 +1,198 @@
+# Sniffer commands.
+# Copyright 2013-2015 Free Software Foundation, Inc.

2015 only, I think, as it is new file.

+def validate_regexp(exp, idstring):
+    try:
+        return re.compile(exp)
+    except SyntaxError:
+        raise SyntaxError("invalid %s regexp: %s" % (idstring, exp))

Please make errors a complete sentence with capitalization and
punctuation.

+    argv = gdb.string_to_argv(arg)
+    argc = len(argv)
+    if argc > 2:
+        raise SyntaxError("too many arguments")

See above note on error messages. This and for all others that follow.

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

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

+            if locus_re.match(objfile.filename):
+                self.list_sniffers("objfile %s sniffers:" % objfile.filename,
+                                   objfile.frame_sniffers, name_re)
+
+
+
+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?

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

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?

I could disable the auto-loaded one, then add my own I guess. But is a
frame sniffing a mutually exclusive operation? Is only one sniffer
only allowed to ever run on one frame in one discrete operation? As it
is right now, without a priority system the auto-loaded frame sniffer
GDB installed will always be run unless I figure out I have to disable
it, and my own sniffer will never be run as the auto-loaded one got
there first.

If it is mutually exclusive operation, that one frame sniffer runs,
and all else are discarded we should either:

Use a priority attribute to decide which sniffer gets run for that
frame.

Or print a warning that relevant sniffers have been discarded in favor
of another.

diff --git a/gdb/python/lib/gdb/sniffer.py b/gdb/python/lib/gdb/sniffer.py
new file mode 100644
index 0000000..6c9f327
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffer.py
@@ -0,0 +1,113 @@
+# Copyright (C) 2013-2015

This and others, 2015 only.

+
+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?


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


+typedef struct
+{
+  PyObject_HEAD
+  struct frame_info *frame_info;
+} sniffer_info_object;


+/* The data we keep for a frame we can unwind: frame_id and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  struct frame_id frame_id;
+  struct gdbarch *gdbarch;
+  int reg_count;
+  struct reg_info
+  {
+    int number;
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;

Each field in a struct needs to be documented.


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

+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?


+/* 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)
+{

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.

+  long long_value;
+  if (pyo_int == NULL || !PyInt_Check (pyo_int))
+    return 0;
+  long_value = PyInt_AsLong (pyo_int);
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}

+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch;
+  struct cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdb_assert (*cache_ptr == NULL);

Can you please document the assert, and the reason for it.

+  gdbarch = (void *)(self->unwind_data);

This cast seems odd to me.

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

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

Cheers

Phil

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-20 11:12     ` Phil Muldoon
@ 2015-02-26  3:09       ` Alexander Smundak
  2015-03-02 22:56         ` Alexander Smundak
  2015-03-03  0:49         ` Alexander Smundak
  0 siblings, 2 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-02-26  3:09 UTC (permalink / raw)
  To: Phil Muldoon; +Cc: Doug Evans, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 13625 bytes --]

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.

[-- Attachment #2: patch4.diff --]
[-- Type: text/plain, Size: 61696 bytes --]

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-26  3:09       ` Alexander Smundak
@ 2015-03-02 22:56         ` Alexander Smundak
  2015-03-03  8:46           ` Andy Wingo
  2015-03-09  9:42           ` Andy Wingo
  2015-03-03  0:49         ` Alexander Smundak
  1 sibling, 2 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-03-02 22:56 UTC (permalink / raw)
  To: Phil Muldoon; +Cc: Doug Evans, gdb-patches, Andy Wingo

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?

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-02-26  3:09       ` Alexander Smundak
  2015-03-02 22:56         ` Alexander Smundak
@ 2015-03-03  0:49         ` Alexander Smundak
  2015-03-03 14:38           ` Andy Wingo
  1 sibling, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-03  0:49 UTC (permalink / raw)
  To: Phil Muldoon; +Cc: Doug Evans, gdb-patches

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.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-02 22:56         ` Alexander Smundak
@ 2015-03-03  8:46           ` Andy Wingo
  2015-03-04  2:36             ` Alexander Smundak
  2015-03-09  9:42           ` Andy Wingo
  1 sibling, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-03  8:46 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Phil Muldoon, Doug Evans, gdb-patches

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-03  0:49         ` Alexander Smundak
@ 2015-03-03 14:38           ` Andy Wingo
  2015-03-04  2:52             ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-03 14:38 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Phil Muldoon, Doug Evans, gdb-patches

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-03  8:46           ` Andy Wingo
@ 2015-03-04  2:36             ` Alexander Smundak
  2015-03-04  7:49               ` Andy Wingo
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-04  2:36 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Phil Muldoon, Doug Evans, gdb-patches

>> 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?

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-03 14:38           ` Andy Wingo
@ 2015-03-04  2:52             ` Alexander Smundak
  0 siblings, 0 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-03-04  2:52 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Phil Muldoon, Doug Evans, gdb-patches

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-04  2:36             ` Alexander Smundak
@ 2015-03-04  7:49               ` Andy Wingo
  2015-03-09 11:02                 ` Phil Muldoon
  0 siblings, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-04  7:49 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Phil Muldoon, Doug Evans, gdb-patches

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-02 22:56         ` Alexander Smundak
  2015-03-03  8:46           ` Andy Wingo
@ 2015-03-09  9:42           ` Andy Wingo
  1 sibling, 0 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-09  9:42 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Phil Muldoon, Doug Evans, gdb-patches

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-04  7:49               ` Andy Wingo
@ 2015-03-09 11:02                 ` Phil Muldoon
  2015-03-11  2:22                   ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Phil Muldoon @ 2015-03-09 11:02 UTC (permalink / raw)
  To: Andy Wingo, Alexander Smundak; +Cc: Doug Evans, gdb-patches

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-09 11:02                 ` Phil Muldoon
@ 2015-03-11  2:22                   ` Alexander Smundak
  2015-03-11  8:49                     ` Andy Wingo
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-11  2:22 UTC (permalink / raw)
  To: Phil Muldoon; +Cc: Andy Wingo, Doug Evans, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 3078 bytes --]

This take revises the API by introducing UnwindInfo class. The main difference
with the proposed Guile interface is that previous frame registers are passed to
the constructor as a tuple and cannot be revised later.

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

        * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
        (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
        (py-unwind.o): New recipe.
        * NEWS: mention Python frame unwinding.
        * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
        * doc/python.texi (Writing a Frame Unwinder in Python): Add
        section.
        * python/lib/gdb/__init__.py (packages): Add frame_sniffers list.
        * python/lib/gdb/command/sniffers.py: New file, implements GDB
        commands to list/enable/disable Python sniffers.
        * python/lib/gdb/function/sniffers.py: New file, implements
        execute_sniffers function.
        * python/lib/gdb/sniffer.py: New file, contains Sniffer class and
        register_sniffer function.
        * python/py-objfile.c (objfile_object): Add frame_sniffers field.
        (objfpy_dealloc): Decrement frame_sniffers reference count.
        (objfpy_initialize): Create frame_sniffers list.
        (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
        getter.
        (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
        setter.
        (objfile_getset): Add frame_sniffers attribute to Objfile.
        * python/py-progspace.c (pspace_object): Add frame_sniffers field.
        (pspy_dealloc): Decrement frame_sniffers reference count.
        (pspy_initialize): Create frame_sniffers list.
        (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        getter.
        (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        setter.
        (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
        * python/py-unwind.c: New file, implements Python frame sniffers
        interface.
        * python/python-internal.h (pspy_get_name_sniffers): New prototype.
        (objpy_get_frame_sniffers): New prototype.
        (gdbpy_initialize_unwind): New prototype.
        * python/python.c (gdbpy_apply_type_printers): Call
        gdbpy_initialize_unwind.

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

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

On Mon, Mar 9, 2015 at 4:02 AM, Phil Muldoon <pmuldoon@redhat.com> wrote:
> 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
>

[-- Attachment #2: patch5.diff --]
[-- Type: text/plain, Size: 66521 bytes --]

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..44683bd 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,111 @@ 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, create UnwindInfo instance from it>
+        <save frame's ID in UnwindInfo instance by calling one of
+         frame_id_build_xxx methods>
+        return <UnwindInfo instance>
+@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 Sniffer return: UnwindInfo
+
+If sniffer's @code{__call__} method recognizes the frame, it should
+return an instance of @code{gdb.UnwindInfo} class describing it. 
+
+@code{UnwindInfo} has the following methods:
+
+@defun gdb.UnwindInfo.__init__ (sniffer_info, previous_frame_registers)
+@var{sniffer_info} is the SnifferInfo instance that has been passed to
+a sniffer on invocation.
+@var{previous_frame_registers} describes 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).
+@end defun
+
+@defun gdb.UnwindInfo.frame_id_build(sp, pc)
+Builds frame ID from the given @var{sp} and @var{pc} values.
+@end defun
+
+@defun gdb.UnwindInfo.frame_id_build_special(sp, pc, special)
+Builds frame ID from the given @var{sp}, @var{pc}, and @var{special}
+values.
+@end defun
+
+@defun gdb.UnwindInfo.frame_id_build_wild(sp)
+Builds frame ID from the given @var{sp} value.
+@end defun
+
+@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..fbd519d
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffer.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""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:
+	    gdb.UnwindInfo instance.
+        """
+        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..3e49766
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,743 @@
+/* Python frame unwinder interface.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "valprint.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+  struct frame_info *frame_info;
+} sniffer_info_object;
+
+/* The data we keep for the PyUnwindInfo: sniffer_info, previous
+ * frame's register set and frame ID.  */
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* gdb.SnifferInfo for the frame we are unwinding.  */
+  PyObject *sniffer_info;
+
+  /* Previous frame registers tuple (2-tuple per register).  */
+  PyObject *previous_frame_registers;
+
+  /* Its ID.  */
+  struct frame_id frame_id;
+} unwind_info_object;
+
+/* The data we keep for a frame we can unwind: frame ID and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  /* Frame ID.  */
+  struct frame_id frame_id;
+
+  /* GDB Architecture.  */
+  struct gdbarch *gdbarch;
+
+  /* Length of the `reg' array below.  */
+  int reg_count;
+
+  struct reg_info
+  {
+    /* Register number.  */
+    int number;
+
+    /* Register data bytes pointer.  */
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static PyTypeObject unwind_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Parse Python Int, saving it at the given address. Returns 1 on success,
+   0 otherwise.  */
+
+static int
+pyuw_parse_int (PyObject *pyo_int, int *valuep)
+{
+  long long_value;
+
+  if (pyo_int == NULL)
+    return 0;
+  /* Make a long logic check first.  In Python 3.x, internally, all
+     integers are represented as longs.  In Python 2.x, there is still
+     a differentiation internally between a PyInt and a PyLong.
+     Explicitly do this long check conversion first. In GDB, for
+     Python 3.x, we #ifdef PyInt = PyLong.  This check has to be done
+     first to ensure we do not lose information in the conversion
+     process.  */
+  else if (PyLong_Check (pyo_int))
+    {
+      LONGEST l = PyLong_AsLongLong (pyo_int);
+
+      if (PyErr_Occurred ())
+        return 0;
+      long_value = (long)l;
+      if (l != long_value)
+        return 0;
+    }
+  else if (PyInt_Check (pyo_int))
+    {
+      long_value = PyInt_AsLong (pyo_int);
+      if (PyErr_Occurred ())
+        return 0;
+    }
+  else
+    return 0;
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* Parses (REGNUM, REGVALUE) tuple.  Returns 1 on success, 0 otherwise.  */
+static int
+pyuw_parse_register_tuple(PyObject *pyo_reg,
+                              int *reg_num, struct value **reg_value)
+{
+
+  if (pyo_reg != NULL && PyTuple_Check (pyo_reg)
+      && PyTuple_Size (pyo_reg) == 2
+      && pyuw_parse_int (PyTuple_GetItem (pyo_reg, 0), reg_num))
+    {
+      PyObject *pyo_reg_value = PyTuple_GetItem (pyo_reg, 1);
+      if (pyo_reg_value != NULL
+          && (*reg_value = value_object_to_value (pyo_reg_value)) != NULL)
+        return 1;
+    }
+  return 0;
+}
+
+/* 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;
+
+  if (frame == NULL)
+    return PyString_FromString ("Stale SnifferInfo instance");
+  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;
+  if (((sniffer_info_object *) self)->frame_info == NULL)
+    error (_("Attempting to read register from stale SnifferInfo"));
+
+  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;
+}
+
+/* Invalidate SnifferInfo object.  */
+static void
+sniffer_info_invalidate (PyObject *pyo_sniffer_info)
+{
+  if (pyo_sniffer_info == NULL)
+    return;
+  ((sniffer_info_object *) pyo_sniffer_info)->frame_info = NULL;
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
+              struct frame_id *this_id)
+{
+  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
+  if (pyuw_debug >= 1)
+    {
+      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+      fprint_frame_id (gdb_stdlog, *this_id);
+      fprintf_unfiltered (gdb_stdlog, "\n");
+    }
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
+                    int regnum)
+{
+  cached_frame_info *cached_frame = *cache_ptr;
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  TRACE_PY_UNWIND (1, "%s(frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
+                   regnum);
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (regnum == reg_info->number)
+        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
+    }
+
+  return frame_unwind_got_optimized (this_frame, regnum);
+}
+
+/* Called by the Python interpreter to obtain string representation
+   if the UnwindInfo object.  */
+static PyObject *
+unwind_infopy_str (PyObject *self)
+{
+  PyObject *result;
+  struct ui_file *strfile = mem_fileopen ();
+  unwind_info_object *pyo_unwind_info = (unwind_info_object *) self;
+
+  fprintf_unfiltered (strfile, "Frame ID: ");
+  fprint_frame_id (strfile, pyo_unwind_info->frame_id);
+  {
+    int reg_count = PyTuple_Size (pyo_unwind_info->previous_frame_registers);
+    int i;
+    char *sep = "";
+    struct value_print_options opts;
+
+    get_user_print_options (&opts);
+    fprintf_unfiltered (strfile, "\nPrevious frame registers: (");
+    for (i = 0; i < reg_count; i++)
+      {
+        int reg_num;
+        struct value *reg_value;
+        PyObject *pyo_reg = PyTuple_GetItem (
+            pyo_unwind_info->previous_frame_registers,i);
+
+        if (pyuw_parse_register_tuple (pyo_reg, &reg_num, &reg_value))
+          {
+            fprintf_unfiltered (strfile, "%s(%d, ", sep, reg_num);
+            value_print (reg_value, strfile, &opts);
+            fprintf_unfiltered (strfile, ")");
+          }
+        else
+          fprintf_unfiltered (strfile, "(BAD)");
+        sep = ", ";
+      }
+    fprintf_unfiltered (strfile, ")");
+  }
+  {
+    char *s = ui_file_xstrdup (strfile, NULL);
+
+    result = PyString_FromString (s);
+    xfree (s);
+  }
+  ui_file_delete (strfile);
+  return result;
+}
+
+/* Convert gdb.Value object to COREADDR.  */
+static int
+pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
+{
+  struct value *value = value_object_to_value (pyo_value);
+
+  if (value == NULL)
+    return 0;
+  *addr = unpack_pointer (value_type (value), value_contents (value));
+  return 1;
+}
+
+/* Implementation of gdb.UnwindInfo.frame_id_build (self, SP, PC) -> None.  */
+
+static PyObject *
+unwind_infopy_frame_id_build (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+
+  if (!PyArg_ParseTuple (args, "OO:frame_id_build", &pyo_sp, &pyo_pc)
+       || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+       || !pyuw_value_obj_to_pointer (pyo_pc, &pc))
+    return NULL;
+
+  ((unwind_info_object *) self)->frame_id = frame_id_build (sp, pc);
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+/* Implementation of
+   gdb.UnwindInfo.frame_id_build_special (self, SP, PC, SPECIAL) -> None.  */
+
+static PyObject *
+unwind_infopy_frame_id_build_special (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  PyObject *pyo_special;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+  CORE_ADDR special;
+
+  if (!PyArg_ParseTuple (args, "OOO:frame_id_build_special",
+                          &pyo_sp, &pyo_pc, &pyo_special)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+      || !pyuw_value_obj_to_pointer (pyo_pc, &pc)
+      || !pyuw_value_obj_to_pointer (pyo_special, &special))
+    return NULL;
+
+  ((unwind_info_object *) self)->frame_id
+              = frame_id_build_special (sp, pc, special);
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+/* Implementation of gdb.UnwindInfo.frame_build_id_wild (self, SP) -> None.  */
+
+static PyObject *
+unwind_infopy_frame_id_build_wild (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  CORE_ADDR sp;
+
+  if (!PyArg_ParseTuple (args, "O:frame_id_build_wild", &pyo_sp)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp))
+    return NULL;
+
+  ((unwind_info_object *) self)->frame_id = frame_id_build_wild (sp);
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+/* Initialize new UnwindInfo object.  */
+static int
+unwind_infopy_init (PyObject *self, PyObject *args, PyObject *kwargs)
+{
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_regs;
+
+  if (PyArg_UnpackTuple (args, "__init__", 2, 2, &pyo_sniffer_info, &pyo_regs)
+      && (pyo_sniffer_info != NULL
+      && PyObject_IsInstance (pyo_sniffer_info,
+                              (PyObject *) &sniffer_info_object_type) > 0)
+      && pyo_regs != NULL
+      && PyTuple_Check (pyo_regs))
+    {
+      unwind_info_object *unwind_info = (unwind_info_object *) self;
+      unwind_info->frame_id = null_frame_id;
+      unwind_info->sniffer_info = pyo_sniffer_info;
+      Py_INCREF (pyo_sniffer_info);
+      unwind_info->previous_frame_registers = pyo_regs;
+      Py_INCREF (pyo_regs);
+      return 0;
+    }
+  return -1;
+}
+
+static void
+unwind_infopy_dealloc (PyObject *self)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  Py_XDECREF (unwind_info->sniffer_info);
+  Py_XDECREF (unwind_info->previous_frame_registers);
+  Py_TYPE (self)->tp_free (self);
+}
+
+/* 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;
+  if (PyObject_IsInstance (pyo_unwind_info,
+                           (PyObject *) &unwind_info_object_type) <= 0)
+    error (_("A Sniffer should return gdb.UnwindInfo instance."));
+
+  {
+    unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
+    PyObject *pyo_registers = unwind_info->previous_frame_registers;
+    int i;
+    int reg_count;
+    size_t cached_frame_size;
+    size_t gdb_bytes_count;
+    gdb_byte *gdb_data_free, *gdb_data_end;
+
+    if (frame_id_eq (unwind_info->frame_id, null_frame_id))
+      error (_("Sniffer should call one of the frame_id_build methods "
+               "on UnwindInfo instance before returning it."));
+    if (pyo_registers == NULL)
+      goto error;
+    if (!PyTuple_Check (pyo_registers))
+      error (_("Registers passed to UnwindInfo constructor 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->frame_id = unwind_info->frame_id;
+    cached_frame->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; i < reg_count; i++)
+      {
+        struct reg_info *reg = &(cached_frame->reg[i]);
+        struct value *value;
+        size_t data_size;
+
+        if (!pyuw_parse_register_tuple (PyTuple_GetItem (pyo_registers, i),
+                                        &reg->number, &value))
+          error (_("Python sniffer returned bad register tuple: "
+                   "item #%d is not a (reg_no, reg_data) tuple "
+                   "with integer reg_no and gdb.Value reg_data."), 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;
+      }
+  }
+
+  *cache_ptr = cached_frame;
+  discard_cleanups (cached_frame_cleanups);
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 1;
+
+error:
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 0;
+}
+
+/* Frame cache release shim.  */
+
+static void
+pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
+  xfree (cache);
+}
+
+struct pyuw_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Python 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)
+{
+  int rc;
+  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;
+  rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo",
+      (PyObject *) &sniffer_info_object_type);
+  if (rc)
+    return rc;
+
+  unwind_info_object_type.tp_new = PyType_GenericNew;
+  if (PyType_Ready (&unwind_info_object_type) < 0)
+    return -1;
+  return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
+      (PyObject *) &unwind_info_object_type);
+}
+
+static PyMethodDef sniffer_info_object_methods[] =
+{
+  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
+    "read_register (REGNUM) -> gdb.Value\n\
+Return the value of the REGNUM 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 */
+};
+
+
+static PyMethodDef unwind_info_object_methods[] =
+{
+  { "frame_id_build", unwind_infopy_frame_id_build, METH_VARARGS,
+    "frame_id_build (SP, PC) -> None\n"
+    "Build frame ID from given SP and PC." },
+  { "frame_id_build_special", unwind_infopy_frame_id_build_special,
+    METH_VARARGS,
+    "frame_id_build_special (SP, PC, SPECIAL) -> None\n"
+    "Build frame ID from given SP, PC and SPECIAL." },
+  { "frame_id_build_wild", unwind_infopy_frame_id_build_wild, METH_VARARGS,
+    "frame_id_build_wild (SP) -> None\nBuild frame ID from given SP." },
+  { NULL }  /* Sentinel */
+};
+
+static PyTypeObject unwind_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.UnwindInfo",               /* tp_name */
+  sizeof (unwind_info_object),    /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  unwind_infopy_dealloc,          /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  unwind_infopy_str,              /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
+  "GDB UnwindInfo object",        /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  unwind_info_object_methods,     /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  unwind_infopy_init,             /* 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..a8d591f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,81 @@
+# 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.
+        """
+        try:
+            bp = sniffer_info.read_register(
+                TestSniffer.AMD64_RBP).cast(self.char_ptr_t)
+            if self._read_word(bp) != bp:
+                return None
+            # Found the frame that the test program has corrupted for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places.
+            previous_bp = self._read_word(bp - 8)
+            previous_ip = self._read_word(bp + 8)
+            previous_sp = bp + 16
+            unwind_info = gdb.UnwindInfo(
+                sniffer_info,
+                ((TestSniffer.AMD64_RBP, previous_bp),
+                 (TestSniffer.AMD64_RIP, previous_ip),
+                 (TestSniffer.AMD64_RSP, previous_sp)))
+            sp = sniffer_info.read_register(TestSniffer.AMD64_RSP)
+            ip = sniffer_info.read_register(TestSniffer.AMD64_RIP)
+            unwind_info.frame_id_build(sp, ip)
+            return unwind_info
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.sniffer.register_sniffer(None, TestSniffer(), True)
+print("Python script imported")

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-11  2:22                   ` Alexander Smundak
@ 2015-03-11  8:49                     ` Andy Wingo
  2015-03-11 17:34                       ` Doug Evans
  2015-03-11 18:48                       ` Alexander Smundak
  0 siblings, 2 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-11  8:49 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Phil Muldoon, Doug Evans, gdb-patches

Hi,

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

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

I note that the patch has no interface to specify registers by name.
I still wouldn't do numbers but I see we disagree :)

> +          /* 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);

As mentioned in a previous comment, can be replaced with:

      val = get_frame_register_value (frame, regnum);

> +/* Initialize new UnwindInfo object.  */
> +static int
> +unwind_infopy_init (PyObject *self, PyObject *args, PyObject *kwargs)
> +{
> +  PyObject *pyo_sniffer_info;
> +  PyObject *pyo_regs;
> +
> +  if (PyArg_UnpackTuple (args, "__init__", 2, 2, &pyo_sniffer_info, &pyo_regs)
> +      && (pyo_sniffer_info != NULL
> +      && PyObject_IsInstance (pyo_sniffer_info,
> +                              (PyObject *) &sniffer_info_object_type) > 0)
> +      && pyo_regs != NULL
> +      && PyTuple_Check (pyo_regs))
> +    {
> +      unwind_info_object *unwind_info = (unwind_info_object *) self;
> +      unwind_info->frame_id = null_frame_id;
> +      unwind_info->sniffer_info = pyo_sniffer_info;
> +      Py_INCREF (pyo_sniffer_info);
> +      unwind_info->previous_frame_registers = pyo_regs;

Probably better to check types and values here, so the user gets a good backtrace.

What do you think about merging the SnifferInfo and UnwindInfo objects
into an EphemeralFrame object?  It would be nice to use the same nouns
on the Guile and Python sides.

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-11  8:49                     ` Andy Wingo
@ 2015-03-11 17:34                       ` Doug Evans
  2015-03-11 18:48                       ` Alexander Smundak
  1 sibling, 0 replies; 60+ messages in thread
From: Doug Evans @ 2015-03-11 17:34 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Alexander Smundak, Phil Muldoon, gdb-patches

On Wed, Mar 11, 2015 at 1:49 AM, Andy Wingo <wingo@igalia.com> wrote:
> [...]
> What do you think about merging the SnifferInfo and UnwindInfo objects
> into an EphemeralFrame object?  It would be nice to use the same nouns
> on the Guile and Python sides.

Agreed.
I'd like to see the two implementations be as identical as is
reasonably possible.
Consistency Is Good.

[Which isn't to advocate for one implementation or the other yet.
You guys are writing both the API side and the client side, so you're
more up to speed on what you need.]

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-11  8:49                     ` Andy Wingo
  2015-03-11 17:34                       ` Doug Evans
@ 2015-03-11 18:48                       ` Alexander Smundak
  2015-03-16 11:29                         ` Andy Wingo
  1 sibling, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-11 18:48 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Phil Muldoon, Doug Evans, gdb-patches

On Wed, Mar 11, 2015 at 1:49 AM, Andy Wingo <wingo@igalia.com> wrote:
>I note that the patch has no interface to specify registers by name.
>I still wouldn't do numbers but I see we disagree :)
Will do in the next revision.

> What do you think about merging the SnifferInfo and UnwindInfo objects
> into an EphemeralFrame object?  It would be nice to use the same nouns
> on the Guile and Python sides.

I prefer the separation of the input (SnifferInfo) and output (UnwindInfo).
I am surprised someone from Guile side is advocating stateful interface :-)

In fact, I'd rather avoid having modifiable UnwindInfo at all -- a sniffer can
return a 2-tuple of (<previous frame registers>, <FrameId instance>),
where FrameId instance can be created by a class method
gdb.SnifferInfo.frame_id_build_xxx(). Can we agree on that?

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-11 18:48                       ` Alexander Smundak
@ 2015-03-16 11:29                         ` Andy Wingo
  2015-03-16 12:01                           ` Andy Wingo
  2015-03-16 17:25                           ` Alexander Smundak
  0 siblings, 2 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-16 11:29 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Doug Evans, gdb-patches

Hi :)

[-pmuldoon as he has already given an LGTM]

On Wed 11 Mar 2015 19:48, Alexander Smundak <asmundak@google.com> writes:

>> What do you think about merging the SnifferInfo and UnwindInfo objects
>> into an EphemeralFrame object?  It would be nice to use the same nouns
>> on the Guile and Python sides.
>
> I prefer the separation of the input (SnifferInfo) and output (UnwindInfo).
> I am surprised someone from Guile side is advocating stateful interface :-)

I know what you mean, but because there should be a link between them --
in that saved register set should be checked for validity against the
architecture of the incoming frame -- then given that, joining them
makes sense to me.  Otherwise you'd have to pass the SnifferInfo to the
UnwindInfo constructor, which would be fine but what if we could avoid
it by joining them...

And then there's the question of what do you call the two things --
SnifferInfo is a bad name, for starters.  It's a frame, albeit some
ephemeral kind of frame.  It's literally a struct frame_info* under the
hood.  Currently you can read a register, but you might want to do other
frame things on it -- get_next_frame, get_frame_arch,
frame_relative_level, etc.  WDYT about the name EphemeralFrame ?

> In fact, I'd rather avoid having modifiable UnwindInfo at all -- a sniffer can
> return a 2-tuple of (<previous frame registers>, <FrameId instance>),
> where FrameId instance can be created by a class method
> gdb.SnifferInfo.frame_id_build_xxx(). Can we agree on that?

Regarding the return value, I think returning a 2-tuple is a bad idea
for all of the reasons I have previously mentioned and which you have
not responded to :)

  * bad error reporting (e.g. gdb.Value width doesn't match register
    width, how to map that back to some source location or program
    state)

  * bad extensibility (e.g. what about stop_reason?  what about other
    register states?)

  * bad ergonomics (is the frame id first, or is it the saved
    registers?)

On the other hand to have to give a name the result object is painful
too -- "this is an UnwindInfo and it is composed of blah blah".  For
that reason I joined the result with the ephemeral frame object, to
avoid introducing another concept.  But checking "did this unwinder
succeed" via "did the unwinder set the frame ID on this ephemeral frame"
is not nice either.

How about let's meet somewhat halfway.

  * We rename SnifferInfo to EphemeralFrame.

  * Unwinders return UnwindInfo (better name welcome) on success or
    None/#f otherwise

    * UnwindInfo takes EphemeralFrame as constructor arg

      - the EphemeralFrame must be valid

      - in practice there is only ever one EphemeralFrame alive because
        unwinding is not recursive

    * UnwindInfo also takes frame ID as positional constructor args

      - setting frame_id is the only thing an unwinder *must* do, so
        this makes an invariant "if return value is an UnwindInfo, then
        it is valid and has all necessary info"

    * UnwindInfo has add_saved_register() API (see discussion with Pedro
      here: http://article.gmane.org/gmane.comp.gdb.patches/105538)

  * After accepting an UnwindInfo as an unwinder return result,
    py-unwinders.c / scm-frame-unwinders.c marks the UnwindInfo as
    frozen so that add_saved_register() etc can't alter the state

  * continuation of unwinder call also checks that the ephemeral frame
    on the unwindinfo is valid

Example of use:

   def unwind(frame):
       if we_can_handle(frame):
           var ret = UnwindInfo(frame, fp, pc)
           ret.add_saved_register(r0)
           return ret

I will rework the Guile patch along these lines, and then hopefully I am
done reworking :)

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-16 11:29                         ` Andy Wingo
@ 2015-03-16 12:01                           ` Andy Wingo
  2015-03-16 17:25                           ` Alexander Smundak
  1 sibling, 0 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-16 12:01 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Doug Evans, gdb-patches

Responding with a thinko:

On Mon 16 Mar 2015 12:29, Andy Wingo <wingo@igalia.com> writes:

>   * After accepting an UnwindInfo as an unwinder return result,
>     py-unwinders.c / scm-frame-unwinders.c marks the UnwindInfo as
>     frozen so that add_saved_register() etc can't alter the state

No need -- marking the associated EphemeralFrame as invalid will prevent
add_saved_register() from working.

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-16 11:29                         ` Andy Wingo
  2015-03-16 12:01                           ` Andy Wingo
@ 2015-03-16 17:25                           ` Alexander Smundak
  2015-03-17  8:57                             ` Andy Wingo
  1 sibling, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-16 17:25 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Doug Evans, gdb-patches

> How about let's meet somewhat halfway.
>
>   * We rename SnifferInfo to EphemeralFrame.
>
>   * Unwinders return UnwindInfo (better name welcome) on success or
>     None/#f otherwise
>
>     * UnwindInfo takes EphemeralFrame as constructor arg
>
>       - the EphemeralFrame must be valid
>
>       - in practice there is only ever one EphemeralFrame alive because
>         unwinding is not recursive
>
>     * UnwindInfo also takes frame ID as positional constructor args
>
>       - setting frame_id is the only thing an unwinder *must* do, so
>         this makes an invariant "if return value is an UnwindInfo, then
>         it is valid and has all necessary info"
>
>     * UnwindInfo has add_saved_register() API (see discussion with Pedro
>       here: http://article.gmane.org/gmane.comp.gdb.patches/105538)
>
>   * After accepting an UnwindInfo as an unwinder return result,
>     py-unwinders.c / scm-frame-unwinders.c marks the UnwindInfo as
>     frozen so that add_saved_register() etc can't alter the state
>
>   * continuation of unwinder call also checks that the ephemeral frame
>     on the unwindinfo is valid
>
> Example of use:
>
>    def unwind(frame):
>        if we_can_handle(frame):
>            var ret = UnwindInfo(frame, fp, pc)
>            ret.add_saved_register(r0)
>            return ret
>
> I will rework the Guile patch along these lines, and then hopefully I am
> done reworking :)

I'd like to propose one improvement on the Python side: UnwinderInfo
is constructed by a frame method instead of an implicit constructor.
I.e., frame.create_frame_with_id(sp, pc) returns UnwindInfo instance
whose ID is the result of calling GDB's frame_id_build(sp, pc),
frame.create_frame_with_id_wild(sp) returns UnwindInfo instance
whose ID is the results of calling frame_id_build_wild(sp), etc.

The example above would then look as follows:
  def unwind(frame):
    if we_can_handle(frame):
      unwind_info = frame.create_frame_with_id(sp, pc)
      unwind_info.set_previous_frame_register("r0", r0)
      unwind_info.set_previous_frame_register(...)
      return unwind_info
    else
      return None

Would this work for Guile?

As to the class of an object passed to a sniffer, how about calling it
FrameData? Note that it's not very important from the user's point of
view as sniffer code does not ever reference it by name.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-16 17:25                           ` Alexander Smundak
@ 2015-03-17  8:57                             ` Andy Wingo
  2015-03-17 19:48                               ` Alexander Smundak
  2015-03-17 22:21                               ` Doug Evans
  0 siblings, 2 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-17  8:57 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Doug Evans, gdb-patches

On Mon 16 Mar 2015 18:25, Alexander Smundak <asmundak@google.com> writes:

> I'd like to propose one improvement on the Python side: UnwinderInfo
> is constructed by a frame method instead of an implicit constructor.
> I.e., frame.create_frame_with_id(sp, pc) returns UnwindInfo instance
> whose ID is the result of calling GDB's frame_id_build(sp, pc),
> frame.create_frame_with_id_wild(sp) returns UnwindInfo instance
> whose ID is the results of calling frame_id_build_wild(sp), etc.
>
> The example above would then look as follows:
>   def unwind(frame):
>     if we_can_handle(frame):
>       unwind_info = frame.create_frame_with_id(sp, pc)
>       unwind_info.set_previous_frame_register("r0", r0)
>       unwind_info.set_previous_frame_register(...)
>       return unwind_info
>     else
>       return None

Looks great to me :)  Thank you for the consideration!

I might consider naming "set_previous_frame_register" as
"add_saved_register", in anticipation of a possible
add_unavailable_register(), add_unmodified_register(), etc, but that is
just a minor nit.

> As to the class of an object passed to a sniffer, how about calling it
> FrameData? Note that it's not very important from the user's point of
> view as sniffer code does not ever reference it by name.

It's true that from user code it barely matters to Python, but Scheme's
monomorphic flavor makes these things more apparent:

  (frame-data-read-register frame "r0")

This doesn't read so well to me -- is it "read-register" on a
"frame-data", or is it "data-read-register" on a "frame" ?  A weak point
but "ephemeral-frame-read-register" avoids the question.

I also think that "ephemeral frame" is easier to document as a concept
than the more generic "frame data", and corresponds better to what is
happening in GDB.  YMMV, though.

As an aside, it seems to me that if we can avoid it, the word "sniffer"
should not enter the documentation or the API.  The Python and Guile
APIs don't just sniff, they do the whole of the unwinding operation, so
it's more clear to call them "unwinders".

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-17  8:57                             ` Andy Wingo
@ 2015-03-17 19:48                               ` Alexander Smundak
  2015-03-17 21:37                                 ` Alexander Smundak
  2015-03-18 23:25                                 ` Doug Evans
  2015-03-17 22:21                               ` Doug Evans
  1 sibling, 2 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-03-17 19:48 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Doug Evans, gdb-patches

On Tue, Mar 17, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
> On Mon 16 Mar 2015 18:25, Alexander Smundak <asmundak@google.com> writes:
>
>> I'd like to propose one improvement on the Python side: UnwinderInfo
>> is constructed by a frame method instead of an implicit constructor.
>> I.e., frame.create_frame_with_id(sp, pc) returns UnwindInfo instance
>> whose ID is the result of calling GDB's frame_id_build(sp, pc),
>> frame.create_frame_with_id_wild(sp) returns UnwindInfo instance
>> whose ID is the results of calling frame_id_build_wild(sp), etc.
>>
>> The example above would then look as follows:
>>   def unwind(frame):
>>     if we_can_handle(frame):
>>       unwind_info = frame.create_frame_with_id(sp, pc)
>>       unwind_info.set_previous_frame_register("r0", r0)
>>       unwind_info.set_previous_frame_register(...)
>>       return unwind_info
>>     else
>>       return None
>
> Looks great to me :)  Thank you for the consideration!
I realized after writing my proposal that 'create_frame_xxx'
misguides about the type of the returned object. The new
name candidate is `unwind_info_with_id' (omitting 'create'
prefix, too).

> I might consider naming "set_previous_frame_register" as
> "add_saved_register", in anticipation of a possible
> add_unavailable_register(), add_unmodified_register(), etc, but that is
> just a minor nit.
It does not reflect that we are setting previous frame's register.

> I also think that "ephemeral frame" is easier to document as a concept
> than the more generic "frame data", and corresponds better to what is
> happening in GDB.  YMMV, though.
EphemeralFrame emphasizes that the object is short-lived. Maybe it's
better to put forward what this object is for and name it InspectedFrame?

> As an aside, it seems to me that if we can avoid it, the word "sniffer"
> should not enter the documentation or the API.  The Python and Guile
> APIs don't just sniff, they do the whole of the unwinding operation, so
> it's more clear to call them "unwinders".
'Sniffer' comes from the "GDB Internals" document; I am not too fond of
it, but I thought it's desirable to use similar terminology.

IMHO we reached an agreement on API.

I would really like the reviewers to express their opinions about the naming
at this point. To summarize:
* We need a name for the entity passed to a sniffer. This entity provides
access to the registers of the frame being sniffed, and has factory methods
to create instances returned by a sniffer.
* We need a name for the entity returned by a sniffer.
* Should the term 'sniffer' be used in the API and documentation describing
implementing frame unwinding in GDB extensions?

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-17 19:48                               ` Alexander Smundak
@ 2015-03-17 21:37                                 ` Alexander Smundak
  2015-03-18  8:54                                   ` Andy Wingo
  2015-03-18 23:25                                 ` Doug Evans
  1 sibling, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-17 21:37 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Doug Evans, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 2784 bytes --]

Here is the updated revision of the patch, synced with upstream and with new API
(the input to a sniffer is still named SnifferInfo, and the output is
named UnwindInfo).

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


        * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
        (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
        (py-unwind.o): New recipe.
        * NEWS: mention Python frame unwinding.
        * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add sniffers.py.
        * doc/python.texi (Writing a Frame Unwinder in Python): Add
        section.
        * python/lib/gdb/__init__.py (packages): Add frame_sniffers list.
        * python/lib/gdb/command/sniffers.py: New file, implements GDB
        commands to list/enable/disable Python sniffers.
        * python/lib/gdb/function/sniffers.py: New file, implements
        execute_sniffers function.
        * python/lib/gdb/sniffer.py: New file, contains Sniffer class and
        register_sniffer function.
        * python/py-objfile.c (objfile_object): Add frame_sniffers field.
        (objfpy_dealloc): Decrement frame_sniffers reference count.
        (objfpy_initialize): Create frame_sniffers list.
        (objfpy_get_frame_sniffers): Implement Objfile.frame_sniffers
        getter.
        (objfpy_set_frame_sniffers): Implement Objfile.frame_sniffers
        setter.
        (objfile_getset): Add frame_sniffers attribute to Objfile.
        * python/py-progspace.c (pspace_object): Add frame_sniffers field.
        (pspy_dealloc): Decrement frame_sniffers reference count.
        (pspy_initialize): Create frame_sniffers list.
        (pspy_get_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        getter.
        (pspy_set_frame_sniffers): Implement gdb.Progspace.frame_sniffers
        setter.
        (pspy_getset): Add frame_sniffers attribute to gdb.Progspace.
        * python/py-unwind.c: New file, implements Python frame sniffers
        interface.
        * python/python-internal.h (pspy_get_name_sniffers): New prototype.
        (objpy_get_frame_sniffers): New prototype.
        (gdbpy_initialize_unwind): New prototype.
        * python/python.c (gdbpy_apply_type_printers): Call
        gdbpy_initialize_unwind.

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

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

[-- Attachment #2: patch7.diff --]
[-- Type: text/plain, Size: 69383 bytes --]

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index dbace2d..0bd3738 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -398,6 +398,7 @@ SUBDIR_PYTHON_OBS = \
 	py-symtab.o \
 	py-threadevent.o \
 	py-type.o \
+	py-unwind.o \
 	py-utils.o \
 	py-value.o \
 	py-varobj.o
@@ -437,6 +438,7 @@ SUBDIR_PYTHON_SRCS = \
 	python/py-symtab.c \
 	python/py-threadevent.c \
 	python/py-type.c \
+	python/py-unwind.c \
 	python/py-utils.c \
 	python/py-value.c \
 	python/py-varobj.c
@@ -2622,6 +2624,10 @@ py-type.o: $(srcdir)/python/py-type.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-type.c
 	$(POSTCOMPILE)
 
+py-unwind.o: $(srcdir)/python/py-unwind.c
+	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-unwind.c
+	$(POSTCOMPILE)
+
 py-utils.o: $(srcdir)/python/py-utils.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-utils.c
 	$(POSTCOMPILE)
diff --git a/gdb/NEWS b/gdb/NEWS
index bda4a35..ac994d9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -12,6 +12,7 @@
   ** gdb.Objfile objects have a new attribute "username",
      which is the name of the objfile as specified by the user,
      without, for example, resolving symlinks.
+  ** You can now write frame unwinders in Python.
 
 * New commands
 
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index c01b86d..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..6cf36c9 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,125 @@ 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 an
+object (an instance of gdb.UnwindInfo class) describing it. If a
+sniffer does not recognize a frame, it should return @code{None}.
+
+@subheading Sniffer Input
+
+An object passed to a sniffer (a @code{SnifferInfo} instance provides
+a method to read frame's registers:
+
+@defun SnifferInfo.read_register (reg)
+This method returns the contents of the register @var{regn} in the
+frame as a @code{gdb.Value} object. @var{reg} can be either a register
+number or a register name; values are platform-specific. They are
+usually found in the corresponding xxx-@code{tdep.h} file in the gdb
+source tree.
+@end defun
+
+It also provides several factory methods. If a sniffer recognizes the
+frame, it should invoke one of them to create a gdb.UnwindInfo
+instance to be returned to GDB:
+
+@defun SnifferInfo.unwind_info_with_id(sp, pc)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{sp} and @var{pc} values. This is the most common way of creating
+instance result.
+@end defun
+
+@defun SnifferInfo.frame_id_build_special(sp, pc, special)
+Returns a new @code{gdb.UnwindInfo} instance identitified by given
+@var{sp}, @var{pc}, and @var{special} values.
+@end defun
+
+@defun gdb.UnwindInfo.frame_id_build_wild(sp)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{sp} value.
+@end defun
+
+@subheading Sniffer Output: UnwindInfo
+
+A @code{gdb.UnwindInfo} object can be constructed by one of the
+methods described above. Use the following method to set the caller
+frame's registers:
+
+@defun gdb.UnwindInfo.set_previous_frame_register(reg, value)
+@var{reg} identifies the register. It can be a number or a name, just
+as for the @code{SnifferInfo.read_register} method above. @var{value}
+is a register value (a @code{gdb.Value} object).
+
+@subheading Sniffer Skeleton Code
+
+GDB comes with the module containing the base @code{Sniffer} class.
+Derive your sniffer class from it and structure the code 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
+        # Create sniffer result. The most common way to achieve this is
+        # to find SP (stack pointer) and PC (program counter) values
+        # in the current frame and then call unwind_info_with_id method:
+        unwind_info = sniffer_info.unwind_info_with_id(sp, pc)
+
+        # Find the values of the registers in the caller's frame and 
+        # save them in the result:
+        unwind_info.set_previous_frame_register(<register>, <value>)
+
+        # Return the result:
+        return unwind_instance
+
+@end smallexample
+
+@subheading Registering a 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..fbd519d
--- /dev/null
+++ b/gdb/python/lib/gdb/sniffer.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""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:
+	    gdb.UnwindInfo instance.
+        """
+        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 157d200..48e6468 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;
 
@@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o)
   Py_XDECREF (self->dict);
   Py_XDECREF (self->printers);
   Py_XDECREF (self->frame_filters);
+  Py_XDECREF (self->frame_sniffers);
   Py_XDECREF (self->type_printers);
   Py_XDECREF (self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_sniffers = PyList_New (0);
+  if (self->frame_sniffers == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
   return 0;
 }
 
+/* Return the frame 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 *
@@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", objfpy_get_frame_filters,
     objfpy_set_frame_filters, "Frame Filters.", NULL },
+  { "frame_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 93fbc14..d59ff4d 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..af1c72e
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,831 @@
+/* Python frame unwinder interface.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "valprint.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* Frame we are unwinding.  */
+  struct frame_info *frame_info;
+
+  /* Its architecture, passed by the sniffer caller.  */
+  struct gdbarch *gdbarch;
+} sniffer_info_object;
+
+/* The data we keep for the PyUnwindInfo: sniffer_info, previous
+ * frame's register set and frame ID.  */
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* gdb.SnifferInfo for the frame we are unwinding.  */
+  PyObject *sniffer_info;
+
+  /* Its ID.  */
+  struct frame_id frame_id;
+
+  /* Previous frame registers array.  */
+  struct reg_pydata
+  {
+    int number;
+    PyObject *value;
+  } *prev_frame_regs;
+
+  /* The current size of the array above.  */
+  int prev_frame_regs_size;
+
+  /* And its capacity.  */
+  int prev_frame_regs_capacity;
+
+} unwind_info_object;
+
+/* The data we keep for a frame we can unwind: frame ID and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  /* Frame ID.  */
+  struct frame_id frame_id;
+
+  /* GDB Architecture.  */
+  struct gdbarch *gdbarch;
+
+  /* Length of the `reg' array below.  */
+  int reg_count;
+
+  struct reg_info
+  {
+    /* Register number.  */
+    int number;
+
+    /* Register data bytes pointer.  */
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static PyTypeObject unwind_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Parse Python Int, saving it at the given address. Returns 1 on success,
+   0 otherwise.  */
+
+static int
+pyuw_parse_int (PyObject *pyo_int, int *valuep)
+{
+  long long_value;
+
+  if (pyo_int == NULL)
+    return 0;
+
+  /* Make a long logic check first.  In Python 3.x, internally, all
+     integers are represented as longs.  In Python 2.x, there is still
+     a differentiation internally between a PyInt and a PyLong.
+     Explicitly do this long check conversion first. In GDB, for
+     Python 3.x, we #ifdef PyInt = PyLong.  This check has to be done
+     first to ensure we do not lose information in the conversion
+     process.  */
+  else if (PyLong_Check (pyo_int))
+    {
+      LONGEST l = PyLong_AsLongLong (pyo_int);
+
+      if (PyErr_Occurred ())
+        return 0;
+      long_value = (long)l;
+      if (l != long_value)
+        return 0;
+    }
+  else if (PyInt_Check (pyo_int))
+    {
+      long_value = PyInt_AsLong (pyo_int);
+      if (PyErr_Occurred ())
+        return 0;
+    }
+  else
+    return 0;
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* Parses register id, which can be either a number or a name.
+   Returns 1 on success, 0 otherwise.  */
+
+static int
+pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
+                        int *reg_num)
+{
+  if (pyo_reg_id == NULL)
+    return 0;
+  if (PyString_Check (pyo_reg_id))
+    {
+      const char *reg_name = PyString_AS_STRING (pyo_reg_id);
+      if (reg_name == NULL)
+        return 0;
+      *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name,
+                                              strlen (reg_name));
+      return *reg_num >= 0;
+    }
+  else if (pyuw_parse_int (pyo_reg_id, reg_num))
+    return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
+  else
+    return 0;
+}
+
+/* Convert gdb.Value object to COREADDR.  */
+
+static int
+pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
+{
+  struct value *value = value_object_to_value (pyo_value);
+
+  if (value == NULL)
+    return 0;
+  *addr = unpack_pointer (value_type (value), value_contents (value));
+  return 1;
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the UnwindInfo object.  */
+
+static PyObject *
+unwind_infopy_str (PyObject *self)
+{
+  PyObject *result;
+  struct ui_file *strfile = mem_fileopen ();
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  sniffer_info_object *sniffer_info
+      = (sniffer_info_object *) (unwind_info->sniffer_info);
+
+  fprintf_unfiltered (strfile, "Frame ID: ");
+  fprint_frame_id (strfile, unwind_info->frame_id);
+  {
+    int i;
+    char *sep = "";
+    struct value_print_options opts;
+
+    get_user_print_options (&opts);
+    fprintf_unfiltered (strfile, "\nPrevious frame registers: (");
+    for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
+      {
+        struct value *value
+            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
+
+        fprintf_unfiltered (strfile, "%s(%d, ", sep,
+                            unwind_info->prev_frame_regs[i].number);
+        if (value != NULL)
+          {
+            value_print (value, strfile, &opts);
+            fprintf_unfiltered (strfile, ")");
+          }
+        else
+          fprintf_unfiltered (strfile, "<BAD>)");
+        sep = ", ";
+      }
+    fprintf_unfiltered (strfile, ")");
+  }
+  {
+    char *s = ui_file_xstrdup (strfile, NULL);
+
+    result = PyString_FromString (s);
+    xfree (s);
+  }
+  ui_file_delete (strfile);
+  return result;
+}
+
+/* Create UnwindInfo instance for given SnifferInfo and frame ID.  */
+
+static PyObject *
+pyuw_create_unwind_info (PyObject *pyo_sniffer_info,
+                         struct frame_id frame_id)
+{
+  unwind_info_object *unwind_info
+      = PyObject_New (unwind_info_object, &unwind_info_object_type);
+
+  if (((sniffer_info_object *) pyo_sniffer_info)->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to use stale SnifferInfo");
+      return NULL;
+    }
+  unwind_info->frame_id = frame_id;
+  Py_INCREF (pyo_sniffer_info);
+  unwind_info->sniffer_info = pyo_sniffer_info;
+  unwind_info->prev_frame_regs_size = 0;
+  unwind_info->prev_frame_regs_capacity = 4;
+  unwind_info->prev_frame_regs =
+      xmalloc (unwind_info->prev_frame_regs_capacity *
+               sizeof (unwind_info->prev_frame_regs[0]));
+  return (PyObject *) unwind_info;
+}
+
+/* The implementation of
+   gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None.  */
+
+static PyObject *
+unwind_infopy_set_previous_frame_register (PyObject *self, PyObject *args)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  sniffer_info_object *sniffer_info
+      = (sniffer_info_object *) (unwind_info->sniffer_info);
+  PyObject *pyo_reg_id;
+  PyObject *pyo_reg_value;
+  int regnum;
+
+  if (sniffer_info->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to read register from stale SnifferInfo");
+      return NULL;
+    }
+  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
+                          &pyo_reg_id, &pyo_reg_value))
+    return NULL;
+  if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+  {
+    struct value *value;
+    size_t data_size;
+
+    if (pyo_reg_value == NULL
+      || (value = value_object_to_value (pyo_reg_value)) == NULL)
+      {
+        PyErr_SetString (PyExc_ValueError, "Bad register value");
+        return NULL;
+      }
+    data_size = register_size (sniffer_info->gdbarch, regnum);
+    if (data_size != TYPE_LENGTH (value_enclosing_type (value)))
+      {
+        PyErr_Format (
+            PyExc_ValueError,
+            "The value of the register returned by the Python "
+            "sniffer has unexpected size: %u instead of %u.",
+            (unsigned) (TYPE_LENGTH (value_enclosing_type (value))),
+            (unsigned) data_size);
+        return NULL;
+      }
+  }
+  {
+    int i;
+
+    for (i = 0;
+         (i < unwind_info->prev_frame_regs_size)
+             && regnum != unwind_info->prev_frame_regs[i].number; i++)
+      ;
+    if (i < unwind_info->prev_frame_regs_size)
+      Py_DECREF (unwind_info->prev_frame_regs[i].value);
+    else
+      {
+        if (i >= unwind_info->prev_frame_regs_capacity)
+          {
+            unwind_info->prev_frame_regs_capacity *= 2;
+            unwind_info->prev_frame_regs = xrealloc
+                (unwind_info->prev_frame_regs,
+                 unwind_info->prev_frame_regs_capacity
+                 * sizeof (unwind_info->prev_frame_regs[0]));
+          }
+        unwind_info->prev_frame_regs_size++;
+        unwind_info->prev_frame_regs[i].number = regnum;
+      }
+    Py_INCREF (pyo_reg_value);
+    unwind_info->prev_frame_regs[i].value = pyo_reg_value;
+  }
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+/* UnwindInfo cleanup.  */
+
+static void
+unwind_infopy_dealloc (PyObject *self)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  int i;
+
+  Py_XDECREF (unwind_info->sniffer_info);
+  for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
+      Py_DECREF (unwind_info->prev_frame_regs[i].value);
+  xfree (unwind_info->prev_frame_regs);
+  Py_TYPE (self)->tp_free (self);
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
+
+  if (frame == NULL)
+    return PyString_FromString ("Stale SnifferInfo instance");
+  return PyString_FromFormat ("SP=%s,PC=%s",
+                              core_addr_to_string_nz (get_frame_sp (frame)),
+                              core_addr_to_string_nz (get_frame_pc (frame)));
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of register REGNUM as gdb.Value instance.  */
+
+static PyObject *
+sniffer_infopy_read_register (PyObject *self, PyObject *args)
+{
+  volatile struct gdb_exception except;
+  int regnum;
+  struct value *val = NULL;
+  sniffer_info_object *sniffer_info = (sniffer_info_object *) self;
+  PyObject *pyo_reg_id;
+
+  if (sniffer_info->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to read register from stale SnifferInfo");
+      return NULL;
+    }
+
+  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
+    return NULL;
+  if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+  TRY
+    {
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      val = get_frame_register_value (
+          ((sniffer_info_object *) self)->frame_info, regnum);
+      if (val == NULL)
+        PyErr_Format (PyExc_ValueError,
+                      "Cannot read register %d from frame.",
+                      regnum);
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+  END_CATCH
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id (self, SP, PC) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+
+  if (!PyArg_ParseTuple (args, "OO:unwind_info_with_id", &pyo_sp, &pyo_pc)
+       || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+       || !pyuw_value_obj_to_pointer (pyo_pc, &pc))
+    return NULL;
+
+  return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id_special (self, SP, PC, SPECIAL) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id_special (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  PyObject *pyo_special;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+  CORE_ADDR special;
+
+  if (!PyArg_ParseTuple (args, "OOO:unwind_info_with_id_special",
+                          &pyo_sp, &pyo_pc, &pyo_special)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+      || !pyuw_value_obj_to_pointer (pyo_pc, &pc)
+      || !pyuw_value_obj_to_pointer (pyo_special, &special))
+    return NULL;
+
+  return pyuw_create_unwind_info (self,
+                                  frame_id_build_special (sp, pc, special));
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id_wild (self, SP) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id_wild (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  CORE_ADDR sp;
+
+  if (!PyArg_ParseTuple (args, "O:unwind_info_with_id_wild", &pyo_sp)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp))
+    return NULL;
+
+  return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct gdbarch *gdbarch,
+                                   struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  sniffer_info->gdbarch = gdbarch;
+  sniffer_info->frame_info = frame;
+  return (PyObject *) sniffer_info;
+}
+
+/* Invalidate SnifferInfo object.  */
+static void
+sniffer_info_invalidate (PyObject *pyo_sniffer_info)
+{
+  if (pyo_sniffer_info == NULL)
+    return;
+  ((sniffer_info_object *) pyo_sniffer_info)->frame_info = NULL;
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
+              struct frame_id *this_id)
+{
+  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
+  if (pyuw_debug >= 1)
+    {
+      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+      fprint_frame_id (gdb_stdlog, *this_id);
+      fprintf_unfiltered (gdb_stdlog, "\n");
+    }
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
+                    int regnum)
+{
+  cached_frame_info *cached_frame = *cache_ptr;
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  TRACE_PY_UNWIND (1, "%s (frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
+                   regnum);
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (regnum == reg_info->number)
+        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
+    }
+
+  return frame_unwind_got_optimized (this_frame, regnum);
+}
+
+/* Frame sniffer dispatch.  */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch;
+  struct cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdbarch = (struct gdbarch *) (self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  TRACE_PY_UNWIND (3, "%s (SP=%s, PC=%s)\n", __FUNCTION__,
+                   paddress (gdbarch, get_frame_sp (this_frame)),
+                   paddress (gdbarch, get_frame_pc (this_frame)));
+  pyo_sniffer_info = frame_info_to_sniffer_info_object (gdbarch, this_frame);
+  if (pyo_sniffer_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_sniffer_info);
+
+  if ((pyo_module = PyImport_ImportModule ("gdb.function.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;
+  if (PyObject_IsInstance (pyo_unwind_info,
+                           (PyObject *) &unwind_info_object_type) <= 0)
+    error (_("A Sniffer should return gdb.UnwindInfo instance."));
+
+  {
+    unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
+    int i;
+    int reg_count;
+    size_t cached_frame_size;
+    size_t gdb_bytes_count;
+    gdb_byte *gdb_data_free, *gdb_data_end;
+
+    /* Figure out how much space we need to allocate.  */
+    reg_count = unwind_info->prev_frame_regs_size;
+    gdb_bytes_count = reg_count * MAX_REGISTER_SIZE;
+    cached_frame_size = sizeof (*cached_frame) +
+        reg_count * sizeof (cached_frame->reg[0]) +
+        gdb_bytes_count * sizeof (gdb_byte);
+
+    cached_frame = xmalloc (cached_frame_size);
+    cached_frame_cleanups = make_cleanup (xfree, cached_frame);
+    /* Allocations after this point will be discarded!  */
+
+    gdb_data_end = (gdb_byte *) ((char *) cached_frame + cached_frame_size);
+    gdb_data_free = gdb_data_end - gdb_bytes_count;
+
+    cached_frame->gdbarch = gdbarch;
+    cached_frame->frame_id = unwind_info->frame_id;
+    cached_frame->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; i < reg_count; i++)
+      {
+        struct reg_info *reg = &(cached_frame->reg[i]);
+        struct value *value
+            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
+        size_t data_size;
+
+        reg->number = unwind_info->prev_frame_regs[i].number;
+        /* `value' validation was done before, just assert.  */
+        gdb_assert (value != NULL);
+        data_size = register_size (gdbarch, reg->number);
+        gdb_assert (data_size == TYPE_LENGTH (value_enclosing_type (value)));
+        /* Should not overflow  the buffer.  */
+        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+        memcpy (gdb_data_free, value_contents (value), data_size);
+        reg->data = gdb_data_free;
+        gdb_data_free += data_size;
+      }
+  }
+
+  *cache_ptr = cached_frame;
+  discard_cleanups (cached_frame_cleanups);
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 1;
+
+error:
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 0;
+}
+
+/* Frame cache release shim.  */
+
+static void
+pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
+  xfree (cache);
+}
+
+struct pyuw_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Python 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)
+{
+  int rc;
+  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;
+  rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo",
+      (PyObject *) &sniffer_info_object_type);
+  if (rc)
+    return rc;
+
+  if (PyType_Ready (&unwind_info_object_type) < 0)
+    return -1;
+  return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
+      (PyObject *) &unwind_info_object_type);
+}
+
+static PyMethodDef sniffer_info_object_methods[] =
+{
+  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
+    "read_register (REGNUM) -> gdb.Value\n"
+    "Return the value of the REGNUM in the frame." },
+  { "unwind_info_with_id",
+    sniffer_infopy_unwind_info_with_id, METH_VARARGS,
+    "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP and PC registers \n"
+    "to identify the frame." },
+  { "unwind_info_with_id_special",
+    sniffer_infopy_unwind_info_with_id_special, METH_VARARGS,
+    "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL "
+    "registers to identify the frame." },
+  { "unwind_info_with_id_wild",
+    sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS,
+    "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP register to \n"
+    "identify the frame." },
+  {NULL}  /* Sentinel */
+};
+
+static PyTypeObject sniffer_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.SnifferInfo",              /* tp_name */
+  sizeof (sniffer_info_object),   /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  0,                              /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  sniffer_infopy_str,             /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT,             /* tp_flags */
+  "GDB snifferInfo object",       /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  sniffer_info_object_methods,    /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
+
+
+static PyMethodDef unwind_info_object_methods[] =
+{
+  { "set_previous_frame_register",
+    unwind_infopy_set_previous_frame_register, METH_VARARGS,
+    "set_previous_frame_register (REG, VALUE) -> None\n"
+    "Set the value of the REG in the previous frame to VALUE." },
+  { NULL }  /* Sentinel */
+};
+
+static PyTypeObject unwind_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.UnwindInfo",               /* tp_name */
+  sizeof (unwind_info_object),    /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  unwind_infopy_dealloc,          /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  unwind_infopy_str,              /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
+  "GDB UnwindInfo object",        /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  unwind_info_object_methods,     /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 4c4d32a..13e0a24 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -391,12 +391,14 @@ PyObject *pspace_to_pspace_object (struct program_space *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *pspy_get_printers (PyObject *, void *);
 PyObject *pspy_get_frame_filters (PyObject *, void *);
+PyObject *pspy_get_frame_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);
 
@@ -491,6 +493,8 @@ int gdbpy_initialize_arch (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_xmethods (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_unwind (void)
+  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 
 struct cleanup *make_cleanup_py_decref (PyObject *py);
 struct cleanup *make_cleanup_py_xdecref (PyObject *py);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 58c7c92..1da63fd 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1821,7 +1821,8 @@ message == an error message without a stack will be printed."),
       || gdbpy_initialize_new_objfile_event ()  < 0
       || gdbpy_initialize_clear_objfiles_event ()  < 0
       || gdbpy_initialize_arch () < 0
-      || gdbpy_initialize_xmethods () < 0)
+      || gdbpy_initialize_xmethods () < 0
+      || gdbpy_initialize_unwind () < 0)
     goto fail;
 
   gdbpy_to_string_cst = PyString_FromString ("to_string");
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
new file mode 100644
index 0000000..8c1d935
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
@@ -0,0 +1,24 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main (void)
+{
+  int i = 0;
+
+  return i; /* next-line */
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
new file mode 100644
index 0000000..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..d98da3c
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+from gdb.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.
+        """
+        try:
+            # NOTE: the registers in Sniffer API can be referenced
+            # either by name or by number. The code below uses both
+            # to achieve more coverage.
+            bp = sniffer_info.read_register("rbp").cast(self.char_ptr_t)
+            if self._read_word(bp) != bp:
+                return None
+            # Found the frame that the test program has corrupted for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places.
+            previous_bp = self._read_word(bp - 8)
+            previous_ip = self._read_word(bp + 8)
+            previous_sp = bp + 16
+
+            sp = sniffer_info.read_register(TestSniffer.AMD64_RSP)
+            ip = sniffer_info.read_register(TestSniffer.AMD64_RIP)
+            unwind_info = sniffer_info.unwind_info_with_id(sp, ip)
+            unwind_info.set_previous_frame_register(TestSniffer.AMD64_RBP,
+                                                    previous_bp)
+            unwind_info.set_previous_frame_register("rip", previous_ip)
+            unwind_info.set_previous_frame_register("rsp", previous_sp)
+            return unwind_info
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.sniffer.register_sniffer(None, TestSniffer(), True)
+print("Python script imported")

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-17  8:57                             ` Andy Wingo
  2015-03-17 19:48                               ` Alexander Smundak
@ 2015-03-17 22:21                               ` Doug Evans
  2015-03-18  8:57                                 ` Andy Wingo
  1 sibling, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-03-17 22:21 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Alexander Smundak, gdb-patches, guile-devel

[+ guile-devel, in case they have an opinion on the spelling of
frame-data-read-register vs frame-data:read-register]

On Tue, Mar 17, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
>> As to the class of an object passed to a sniffer, how about calling it
>> FrameData? Note that it's not very important from the user's point of
>> view as sniffer code does not ever reference it by name.
>
> It's true that from user code it barely matters to Python, but Scheme's
> monomorphic flavor makes these things more apparent:
>
>   (frame-data-read-register frame "r0")
>
> This doesn't read so well to me -- is it "read-register" on a
> "frame-data", or is it "data-read-register" on a "frame" ?  A weak point
> but "ephemeral-frame-read-register" avoids the question.

As food for discussion,
I know some people use foo:bar in Scheme to separate
the object "foo" from the operation on it "bar".
-> frame-data:read-register
I like having some separator, but I went with what
I thought was the preferred spelling (all -'s).
It's not too late to change gdb/guile to use foo:bar throughout (IMO),
but the door is closing.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-17 21:37                                 ` Alexander Smundak
@ 2015-03-18  8:54                                   ` Andy Wingo
  2015-03-18 22:57                                     ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-18  8:54 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Doug Evans, gdb-patches

On Tue 17 Mar 2015 22:37, Alexander Smundak <asmundak@google.com> writes:

> +/* The implementation of
> +   gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None.  */
> +
> +static PyObject *
> +unwind_infopy_set_previous_frame_register (PyObject *self, PyObject *args)
> +{
> +  unwind_info_object *unwind_info = (unwind_info_object *) self;
> +  sniffer_info_object *sniffer_info
> +      = (sniffer_info_object *) (unwind_info->sniffer_info);
> +  PyObject *pyo_reg_id;
> +  PyObject *pyo_reg_value;
> +  int regnum;
> +
> +  if (sniffer_info->frame_info == NULL)
> +    {
> +      PyErr_SetString (PyExc_ValueError,
> +                       "Attempting to read register from stale SnifferInfo");
> +      return NULL;
> +    }

Nit: we are setting the register here.

> +int
> +gdbpy_initialize_unwind (void)
> +{
> +  int rc;
> +  add_setshow_zuinteger_cmd
> +      ("py-unwind", class_maintenance, &pyuw_debug,
> +        _("Set Python unwinder debugging."),
> +        _("Show Python unwinder debugging."),
> +        _("When non-zero, Pythin unwinder debugging is enabled."),

"Python"

> +static PyMethodDef sniffer_info_object_methods[] =
> +{
> +  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
> +    "read_register (REGNUM) -> gdb.Value\n"
> +    "Return the value of the REGNUM in the frame." },
> +  { "unwind_info_with_id",
> +    sniffer_infopy_unwind_info_with_id, METH_VARARGS,
> +    "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n"
> +    "Construct UnwindInfo for this FrameData, using given SP and PC registers \n"
> +    "to identify the frame." },
> +  { "unwind_info_with_id_special",
> +    sniffer_infopy_unwind_info_with_id_special, METH_VARARGS,
> +    "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n"
> +    "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL "
> +    "registers to identify the frame." },
> +  { "unwind_info_with_id_wild",
> +    sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS,
> +    "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n"
> +    "Construct UnwindInfo for this FrameData, using given SP register to \n"
> +    "identify the frame." },
> +  {NULL}  /* Sentinel */
> +};

Still no support for register names.

> +import gdb
> +from gdb.sniffer import Sniffer
> +
> +class TestSniffer(Sniffer):

I still think it's much better to call these "unwinders".  You say that
it's the terminology that GDB uses but that's not really the case --
"sniffer" names part of the unwinder interface, which Python and Guile
implement the whole of.  You chose "unwinder" as the documentation
heading and the file name; why introduce a new term to the user?

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-17 22:21                               ` Doug Evans
@ 2015-03-18  8:57                                 ` Andy Wingo
  2015-03-18 16:48                                   ` Doug Evans
  0 siblings, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-18  8:57 UTC (permalink / raw)
  To: Doug Evans; +Cc: gdb-patches, guile-devel

Hi,

[-asmundak, as he probably doesn't care :)]

On Tue 17 Mar 2015 23:21, Doug Evans <dje@google.com> writes:

> On Tue, Mar 17, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
>>> As to the class of an object passed to a sniffer, how about calling it
>>> FrameData? Note that it's not very important from the user's point of
>>> view as sniffer code does not ever reference it by name.
>>
>> It's true that from user code it barely matters to Python, but Scheme's
>> monomorphic flavor makes these things more apparent:
>>
>>   (frame-data-read-register frame "r0")
>>
>> This doesn't read so well to me -- is it "read-register" on a
>> "frame-data", or is it "data-read-register" on a "frame" ?  A weak point
>> but "ephemeral-frame-read-register" avoids the question.
>
> As food for discussion,
> I know some people use foo:bar in Scheme to separate
> the object "foo" from the operation on it "bar".
> -> frame-data:read-register

This convention is not often used in Guile.  When it is used, it often
denotes field access rather than some more involved procedure call --
similar to the lowercase "foo_bar()" versus camel-cased "FooBar()" in
Google C++ guidelines.

> I like having some separator, but I went with what
> I thought was the preferred spelling (all -'s).
> It's not too late to change gdb/guile to use foo:bar throughout (IMO),
> but the door is closing.

FWIW, I prefer "-".

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-18  8:57                                 ` Andy Wingo
@ 2015-03-18 16:48                                   ` Doug Evans
  2015-03-19  8:04                                     ` Andy Wingo
  0 siblings, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-03-18 16:48 UTC (permalink / raw)
  To: Andy Wingo; +Cc: gdb-patches, guile-devel

On Wed, Mar 18, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
> Hi,
>
> [-asmundak, as he probably doesn't care :)]
>
> On Tue 17 Mar 2015 23:21, Doug Evans <dje@google.com> writes:
>
>> On Tue, Mar 17, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
>>>> As to the class of an object passed to a sniffer, how about calling it
>>>> FrameData? Note that it's not very important from the user's point of
>>>> view as sniffer code does not ever reference it by name.
>>>
>>> It's true that from user code it barely matters to Python, but Scheme's
>>> monomorphic flavor makes these things more apparent:
>>>
>>>   (frame-data-read-register frame "r0")
>>>
>>> This doesn't read so well to me -- is it "read-register" on a
>>> "frame-data", or is it "data-read-register" on a "frame" ?  A weak point
>>> but "ephemeral-frame-read-register" avoids the question.
>>
>> As food for discussion,
>> I know some people use foo:bar in Scheme to separate
>> the object "foo" from the operation on it "bar".
>> -> frame-data:read-register
>
> This convention is not often used in Guile.  When it is used, it often
> denotes field access rather than some more involved procedure call --
> similar to the lowercase "foo_bar()" versus camel-cased "FooBar()" in
> Google C++ guidelines.
>
>> I like having some separator, but I went with what
>> I thought was the preferred spelling (all -'s).
>> It's not too late to change gdb/guile to use foo:bar throughout (IMO),
>> but the door is closing.
>
> FWIW, I prefer "-".

Even though a different character solves a problem?
What problem does it introduce?

The comparison with _ vs CamelCase is apples and oranges.
They don't separate object/class name from method name.
If I were to invoke static method read_register on class
ephemeral_frame it would be ephemeral_frame::read_register().
The problem of the readability of frame-data-read-register
that ephemeral-frame-read-register attempts to solve
just doesn't arise. Same with frame-data:read-register.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-18  8:54                                   ` Andy Wingo
@ 2015-03-18 22:57                                     ` Alexander Smundak
  2015-03-23 19:58                                       ` Doug Evans
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-18 22:57 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Doug Evans, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 3362 bytes --]

> Nit: we are setting the register here.
Fixed.

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

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

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

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

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

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

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

[-- Attachment #2: patch8.diff --]
[-- Type: text/plain, Size: 70032 bytes --]

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index dbace2d..0bd3738 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -398,6 +398,7 @@ SUBDIR_PYTHON_OBS = \
 	py-symtab.o \
 	py-threadevent.o \
 	py-type.o \
+	py-unwind.o \
 	py-utils.o \
 	py-value.o \
 	py-varobj.o
@@ -437,6 +438,7 @@ SUBDIR_PYTHON_SRCS = \
 	python/py-symtab.c \
 	python/py-threadevent.c \
 	python/py-type.c \
+	python/py-unwind.c \
 	python/py-utils.c \
 	python/py-value.c \
 	python/py-varobj.c
@@ -2622,6 +2624,10 @@ py-type.o: $(srcdir)/python/py-type.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-type.c
 	$(POSTCOMPILE)
 
+py-unwind.o: $(srcdir)/python/py-unwind.c
+	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-unwind.c
+	$(POSTCOMPILE)
+
 py-utils.o: $(srcdir)/python/py-utils.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-utils.c
 	$(POSTCOMPILE)
diff --git a/gdb/NEWS b/gdb/NEWS
index bda4a35..ac994d9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -12,6 +12,7 @@
   ** gdb.Objfile objects have a new attribute "username",
      which is the name of the objfile as specified by the user,
      without, for example, resolving symlinks.
+  ** You can now write frame unwinders in Python.
 
 * New commands
 
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index c01b86d..049aa05 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -62,17 +62,20 @@ PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/types.py \
 	gdb/printing.py \
+	gdb/unwinder.py \
 	gdb/prompt.py \
 	gdb/xmethod.py \
 	gdb/command/__init__.py \
 	gdb/command/xmethods.py \
 	gdb/command/frame_filters.py \
+	gdb/command/unwinders.py \
 	gdb/command/type_printers.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/explore.py \
 	gdb/function/__init__.py \
 	gdb/function/caller_is.py \
+        gdb/function/unwinders.py \
 	gdb/function/strfns.py \
 	gdb/printer/__init__.py \
 	gdb/printer/bound_registers.py
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index d725eb0..a2685e8 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -144,6 +144,7 @@ optional arguments while skipping others.  Example:
 * Frame Filter API::            Filtering Frames.
 * Frame Decorator API::         Decorating Frames.
 * Writing a Frame Filter::      Writing a Frame Filter.
+* Unwinding Frames in Python::  Writing frame unwinder.
 * Xmethods In Python::          Adding and replacing methods of C++ classes.
 * Xmethod API::                 Xmethod types.
 * Writing an Xmethod::          Writing an xmethod.
@@ -2178,6 +2179,132 @@ printed hierarchically.  Another approach would be to combine the
 marker in the inlined frame, and also show the hierarchical
 relationship.
 
+@node Unwinding Frames in Python
+@subsubsection Unwinding Frames in Python
+@cindex Unwinding frames in Python.
+
+In GDB terminology ``unwinding'' is the process of finding the
+previous frame (that is, caller's) from the current one. An unwinder
+has three methods. The first one checks if it can handle given frame
+(``sniff'' it). For the frames it can sniff an unwinder provides two
+additional methods: it can return frame's ID, and it can fetch
+registers from the previous frame. A running GDB mantains a list of
+the unwinders and calls each unwinder's sniffer in turn until it finds
+the one that recognizes the current frame. There is an API to register
+an unwinder.
+
+The unwinders that come with GDB handle standard frames for each
+platform where GDB is running. However, mixed language applications
+(for example, and application running Java Virtual Machine) sometimes
+use frame layouts that cannot be handled by the GDB unwinders. You can
+write Python code that can handle such custom frames.
+
+You implement a frame unwinder in Python as a class with which has two
+attributes, @code{name} and @code{enabled}, with obvious meanings, and
+a single method @code{__call__}, which examines a given frame and
+returns an object (an instance of gdb.UnwindInfo class) describing
+it. If an unwinder does not recognize a frame, it should return
+@code{None}. The code in GDB that enables writing unwinders in Python
+uses this object to return frame's ID and previous frame registers
+when GDB core asks for them.
+
+@subheading Unwinder Input
+
+An object passed to an unwinder (a @code{SnifferInfo} instance) provides
+a method to read frame's registers:
+
+@defun SnifferInfo.read_register (reg)
+This method returns the contents of the register @var{regn} in the
+frame as a @code{gdb.Value} object. @var{reg} can be either a register
+number or a register name; the values are platform-specific. They are
+usually found in the corresponding xxx-@code{tdep.h} file in the gdb
+source tree.
+@end defun
+
+It also provides several factory methods. If an unwinder recognizes
+the frame, it should invoke one of them to create a gdb.UnwindInfo
+instance to be returned to GDB:
+
+@defun SnifferInfo.unwind_info_with_id(sp, pc)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{sp} and @var{pc} values. This is the most common way of creating
+instance result.
+@end defun
+
+@defun SnifferInfo.frame_id_build_special(sp, pc, special)
+Returns a new @code{gdb.UnwindInfo} instance identitified by given
+@var{sp}, @var{pc}, and @var{special} values.
+@end defun
+
+@defun gdb.UnwindInfo.frame_id_build_wild(sp)
+Returns a new @code{gdb.UnwindInfo} instance identified by given
+@var{sp} value.
+@end defun
+
+@subheading Unwinder Output: UnwindInfo
+
+A @code{gdb.UnwindInfo} object can be constructed by one of the
+methods described above. Use the following method to set the caller
+frame's registers:
+
+@defun gdb.UnwindInfo.set_previous_frame_register(reg, value)
+@var{reg} identifies the register. It can be a number or a name, just
+as for the @code{SnifferInfo.read_register} method above. @var{value}
+is a register value (a @code{gdb.Value} object).
+
+@subheading Unwinder Skeleton Code
+
+GDB comes with the module containing the base @code{Unwinder} class.
+Derive your unwinder class from it and structure the code as follows:
+
+@smallexample
+from gdb.unwinders import Unwinder
+
+class MyUnwinder(Unwinder):
+    def __init__(....):
+        super(MyUnwinder, self).__init___(<expects unwinder name argument>)
+    def __call__(sniffer_info):
+        if not <we recognize frame>:
+            return None
+        # Create unwinder result. The most common way to achieve this is
+        # to find SP (stack pointer) and PC (program counter) values
+        # in the current frame and then call unwind_info_with_id method:
+        unwind_info = sniffer_info.unwind_info_with_id(sp, pc)
+
+        # Find the values of the registers in the caller's frame and 
+        # save them in the result:
+        unwind_info.set_previous_frame_register(<register>, <value>)
+
+        # Return the result:
+        return unwind_instance
+
+@end smallexample
+
+@subheading Registering a Unwinder
+
+An object file, a program space, and the @value{GDBN} proper can have
+unwinders registered with it.
+
+The @code{gdb.unwinders} module provides the function to register a
+unwinder:
+
+@defun gdb.unwinder.register_unwinder (locus, unwinder, replace=False)
+@var{locus} is specifies an object file or a program space to which
+@var{unwinder} is added. Passing @code{None} or @code{gdb} adds
+@var{unwinder} to the @value{GDBN}'s global unwinder list.  The newly
+added @var{unwinder} will be called before any other unwinder from the
+same locus.  Two unwinders in the same locus cannot have the same
+name. An attempt to add a unwinder with already existing name raises an
+exception unless @var{replace} is @code{True}, in which case the old
+unwinder is deleted.
+@end defun
+
+@subheading Unwinder Precedence
+
+@value{GDBN} first calls the unwinders from all the object files in no
+particular order, then the unwinders from the current program space,
+and finally the unwinders from @value{GDBN}.
+
 @node Xmethods In Python
 @subsubsection Xmethods In Python
 @cindex xmethods in Python
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 92b06f2..734e5ca 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -71,6 +71,8 @@ type_printers = []
 xmethods = []
 # Initial frame filters.
 frame_filters = {}
+# Initial frame unwinders.
+frame_unwinders = []
 
 # Convenience variable to GDB's python directory
 PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py
new file mode 100644
index 0000000..1f0c58b
--- /dev/null
+++ b/gdb/python/lib/gdb/command/unwinders.py
@@ -0,0 +1,199 @@
+# Unwinder commands.
+# Copyright 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+import re
+
+
+def validate_regexp(exp, idstring):
+    try:
+        return re.compile(exp)
+    except SyntaxError:
+        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
+
+
+def parse_unwinder_command_args(arg):
+    """Internal utility to parse unwinder command argv.
+
+    Arguments:
+        arg: The arguments to the command. The format is:
+             [locus-regexp [name-regexp]]
+
+    Returns:
+        A 2-tuple of compiled regular expressions.
+
+    Raises:
+        SyntaxError: an error processing ARG
+    """
+
+    argv = gdb.string_to_argv(arg)
+    argc = len(argv)
+    if argc > 2:
+        raise SyntaxError("Too many arguments.")
+    locus_regexp = ""
+    name_regexp = ""
+    if argc >= 1:
+        locus_regexp = argv[0]
+        if argc >= 2:
+            name_regexp = argv[1]
+    return (validate_regexp(locus_regexp, "locus"),
+            validate_regexp(name_regexp, "unwinder"))
+
+
+class InfoUnwinder(gdb.Command):
+    """GDB command to list unwinders.
+
+    Usage: info unwinder [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the location of the
+    unwinder.  If it is omitted, all registered unwinders from all
+    loci are listed.  A locus could be 'global', a regular expression
+    matching the current program space's filename, or a regular
+    expression matching filenames of objfiles.  Locus could be
+    'progspace' to specify that only unwinders from the current
+    progspace should be listed.
+
+    NAME-REGEXP is a regular expression to filter unwinder names.  If
+    this omitted for a specified locus, then all registered unwinders
+    in the locus are listed.
+
+    """
+
+    def __init__(self):
+        super(InfoUnwinder, self).__init__("info unwinder",
+                                            gdb.COMMAND_DATA)
+
+    def list_unwinders(self, title, unwinders, name_re):
+        """Lists the unwinders whose name matches regexp.
+
+        Arguments:
+            title: The line to print before the list.
+            unwinders: The list of the unwinders.
+            name_re: unwinder name filter.
+        """
+        if not unwinders:
+            return
+        print title
+        for unwinder in unwinders:
+            if name_re.match(unwinder.name):
+                print("  %s%s" % (unwinder.name,
+                                  "" if unwinder.enabled else "[disabled]"))
+
+    def invoke(self, arg, from_tty):
+        locus_re, name_re = parse_unwinder_command_args(arg)
+        if locus_re.match("global"):
+            self.list_unwinders("global unwinders:", gdb.frame_unwinders,
+                                name_re)
+        if locus_re.match("progspace"):
+            cp = gdb.current_progspace()
+            self.list_unwinders("progspace %s unwinders:" % cp.filename,
+                                cp.frame_unwinders, name_re)
+        for objfile in gdb.objfiles():
+            if locus_re.match(objfile.filename):
+                self.list_unwinders("objfile %s unwinders:" % objfile.filename,
+                                    objfile.frame_unwinders, name_re)
+
+
+def do_enable_unwinder1(unwinders, name_re, flag):
+    """Enable/disable unwinders whose names match given regex.
+
+    Arguments:
+        unwinders: The list of unwinders.
+        name_re: Unwinder name filter.
+        flag: Enable/disable.
+
+    Returns:
+        The number of unwinders affected.
+    """
+    total = 0
+    for unwinder in unwinders:
+        if name_re.match(unwinder.name):
+            unwinder.enabled = flag
+            total += 1
+    return total
+
+
+def do_enable_unwinder(arg, flag):
+    """Enable/disable unwinder(s)."""
+    (locus_re, name_re) = parse_unwinder_command_args(arg)
+    total = 0
+    if locus_re.match("global"):
+        total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag)
+    if locus_re.match("progspace"):
+        total += do_enable_unwinder1(gdb.current_progspace().frame_unwinders,
+                                     name_re, flag)
+    for objfile in gdb.objfiles():
+        if locus_re.match(objfile.filename):
+            total += do_enable_unwinder1(objfile.frame_unwinders, name_re,
+                                         flag)
+    print("%d unwinder%s %s" % (total, "" if total == 1 else "s",
+                                "enabled" if flag else "disabled"))
+
+
+class EnableUnwinder(gdb.Command):
+    """GDB command to enable unwinders.
+
+    Usage: enable unwinder [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the objects to examine.
+    Loci are "global", the program space's file, and the objfiles within
+    that program space.
+
+    NAME_REGEXP is a regular expression to filter unwinder names.
+    If this omitted for a specified locus, then all registered
+    unwinders in the locus are affected.
+    """
+
+    def __init__(self):
+        super(EnableUnwinder, self).__init__("enable unwinder",
+                                             gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_unwinder(arg, True)
+
+
+class DisableUnwinder(gdb.Command):
+    """GDB command to disable the specified unwinder.
+
+    Usage: disable unwinder [locus-regexp [name-regexp]]
+
+    LOCUS-REGEXP is a regular expression matching the objects to examine.
+    Loci are "global", the program space's file, and the objfiles within
+    that program space.
+
+    NAME_REGEXP is a regular expression to filter unwinder names.
+    If this omitted for a specified locus, then all registered
+    unwinders in the locus are affected.
+    """
+
+    def __init__(self):
+        super(DisableUnwinder, self).__init__("disable unwinder",
+                                              gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_unwinder(arg, False)
+
+
+def register_unwinder_commands():
+    """Installs the unwinder commands."""
+    InfoUnwinder()
+    EnableUnwinder()
+    DisableUnwinder()
+
+
+register_unwinder_commands()
diff --git a/gdb/python/lib/gdb/function/unwinders.py b/gdb/python/lib/gdb/function/unwinders.py
new file mode 100644
index 0000000..9be8b8b
--- /dev/null
+++ b/gdb/python/lib/gdb/function/unwinders.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Internal functions for working with frame unwinders."""
+
+import gdb
+
+
+def execute_unwinders(sniffer_info):
+    """Internal function called from GDB to execute all unwinders.
+
+    Runs each currently enabled unwinder until it finds the one that can
+    unwind given frame.
+
+    Arguments:
+        sniffer_info: an instance of gdb.SnifferInfo.
+    Returns:
+        UnwindInfo instance or None.
+    """
+    for objfile in gdb.objfiles():
+        for unwinder in objfile.frame_unwinders:
+            if unwinder.enabled:
+                unwind_info = unwinder.__call__(sniffer_info)
+                if unwind_info is not None:
+                    return unwind_info
+
+    current_progspace = gdb.current_progspace()
+    for unwinder in current_progspace.frame_unwinders:
+        if unwinder.enabled:
+            unwind_info = unwinder.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    for unwinder in gdb.frame_unwinders:
+        if unwinder.enabled:
+            unwind_info = unwinder.__call__(sniffer_info)
+            if unwind_info is not None:
+                return unwind_info
+
+    return None
diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
new file mode 100644
index 0000000..2d65a5e
--- /dev/null
+++ b/gdb/python/lib/gdb/unwinder.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Unwinder class and register_unwinder function."""
+
+import gdb
+
+
+class Unwinder(object):
+    """Base class (or a template) for frame unwinders written in Python.
+
+    A unwinder has a single method __call__ and the attributes described below.
+
+    Attributes:
+        name: The name of the unwinder.
+        enabled: A boolean indicating whether the unwinder is enabled.
+    """
+
+    def __init__(self, name):
+        """Constructor.
+
+        Args:
+            name: An identifying name for the unwinder.
+        """
+        self.name = name
+        self.enabled = True
+
+    def __call__(self, sniffer_info):
+        """GDB calls this method to unwind a frame.
+
+        Arguments:
+            sniffer_info: An instance of gdb.SnifferInfo describing the frame.
+
+        Returns:
+	    gdb.UnwindInfo instance.
+        """
+        raise NotImplementedError("Unwinder __call__.")
+
+
+def register_unwinder(locus, unwinder, replace=False):
+    """Register unwinder in given locus.
+
+    The unwinder is prepended to the locus's unwinders list. Unwinder
+    name should be unique.
+
+    Arguments:
+        locus: Either an objfile, progspace, or None (in which case
+               the unwinder is registered globally).
+        unwinder: An object of a gdb.Unwinder subclass
+        replace: If True, replaces existing unwinder with the same name.
+                 Otherwise, raises exception if unwinder with the same
+                 name already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        RuntimeError: Unwinder name is not unique.
+
+    """
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s unwinder ...\n" % unwinder.name)
+        locus = gdb
+    else:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering %s unwinder for %s ...\n" %
+                      (unwinder.name, locus.filename))
+    i = 0
+    for needle in locus.frame_unwinders:
+        if needle.name == unwinder.name:
+            if replace:
+                del locus.frame_unwinders[i]
+            else:
+                raise RuntimeError("Unwinder %s already exists." % unwinder.name)
+        i += 1
+    locus.frame_unwinders.insert(0, unwinder)
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 157d200..c9528c3 100644
--- a/gdb/python/py-objfile.c
+++ b/gdb/python/py-objfile.c
@@ -42,6 +42,10 @@ typedef struct
 
   /* The frame filter list of functions.  */
   PyObject *frame_filters;
+
+  /* The list of frame unwinders.  */
+  PyObject *frame_unwinders;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o)
   Py_XDECREF (self->dict);
   Py_XDECREF (self->printers);
   Py_XDECREF (self->frame_filters);
+  Py_XDECREF (self->frame_unwinders);
   Py_XDECREF (self->type_printers);
   Py_XDECREF (self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_unwinders = PyList_New (0);
+  if (self->frame_unwinders == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
   return 0;
 }
 
+/* Return the frame unwinders attribute for this object file.  */
+
+PyObject *
+objfpy_get_frame_unwinders (PyObject *o, void *ignore)
+{
+  objfile_object *self = (objfile_object *) o;
+
+  Py_INCREF (self->frame_unwinders);
+  return self->frame_unwinders;
+}
+
+/* Set this object file's frame unwinders list to UNWINDERS.  */
+
+static int
+objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
+{
+  PyObject *tmp;
+  objfile_object *self = (objfile_object *) o;
+
+  if (!unwinders)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete the frame unwinders attribute."));
+      return -1;
+    }
+
+  if (!PyList_Check (unwinders))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The frame_unwinders attribute must be a list."));
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_unwinders;
+  Py_INCREF (unwinders);
+  self->frame_unwinders = unwinders;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", objfpy_get_frame_filters,
     objfpy_set_frame_filters, "Frame Filters.", NULL },
+  { "frame_unwinders", objfpy_get_frame_unwinders,
+    objfpy_set_frame_unwinders, "Frame Unwinders", NULL },
   { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
     "Type printers.", NULL },
   { "xmethods", objfpy_get_xmethods, NULL,
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index 93fbc14..17da3d1 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -41,6 +41,10 @@ typedef struct
 
   /* The frame filter list of functions.  */
   PyObject *frame_filters;
+
+  /* The frame unwinder list.  */
+  PyObject *frame_unwinders;
+
   /* The type-printer list.  */
   PyObject *type_printers;
 
@@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self)
   Py_XDECREF (ps_self->dict);
   Py_XDECREF (ps_self->printers);
   Py_XDECREF (ps_self->frame_filters);
+  Py_XDECREF (ps_self->frame_unwinders);
   Py_XDECREF (ps_self->type_printers);
   Py_XDECREF (ps_self->xmethods);
   Py_TYPE (self)->tp_free (self);
@@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self)
   if (self->frame_filters == NULL)
     return 0;
 
+  self->frame_unwinders = PyList_New (0);
+  if (self->frame_unwinders == NULL)
+    return 0;
+
   self->type_printers = PyList_New (0);
   if (self->type_printers == NULL)
     return 0;
@@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
   return 0;
 }
 
+/* Return the list of the frame unwinders for this program space.  */
+
+PyObject *
+pspy_get_frame_unwinders (PyObject *o, void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  Py_INCREF (self->frame_unwinders);
+  return self->frame_unwinders;
+}
+
+/* Set this program space's list of the unwinders to UNWINDERS.  */
+
+static int
+pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
+{
+  PyObject *tmp;
+  pspace_object *self = (pspace_object *) o;
+
+  if (!unwinders)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "cannot delete the frame unwinders list");
+      return -1;
+    }
+
+  if (!PyList_Check (unwinders))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "the frame unwinders attribute must be a list");
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  tmp = self->frame_unwinders;
+  Py_INCREF (unwinders);
+  self->frame_unwinders = unwinders;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
 /* Get the 'type_printers' attribute.  */
 
 static PyObject *
@@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] =
     "Pretty printers.", NULL },
   { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
     "Frame filters.", NULL },
+  { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders,
+    "Frame unwinders.", NULL },
   { "type_printers", pspy_get_type_printers, pspy_set_type_printers,
     "Type printers.", NULL },
   { "xmethods", pspy_get_xmethods, NULL,
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
new file mode 100644
index 0000000..128d710
--- /dev/null
+++ b/gdb/python/py-unwind.c
@@ -0,0 +1,831 @@
+/* Python frame unwinder interface.
+
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "arch-utils.h"
+#include "frame-unwind.h"
+#include "gdb_obstack.h"
+#include "gdbcmd.h"
+#include "language.h"
+#include "observer.h"
+#include "python-internal.h"
+#include "regcache.h"
+#include "valprint.h"
+#include "user-regs.h"
+
+#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
+  { fprintf_unfiltered (gdb_stdlog, args); }
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* Frame we are unwinding.  */
+  struct frame_info *frame_info;
+
+  /* Its architecture, passed by the sniffer caller.  */
+  struct gdbarch *gdbarch;
+} sniffer_info_object;
+
+/* The data we keep for the PyUnwindInfo: sniffer_info, previous
+ * frame's register set and frame ID.  */
+
+typedef struct
+{
+  PyObject_HEAD
+
+  /* gdb.SnifferInfo for the frame we are unwinding.  */
+  PyObject *sniffer_info;
+
+  /* Its ID.  */
+  struct frame_id frame_id;
+
+  /* Previous frame registers array.  */
+  struct reg_pydata
+  {
+    int number;
+    PyObject *value;
+  } *prev_frame_regs;
+
+  /* The current size of the array above.  */
+  int prev_frame_regs_size;
+
+  /* And its capacity.  */
+  int prev_frame_regs_capacity;
+
+} unwind_info_object;
+
+/* The data we keep for a frame we can unwind: frame ID and an array of
+   (register_number, register_value) pairs.  */
+
+typedef struct
+{
+  /* Frame ID.  */
+  struct frame_id frame_id;
+
+  /* GDB Architecture.  */
+  struct gdbarch *gdbarch;
+
+  /* Length of the `reg' array below.  */
+  int reg_count;
+
+  struct reg_info
+  {
+    /* Register number.  */
+    int number;
+
+    /* Register data bytes pointer.  */
+    gdb_byte *data;
+  } reg[];
+} cached_frame_info;
+
+static PyTypeObject sniffer_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
+
+static PyTypeObject unwind_info_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
+
+static unsigned int pyuw_debug = 0;
+
+static struct gdbarch_data *pyuw_gdbarch_data;
+
+/* Parse Python Int, saving it at the given address. Returns 1 on success,
+   0 otherwise.  */
+
+static int
+pyuw_parse_int (PyObject *pyo_int, int *valuep)
+{
+  long long_value;
+
+  if (pyo_int == NULL)
+    return 0;
+
+  /* Make a long logic check first.  In Python 3.x, internally, all
+     integers are represented as longs.  In Python 2.x, there is still
+     a differentiation internally between a PyInt and a PyLong.
+     Explicitly do this long check conversion first. In GDB, for
+     Python 3.x, we #ifdef PyInt = PyLong.  This check has to be done
+     first to ensure we do not lose information in the conversion
+     process.  */
+  else if (PyLong_Check (pyo_int))
+    {
+      LONGEST l = PyLong_AsLongLong (pyo_int);
+
+      if (PyErr_Occurred ())
+        return 0;
+      long_value = (long)l;
+      if (l != long_value)
+        return 0;
+    }
+  else if (PyInt_Check (pyo_int))
+    {
+      long_value = PyInt_AsLong (pyo_int);
+      if (PyErr_Occurred ())
+        return 0;
+    }
+  else
+    return 0;
+  if (long_value != (int) long_value)
+    return 0;
+  *valuep = (int) long_value;
+  return 1;
+}
+
+/* Parses register id, which can be either a number or a name.
+   Returns 1 on success, 0 otherwise.  */
+
+static int
+pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
+                        int *reg_num)
+{
+  if (pyo_reg_id == NULL)
+    return 0;
+  if (PyString_Check (pyo_reg_id))
+    {
+      const char *reg_name = PyString_AS_STRING (pyo_reg_id);
+      if (reg_name == NULL)
+        return 0;
+      *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name,
+                                              strlen (reg_name));
+      return *reg_num >= 0;
+    }
+  else if (pyuw_parse_int (pyo_reg_id, reg_num))
+    return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
+  else
+    return 0;
+}
+
+/* Convert gdb.Value object to COREADDR.  */
+
+static int
+pyuw_value_obj_to_pointer (PyObject *pyo_value, CORE_ADDR *addr)
+{
+  struct value *value = value_object_to_value (pyo_value);
+
+  if (value == NULL)
+    return 0;
+  *addr = unpack_pointer (value_type (value), value_contents (value));
+  return 1;
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the UnwindInfo object.  */
+
+static PyObject *
+unwind_infopy_str (PyObject *self)
+{
+  PyObject *result;
+  struct ui_file *strfile = mem_fileopen ();
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  sniffer_info_object *sniffer_info
+      = (sniffer_info_object *) (unwind_info->sniffer_info);
+
+  fprintf_unfiltered (strfile, "Frame ID: ");
+  fprint_frame_id (strfile, unwind_info->frame_id);
+  {
+    int i;
+    char *sep = "";
+    struct value_print_options opts;
+
+    get_user_print_options (&opts);
+    fprintf_unfiltered (strfile, "\nPrevious frame registers: (");
+    for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
+      {
+        struct value *value
+            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
+
+        fprintf_unfiltered (strfile, "%s(%d, ", sep,
+                            unwind_info->prev_frame_regs[i].number);
+        if (value != NULL)
+          {
+            value_print (value, strfile, &opts);
+            fprintf_unfiltered (strfile, ")");
+          }
+        else
+          fprintf_unfiltered (strfile, "<BAD>)");
+        sep = ", ";
+      }
+    fprintf_unfiltered (strfile, ")");
+  }
+  {
+    char *s = ui_file_xstrdup (strfile, NULL);
+
+    result = PyString_FromString (s);
+    xfree (s);
+  }
+  ui_file_delete (strfile);
+  return result;
+}
+
+/* Create UnwindInfo instance for given SnifferInfo and frame ID.  */
+
+static PyObject *
+pyuw_create_unwind_info (PyObject *pyo_sniffer_info,
+                         struct frame_id frame_id)
+{
+  unwind_info_object *unwind_info
+      = PyObject_New (unwind_info_object, &unwind_info_object_type);
+
+  if (((sniffer_info_object *) pyo_sniffer_info)->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to use stale SnifferInfo");
+      return NULL;
+    }
+  unwind_info->frame_id = frame_id;
+  Py_INCREF (pyo_sniffer_info);
+  unwind_info->sniffer_info = pyo_sniffer_info;
+  unwind_info->prev_frame_regs_size = 0;
+  unwind_info->prev_frame_regs_capacity = 4;
+  unwind_info->prev_frame_regs =
+      xmalloc (unwind_info->prev_frame_regs_capacity *
+               sizeof (unwind_info->prev_frame_regs[0]));
+  return (PyObject *) unwind_info;
+}
+
+/* The implementation of
+   gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None.  */
+
+static PyObject *
+unwind_infopy_set_previous_frame_register (PyObject *self, PyObject *args)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  sniffer_info_object *sniffer_info
+      = (sniffer_info_object *) (unwind_info->sniffer_info);
+  PyObject *pyo_reg_id;
+  PyObject *pyo_reg_value;
+  int regnum;
+
+  if (sniffer_info->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "UnwindInfo instance refers to a stale SnifferInfo");
+      return NULL;
+    }
+  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
+                          &pyo_reg_id, &pyo_reg_value))
+    return NULL;
+  if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+  {
+    struct value *value;
+    size_t data_size;
+
+    if (pyo_reg_value == NULL
+      || (value = value_object_to_value (pyo_reg_value)) == NULL)
+      {
+        PyErr_SetString (PyExc_ValueError, "Bad register value");
+        return NULL;
+      }
+    data_size = register_size (sniffer_info->gdbarch, regnum);
+    if (data_size != TYPE_LENGTH (value_enclosing_type (value)))
+      {
+        PyErr_Format (
+            PyExc_ValueError,
+            "The value of the register returned by the Python "
+            "sniffer has unexpected size: %u instead of %u.",
+            (unsigned) (TYPE_LENGTH (value_enclosing_type (value))),
+            (unsigned) data_size);
+        return NULL;
+      }
+  }
+  {
+    int i;
+
+    for (i = 0;
+         (i < unwind_info->prev_frame_regs_size)
+             && regnum != unwind_info->prev_frame_regs[i].number; i++)
+      ;
+    if (i < unwind_info->prev_frame_regs_size)
+      Py_DECREF (unwind_info->prev_frame_regs[i].value);
+    else
+      {
+        if (i >= unwind_info->prev_frame_regs_capacity)
+          {
+            unwind_info->prev_frame_regs_capacity *= 2;
+            unwind_info->prev_frame_regs = xrealloc
+                (unwind_info->prev_frame_regs,
+                 unwind_info->prev_frame_regs_capacity
+                 * sizeof (unwind_info->prev_frame_regs[0]));
+          }
+        unwind_info->prev_frame_regs_size++;
+        unwind_info->prev_frame_regs[i].number = regnum;
+      }
+    Py_INCREF (pyo_reg_value);
+    unwind_info->prev_frame_regs[i].value = pyo_reg_value;
+  }
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+/* UnwindInfo cleanup.  */
+
+static void
+unwind_infopy_dealloc (PyObject *self)
+{
+  unwind_info_object *unwind_info = (unwind_info_object *) self;
+  int i;
+
+  Py_XDECREF (unwind_info->sniffer_info);
+  for (i = 0; i < unwind_info->prev_frame_regs_size; i++)
+      Py_DECREF (unwind_info->prev_frame_regs[i].value);
+  xfree (unwind_info->prev_frame_regs);
+  Py_TYPE (self)->tp_free (self);
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the SnifferInfo object.  */
+
+static PyObject *
+sniffer_infopy_str (PyObject *self)
+{
+  struct frame_info *frame = ((sniffer_info_object *) self)->frame_info;
+
+  if (frame == NULL)
+    return PyString_FromString ("Stale SnifferInfo instance");
+  return PyString_FromFormat ("SP=%s,PC=%s",
+                              core_addr_to_string_nz (get_frame_sp (frame)),
+                              core_addr_to_string_nz (get_frame_pc (frame)));
+}
+
+/* Implementation of gdb.SnifferInfo.read_register (self, regnum) -> gdb.Value.
+   Returns the value of register REGNUM as gdb.Value instance.  */
+
+static PyObject *
+sniffer_infopy_read_register (PyObject *self, PyObject *args)
+{
+  volatile struct gdb_exception except;
+  int regnum;
+  struct value *val = NULL;
+  sniffer_info_object *sniffer_info = (sniffer_info_object *) self;
+  PyObject *pyo_reg_id;
+
+  if (sniffer_info->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+                       "Attempting to read register from stale SnifferInfo");
+      return NULL;
+    }
+
+  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
+    return NULL;
+  if (!pyuw_parse_register_id (sniffer_info->gdbarch, pyo_reg_id, &regnum))
+    {
+      PyErr_SetString (PyExc_ValueError, "Bad register");
+      return NULL;
+    }
+  TRY
+    {
+      gdb_byte buffer[MAX_REGISTER_SIZE];
+
+      val = get_frame_register_value (
+          ((sniffer_info_object *) self)->frame_info, regnum);
+      if (val == NULL)
+        PyErr_Format (PyExc_ValueError,
+                      "Cannot read register %d from frame.",
+                      regnum);
+    }
+  CATCH (except, RETURN_MASK_ALL)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+  END_CATCH
+
+  return val == NULL ? NULL : value_to_value_object (val);
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id (self, SP, PC) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+
+  if (!PyArg_ParseTuple (args, "OO:unwind_info_with_id", &pyo_sp, &pyo_pc)
+       || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+       || !pyuw_value_obj_to_pointer (pyo_pc, &pc))
+    return NULL;
+
+  return pyuw_create_unwind_info (self, frame_id_build (sp, pc));
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id_special (self, SP, PC, SPECIAL) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id_special (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  PyObject *pyo_pc;
+  PyObject *pyo_special;
+  CORE_ADDR sp;
+  CORE_ADDR pc;
+  CORE_ADDR special;
+
+  if (!PyArg_ParseTuple (args, "OOO:unwind_info_with_id_special",
+                          &pyo_sp, &pyo_pc, &pyo_special)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp)
+      || !pyuw_value_obj_to_pointer (pyo_pc, &pc)
+      || !pyuw_value_obj_to_pointer (pyo_special, &special))
+    return NULL;
+
+  return pyuw_create_unwind_info (self,
+                                  frame_id_build_special (sp, pc, special));
+}
+
+/* Implementation of
+   gdb.SnifferInfo.unwind_info_with_id_wild (self, SP) -> None.  */
+
+static PyObject *
+sniffer_infopy_unwind_info_with_id_wild (PyObject *self, PyObject *args)
+{
+  PyObject *pyo_sp;
+  CORE_ADDR sp;
+
+  if (!PyArg_ParseTuple (args, "O:unwind_info_with_id_wild", &pyo_sp)
+      || !pyuw_value_obj_to_pointer (pyo_sp, &sp))
+    return NULL;
+
+  return pyuw_create_unwind_info (self, frame_id_build_wild (sp));
+}
+
+/* Create Python SnifferInfo object.  */
+
+static PyObject *
+frame_info_to_sniffer_info_object (struct gdbarch *gdbarch,
+                                   struct frame_info *frame)
+{
+  sniffer_info_object *sniffer_info
+      = PyObject_New (sniffer_info_object, &sniffer_info_object_type);
+
+  sniffer_info->gdbarch = gdbarch;
+  sniffer_info->frame_info = frame;
+  return (PyObject *) sniffer_info;
+}
+
+/* Invalidate SnifferInfo object.  */
+static void
+sniffer_info_invalidate (PyObject *pyo_sniffer_info)
+{
+  if (pyo_sniffer_info == NULL)
+    return;
+  ((sniffer_info_object *) pyo_sniffer_info)->frame_info = NULL;
+}
+
+/* frame_unwind.this_id method.  */
+
+static void
+pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
+              struct frame_id *this_id)
+{
+  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
+  if (pyuw_debug >= 1)
+    {
+      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
+      fprint_frame_id (gdb_stdlog, *this_id);
+      fprintf_unfiltered (gdb_stdlog, "\n");
+    }
+}
+
+/* frame_unwind.prev_register.  */
+
+static struct value *
+pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
+                    int regnum)
+{
+  cached_frame_info *cached_frame = *cache_ptr;
+  struct reg_info *reg_info = cached_frame->reg;
+  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
+
+  TRACE_PY_UNWIND (1, "%s (frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
+                   regnum);
+  for (; reg_info < reg_info_end; ++reg_info)
+    {
+      if (regnum == reg_info->number)
+        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
+    }
+
+  return frame_unwind_got_optimized (this_frame, regnum);
+}
+
+/* Frame sniffer dispatch.  */
+
+static int
+pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
+              void **cache_ptr)
+{
+  struct gdbarch *gdbarch;
+  struct cleanup *cleanups;
+  struct cleanup *cached_frame_cleanups;
+  PyObject *pyo_module;
+  PyObject *pyo_execute;
+  PyObject *pyo_sniffer_info;
+  PyObject *pyo_unwind_info;
+  cached_frame_info *cached_frame = NULL;
+
+  gdbarch = (struct gdbarch *) (self->unwind_data);
+  cleanups = ensure_python_env (gdbarch, current_language);
+  TRACE_PY_UNWIND (3, "%s (SP=%s, PC=%s)\n", __FUNCTION__,
+                   paddress (gdbarch, get_frame_sp (this_frame)),
+                   paddress (gdbarch, get_frame_pc (this_frame)));
+  pyo_sniffer_info = frame_info_to_sniffer_info_object (gdbarch, this_frame);
+  if (pyo_sniffer_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_sniffer_info);
+
+  if ((pyo_module = PyImport_ImportModule ("gdb.function.unwinders")) == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_module);
+
+  pyo_execute = PyObject_GetAttrString (pyo_module, "execute_unwinders");
+  if (pyo_execute == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_execute);
+
+  pyo_unwind_info
+      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_sniffer_info, NULL);
+  if (pyo_unwind_info == NULL)
+    goto error;
+  make_cleanup_py_decref (pyo_unwind_info);
+  if (pyo_unwind_info == Py_None)
+    goto error;
+  if (PyObject_IsInstance (pyo_unwind_info,
+                           (PyObject *) &unwind_info_object_type) <= 0)
+    error (_("A Unwinder should return gdb.UnwindInfo instance."));
+
+  {
+    unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
+    int i;
+    int reg_count;
+    size_t cached_frame_size;
+    size_t gdb_bytes_count;
+    gdb_byte *gdb_data_free, *gdb_data_end;
+
+    /* Figure out how much space we need to allocate.  */
+    reg_count = unwind_info->prev_frame_regs_size;
+    gdb_bytes_count = reg_count * MAX_REGISTER_SIZE;
+    cached_frame_size = sizeof (*cached_frame) +
+        reg_count * sizeof (cached_frame->reg[0]) +
+        gdb_bytes_count * sizeof (gdb_byte);
+
+    cached_frame = xmalloc (cached_frame_size);
+    cached_frame_cleanups = make_cleanup (xfree, cached_frame);
+    /* Allocations after this point will be discarded!  */
+
+    gdb_data_end = (gdb_byte *) ((char *) cached_frame + cached_frame_size);
+    gdb_data_free = gdb_data_end - gdb_bytes_count;
+
+    cached_frame->gdbarch = gdbarch;
+    cached_frame->frame_id = unwind_info->frame_id;
+    cached_frame->reg_count = reg_count;
+
+    /* Populate registers array.  */
+    for (i = 0; i < reg_count; i++)
+      {
+        struct reg_info *reg = &(cached_frame->reg[i]);
+        struct value *value
+            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
+        size_t data_size;
+
+        reg->number = unwind_info->prev_frame_regs[i].number;
+        /* `value' validation was done before, just assert.  */
+        gdb_assert (value != NULL);
+        data_size = register_size (gdbarch, reg->number);
+        gdb_assert (data_size == TYPE_LENGTH (value_enclosing_type (value)));
+        /* Should not overflow  the buffer.  */
+        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
+        memcpy (gdb_data_free, value_contents (value), data_size);
+        reg->data = gdb_data_free;
+        gdb_data_free += data_size;
+      }
+  }
+
+  *cache_ptr = cached_frame;
+  discard_cleanups (cached_frame_cleanups);
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 1;
+
+error:
+  do_cleanups (cleanups);
+  sniffer_info_invalidate (pyo_sniffer_info);
+  return 0;
+}
+
+/* Frame cache release shim.  */
+
+static void
+pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
+{
+  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
+  xfree (cache);
+}
+
+struct pyuw_gdbarch_data_type
+{
+  /* Has the unwinder shim been prepended? */
+  int unwinder_registered;
+};
+
+static void *
+pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
+{
+  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
+}
+
+/* New inferior architecture callback: register the Python unwinders
+   intermediary.  */
+
+static void
+pyuw_on_new_gdbarch (struct gdbarch *newarch)
+{
+  struct pyuw_gdbarch_data_type *data =
+      gdbarch_data (newarch, pyuw_gdbarch_data);
+
+  if (!data->unwinder_registered)
+    {
+      struct frame_unwind *unwinder
+          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
+
+      unwinder->type = NORMAL_FRAME;
+      unwinder->stop_reason = default_frame_unwind_stop_reason;
+      unwinder->this_id = pyuw_this_id;
+      unwinder->prev_register = pyuw_prev_register;
+      unwinder->unwind_data = (void *) newarch;
+      unwinder->sniffer = pyuw_sniffer;
+      unwinder->dealloc_cache = pyuw_dealloc_cache;
+      frame_unwind_prepend_unwinder (newarch, unwinder);
+      TRACE_PY_UNWIND (1, "%s: registered unwinder for %s\n", __FUNCTION__,
+                       gdbarch_bfd_arch_info (newarch)->printable_name);
+      data->unwinder_registered = 1;
+    }
+}
+
+/* Initialize unwind machinery.  */
+
+int
+gdbpy_initialize_unwind (void)
+{
+  int rc;
+  add_setshow_zuinteger_cmd
+      ("py-unwind", class_maintenance, &pyuw_debug,
+        _("Set Python unwinder debugging."),
+        _("Show Python unwinder debugging."),
+        _("When non-zero, Python unwinder debugging is enabled."),
+        NULL,
+        NULL,
+        &setdebuglist, &showdebuglist);
+  pyuw_gdbarch_data
+      = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
+  observer_attach_architecture_changed (pyuw_on_new_gdbarch);
+
+  if (PyType_Ready (&sniffer_info_object_type) < 0)
+    return -1;
+  rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo",
+      (PyObject *) &sniffer_info_object_type);
+  if (rc)
+    return rc;
+
+  if (PyType_Ready (&unwind_info_object_type) < 0)
+    return -1;
+  return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
+      (PyObject *) &unwind_info_object_type);
+}
+
+static PyMethodDef sniffer_info_object_methods[] =
+{
+  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
+    "read_register (REG) -> gdb.Value\n"
+    "Return the value of the REG in the frame." },
+  { "unwind_info_with_id",
+    sniffer_infopy_unwind_info_with_id, METH_VARARGS,
+    "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP and PC registers \n"
+    "to identify the frame." },
+  { "unwind_info_with_id_special",
+    sniffer_infopy_unwind_info_with_id_special, METH_VARARGS,
+    "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL "
+    "registers to identify the frame." },
+  { "unwind_info_with_id_wild",
+    sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS,
+    "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n"
+    "Construct UnwindInfo for this FrameData, using given SP register to \n"
+    "identify the frame." },
+  {NULL}  /* Sentinel */
+};
+
+static PyTypeObject sniffer_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.SnifferInfo",              /* tp_name */
+  sizeof (sniffer_info_object),   /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  0,                              /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  sniffer_infopy_str,             /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT,             /* tp_flags */
+  "GDB SnifferInfo object",       /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  sniffer_info_object_methods,    /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
+
+
+static PyMethodDef unwind_info_object_methods[] =
+{
+  { "set_previous_frame_register",
+    unwind_infopy_set_previous_frame_register, METH_VARARGS,
+    "set_previous_frame_register (REG, VALUE) -> None\n"
+    "Set the value of the REG in the previous frame to VALUE." },
+  { NULL }  /* Sentinel */
+};
+
+static PyTypeObject unwind_info_object_type =
+{
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.UnwindInfo",               /* tp_name */
+  sizeof (unwind_info_object),    /* tp_basicsize */
+  0,                              /* tp_itemsize */
+  unwind_infopy_dealloc,          /* tp_dealloc */
+  0,                              /* tp_print */
+  0,                              /* tp_getattr */
+  0,                              /* tp_setattr */
+  0,                              /* tp_compare */
+  0,                              /* tp_repr */
+  0,                              /* tp_as_number */
+  0,                              /* tp_as_sequence */
+  0,                              /* tp_as_mapping */
+  0,                              /* tp_hash  */
+  0,                              /* tp_call */
+  unwind_infopy_str,              /* tp_str */
+  0,                              /* tp_getattro */
+  0,                              /* tp_setattro */
+  0,                              /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
+  "GDB UnwindInfo object",        /* tp_doc */
+  0,                              /* tp_traverse */
+  0,                              /* tp_clear */
+  0,                              /* tp_richcompare */
+  0,                              /* tp_weaklistoffset */
+  0,                              /* tp_iter */
+  0,                              /* tp_iternext */
+  unwind_info_object_methods,     /* tp_methods */
+  0,                              /* tp_members */
+  0,                              /* tp_getset */
+  0,                              /* tp_base */
+  0,                              /* tp_dict */
+  0,                              /* tp_descr_get */
+  0,                              /* tp_descr_set */
+  0,                              /* tp_dictoffset */
+  0,                              /* tp_init */
+  0,                              /* tp_alloc */
+};
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 4c4d32a..0581b33 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -391,12 +391,14 @@ PyObject *pspace_to_pspace_object (struct program_space *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *pspy_get_printers (PyObject *, void *);
 PyObject *pspy_get_frame_filters (PyObject *, void *);
+PyObject *pspy_get_frame_unwinders (PyObject *, void *);
 PyObject *pspy_get_xmethods (PyObject *, void *);
 
 PyObject *objfile_to_objfile_object (struct objfile *)
     CPYCHECKER_RETURNS_BORROWED_REF;
 PyObject *objfpy_get_printers (PyObject *, void *);
 PyObject *objfpy_get_frame_filters (PyObject *, void *);
+PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
 PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
@@ -491,6 +493,8 @@ int gdbpy_initialize_arch (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_xmethods (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_unwind (void)
+  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 
 struct cleanup *make_cleanup_py_decref (PyObject *py);
 struct cleanup *make_cleanup_py_xdecref (PyObject *py);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 58c7c92..1da63fd 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1821,7 +1821,8 @@ message == an error message without a stack will be printed."),
       || gdbpy_initialize_new_objfile_event ()  < 0
       || gdbpy_initialize_clear_objfiles_event ()  < 0
       || gdbpy_initialize_arch () < 0
-      || gdbpy_initialize_xmethods () < 0)
+      || gdbpy_initialize_xmethods () < 0
+      || gdbpy_initialize_unwind () < 0)
     goto fail;
 
   gdbpy_to_string_cst = PyString_FromString ("to_string");
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
new file mode 100644
index 0000000..8c1d935
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
@@ -0,0 +1,24 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main (void)
+{
+  int i = 0;
+
+  return i; /* next-line */
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
new file mode 100644
index 0000000..df2168b
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
@@ -0,0 +1,64 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests Python-based
+# unwinding CLI.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+if ![runto_main ] then {
+    fail "Can't run to main"
+    return -1
+}
+
+gdb_test "source ${pyfile}" "Python script imported" "import python scripts"
+
+gdb_test_sequence "info unwinder" "Show all unwinders" {
+    "global unwinders:"
+    "  global_unwinder"
+    "progspace.*unwinders:"
+    "py_unwind_maint_ps_unwinder"
+}
+
+gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
+
+gdb_test_sequence "continue" "Unwinders called" {
+    "py_unwind_maint_ps_unwinder called"
+    "global_unwinder called"
+}
+
+gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled"
+
+gdb_test_sequence "info unwinder" "Show with global unwinder disabled" {
+    "global unwinders:"
+    "  global_unwinder\\[disabled\\]"
+    "progspace.*unwinders:"
+    "  py_unwind_maint_ps_unwinder"
+}
+
+gdb_test_sequence "where" "Global unwinder disabled" {
+    "py_unwind_maint_ps_unwinder called\r\n#0  main"
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py
new file mode 100644
index 0000000..35d5313
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests python unwinders.
+
+import re
+import gdb.types
+from gdb.unwinder import Unwinder, register_unwinder
+
+class TestGlobalUnwinder(Unwinder):
+    def __init__(self):
+        super(TestGlobalUnwinder, self).__init__("global_unwinder")
+
+    def __call__(self, unwinder_info):
+        print "%s called" % self.name
+        return None
+
+class TestProgspaceUnwinder(Unwinder):
+    def __init__(self, name):
+        super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name)
+
+    def __call__(self, unwinder_info):
+        print "%s called" % self.name
+        return None
+
+class TestObjfileUnwinder(Unwinder):
+    def __init__(self, name):
+        super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name)
+
+    def __call__(self, unwinder_info):
+        print "%s called" % self.name
+        return None
+
+
+
+gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder())
+saw_runtime_error = False
+try:
+    gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder(), replace=False)
+except RuntimeError:
+    saw_runtime_error = True
+if not saw_runtime_error:
+    raise RuntimeError("Missing runtime error from register_unwinder.")
+gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder(), replace=True)
+gdb.unwinder.register_unwinder(gdb.current_progspace(),
+                               TestProgspaceUnwinder("py_unwind_maint"))
+print "Python script imported"
diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c
new file mode 100644
index 0000000..cf41d78
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.c
@@ -0,0 +1,81 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This is the test program loaded into GDB by the py-unwind test.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void *
+swap_value (void **location, void *new_value)
+{
+  void *old_value = *location;
+  *location = new_value;
+  return old_value;
+}
+
+static void
+bad_layout(void **variable_ptr, void *fp)
+{
+  fprintf (stderr, "First variable should be allocated one word below "
+           "the frame.  Got variable's address %p, frame at %p instead.\n",
+           variable_ptr, fp);
+  abort();
+}
+
+#define MY_FRAME (__builtin_frame_address (0))
+
+static void
+corrupt_frame_inner (void)
+{
+  /* Save outer frame address, then corrupt the unwind chain by
+     setting the outer frame address in it to self.  This is
+     ABI-specific: the first word of the frame contains previous frame
+     address in amd64.  */
+  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+
+  /* Verify the compiler allocates the first local variable one word
+     below frame.  This is where the test unwinder expects to find the
+     correct outer frame address.  */
+  if (&previous_fp + 1 != (void **) MY_FRAME)
+    bad_layout (&previous_fp + 1, MY_FRAME);
+
+  /* Now restore it so that we can return.  The test sets the
+     breakpoint just before this happens, and GDB will not be able to
+     show the backtrace without JIT reader.  */
+  swap_value ((void **) MY_FRAME, previous_fp); /* break backtrace-broken */
+}
+
+static void
+corrupt_frame_outer (void)
+{
+  /* See above for the explanation of the code here.  This function
+     corrupts its frame, too, and then calls the inner one.  */
+  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
+  if (&previous_fp + 1 != (void **) MY_FRAME)
+    bad_layout (&previous_fp, MY_FRAME);
+  corrupt_frame_inner ();
+  swap_value ((void **) MY_FRAME, previous_fp);
+}
+
+int
+main ()
+{
+  corrupt_frame_outer ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
new file mode 100644
index 0000000..53d6746
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -0,0 +1,54 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It verifies that frame
+# unwinders can be implemented in Python.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+# This test runs on a specific platform.
+if { ! [istarget x86_64-*]} { continue }
+
+# The following tests require execution.
+
+if ![runto_main] then {
+    fail "Can't run to main"
+    return 0
+}
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
+
+gdb_test "source ${pyfile}" "Python script imported" \
+         "import python scripts"
+
+gdb_continue_to_breakpoint "break backtrace-broken"
+gdb_test_sequence "where"  "Backtrace restored by unwinder" {
+    "\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
+    "\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
+    "\\r\\n#2 .* main \\(.*\\) at"
+}
+
+
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
new file mode 100644
index 0000000..8770578
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+from gdb.unwinder import Unwinder
+
+class TestUnwinder(Unwinder):
+    AMD64_RBP = 6
+    AMD64_RSP = 7
+    AMD64_RIP = 16
+
+    def __init__(self):
+        Unwinder.__init__(self, "test unwinder")
+        self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
+        self.char_ptr_ptr_t = self.char_ptr_t.pointer()
+
+    def _read_word(self, address):
+        return address.cast(self.char_ptr_ptr_t).dereference()
+
+    def __call__(self, sniffer_info):
+        """Test unwinder written in Python.
+
+        This unwinder can unwind the frames that have been deliberately
+        corrupted in a specific way (functions in the accompanying
+        py-unwind.c file do that.)
+        This code is only on AMD64.
+        On AMD64 $RBP points to the innermost frame (unless the code
+        was compiled with -fomit-frame-pointer), which contains the
+        address of the previous frame at offset 0. The functions
+        deliberately corrupt their frames as follows:
+                     Before                 After
+                   Corruption:           Corruption:
+                +--------------+       +--------------+
+        RBP-8   |              |       | Previous RBP |
+                +--------------+       +--------------+
+        RBP     + Previous RBP |       |    RBP       |
+                +--------------+       +--------------+
+        RBP+8   | Return RIP   |       | Return  RIP  |
+                +--------------+       +--------------+
+        Old SP  |              |       |              |
+
+        This unwinder recognizes the corrupt frames by checking that
+        *RBP == RBP, and restores previous RBP from the word above it.
+        """
+        try:
+            # NOTE: the registers in Unwinder API can be referenced
+            # either by name or by number. The code below uses both
+            # to achieve more coverage.
+            bp = sniffer_info.read_register("rbp").cast(self.char_ptr_t)
+            if self._read_word(bp) != bp:
+                return None
+            # Found the frame that the test program has corrupted for us.
+            # The correct BP for the outer frame has been saved one word
+            # above, previous IP and SP are at the expected places.
+            previous_bp = self._read_word(bp - 8)
+            previous_ip = self._read_word(bp + 8)
+            previous_sp = bp + 16
+
+            sp = sniffer_info.read_register(TestUnwinder.AMD64_RSP)
+            ip = sniffer_info.read_register(TestUnwinder.AMD64_RIP)
+            unwind_info = sniffer_info.unwind_info_with_id(sp, ip)
+            unwind_info.set_previous_frame_register(TestUnwinder.AMD64_RBP,
+                                                    previous_bp)
+            unwind_info.set_previous_frame_register("rip", previous_ip)
+            unwind_info.set_previous_frame_register("rsp", previous_sp)
+            return unwind_info
+        except (gdb.error, RuntimeError):
+            return None
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
+print("Python script imported")

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-17 19:48                               ` Alexander Smundak
  2015-03-17 21:37                                 ` Alexander Smundak
@ 2015-03-18 23:25                                 ` Doug Evans
  2015-03-19  0:36                                   ` Alexander Smundak
  1 sibling, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-03-18 23:25 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

On Tue, Mar 17, 2015 at 12:48 PM, Alexander Smundak <asmundak@google.com> wrote:
> IMHO we reached an agreement on API.
>
> I would really like the reviewers to express their opinions about the naming
> at this point. To summarize:
> * We need a name for the entity passed to a sniffer. This entity provides
> access to the registers of the frame being sniffed, and has factory methods
> to create instances returned by a sniffer.
> * We need a name for the entity returned by a sniffer.
> * Should the term 'sniffer' be used in the API and documentation describing
> implementing frame unwinding in GDB extensions?

I'm not sure I'm going to be of much help here.

I don't mind "sniffing" as the name of the act of deciding whether
a frame as recognized, and I do like "unwinder" as the name of
the object doing the sniffing. But then "sniffer" instead of
"unwinder" seems fine too.

Regarding the result of an unwinder/sniffer,
If I approach this from the point of view of what's
easy to explain, it feels like the result of an Unwinder
is a Frame.

Would it make sense to speak of an Unwinder taking
an EphemeralFrame as input and returning a Frame as output?

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-18 23:25                                 ` Doug Evans
@ 2015-03-19  0:36                                   ` Alexander Smundak
  2015-03-19  8:12                                     ` Andy Wingo
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-19  0:36 UTC (permalink / raw)
  To: Doug Evans; +Cc: Andy Wingo, gdb-patches

> Regarding the result of an unwinder/sniffer,
> If I approach this from the point of view of what's
> easy to explain, it feels like the result of an Unwinder
> is a Frame.
It is logical, but that's not what GDB core expects from the unwinder.
I hesitate to have Python frame unwinder API differ too much from
 the underlying GDB core API.

> Would it make sense to speak of an Unwinder taking
> an EphemeralFrame as input and returning a Frame as output?
Existing gdb.Frame interface wraps GDB core's `frame_info',
providing read-only capability and no instance construction.
An object returned by a Python unwinder is quite different.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-18 16:48                                   ` Doug Evans
@ 2015-03-19  8:04                                     ` Andy Wingo
  0 siblings, 0 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-19  8:04 UTC (permalink / raw)
  To: Doug Evans; +Cc: gdb-patches, guile-devel

Hi :)

On Wed 18 Mar 2015 17:48, Doug Evans <dje@google.com> writes:

> On Wed, Mar 18, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
>>
>> On Tue 17 Mar 2015 23:21, Doug Evans <dje@google.com> writes:
>>
>>> On Tue, Mar 17, 2015 at 1:57 AM, Andy Wingo <wingo@igalia.com> wrote:
>>>>> As to the class of an object passed to a sniffer, how about calling it
>>>>> FrameData? Note that it's not very important from the user's point of
>>>>> view as sniffer code does not ever reference it by name.
>>>>
>>>> It's true that from user code it barely matters to Python, but Scheme's
>>>> monomorphic flavor makes these things more apparent:
>>>>
>>>>   (frame-data-read-register frame "r0")
>>>>
>>>> This doesn't read so well to me -- is it "read-register" on a
>>>> "frame-data", or is it "data-read-register" on a "frame" ?  A weak point
>>>> but "ephemeral-frame-read-register" avoids the question.
>>>
>>> As food for discussion,
>>> I know some people use foo:bar in Scheme to separate
>>> the object "foo" from the operation on it "bar".
>>> -> frame-data:read-register
>>
>> This convention is not often used in Guile.  When it is used, it often
>> denotes field access rather than some more involved procedure call --
>> similar to the lowercase "foo_bar()" versus camel-cased "FooBar()" in
>> Google C++ guidelines.
>>
>>> I like having some separator, but I went with what
>>> I thought was the preferred spelling (all -'s).
>>> It's not too late to change gdb/guile to use foo:bar throughout (IMO),
>>> but the door is closing.
>>
>> FWIW, I prefer "-".
>
> Even though a different character solves a problem?
> What problem does it introduce?

I find it to be uncommon style in Guile and so it doesn't appeal to me.
YMMV.

Of course, what appeals to me is a function of what is common, and I'm
sure anything can work well as long as it's consistent.

Adding a convention for using colons also makes me wonder when to use
colons or dashes.  Perhaps that is my real objection.

Note that actually my original concern was not valid in this case;
"frame-data-read-register" is unlikely to be misread, because people
tend to break the name at the verb, if present.  "read-register" on a
"frame-data".

> The comparison with _ vs CamelCase is apples and oranges.

At least in V8 there is a similarity; there are two conventions for
writing method names: underscores if it's a simple field access or
camel-case for more complicated things.  Same decision criteria as colon
versus dash, as I understand the convention.  You don't have the
subject/verb punctuation problem but you do have different ways to write
the verb based on what's going on underneath.

Doug if you want a specific coding style do let me know and I'm happy to
change.  Please consider these comments to be just gut reactions /
reflections.

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-19  0:36                                   ` Alexander Smundak
@ 2015-03-19  8:12                                     ` Andy Wingo
  2015-03-20  0:15                                       ` Doug Evans
  0 siblings, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-19  8:12 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Doug Evans, gdb-patches

On Thu 19 Mar 2015 01:36, Alexander Smundak <asmundak@google.com> writes:

>> Regarding the result of an unwinder/sniffer,
>> If I approach this from the point of view of what's
>> easy to explain, it feels like the result of an Unwinder
>> is a Frame.
> It is logical, but that's not what GDB core expects from the unwinder.
> I hesitate to have Python frame unwinder API differ too much from
>  the underlying GDB core API.

I agree with Alexander.  If it were really a frame you'd have to expose
a frame constructor to Python/GDB, and then would the resulting frame be
interned?  What would happen if you built a frame but then returned
None, or threw an exception?  Would there be side effects to the frame
cache?  And if not, could you hold on to the frame?  Would it be equal
to frame.newer().older() ?  Better to return data instead, which GDB
uses to build the actual frame.

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-19  8:12                                     ` Andy Wingo
@ 2015-03-20  0:15                                       ` Doug Evans
  2015-03-20  2:27                                         ` Alexander Smundak
  2015-03-20  8:26                                         ` Andy Wingo
  0 siblings, 2 replies; 60+ messages in thread
From: Doug Evans @ 2015-03-20  0:15 UTC (permalink / raw)
  To: Andy Wingo, Alexander Smundak; +Cc: gdb-patches

Andy Wingo writes:
 > On Thu 19 Mar 2015 01:36, Alexander Smundak <asmundak@google.com> writes:
 > 
 > >> Regarding the result of an unwinder/sniffer,
 > >> If I approach this from the point of view of what's
 > >> easy to explain, it feels like the result of an Unwinder
 > >> is a Frame.
 > > It is logical, but that's not what GDB core expects from the unwinder.
 > > I hesitate to have Python frame unwinder API differ too much from
 > >  the underlying GDB core API.
 > 
 > I agree with Alexander.  If it were really a frame you'd have to expose
 > a frame constructor to Python/GDB, and then would the resulting frame be
 > interned?  What would happen if you built a frame but then returned
 > None, or threw an exception?  Would there be side effects to the frame
 > cache?  And if not, could you hold on to the frame?  Would it be equal
 > to frame.newer().older() ?  Better to return data instead, which GDB
 > uses to build the actual frame.

Yeah.

Having read everything through last night,
here are some thoughts in no particular order.
Don't rush off and resubmit another patch (unless you want to).
I think there's enough on the table now to get the
high level details decided on. I do see a few
implementation issues in the patches but I
don't want to discuss them prematurely.

1) I like the idea of the input to the unwinder being
a frame (of some kind, ephemeral or whatever).

In python, let's use EphemeralFrame instead of SnifferInfo.

But the only real output of the sniffing action
is a boolean saying "I recognize this frame".
ref: frame-unwind.c:frame_unwind_try_unwinder().
If the unwinder wants to cache something during sniffing
that's its business, and it can cache whatever is appropriate.
IIUC, What the patches do is use the result
of the sniffing action to both say "I do/don't recognize
this frame" and to return an object that is the cache
of random data (which it calls "UnwindInfo").
Thus "UnwindInfo" is as good a name as anything.

2) IIUC, setting aside hitting the outermost frame and such,
it's a rule that some unwinder will recognize the frame.
Therefore "ephemeral" isn't quite right, even "real"
frames are ephemeral since we toss the frame cache when
the inferior resumes. OTOH, we need to make progress
and I'm just throwing this out there. "ephemeral" is
fine with me: we don't use that term for anything else
which is nice.

3) We need to allow sniffers to record anything they
want in gdb.UnwindInfo/<gdb:unwind-info>. In Python
I'm guessing one can just add extra attributes to the
object or subclass. In Guile I guess one could use
an external container (weakly?) indexed by the <gdb:unwind_info>
object. In both cases the documentation should
make recommendations to the reader.
[If it does and I missed it apologies.]

4) I gather the UnwindInfo/<gdb:unwind-info> objects provide
direct support for recording registers so that one doesn't
have to go back out to the extension language to implement
frame_unwind.prev_register.

Plus, they also create the frame id when creating the UnwindInfo
object so that frame_unwind.this_id doesn't have to call back
out to the extension language.

That's fine. Just wanted to write this down to confirm.
Both implementations allow for adding future support for
having frame_unwind.prev_register/this_id/stop_reason
calling back out to the extension language if we need it.

5) The docs don't make any mention of target endianness
in the saved register values. They probably should.

6) I noticed several routines for building frame ids in Python.
Both Python and Guile support keyword based arguments to functions.
Can we just have one frame id builder function and each take
sp and pc (and special if we need to) as keyword arguments?
E.g. (make-unwind-info ephem-frame #:sp sp #:pc pc)
But see (7) below.

7) If we ever need to use frame ids in the extension language
IWBN if they were their own object, in which case we might have
(make-unwind-info ephem-frame #:frame-id frame-id)
but then I'm wondering if there should be an
unwind-info-set-frame-id! function and move sp,pc there.

IOW change make-unwind-info to this so that the various
ways of creating a frame idea are don't complicate make-unwind-info.
[I realize one might want to not let make-unwind-info return
an object without a frame id, but I don't see this as a problem.
uwscm_sniffer could verify a frame id is present.]

(let ((unwind-info (make-unwind-info ephem-frame)))
  (unwind-info-set-frame-id! #:sp sp #:pc pc)
  (unwind-info-add-saved-register! unwind-info "rip" rip)
  ...)

And for python:

I'm not sure where to put the UnwindInfo creator (factory method).
Do we need one?  Can we just create them via normal object construction?

unwind_info = gdb.UnwindInfo(ephem_frame)
unwind_info.set_frame_id(...)
unwind_info.set_previous_frame_register(...)

This could all be done in pure Python (I think), and then
pyuw_sniffer could process the object looking for expected
members(attributes) with expected types (and throw an error
if there's a problem).

At the least let's combine unwind_info_with_id, frame_id_build_special,
and frame_id_build_wild into one function that takes keyword
arguments for each of sp, pc, special.

8) Re: set_previous_frame_register vs unwind-info-add-saved-register!

Dunno. On this particular point I guess I don't have a strong
enough interesting in being consistent.
I'll let you guys decide.

---

That's it for now.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-20  0:15                                       ` Doug Evans
@ 2015-03-20  2:27                                         ` Alexander Smundak
  2015-03-20 17:48                                           ` Doug Evans
  2015-03-20  8:26                                         ` Andy Wingo
  1 sibling, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-20  2:27 UTC (permalink / raw)
  To: Doug Evans; +Cc: Andy Wingo, gdb-patches

> In python, let's use EphemeralFrame instead of SnifferInfo.

I'd rather have InspectedFrame than EphemeralFrame,
but it's fine with me if it expedites the review.

> 3) We need to allow sniffers to record anything they
> want in gdb.UnwindInfo/<gdb:unwind-info>. In Python
> I'm guessing one can just add extra attributes to the
> object or subclass. In Guile I guess one could use
> an external container (weakly?) indexed by the <gdb:unwind_info>
> object. In both cases the documentation should
> make recommendations to the reader.
> [If it does and I missed it apologies.]

It isn't a big issue for the JVM unwinder to do the lookups
twice (first in unwinder, then in the frame filter), but it might
be expensive in other languages. Not sure there is a need for
multiple attributes -- wouldn't a single attribute whose value is
an object suffice?

> 5) The docs don't make any mention of target endianness
> in the saved register values. They probably should.
Register values passed are gdb.Value instances, so I thought
that if they are created using the official interface (get inspected
frame's register, call its `cast' method to cast it to inferior's memory
pointer. then call `dereference' we wolud be fine.

> 6) I noticed several routines for building frame ids in Python.
> Both Python and Guile support keyword based arguments to functions.
> Can we just have one frame id builder function and each take
> sp and pc (and special if we need to) as keyword arguments?
> E.g. (make-unwind-info ephem-frame #:sp sp #:pc pc)
> But see (7) below.
>
> 7) If we ever need to use frame ids in the extension language
> IWBN if they were their own object, in which case we might have
> (make-unwind-info ephem-frame #:frame-id frame-id)
> but then I'm wondering if there should be an
> unwind-info-set-frame-id! function and move sp,pc there.
>
> IOW change make-unwind-info to this so that the various
> ways of creating a frame idea are don't complicate make-unwind-info.
> [I realize one might want to not let make-unwind-info return
> an object without a frame id, but I don't see this as a problem.
> uwscm_sniffer could verify a frame id is present.]
>
> (let ((unwind-info (make-unwind-info ephem-frame)))
>   (unwind-info-set-frame-id! #:sp sp #:pc pc)
>   (unwind-info-add-saved-register! unwind-info "rip" rip)
>   ...)
>
> And for python:
>
> I'm not sure where to put the UnwindInfo creator (factory method).
> Do we need one?  Can we just create them via normal object construction?
We can (I believe penultimate patch had UnwindInfo constructor and frame_id
methods). The advantage of the factory method is that it's impossible to create
an UnwindInfo which lacks frame ID.

> unwind_info = gdb.UnwindInfo(ephem_frame)
> unwind_info.set_frame_id(...)
> unwind_info.set_previous_frame_register(...)
>
> This could all be done in pure Python (I think), and then
> pyuw_sniffer could process the object looking for expected
> members(attributes) with expected types (and throw an error
> if there's a problem).
This way the error checking happens on return. If we have several
unwinders, it will not be obvious which one sniffer the frame and
returned bad UnwindInfo.

> At the least let's combine unwind_info_with_id, frame_id_build_special,
> and frame_id_build_wild into one function that takes keyword
> arguments for each of sp, pc, special.
This will loose the association with the underlying GDB API,
but I am fine with this.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-20  0:15                                       ` Doug Evans
  2015-03-20  2:27                                         ` Alexander Smundak
@ 2015-03-20  8:26                                         ` Andy Wingo
  2015-03-20 18:32                                           ` Doug Evans
  1 sibling, 1 reply; 60+ messages in thread
From: Andy Wingo @ 2015-03-20  8:26 UTC (permalink / raw)
  To: Doug Evans; +Cc: Alexander Smundak, gdb-patches

Hi,

Thanks for taking the time to look at the patches, Doug!

On Fri 20 Mar 2015 01:15, Doug Evans <dje@google.com> writes:

> 2) IIUC, setting aside hitting the outermost frame and such,
> it's a rule that some unwinder will recognize the frame.
> Therefore "ephemeral" isn't quite right, even "real"
> frames are ephemeral since we toss the frame cache when
> the inferior resumes. OTOH, we need to make progress
> and I'm just throwing this out there. "ephemeral" is
> fine with me: we don't use that term for anything else
> which is nice.

Just to throw out some other names -- PendingFrame.  Alexander mentioned
InspectedFrame.  IntermediateFrame.  FrameTemplate.  SkeletonFrame.
MinimalFrame.  FrameStub.

Dunno, just some ideas :)

> 3) We need to allow sniffers to record anything they
> want in gdb.UnwindInfo/<gdb:unwind-info>.

I see from your point (4) later that you are thinking that if we add
some kind of callback interface to UnwindInfo that e.g. Guile code might
need to fetch arbitrary data from the UnwindInfo.

You also note correctly that this is not currently something that the
patches require -- the UnwindInfo contains all of the information, and
the C prev_register/this_id callbacks just look into the UnwindInfo
data.

If we did add a callback interface I don't think there would be a
problem.

Let's say we want to add a way for prev_register to call back into
Guile.  If the prev-register callback is added to the unwind info via
unwind-info-set-prev-register-callback! or something then the callback
can be a closure that captures the data it needs.  Or it can use an
external weak hash table.  Or when we add the
set-prev-register-callback! interface, we can add some other associated
interface to store data in the unwind-info.  There are multiple fine
options here.

> 5) The docs don't make any mention of target endianness
> in the saved register values. They probably should.

+1 to Alexander's answer -- as they are GDB values, there shouldn't be
an issue, should there?

> 6) I noticed several routines for building frame ids in Python.
> Both Python and Guile support keyword based arguments to functions.
> Can we just have one frame id builder function and each take
> sp and pc (and special if we need to) as keyword arguments?
> E.g. (make-unwind-info ephem-frame #:sp sp #:pc pc)
> But see (7) below.
>
> 7) If we ever need to use frame ids in the extension language
> IWBN if they were their own object, in which case we might have
> (make-unwind-info ephem-frame #:frame-id frame-id)

This would be fine; and actually at this point the kwarg is unnecessary.

  (make-unwind-info ephem-frame frame-id)

> but then I'm wondering if there should be an
> unwind-info-set-frame-id! function and move sp,pc there.

I agree with Alexander that this isn't great.  The only thing that an
unwinder *must* do is set a frame ID.  It must produce a frame_id at the
same time as the sniffer returns TRUE.  (They are different callbacks
but always called one after the other.)  Therefore it makes sense to
have the invariant:

  Is it an UnwindInfo?  Then it definitely has a frame ID.

That way it takes away one possible error case.  Also you wouldn't want
to set the frame ID on an UnwindInfo twice, it doesn't make sense.

> I'm not sure where to put the UnwindInfo creator (factory method).
> Do we need one?  Can we just create them via normal object construction?

In Guile you have to give it a name of course -- make-unwind-info.  But
in Python I liked Alexander's use of a factory method.  But I dunno,
I'll leave the Python discussion to you all :)

> 8) Re: set_previous_frame_register vs unwind-info-add-saved-register!
>
> Dunno. On this particular point I guess I don't have a strong
> enough interesting in being consistent.
> I'll let you guys decide.

If you don't have such an interest on being consistent for this piece
then I guess the easy way is to leave these as they are then.  Avoids
multiple-day awkward backs and forths :)  But if you decide they need to
be the same let us know.

Speaking for myself I prefer the Guile one of course, but I prefer
executive decisions over having to endure more days of naming
discussions over email :)

Thank you again Doug for the time!

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-20  2:27                                         ` Alexander Smundak
@ 2015-03-20 17:48                                           ` Doug Evans
  0 siblings, 0 replies; 60+ messages in thread
From: Doug Evans @ 2015-03-20 17:48 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

Alexander Smundak writes:
 > > In python, let's use EphemeralFrame instead of SnifferInfo.
 > 
 > I'd rather have InspectedFrame than EphemeralFrame,
 > but it's fine with me if it expedites the review.

Andy suggested PendingFrame.
Let's go with that.
It is a frame, but not a "real" one yet.

 > > 3) We need to allow sniffers to record anything they
 > > want in gdb.UnwindInfo/<gdb:unwind-info>. In Python
 > > I'm guessing one can just add extra attributes to the
 > > object or subclass. In Guile I guess one could use
 > > an external container (weakly?) indexed by the <gdb:unwind_info>
 > > object. In both cases the documentation should
 > > make recommendations to the reader.
 > > [If it does and I missed it apologies.]
 > 
 > It isn't a big issue for the JVM unwinder to do the lookups
 > twice (first in unwinder, then in the frame filter), but it might
 > be expensive in other languages. Not sure there is a need for
 > multiple attributes -- wouldn't a single attribute whose value is
 > an object suffice?

I was looking through all the internal unwinders in gdb.
Some of them cache more than just registers.
The extension languages don't export the stop_reason, this_id
or prev_register API calls, but some day they might,
and I want to make sure that we don't make it difficult
to do so later. But (3) was written early, I should have
edited it out so disregard.

 > > 5) The docs don't make any mention of target endianness
 > > in the saved register values. They probably should.
 > Register values passed are gdb.Value instances, so I thought
 > that if they are created using the official interface (get inspected
 > frame's register, call its `cast' method to cast it to inferior's memory
 > pointer. then call `dereference' we wolud be fine.

We can't control how the user will construct the value.
We can only provide guidance/rules on what will work.
The value has to be the same size as the register,
but we don't say that anywhere either (we should).

 > > 6) I noticed several routines for building frame ids in Python.
 > > Both Python and Guile support keyword based arguments to functions.
 > > Can we just have one frame id builder function and each take
 > > sp and pc (and special if we need to) as keyword arguments?
 > > E.g. (make-unwind-info ephem-frame #:sp sp #:pc pc)
 > > But see (7) below.
 > >
 > > 7) If we ever need to use frame ids in the extension language
 > > IWBN if they were their own object, in which case we might have
 > > (make-unwind-info ephem-frame #:frame-id frame-id)
 > > but then I'm wondering if there should be an
 > > unwind-info-set-frame-id! function and move sp,pc there.
 > >
 > > IOW change make-unwind-info to this so that the various
 > > ways of creating a frame idea are don't complicate make-unwind-info.
 > > [I realize one might want to not let make-unwind-info return
 > > an object without a frame id, but I don't see this as a problem.
 > > uwscm_sniffer could verify a frame id is present.]
 > >
 > > (let ((unwind-info (make-unwind-info ephem-frame)))
 > >   (unwind-info-set-frame-id! #:sp sp #:pc pc)
 > >   (unwind-info-add-saved-register! unwind-info "rip" rip)
 > >   ...)
 > >
 > > And for python:
 > >
 > > I'm not sure where to put the UnwindInfo creator (factory method).
 > > Do we need one?  Can we just create them via normal object construction?
 > We can (I believe penultimate patch had UnwindInfo constructor and frame_id
 > methods). The advantage of the factory method is that it's impossible to create
 > an UnwindInfo which lacks frame ID.

The constructor could take a frame ID too.

One problem I have is that while frame IDs are just sp,pc,special
today who knows what they will have tomorrow. Hopefully we could
just use special, but I wouldn't assume that.
If we separate out frame ID construction from UnwindInfo construction
then we can grow frame IDs without having to complicate UnwindInfo.
The complexity lives where it should live: in the FrameID constructor.

 > > unwind_info = gdb.UnwindInfo(ephem_frame)
 > > unwind_info.set_frame_id(...)
 > > unwind_info.set_previous_frame_register(...)
 > >
 > > This could all be done in pure Python (I think), and then
 > > pyuw_sniffer could process the object looking for expected
 > > members(attributes) with expected types (and throw an error
 > > if there's a problem).
 > This way the error checking happens on return. If we have several
 > unwinders, it will not be obvious which one sniffer the frame and
 > returned bad UnwindInfo.

Good point, but there's a general problem here: E.g., What if
the sniffer created a frame id with sp and pc swapped?
Or what if the pc got errantly elided?

I recognize the goal of detecting errors at construction time,
but in this case I think you're only solving one piece of a
larger problem. If you feel it's important in this case I
don't make making the frame id a required argument to the
UnwindInfo constructor, but let's pass it an object of type FrameID.

I have similar problems with pretty-printers btw: sometimes
I don't always know which printer got selected.
We need to provide ways to debug these kinds of problems.

For debugging purposes IWBN if the result had a backlink to
its creator. Then any error messages could reference the creator.
Thoughts?

Also, I was thinking of using "set debug foo" options, but those are for
maintainers and these aren't gdb bugs. OTOH "set debug pretty-printer"
is appealing. It's harder for unwinders because we need a unique name
for extension language provided unwinders: we don't want the option
to also turn on debugging of gdb's internal unwinders (it's likely
just noise to the user).

set debug python unwinder?
set python debug unwinder?

I kinda like leaving "set debug foo" for internal gdb usage,
and we do already have set python foo, so "set python debug unwinder"
is what I'm proposing.
I thought of names not containing the word python/guile
but in this case we know which extension language we're working in.
The code that looped over all unwinders could check this parameter
and if set print the name of winning unwinder before it returns.
There's no need to complicate the current patch with this
additional functionality though, we can add it as a follow-on
patch if you want.

 > > At the least let's combine unwind_info_with_id, frame_id_build_special,
 > > and frame_id_build_wild into one function that takes keyword
 > > arguments for each of sp, pc, special.
 > This will loose the association with the underlying GDB API,
 > but I am fine with this.

Cool.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-20  8:26                                         ` Andy Wingo
@ 2015-03-20 18:32                                           ` Doug Evans
  0 siblings, 0 replies; 60+ messages in thread
From: Doug Evans @ 2015-03-20 18:32 UTC (permalink / raw)
  To: Andy Wingo; +Cc: Alexander Smundak, gdb-patches

Andy Wingo writes:
 > Hi,
 > 
 > Thanks for taking the time to look at the patches, Doug!
 > 
 > On Fri 20 Mar 2015 01:15, Doug Evans <dje@google.com> writes:
 > 
 > > 2) IIUC, setting aside hitting the outermost frame and such,
 > > it's a rule that some unwinder will recognize the frame.
 > > Therefore "ephemeral" isn't quite right, even "real"
 > > frames are ephemeral since we toss the frame cache when
 > > the inferior resumes. OTOH, we need to make progress
 > > and I'm just throwing this out there. "ephemeral" is
 > > fine with me: we don't use that term for anything else
 > > which is nice.
 > 
 > Just to throw out some other names -- PendingFrame.  Alexander mentioned
 > InspectedFrame.  IntermediateFrame.  FrameTemplate.  SkeletonFrame.
 > MinimalFrame.  FrameStub.
 > 
 > Dunno, just some ideas :)

I like PendingFrame.
It's a frame, but not a real one yet.

 > > 3) We need to allow sniffers to record anything they
 > > want in gdb.UnwindInfo/<gdb:unwind-info>.
 > 
 > I see from your point (4) later that you are thinking that if we add
 > some kind of callback interface to UnwindInfo that e.g. Guile code might
 > need to fetch arbitrary data from the UnwindInfo.
 > 
 > You also note correctly that this is not currently something that the
 > patches require -- the UnwindInfo contains all of the information, and
 > the C prev_register/this_id callbacks just look into the UnwindInfo
 > data.
 > 
 > If we did add a callback interface I don't think there would be a
 > problem.

Cool.

 > Let's say we want to add a way for prev_register to call back into
 > Guile.  If the prev-register callback is added to the unwind info via
 > unwind-info-set-prev-register-callback! or something then the callback
 > can be a closure that captures the data it needs.  Or it can use an
 > external weak hash table.  Or when we add the
 > set-prev-register-callback! interface, we can add some other associated
 > interface to store data in the unwind-info.  There are multiple fine
 > options here.
 > 
 > > 5) The docs don't make any mention of target endianness
 > > in the saved register values. They probably should.
 > 
 > +1 to Alexander's answer -- as they are GDB values, there shouldn't be
 > an issue, should there?

Well, as I wrote in my reply to Alexander,
we can't control how the user will create these values.
They *might* want to do something off the beaten path, so to speak.
We should specify what's required.

 > > 6) I noticed several routines for building frame ids in Python.
 > > Both Python and Guile support keyword based arguments to functions.
 > > Can we just have one frame id builder function and each take
 > > sp and pc (and special if we need to) as keyword arguments?
 > > E.g. (make-unwind-info ephem-frame #:sp sp #:pc pc)
 > > But see (7) below.
 > >
 > > 7) If we ever need to use frame ids in the extension language
 > > IWBN if they were their own object, in which case we might have
 > > (make-unwind-info ephem-frame #:frame-id frame-id)
 > 
 > This would be fine; and actually at this point the kwarg is unnecessary.

I'd have make-frame-id take kwargs though.

 >   (make-unwind-info ephem-frame frame-id)
 > 
 > > but then I'm wondering if there should be an
 > > unwind-info-set-frame-id! function and move sp,pc there.
 > 
 > I agree with Alexander that this isn't great.  The only thing that an
 > unwinder *must* do is set a frame ID.  It must produce a frame_id at the
 > same time as the sniffer returns TRUE.  (They are different callbacks
 > but always called one after the other.)  Therefore it makes sense to
 > have the invariant:
 > 
 >   Is it an UnwindInfo?  Then it definitely has a frame ID.
 > 
 > That way it takes away one possible error case.

I recognize these points, but I guess in this particular case
I don't feel as strongly. But I don't mind making
frame-id a required argument to make-unwind-info.
So go for it.

 > Also you wouldn't want
 > to set the frame ID on an UnwindInfo twice, it doesn't make sense.

One could say the same of saved registers.

 > > I'm not sure where to put the UnwindInfo creator (factory method).
 > > Do we need one?  Can we just create them via normal object construction?
 > 
 > In Guile you have to give it a name of course -- make-unwind-info.  But
 > in Python I liked Alexander's use of a factory method.  But I dunno,
 > I'll leave the Python discussion to you all :)

A factory method is a layer of abstraction
which is nice (e.g., if later we need to change/extend things
it'd be easier to add a new method or whatever than to play
with the constructor). So ok let's use a factory method.

 > > 8) Re: set_previous_frame_register vs unwind-info-add-saved-register!
 > >
 > > Dunno. On this particular point I guess I don't have a strong
 > > enough interesting in being consistent.
 > > I'll let you guys decide.
 > 
 > If you don't have such an interest on being consistent for this piece
 > then I guess the easy way is to leave these as they are then.  Avoids
 > multiple-day awkward backs and forths :)  But if you decide they need to
 > be the same let us know.
 > 
 > Speaking for myself I prefer the Guile one of course, but I prefer
 > executive decisions over having to endure more days of naming
 > discussions over email :)

I'll make an executive decision to let you guys decide.  1/2 :-)

"But seriously, ..."
Let's go with add_saved_register / unwind-info-add-saved-register!
Sorry Alexander. :-)
I have to pause whenever I read "previous" vs "next": the meaning
is clear in frame-unwind.h, but it just doesn't stick.
E.g., The next frame to unwind is the previous frame on the stack.
Thank goodness frame.c uses all of next/down/inner/younger
and prev/up/outer/older.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-18 22:57                                     ` Alexander Smundak
@ 2015-03-23 19:58                                       ` Doug Evans
  2015-03-24  9:06                                         ` Andy Wingo
  2015-03-26  3:31                                         ` Alexander Smundak
  0 siblings, 2 replies; 60+ messages in thread
From: Doug Evans @ 2015-03-23 19:58 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

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

Hi.

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

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

 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >         * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
 >         (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
 >         (py-unwind.o): New recipe.
 >         * NEWS: mention Python frame unwinding.
 >         * data-directory/Makefile.in (PYTHON_FILE_LIST):  Add unwinders.py.
 >         * doc/python.texi (Writing a Frame Unwinder in Python): Add
 >         section.
 >         * python/lib/gdb/__init__.py (packages): Add frame_unwinders list.
 >         * python/lib/gdb/command/unwinders.py: New file, implements GDB
 >         commands to list/enable/disable Python unwinders.
 >         * python/lib/gdb/function/unwinders.py: New file, implements
 >         execute_unwinders function.
 >         * python/lib/gdb/unwinder.py: New file, contains Unwinder class and
 >         register_unwinder function.
 >         * python/py-objfile.c (objfile_object): Add frame_unwinders field.
 >         (objfpy_dealloc): Decrement frame_unwinders reference count.
 >         (objfpy_initialize): Create frame_unwinders list.
 >         (objfpy_get_frame_unwinders): Implement Objfile.frame_unwinders
 >         getter.

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

 >         (objfpy_set_frame_unwinders): Implement Objfile.frame_unwinders
 >         setter.
 >         (objfile_getset): Add frame_unwinders attribute to Objfile.
 >         * python/py-progspace.c (pspace_object): Add frame_unwinders field.
 >         (pspy_dealloc): Decrement frame_unwinders reference count.
 >         (pspy_initialize): Create frame_unwinders list.
 >         (pspy_get_frame_unwinders): Implement gdb.Progspace.frame_unwinders
 >         getter.
 >         (pspy_set_frame_unwinders): Implement gdb.Progspace.frame_unwinders
 >         setter.
 >         (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
 >         * python/py-unwind.c: New file, implements Python frame unwinders
 >         interface.
 >         * python/python-internal.h (pspy_get_name_unwinders): New prototype.
 >         (objpy_get_frame_unwinders): New prototype.
 >         (gdbpy_initialize_unwind): New prototype.
 >         * python/python.c (gdbpy_apply_type_printers): Call
 >         gdbpy_initialize_unwind.
 > 
 > 2015-03-28  Sasha Smundak  <asmundak@google.com>
 > 
 >         * gdb.python/py-unwind-maint.c: Test program for py-unwind-maint.
 >         * gdb.python/py-unwind-maint.exp: Tests unwinder-related GDB
 >         commands.
 >         * gdb.python/py-unwind-maint.py: Pythons frame unwinders for the test.
 >         * gdb.python/py-unwind.c: Test program for the py-unwind test.
 >         * gdb.python/py-unwind.exp: Python frame unwinders test.
 >         * gdb.python/py-unwind.py: Python frame unwinder tested by
 >         py-unwind test.

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

 > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
 > index dbace2d..0bd3738 100644
 > --- a/gdb/Makefile.in
 > +++ b/gdb/Makefile.in
 > @@ -398,6 +398,7 @@ SUBDIR_PYTHON_OBS = \
 >  	py-symtab.o \
 >  	py-threadevent.o \
 >  	py-type.o \
 > +	py-unwind.o \
 >  	py-utils.o \
 >  	py-value.o \
 >  	py-varobj.o
 > @@ -437,6 +438,7 @@ SUBDIR_PYTHON_SRCS = \
 >  	python/py-symtab.c \
 >  	python/py-threadevent.c \
 >  	python/py-type.c \
 > +	python/py-unwind.c \
 >  	python/py-utils.c \
 >  	python/py-value.c \
 >  	python/py-varobj.c
 > @@ -2622,6 +2624,10 @@ py-type.o: $(srcdir)/python/py-type.c
 >  	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-type.c
 >  	$(POSTCOMPILE)
 >  
 > +py-unwind.o: $(srcdir)/python/py-unwind.c
 > +	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-unwind.c
 > +	$(POSTCOMPILE)
 > +
 >  py-utils.o: $(srcdir)/python/py-utils.c
 >  	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-utils.c
 >  	$(POSTCOMPILE)
 > diff --git a/gdb/NEWS b/gdb/NEWS
 > index bda4a35..ac994d9 100644
 > --- a/gdb/NEWS
 > +++ b/gdb/NEWS
 > @@ -12,6 +12,7 @@
 >    ** gdb.Objfile objects have a new attribute "username",
 >       which is the name of the objfile as specified by the user,
 >       without, for example, resolving symlinks.
 > +  ** You can now write frame unwinders in Python.
 >  
 >  * New commands
 >  
 > diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
 > index c01b86d..049aa05 100644
 > --- a/gdb/data-directory/Makefile.in
 > +++ b/gdb/data-directory/Makefile.in
 > @@ -62,17 +62,20 @@ PYTHON_FILE_LIST = \
 >  	gdb/FrameDecorator.py \
 >  	gdb/types.py \
 >  	gdb/printing.py \
 > +	gdb/unwinder.py \
 >  	gdb/prompt.py \
 >  	gdb/xmethod.py \
 >  	gdb/command/__init__.py \
 >  	gdb/command/xmethods.py \
 >  	gdb/command/frame_filters.py \
 > +	gdb/command/unwinders.py \
 >  	gdb/command/type_printers.py \
 >  	gdb/command/pretty_printers.py \
 >  	gdb/command/prompt.py \
 >  	gdb/command/explore.py \
 >  	gdb/function/__init__.py \
 >  	gdb/function/caller_is.py \
 > +        gdb/function/unwinders.py \

Use tab here instead of spaces for consistency.

 >  	gdb/function/strfns.py \
 >  	gdb/printer/__init__.py \
 >  	gdb/printer/bound_registers.py
 > diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
 > index d725eb0..a2685e8 100644
 > --- a/gdb/doc/python.texi
 > +++ b/gdb/doc/python.texi
 > @@ -144,6 +144,7 @@ optional arguments while skipping others.  Example:
 >  * Frame Filter API::            Filtering Frames.
 >  * Frame Decorator API::         Decorating Frames.
 >  * Writing a Frame Filter::      Writing a Frame Filter.
 > +* Unwinding Frames in Python::  Writing frame unwinder.
 >  * Xmethods In Python::          Adding and replacing methods of C++ classes.
 >  * Xmethod API::                 Xmethod types.
 >  * Writing an Xmethod::          Writing an xmethod.
 > @@ -2178,6 +2179,132 @@ printed hierarchically.  Another approach would be to combine the
 >  marker in the inlined frame, and also show the hierarchical
 >  relationship.
 >  
 > +@node Unwinding Frames in Python
 > +@subsubsection Unwinding Frames in Python
 > +@cindex Unwinding frames in Python.

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

 > +
 > +In GDB terminology ``unwinding'' is the process of finding the
 > +previous frame (that is, caller's) from the current one. An unwinder
 > +has three methods. The first one checks if it can handle given frame
 > +(``sniff'' it). For the frames it can sniff an unwinder provides two
 > +additional methods: it can return frame's ID, and it can fetch
 > +registers from the previous frame. A running GDB mantains a list of
 > +the unwinders and calls each unwinder's sniffer in turn until it finds
 > +the one that recognizes the current frame. There is an API to register
 > +an unwinder.
 > +
 > +The unwinders that come with GDB handle standard frames for each
 > +platform where GDB is running. However, mixed language applications
 > +(for example, and application running Java Virtual Machine) sometimes
 > +use frame layouts that cannot be handled by the GDB unwinders. You can
 > +write Python code that can handle such custom frames.
 > +
 > +You implement a frame unwinder in Python as a class with which has two
 > +attributes, @code{name} and @code{enabled}, with obvious meanings, and
 > +a single method @code{__call__}, which examines a given frame and
 > +returns an object (an instance of gdb.UnwindInfo class) describing
 > +it. If an unwinder does not recognize a frame, it should return
 > +@code{None}. The code in GDB that enables writing unwinders in Python
 > +uses this object to return frame's ID and previous frame registers
 > +when GDB core asks for them.
 > +
 > +@subheading Unwinder Input
 > +
 > +An object passed to an unwinder (a @code{SnifferInfo} instance) provides
 > +a method to read frame's registers:
 > +
 > +@defun SnifferInfo.read_register (reg)
 > +This method returns the contents of the register @var{regn} in the
 > +frame as a @code{gdb.Value} object. @var{reg} can be either a register
 > +number or a register name; the values are platform-specific. They are
 > +usually found in the corresponding xxx-@code{tdep.h} file in the gdb
 > +source tree.
 > +@end defun
 > +
 > +It also provides several factory methods. If an unwinder recognizes
 > +the frame, it should invoke one of them to create a gdb.UnwindInfo
 > +instance to be returned to GDB:
 > +
 > +@defun SnifferInfo.unwind_info_with_id(sp, pc)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{sp} and @var{pc} values. This is the most common way of creating
 > +instance result.
 > +@end defun
 > +
 > +@defun SnifferInfo.frame_id_build_special(sp, pc, special)
 > +Returns a new @code{gdb.UnwindInfo} instance identitified by given
 > +@var{sp}, @var{pc}, and @var{special} values.
 > +@end defun
 > +
 > +@defun gdb.UnwindInfo.frame_id_build_wild(sp)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{sp} value.
 > +@end defun
 > +
 > +@subheading Unwinder Output: UnwindInfo
 > +
 > +A @code{gdb.UnwindInfo} object can be constructed by one of the
 > +methods described above. Use the following method to set the caller
 > +frame's registers:
 > +
 > +@defun gdb.UnwindInfo.set_previous_frame_register(reg, value)
 > +@var{reg} identifies the register. It can be a number or a name, just
 > +as for the @code{SnifferInfo.read_register} method above. @var{value}
 > +is a register value (a @code{gdb.Value} object).
 > +
 > +@subheading Unwinder Skeleton Code
 > +
 > +GDB comes with the module containing the base @code{Unwinder} class.
 > +Derive your unwinder class from it and structure the code as follows:
 > +
 > +@smallexample
 > +from gdb.unwinders import Unwinder
 > +
 > +class MyUnwinder(Unwinder):
 > +    def __init__(....):
 > +        super(MyUnwinder, self).__init___(<expects unwinder name argument>)
 > +    def __call__(sniffer_info):
 > +        if not <we recognize frame>:
 > +            return None
 > +        # Create unwinder result. The most common way to achieve this is
 > +        # to find SP (stack pointer) and PC (program counter) values
 > +        # in the current frame and then call unwind_info_with_id method:
 > +        unwind_info = sniffer_info.unwind_info_with_id(sp, pc)
 > +
 > +        # Find the values of the registers in the caller's frame and 
 > +        # save them in the result:
 > +        unwind_info.set_previous_frame_register(<register>, <value>)
 > +
 > +        # Return the result:
 > +        return unwind_instance
 > +
 > +@end smallexample
 > +
 > +@subheading Registering a Unwinder
 > +
 > +An object file, a program space, and the @value{GDBN} proper can have
 > +unwinders registered with it.
 > +
 > +The @code{gdb.unwinders} module provides the function to register a
 > +unwinder:
 > +
 > +@defun gdb.unwinder.register_unwinder (locus, unwinder, replace=False)
 > +@var{locus} is specifies an object file or a program space to which
 > +@var{unwinder} is added. Passing @code{None} or @code{gdb} adds
 > +@var{unwinder} to the @value{GDBN}'s global unwinder list.  The newly
 > +added @var{unwinder} will be called before any other unwinder from the
 > +same locus.  Two unwinders in the same locus cannot have the same
 > +name. An attempt to add a unwinder with already existing name raises an
 > +exception unless @var{replace} is @code{True}, in which case the old
 > +unwinder is deleted.
 > +@end defun
 > +
 > +@subheading Unwinder Precedence
 > +
 > +@value{GDBN} first calls the unwinders from all the object files in no
 > +particular order, then the unwinders from the current program space,
 > +and finally the unwinders from @value{GDBN}.
 > +
 >  @node Xmethods In Python
 >  @subsubsection Xmethods In Python
 >  @cindex xmethods in Python
 > diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
 > index 92b06f2..734e5ca 100644
 > --- a/gdb/python/lib/gdb/__init__.py
 > +++ b/gdb/python/lib/gdb/__init__.py
 > @@ -71,6 +71,8 @@ type_printers = []
 >  xmethods = []
 >  # Initial frame filters.
 >  frame_filters = {}
 > +# Initial frame unwinders.
 > +frame_unwinders = []
 >  
 >  # Convenience variable to GDB's python directory
 >  PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
 > diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py
 > new file mode 100644
 > index 0000000..1f0c58b
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/command/unwinders.py
 > @@ -0,0 +1,199 @@
 > +# Unwinder commands.
 > +# Copyright 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +import gdb
 > +import re
 > +
 > +
 > +def validate_regexp(exp, idstring):
 > +    try:
 > +        return re.compile(exp)
 > +    except SyntaxError:
 > +        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
 > +
 > +
 > +def parse_unwinder_command_args(arg):
 > +    """Internal utility to parse unwinder command argv.
 > +
 > +    Arguments:
 > +        arg: The arguments to the command. The format is:
 > +             [locus-regexp [name-regexp]]
 > +
 > +    Returns:
 > +        A 2-tuple of compiled regular expressions.
 > +
 > +    Raises:
 > +        SyntaxError: an error processing ARG
 > +    """
 > +
 > +    argv = gdb.string_to_argv(arg)
 > +    argc = len(argv)
 > +    if argc > 2:
 > +        raise SyntaxError("Too many arguments.")
 > +    locus_regexp = ""
 > +    name_regexp = ""
 > +    if argc >= 1:
 > +        locus_regexp = argv[0]
 > +        if argc >= 2:
 > +            name_regexp = argv[1]
 > +    return (validate_regexp(locus_regexp, "locus"),
 > +            validate_regexp(name_regexp, "unwinder"))
 > +
 > +
 > +class InfoUnwinder(gdb.Command):
 > +    """GDB command to list unwinders.
 > +
 > +    Usage: info unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression matching the location of the
 > +    unwinder.  If it is omitted, all registered unwinders from all
 > +    loci are listed.  A locus could be 'global', a regular expression
 > +    matching the current program space's filename, or a regular
 > +    expression matching filenames of objfiles.  Locus could be
 > +    'progspace' to specify that only unwinders from the current
 > +    progspace should be listed.
 > +
 > +    NAME-REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are listed.
 > +

Remove blank line.

 > +    """
 > +
 > +    def __init__(self):
 > +        super(InfoUnwinder, self).__init__("info unwinder",
 > +                                            gdb.COMMAND_DATA)
 > +
 > +    def list_unwinders(self, title, unwinders, name_re):
 > +        """Lists the unwinders whose name matches regexp.
 > +
 > +        Arguments:
 > +            title: The line to print before the list.
 > +            unwinders: The list of the unwinders.
 > +            name_re: unwinder name filter.
 > +        """
 > +        if not unwinders:
 > +            return
 > +        print title
 > +        for unwinder in unwinders:
 > +            if name_re.match(unwinder.name):
 > +                print("  %s%s" % (unwinder.name,
 > +                                  "" if unwinder.enabled else "[disabled]"))
 > +
 > +    def invoke(self, arg, from_tty):
 > +        locus_re, name_re = parse_unwinder_command_args(arg)
 > +        if locus_re.match("global"):
 > +            self.list_unwinders("global unwinders:", gdb.frame_unwinders,
 > +                                name_re)
 > +        if locus_re.match("progspace"):
 > +            cp = gdb.current_progspace()
 > +            self.list_unwinders("progspace %s unwinders:" % cp.filename,
 > +                                cp.frame_unwinders, name_re)
 > +        for objfile in gdb.objfiles():
 > +            if locus_re.match(objfile.filename):
 > +                self.list_unwinders("objfile %s unwinders:" % objfile.filename,
 > +                                    objfile.frame_unwinders, name_re)

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

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

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

 > +
 > +
 > +def do_enable_unwinder1(unwinders, name_re, flag):
 > +    """Enable/disable unwinders whose names match given regex.
 > +
 > +    Arguments:
 > +        unwinders: The list of unwinders.
 > +        name_re: Unwinder name filter.
 > +        flag: Enable/disable.
 > +
 > +    Returns:
 > +        The number of unwinders affected.
 > +    """
 > +    total = 0
 > +    for unwinder in unwinders:
 > +        if name_re.match(unwinder.name):
 > +            unwinder.enabled = flag
 > +            total += 1
 > +    return total
 > +
 > +
 > +def do_enable_unwinder(arg, flag):
 > +    """Enable/disable unwinder(s)."""
 > +    (locus_re, name_re) = parse_unwinder_command_args(arg)
 > +    total = 0
 > +    if locus_re.match("global"):
 > +        total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag)
 > +    if locus_re.match("progspace"):
 > +        total += do_enable_unwinder1(gdb.current_progspace().frame_unwinders,
 > +                                     name_re, flag)
 > +    for objfile in gdb.objfiles():
 > +        if locus_re.match(objfile.filename):
 > +            total += do_enable_unwinder1(objfile.frame_unwinders, name_re,
 > +                                         flag)
 > +    print("%d unwinder%s %s" % (total, "" if total == 1 else "s",
 > +                                "enabled" if flag else "disabled"))

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

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

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

 > +
 > +    NAME_REGEXP is a regular expression to filter unwinder names.
 > +    If this omitted for a specified locus, then all registered
 > +    unwinders in the locus are affected.
 > +    """
 > +
 > +    def __init__(self):
 > +        super(EnableUnwinder, self).__init__("enable unwinder",
 > +                                             gdb.COMMAND_DATA)

gdb.COMMAND_STACK

 > +
 > +    def invoke(self, arg, from_tty):
 > +        """GDB calls this to perform the command."""
 > +        do_enable_unwinder(arg, True)
 > +
 > +
 > +class DisableUnwinder(gdb.Command):
 > +    """GDB command to disable the specified unwinder.
 > +
 > +    Usage: disable unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression matching the objects to examine.
 > +    Loci are "global", the program space's file, and the objfiles within
 > +    that program space.

"the program space's file" should be "progspace".

 > +
 > +    NAME_REGEXP is a regular expression to filter unwinder names.
 > +    If this omitted for a specified locus, then all registered
 > +    unwinders in the locus are affected.
 > +    """
 > +
 > +    def __init__(self):
 > +        super(DisableUnwinder, self).__init__("disable unwinder",
 > +                                              gdb.COMMAND_DATA)

gdb.COMMAND_STACK

 > +
 > +    def invoke(self, arg, from_tty):
 > +        """GDB calls this to perform the command."""
 > +        do_enable_unwinder(arg, False)
 > +
 > +
 > +def register_unwinder_commands():
 > +    """Installs the unwinder commands."""
 > +    InfoUnwinder()
 > +    EnableUnwinder()
 > +    DisableUnwinder()
 > +
 > +
 > +register_unwinder_commands()
 > diff --git a/gdb/python/lib/gdb/function/unwinders.py b/gdb/python/lib/gdb/function/unwinders.py
 > new file mode 100644
 > index 0000000..9be8b8b
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/function/unwinders.py
 > @@ -0,0 +1,52 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +"""Internal functions for working with frame unwinders."""
 > +
 > +import gdb
 > +
 > +
 > +def execute_unwinders(sniffer_info):

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

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

 > +    """Internal function called from GDB to execute all unwinders.
 > +
 > +    Runs each currently enabled unwinder until it finds the one that can
 > +    unwind given frame.
 > +
 > +    Arguments:
 > +        sniffer_info: an instance of gdb.SnifferInfo.
 > +    Returns:
 > +        UnwindInfo instance or None.
 > +    """
 > +    for objfile in gdb.objfiles():
 > +        for unwinder in objfile.frame_unwinders:
 > +            if unwinder.enabled:
 > +                unwind_info = unwinder.__call__(sniffer_info)

Write this as:

                unwind_info = unwinder(sniffer_info)

and similarly throughout.

 > +                if unwind_info is not None:
 > +                    return unwind_info
 > +
 > +    current_progspace = gdb.current_progspace()
 > +    for unwinder in current_progspace.frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder.__call__(sniffer_info)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    for unwinder in gdb.frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder.__call__(sniffer_info)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    return None
 > diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
 > new file mode 100644
 > index 0000000..2d65a5e
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/unwinder.py
 > @@ -0,0 +1,89 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +"""Unwinder class and register_unwinder function."""
 > +
 > +import gdb
 > +
 > +
 > +class Unwinder(object):
 > +    """Base class (or a template) for frame unwinders written in Python.
 > +
 > +    A unwinder has a single method __call__ and the attributes described below.

An unwinder ...

 > +
 > +    Attributes:
 > +        name: The name of the unwinder.
 > +        enabled: A boolean indicating whether the unwinder is enabled.
 > +    """
 > +
 > +    def __init__(self, name):
 > +        """Constructor.
 > +
 > +        Args:
 > +            name: An identifying name for the unwinder.
 > +        """
 > +        self.name = name
 > +        self.enabled = True
 > +
 > +    def __call__(self, sniffer_info):
 > +        """GDB calls this method to unwind a frame.

s/sniffer_info/pending_frame/

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

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

 > +        """
 > +        raise NotImplementedError("Unwinder __call__.")
 > +
 > +
 > +def register_unwinder(locus, unwinder, replace=False):
 > +    """Register unwinder in given locus.
 > +
 > +    The unwinder is prepended to the locus's unwinders list. Unwinder
 > +    name should be unique.
 > +
 > +    Arguments:
 > +        locus: Either an objfile, progspace, or None (in which case
 > +               the unwinder is registered globally).
 > +        unwinder: An object of a gdb.Unwinder subclass
 > +        replace: If True, replaces existing unwinder with the same name.
 > +                 Otherwise, raises exception if unwinder with the same
 > +                 name already exists.
 > +
 > +    Returns:
 > +        Nothing.
 > +
 > +    Raises:
 > +        RuntimeError: Unwinder name is not unique.
 > +
 > +    """
 > +    if locus is None:
 > +        if gdb.parameter("verbose"):
 > +            gdb.write("Registering global %s unwinder ...\n" % unwinder.name)
 > +        locus = gdb
 > +    else:

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

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

 > +        if gdb.parameter("verbose"):
 > +            gdb.write("Registering %s unwinder for %s ...\n" %
 > +                      (unwinder.name, locus.filename))
 > +    i = 0
 > +    for needle in locus.frame_unwinders:
 > +        if needle.name == unwinder.name:
 > +            if replace:
 > +                del locus.frame_unwinders[i]
 > +            else:
 > +                raise RuntimeError("Unwinder %s already exists." % unwinder.name)
 > +        i += 1
 > +    locus.frame_unwinders.insert(0, unwinder)
 > diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
 > index 157d200..c9528c3 100644
 > --- a/gdb/python/py-objfile.c
 > +++ b/gdb/python/py-objfile.c
 > @@ -42,6 +42,10 @@ typedef struct
 >  
 >    /* The frame filter list of functions.  */
 >    PyObject *frame_filters;
 > +
 > +  /* The list of frame unwinders.  */
 > +  PyObject *frame_unwinders;
 > +
 >    /* The type-printer list.  */
 >    PyObject *type_printers;
 >  
 > @@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o)
 >    Py_XDECREF (self->dict);
 >    Py_XDECREF (self->printers);
 >    Py_XDECREF (self->frame_filters);
 > +  Py_XDECREF (self->frame_unwinders);
 >    Py_XDECREF (self->type_printers);
 >    Py_XDECREF (self->xmethods);
 >    Py_TYPE (self)->tp_free (self);
 > @@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self)
 >    if (self->frame_filters == NULL)
 >      return 0;
 >  
 > +  self->frame_unwinders = PyList_New (0);
 > +  if (self->frame_unwinders == NULL)
 > +    return 0;
 > +
 >    self->type_printers = PyList_New (0);
 >    if (self->type_printers == NULL)
 >      return 0;
 > @@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
 >    return 0;
 >  }
 >  
 > +/* Return the frame unwinders attribute for this object file.  */
 > +
 > +PyObject *
 > +objfpy_get_frame_unwinders (PyObject *o, void *ignore)
 > +{
 > +  objfile_object *self = (objfile_object *) o;
 > +
 > +  Py_INCREF (self->frame_unwinders);
 > +  return self->frame_unwinders;
 > +}
 > +
 > +/* Set this object file's frame unwinders list to UNWINDERS.  */
 > +
 > +static int
 > +objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
 > +{
 > +  PyObject *tmp;
 > +  objfile_object *self = (objfile_object *) o;
 > +
 > +  if (!unwinders)
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       _("Cannot delete the frame unwinders attribute."));
 > +      return -1;
 > +    }
 > +
 > +  if (!PyList_Check (unwinders))
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       _("The frame_unwinders attribute must be a list."));
 > +      return -1;
 > +    }
 > +
 > +  /* Take care in case the LHS and RHS are related somehow.  */
 > +  tmp = self->frame_unwinders;
 > +  Py_INCREF (unwinders);
 > +  self->frame_unwinders = unwinders;
 > +  Py_XDECREF (tmp);
 > +
 > +  return 0;
 > +}
 > +
 >  /* Get the 'type_printers' attribute.  */
 >  
 >  static PyObject *
 > @@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] =
 >      "Pretty printers.", NULL },
 >    { "frame_filters", objfpy_get_frame_filters,
 >      objfpy_set_frame_filters, "Frame Filters.", NULL },
 > +  { "frame_unwinders", objfpy_get_frame_unwinders,
 > +    objfpy_set_frame_unwinders, "Frame Unwinders", NULL },
 >    { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
 >      "Type printers.", NULL },
 >    { "xmethods", objfpy_get_xmethods, NULL,
 > diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
 > index 93fbc14..17da3d1 100644
 > --- a/gdb/python/py-progspace.c
 > +++ b/gdb/python/py-progspace.c
 > @@ -41,6 +41,10 @@ typedef struct
 >  
 >    /* The frame filter list of functions.  */
 >    PyObject *frame_filters;
 > +
 > +  /* The frame unwinder list.  */
 > +  PyObject *frame_unwinders;
 > +
 >    /* The type-printer list.  */
 >    PyObject *type_printers;
 >  
 > @@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self)
 >    Py_XDECREF (ps_self->dict);
 >    Py_XDECREF (ps_self->printers);
 >    Py_XDECREF (ps_self->frame_filters);
 > +  Py_XDECREF (ps_self->frame_unwinders);
 >    Py_XDECREF (ps_self->type_printers);
 >    Py_XDECREF (ps_self->xmethods);
 >    Py_TYPE (self)->tp_free (self);
 > @@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self)
 >    if (self->frame_filters == NULL)
 >      return 0;
 >  
 > +  self->frame_unwinders = PyList_New (0);
 > +  if (self->frame_unwinders == NULL)
 > +    return 0;
 > +
 >    self->type_printers = PyList_New (0);
 >    if (self->type_printers == NULL)
 >      return 0;
 > @@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
 >    return 0;
 >  }
 >  
 > +/* Return the list of the frame unwinders for this program space.  */
 > +
 > +PyObject *
 > +pspy_get_frame_unwinders (PyObject *o, void *ignore)
 > +{
 > +  pspace_object *self = (pspace_object *) o;
 > +
 > +  Py_INCREF (self->frame_unwinders);
 > +  return self->frame_unwinders;
 > +}
 > +
 > +/* Set this program space's list of the unwinders to UNWINDERS.  */
 > +
 > +static int
 > +pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
 > +{
 > +  PyObject *tmp;
 > +  pspace_object *self = (pspace_object *) o;
 > +
 > +  if (!unwinders)
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       "cannot delete the frame unwinders list");
 > +      return -1;
 > +    }
 > +
 > +  if (!PyList_Check (unwinders))
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       "the frame unwinders attribute must be a list");
 > +      return -1;
 > +    }
 > +
 > +  /* Take care in case the LHS and RHS are related somehow.  */
 > +  tmp = self->frame_unwinders;
 > +  Py_INCREF (unwinders);
 > +  self->frame_unwinders = unwinders;
 > +  Py_XDECREF (tmp);
 > +
 > +  return 0;
 > +}
 > +
 >  /* Get the 'type_printers' attribute.  */
 >  
 >  static PyObject *
 > @@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] =
 >      "Pretty printers.", NULL },
 >    { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
 >      "Frame filters.", NULL },
 > +  { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders,
 > +    "Frame unwinders.", NULL },
 >    { "type_printers", pspy_get_type_printers, pspy_set_type_printers,
 >      "Type printers.", NULL },
 >    { "xmethods", pspy_get_xmethods, NULL,
 > diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
 > new file mode 100644
 > index 0000000..128d710
 > --- /dev/null
 > +++ b/gdb/python/py-unwind.c
 > @@ -0,0 +1,831 @@
 > +/* Python frame unwinder interface.
 > +
 > +   Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +   This file is part of GDB.
 > +
 > +   This program is free software; you can redistribute it and/or modify
 > +   it under the terms of the GNU General Public License as published by
 > +   the Free Software Foundation; either version 3 of the License, or
 > +   (at your option) any later version.
 > +
 > +   This program is distributed in the hope that it will be useful,
 > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +   GNU General Public License for more details.
 > +
 > +   You should have received a copy of the GNU General Public License
 > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 > +
 > +#include "defs.h"
 > +#include "arch-utils.h"
 > +#include "frame-unwind.h"
 > +#include "gdb_obstack.h"
 > +#include "gdbcmd.h"
 > +#include "language.h"
 > +#include "observer.h"
 > +#include "python-internal.h"
 > +#include "regcache.h"
 > +#include "valprint.h"
 > +#include "user-regs.h"
 > +
 > +#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
 > +  { fprintf_unfiltered (gdb_stdlog, args); }
 > +
 > +typedef struct
 > +{
 > +  PyObject_HEAD
 > +
 > +  /* Frame we are unwinding.  */
 > +  struct frame_info *frame_info;
 > +
 > +  /* Its architecture, passed by the sniffer caller.  */
 > +  struct gdbarch *gdbarch;
 > +} sniffer_info_object;

s/sniffer_info/pending_frame/
[and similarly throughout]

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

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

s/prev_frame_regs/saved_regs/

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

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

 > +
 > +} unwind_info_object;
 > +
 > +/* The data we keep for a frame we can unwind: frame ID and an array of
 > +   (register_number, register_value) pairs.  */
 > +
 > +typedef struct
 > +{
 > +  /* Frame ID.  */
 > +  struct frame_id frame_id;
 > +
 > +  /* GDB Architecture.  */
 > +  struct gdbarch *gdbarch;
 > +
 > +  /* Length of the `reg' array below.  */
 > +  int reg_count;
 > +
 > +  struct reg_info
 > +  {
 > +    /* Register number.  */
 > +    int number;
 > +
 > +    /* Register data bytes pointer.  */
 > +    gdb_byte *data;

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

 > +  } reg[];
 > +} cached_frame_info;
 > +
 > +static PyTypeObject sniffer_info_object_type
 > +    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("sniffer_info_object");
 > +
 > +static PyTypeObject unwind_info_object_type
 > +    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");
 > +
 > +static unsigned int pyuw_debug = 0;
 > +
 > +static struct gdbarch_data *pyuw_gdbarch_data;
 > +
 > +/* Parse Python Int, saving it at the given address. Returns 1 on success,
 > +   0 otherwise.  */

There's some ambiguity on whether the Python error indicator will
or will not be set on return.  Currently, sometimes it will and sometimes
it won't.  I think the code is such that it won't matter today, but
it would be good to either clear the error or at least document
that the Python error indicator may or may not be set on return
if the result is zero.

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

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

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

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

 > +
 > +static int
 > +pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
 > +                        int *reg_num)
 > +{
 > +  if (pyo_reg_id == NULL)
 > +    return 0;
 > +  if (PyString_Check (pyo_reg_id))
 > +    {
 > +      const char *reg_name = PyString_AS_STRING (pyo_reg_id);
 > +      if (reg_name == NULL)
 > +        return 0;
 > +      *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name,
 > +                                              strlen (reg_name));
 > +      return *reg_num >= 0;
 > +    }
 > +  else if (pyuw_parse_int (pyo_reg_id, reg_num))
 > +    return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;

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

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

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

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

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

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

s/Previous frame/Saved/

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

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

Wrap call to value_print in TRY/CATCH/END_CATCH.

 > +            fprintf_unfiltered (strfile, ")");
 > +          }
 > +        else
 > +          fprintf_unfiltered (strfile, "<BAD>)");
 > +        sep = ", ";
 > +      }
 > +    fprintf_unfiltered (strfile, ")");
 > +  }
 > +  {
 > +    char *s = ui_file_xstrdup (strfile, NULL);
 > +
 > +    result = PyString_FromString (s);
 > +    xfree (s);
 > +  }
 > +  ui_file_delete (strfile);
 > +  return result;
 > +}
 > +
 > +/* Create UnwindInfo instance for given SnifferInfo and frame ID.  */
 > +
 > +static PyObject *
 > +pyuw_create_unwind_info (PyObject *pyo_sniffer_info,
 > +                         struct frame_id frame_id)
 > +{
 > +  unwind_info_object *unwind_info
 > +      = PyObject_New (unwind_info_object, &unwind_info_object_type);
 > +
 > +  if (((sniffer_info_object *) pyo_sniffer_info)->frame_info == NULL)
 > +    {
 > +      PyErr_SetString (PyExc_ValueError,
 > +                       "Attempting to use stale SnifferInfo");
 > +      return NULL;
 > +    }
 > +  unwind_info->frame_id = frame_id;
 > +  Py_INCREF (pyo_sniffer_info);
 > +  unwind_info->sniffer_info = pyo_sniffer_info;
 > +  unwind_info->prev_frame_regs_size = 0;
 > +  unwind_info->prev_frame_regs_capacity = 4;
 > +  unwind_info->prev_frame_regs =
 > +      xmalloc (unwind_info->prev_frame_regs_capacity *
 > +               sizeof (unwind_info->prev_frame_regs[0]));
 > +  return (PyObject *) unwind_info;
 > +}
 > +
 > +/* The implementation of
 > +   gdb.UnwindInfo.set_previous_frame_register (REG, VALUE) -> None.  */

s/set_previous_frame_register/add_saved_register/

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

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

 > +      {
 > +        PyErr_Format (
 > +            PyExc_ValueError,
 > +            "The value of the register returned by the Python "
 > +            "sniffer has unexpected size: %u instead of %u.",
 > +            (unsigned) (TYPE_LENGTH (value_enclosing_type (value))),

value_type

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

This code will reduce to VEC_safe_push with vec.h.

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

Use Py_RETURN_NONE.

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

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

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

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

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

buffer is unused

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

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

 > +      if (val == NULL)
 > +        PyErr_Format (PyExc_ValueError,
 > +                      "Cannot read register %d from frame.",
 > +                      regnum);
 > +    }
 > +  CATCH (except, RETURN_MASK_ALL)
 > +    {
 > +      GDB_PY_HANDLE_EXCEPTION (except);
 > +    }
 > +  END_CATCH
 > +
 > +  return val == NULL ? NULL : value_to_value_object (val);
 > +}
 > +
 > +/* Implementation of
 > +   gdb.SnifferInfo.unwind_info_with_id (self, SP, PC) -> None.  */

Rewrite to take a gdb.FrameID argument.

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

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

Delete.

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

Delete.

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

insert blank line

 > +static void
 > +sniffer_info_invalidate (PyObject *pyo_sniffer_info)
 > +{
 > +  if (pyo_sniffer_info == NULL)
 > +    return;
 > +  ((sniffer_info_object *) pyo_sniffer_info)->frame_info = NULL;
 > +}
 > +
 > +/* frame_unwind.this_id method.  */
 > +
 > +static void
 > +pyuw_this_id (struct frame_info *this_frame, void **cache_ptr,
 > +              struct frame_id *this_id)
 > +{
 > +  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
 > +  if (pyuw_debug >= 1)
 > +    {
 > +      fprintf_unfiltered (gdb_stdlog, "%s: frame_id: ", __FUNCTION__);
 > +      fprint_frame_id (gdb_stdlog, *this_id);
 > +      fprintf_unfiltered (gdb_stdlog, "\n");
 > +    }
 > +}
 > +
 > +/* frame_unwind.prev_register.  */
 > +
 > +static struct value *
 > +pyuw_prev_register (struct frame_info *this_frame, void **cache_ptr,
 > +                    int regnum)
 > +{
 > +  cached_frame_info *cached_frame = *cache_ptr;
 > +  struct reg_info *reg_info = cached_frame->reg;
 > +  struct reg_info *reg_info_end = reg_info + cached_frame->reg_count;
 > +
 > +  TRACE_PY_UNWIND (1, "%s (frame=%p,...,reg=%d)\n", __FUNCTION__, this_frame,
 > +                   regnum);
 > +  for (; reg_info < reg_info_end; ++reg_info)
 > +    {
 > +      if (regnum == reg_info->number)
 > +        return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
 > +    }
 > +
 > +  return frame_unwind_got_optimized (this_frame, regnum);
 > +}
 > +
 > +/* Frame sniffer dispatch.  */
 > +
 > +static int
 > +pyuw_sniffer (const struct frame_unwind *self, struct frame_info *this_frame,
 > +              void **cache_ptr)
 > +{
 > +  struct gdbarch *gdbarch;
 > +  struct cleanup *cleanups;
 > +  struct cleanup *cached_frame_cleanups;
 > +  PyObject *pyo_module;
 > +  PyObject *pyo_execute;
 > +  PyObject *pyo_sniffer_info;
 > +  PyObject *pyo_unwind_info;
 > +  cached_frame_info *cached_frame = NULL;
 > +
 > +  gdbarch = (struct gdbarch *) (self->unwind_data);
 > +  cleanups = ensure_python_env (gdbarch, current_language);
 > +  TRACE_PY_UNWIND (3, "%s (SP=%s, PC=%s)\n", __FUNCTION__,
 > +                   paddress (gdbarch, get_frame_sp (this_frame)),
 > +                   paddress (gdbarch, get_frame_pc (this_frame)));
 > +  pyo_sniffer_info = frame_info_to_sniffer_info_object (gdbarch, this_frame);
 > +  if (pyo_sniffer_info == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_sniffer_info);
 > +
 > +  if ((pyo_module = PyImport_ImportModule ("gdb.function.unwinders")) == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_module);
 > +
 > +  pyo_execute = PyObject_GetAttrString (pyo_module, "execute_unwinders");
 > +  if (pyo_execute == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_execute);
 > +
 > +  pyo_unwind_info
 > +      = PyObject_CallFunctionObjArgs (pyo_execute, pyo_sniffer_info, NULL);
 > +  if (pyo_unwind_info == NULL)
 > +    goto error;
 > +  make_cleanup_py_decref (pyo_unwind_info);
 > +  if (pyo_unwind_info == Py_None)
 > +    goto error;
 > +  if (PyObject_IsInstance (pyo_unwind_info,
 > +                           (PyObject *) &unwind_info_object_type) <= 0)
 > +    error (_("A Unwinder should return gdb.UnwindInfo instance."));
 > +
 > +  {
 > +    unwind_info_object *unwind_info = (unwind_info_object *) pyo_unwind_info;
 > +    int i;
 > +    int reg_count;
 > +    size_t cached_frame_size;
 > +    size_t gdb_bytes_count;
 > +    gdb_byte *gdb_data_free, *gdb_data_end;
 > +
 > +    /* Figure out how much space we need to allocate.  */
 > +    reg_count = unwind_info->prev_frame_regs_size;
 > +    gdb_bytes_count = reg_count * MAX_REGISTER_SIZE;
 > +    cached_frame_size = sizeof (*cached_frame) +
 > +        reg_count * sizeof (cached_frame->reg[0]) +
 > +        gdb_bytes_count * sizeof (gdb_byte);
 > +
 > +    cached_frame = xmalloc (cached_frame_size);
 > +    cached_frame_cleanups = make_cleanup (xfree, cached_frame);
 > +    /* Allocations after this point will be discarded!  */
 > +
 > +    gdb_data_end = (gdb_byte *) ((char *) cached_frame + cached_frame_size);
 > +    gdb_data_free = gdb_data_end - gdb_bytes_count;
 > +
 > +    cached_frame->gdbarch = gdbarch;
 > +    cached_frame->frame_id = unwind_info->frame_id;
 > +    cached_frame->reg_count = reg_count;
 > +
 > +    /* Populate registers array.  */
 > +    for (i = 0; i < reg_count; i++)
 > +      {
 > +        struct reg_info *reg = &(cached_frame->reg[i]);
 > +        struct value *value
 > +            = value_object_to_value (unwind_info->prev_frame_regs[i].value);
 > +        size_t data_size;
 > +
 > +        reg->number = unwind_info->prev_frame_regs[i].number;
 > +        /* `value' validation was done before, just assert.  */
 > +        gdb_assert (value != NULL);
 > +        data_size = register_size (gdbarch, reg->number);
 > +        gdb_assert (data_size == TYPE_LENGTH (value_enclosing_type (value)));
 > +        /* Should not overflow  the buffer.  */
 > +        gdb_assert ((gdb_data_free + data_size) <= gdb_data_end);
 > +        memcpy (gdb_data_free, value_contents (value), data_size);
 > +        reg->data = gdb_data_free;
 > +        gdb_data_free += data_size;
 > +      }
 > +  }
 > +
 > +  *cache_ptr = cached_frame;
 > +  discard_cleanups (cached_frame_cleanups);
 > +  do_cleanups (cleanups);
 > +  sniffer_info_invalidate (pyo_sniffer_info);

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

 > +  return 1;
 > +
 > +error:
 > +  do_cleanups (cleanups);
 > +  sniffer_info_invalidate (pyo_sniffer_info);
 > +  return 0;
 > +}
 > +
 > +/* Frame cache release shim.  */
 > +
 > +static void
 > +pyuw_dealloc_cache (struct frame_info *this_frame, void *cache)
 > +{
 > +  TRACE_PY_UNWIND (3, "%s: enter", __FUNCTION__);
 > +  xfree (cache);
 > +}
 > +
 > +struct pyuw_gdbarch_data_type
 > +{
 > +  /* Has the unwinder shim been prepended? */
 > +  int unwinder_registered;
 > +};
 > +
 > +static void *
 > +pyuw_gdbarch_data_init (struct gdbarch *gdbarch)
 > +{
 > +  return GDBARCH_OBSTACK_ZALLOC (gdbarch, struct pyuw_gdbarch_data_type);
 > +}
 > +
 > +/* New inferior architecture callback: register the Python unwinders
 > +   intermediary.  */
 > +
 > +static void
 > +pyuw_on_new_gdbarch (struct gdbarch *newarch)
 > +{
 > +  struct pyuw_gdbarch_data_type *data =
 > +      gdbarch_data (newarch, pyuw_gdbarch_data);
 > +
 > +  if (!data->unwinder_registered)
 > +    {
 > +      struct frame_unwind *unwinder
 > +          = GDBARCH_OBSTACK_ZALLOC (newarch, struct frame_unwind);
 > +
 > +      unwinder->type = NORMAL_FRAME;
 > +      unwinder->stop_reason = default_frame_unwind_stop_reason;
 > +      unwinder->this_id = pyuw_this_id;
 > +      unwinder->prev_register = pyuw_prev_register;
 > +      unwinder->unwind_data = (void *) newarch;
 > +      unwinder->sniffer = pyuw_sniffer;
 > +      unwinder->dealloc_cache = pyuw_dealloc_cache;
 > +      frame_unwind_prepend_unwinder (newarch, unwinder);
 > +      TRACE_PY_UNWIND (1, "%s: registered unwinder for %s\n", __FUNCTION__,
 > +                       gdbarch_bfd_arch_info (newarch)->printable_name);

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

 > +      data->unwinder_registered = 1;
 > +    }
 > +}
 > +
 > +/* Initialize unwind machinery.  */
 > +
 > +int
 > +gdbpy_initialize_unwind (void)
 > +{
 > +  int rc;
 > +  add_setshow_zuinteger_cmd
 > +      ("py-unwind", class_maintenance, &pyuw_debug,
 > +        _("Set Python unwinder debugging."),
 > +        _("Show Python unwinder debugging."),
 > +        _("When non-zero, Python unwinder debugging is enabled."),
 > +        NULL,
 > +        NULL,
 > +        &setdebuglist, &showdebuglist);
 > +  pyuw_gdbarch_data
 > +      = gdbarch_data_register_post_init (pyuw_gdbarch_data_init);
 > +  observer_attach_architecture_changed (pyuw_on_new_gdbarch);
 > +
 > +  if (PyType_Ready (&sniffer_info_object_type) < 0)
 > +    return -1;
 > +  rc = gdb_pymodule_addobject (gdb_module, "SnifferInfo",
 > +      (PyObject *) &sniffer_info_object_type);
 > +  if (rc)
 > +    return rc;
 > +
 > +  if (PyType_Ready (&unwind_info_object_type) < 0)
 > +    return -1;
 > +  return gdb_pymodule_addobject (gdb_module, "UnwindInfo",
 > +      (PyObject *) &unwind_info_object_type);
 > +}
 > +
 > +static PyMethodDef sniffer_info_object_methods[] =
 > +{
 > +  { "read_register", sniffer_infopy_read_register, METH_VARARGS,
 > +    "read_register (REG) -> gdb.Value\n"
 > +    "Return the value of the REG in the frame." },
 > +  { "unwind_info_with_id",
 > +    sniffer_infopy_unwind_info_with_id, METH_VARARGS,
 > +    "unwind_info_with_id (SP, PC) -> gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this FrameData, using given SP and PC registers \n"
 > +    "to identify the frame." },
 > +  { "unwind_info_with_id_special",
 > +    sniffer_infopy_unwind_info_with_id_special, METH_VARARGS,
 > +    "unwind_info_with_id_special (SP, PC, SPECIAL) -> gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this FrameData, using given SP, PC, and SPECIAL "
 > +    "registers to identify the frame." },
 > +  { "unwind_info_with_id_wild",
 > +    sniffer_infopy_unwind_info_with_id_wild, METH_VARARGS,
 > +    "unwind_info_with_id_wild (SP) ->gdb.UnwindInfo\n"
 > +    "Construct UnwindInfo for this FrameData, using given SP register to \n"
 > +    "identify the frame." },
 > +  {NULL}  /* Sentinel */
 > +};
 > +
 > +static PyTypeObject sniffer_info_object_type =
 > +{
 > +  PyVarObject_HEAD_INIT (NULL, 0)
 > +  "gdb.SnifferInfo",              /* tp_name */
 > +  sizeof (sniffer_info_object),   /* tp_basicsize */
 > +  0,                              /* tp_itemsize */
 > +  0,                              /* tp_dealloc */
 > +  0,                              /* tp_print */
 > +  0,                              /* tp_getattr */
 > +  0,                              /* tp_setattr */
 > +  0,                              /* tp_compare */
 > +  0,                              /* tp_repr */
 > +  0,                              /* tp_as_number */
 > +  0,                              /* tp_as_sequence */
 > +  0,                              /* tp_as_mapping */
 > +  0,                              /* tp_hash  */
 > +  0,                              /* tp_call */
 > +  sniffer_infopy_str,             /* tp_str */
 > +  0,                              /* tp_getattro */
 > +  0,                              /* tp_setattro */
 > +  0,                              /* tp_as_buffer */
 > +  Py_TPFLAGS_DEFAULT,             /* tp_flags */
 > +  "GDB SnifferInfo object",       /* tp_doc */
 > +  0,                              /* tp_traverse */
 > +  0,                              /* tp_clear */
 > +  0,                              /* tp_richcompare */
 > +  0,                              /* tp_weaklistoffset */
 > +  0,                              /* tp_iter */
 > +  0,                              /* tp_iternext */
 > +  sniffer_info_object_methods,    /* tp_methods */
 > +  0,                              /* tp_members */
 > +  0,                              /* tp_getset */
 > +  0,                              /* tp_base */
 > +  0,                              /* tp_dict */
 > +  0,                              /* tp_descr_get */
 > +  0,                              /* tp_descr_set */
 > +  0,                              /* tp_dictoffset */
 > +  0,                              /* tp_init */
 > +  0,                              /* tp_alloc */
 > +};
 > +

extra blank line

 > +
 > +static PyMethodDef unwind_info_object_methods[] =
 > +{
 > +  { "set_previous_frame_register",
 > +    unwind_infopy_set_previous_frame_register, METH_VARARGS,
 > +    "set_previous_frame_register (REG, VALUE) -> None\n"
 > +    "Set the value of the REG in the previous frame to VALUE." },
 > +  { NULL }  /* Sentinel */
 > +};
 > +
 > +static PyTypeObject unwind_info_object_type =
 > +{
 > +  PyVarObject_HEAD_INIT (NULL, 0)
 > +  "gdb.UnwindInfo",               /* tp_name */
 > +  sizeof (unwind_info_object),    /* tp_basicsize */
 > +  0,                              /* tp_itemsize */
 > +  unwind_infopy_dealloc,          /* tp_dealloc */
 > +  0,                              /* tp_print */
 > +  0,                              /* tp_getattr */
 > +  0,                              /* tp_setattr */
 > +  0,                              /* tp_compare */
 > +  0,                              /* tp_repr */
 > +  0,                              /* tp_as_number */
 > +  0,                              /* tp_as_sequence */
 > +  0,                              /* tp_as_mapping */
 > +  0,                              /* tp_hash  */
 > +  0,                              /* tp_call */
 > +  unwind_infopy_str,              /* tp_str */
 > +  0,                              /* tp_getattro */
 > +  0,                              /* tp_setattro */
 > +  0,                              /* tp_as_buffer */
 > +  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,  /* tp_flags */
 > +  "GDB UnwindInfo object",        /* tp_doc */
 > +  0,                              /* tp_traverse */
 > +  0,                              /* tp_clear */
 > +  0,                              /* tp_richcompare */
 > +  0,                              /* tp_weaklistoffset */
 > +  0,                              /* tp_iter */
 > +  0,                              /* tp_iternext */
 > +  unwind_info_object_methods,     /* tp_methods */
 > +  0,                              /* tp_members */
 > +  0,                              /* tp_getset */
 > +  0,                              /* tp_base */
 > +  0,                              /* tp_dict */
 > +  0,                              /* tp_descr_get */
 > +  0,                              /* tp_descr_set */
 > +  0,                              /* tp_dictoffset */
 > +  0,                              /* tp_init */
 > +  0,                              /* tp_alloc */
 > +};
 > diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
 > index 4c4d32a..0581b33 100644
 > --- a/gdb/python/python-internal.h
 > +++ b/gdb/python/python-internal.h
 > @@ -391,12 +391,14 @@ PyObject *pspace_to_pspace_object (struct program_space *)
 >      CPYCHECKER_RETURNS_BORROWED_REF;
 >  PyObject *pspy_get_printers (PyObject *, void *);
 >  PyObject *pspy_get_frame_filters (PyObject *, void *);
 > +PyObject *pspy_get_frame_unwinders (PyObject *, void *);
 >  PyObject *pspy_get_xmethods (PyObject *, void *);
 >  
 >  PyObject *objfile_to_objfile_object (struct objfile *)
 >      CPYCHECKER_RETURNS_BORROWED_REF;
 >  PyObject *objfpy_get_printers (PyObject *, void *);
 >  PyObject *objfpy_get_frame_filters (PyObject *, void *);
 > +PyObject *objfpy_get_frame_unwinders (PyObject *, void *);
 >  PyObject *objfpy_get_xmethods (PyObject *, void *);
 >  PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 >  
 > @@ -491,6 +493,8 @@ int gdbpy_initialize_arch (void)
 >    CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 >  int gdbpy_initialize_xmethods (void)
 >    CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 > +int gdbpy_initialize_unwind (void)
 > +  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 >  
 >  struct cleanup *make_cleanup_py_decref (PyObject *py);
 >  struct cleanup *make_cleanup_py_xdecref (PyObject *py);
 > diff --git a/gdb/python/python.c b/gdb/python/python.c
 > index 58c7c92..1da63fd 100644
 > --- a/gdb/python/python.c
 > +++ b/gdb/python/python.c
 > @@ -1821,7 +1821,8 @@ message == an error message without a stack will be printed."),
 >        || gdbpy_initialize_new_objfile_event ()  < 0
 >        || gdbpy_initialize_clear_objfiles_event ()  < 0
 >        || gdbpy_initialize_arch () < 0
 > -      || gdbpy_initialize_xmethods () < 0)
 > +      || gdbpy_initialize_xmethods () < 0
 > +      || gdbpy_initialize_unwind () < 0)
 >      goto fail;
 >  
 >    gdbpy_to_string_cst = PyString_FromString ("to_string");
 > diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.c b/gdb/testsuite/gdb.python/py-unwind-maint.c
 > new file mode 100644
 > index 0000000..8c1d935
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind-maint.c
 > @@ -0,0 +1,24 @@
 > +/* This testcase is part of GDB, the GNU debugger.
 > +
 > +   Copyright 2015 Free Software Foundation, Inc.
 > +
 > +   This program is free software; you can redistribute it and/or modify
 > +   it under the terms of the GNU General Public License as published by
 > +   the Free Software Foundation; either version 3 of the License, or
 > +   (at your option) any later version.
 > +
 > +   This program is distributed in the hope that it will be useful,
 > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +   GNU General Public License for more details.
 > +
 > +   You should have received a copy of the GNU General Public License
 > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 > +
 > +int
 > +main (void)
 > +{
 > +  int i = 0;
 > +
 > +  return i; /* next-line */
 > +}
 > diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.exp b/gdb/testsuite/gdb.python/py-unwind-maint.exp
 > new file mode 100644
 > index 0000000..df2168b
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind-maint.exp
 > @@ -0,0 +1,64 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +# This file is part of the GDB testsuite.  It tests Python-based
 > +# unwinding CLI.
 > +
 > +load_lib gdb-python.exp
 > +
 > +standard_testfile
 > +
 > +if {[prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
 > +    return -1
 > +}
 > +
 > +# Skip all tests if Python scripting is not enabled.
 > +if { [skip_python_tests] } { continue }
 > +
 > +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
 > +
 > +if ![runto_main ] then {
 > +    fail "Can't run to main"
 > +    return -1
 > +}
 > +
 > +gdb_test "source ${pyfile}" "Python script imported" "import python scripts"
 > +
 > +gdb_test_sequence "info unwinder" "Show all unwinders" {
 > +    "global unwinders:"
 > +    "  global_unwinder"
 > +    "progspace.*unwinders:"
 > +    "py_unwind_maint_ps_unwinder"
 > +}
 > +
 > +gdb_breakpoint ${srcfile}:[gdb_get_line_number "next-line"]
 > +
 > +gdb_test_sequence "continue" "Unwinders called" {
 > +    "py_unwind_maint_ps_unwinder called"
 > +    "global_unwinder called"
 > +}
 > +
 > +gdb_test "disable unwinder global .*" "1 unwinder disabled" "Unwinder disabled"
 > +
 > +gdb_test_sequence "info unwinder" "Show with global unwinder disabled" {
 > +    "global unwinders:"
 > +    "  global_unwinder\\[disabled\\]"
 > +    "progspace.*unwinders:"
 > +    "  py_unwind_maint_ps_unwinder"
 > +}
 > +
 > +gdb_test_sequence "where" "Global unwinder disabled" {
 > +    "py_unwind_maint_ps_unwinder called\r\n#0  main"
 > +}
 > diff --git a/gdb/testsuite/gdb.python/py-unwind-maint.py b/gdb/testsuite/gdb.python/py-unwind-maint.py
 > new file mode 100644
 > index 0000000..35d5313
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind-maint.py
 > @@ -0,0 +1,59 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +# This file is part of the GDB testsuite.  It tests python unwinders.
 > +
 > +import re
 > +import gdb.types
 > +from gdb.unwinder import Unwinder, register_unwinder
 > +
 > +class TestGlobalUnwinder(Unwinder):
 > +    def __init__(self):
 > +        super(TestGlobalUnwinder, self).__init__("global_unwinder")
 > +
 > +    def __call__(self, unwinder_info):
 > +        print "%s called" % self.name
 > +        return None
 > +
 > +class TestProgspaceUnwinder(Unwinder):
 > +    def __init__(self, name):
 > +        super(TestProgspaceUnwinder, self).__init__("%s_ps_unwinder" % name)
 > +
 > +    def __call__(self, unwinder_info):
 > +        print "%s called" % self.name
 > +        return None
 > +
 > +class TestObjfileUnwinder(Unwinder):
 > +    def __init__(self, name):
 > +        super(TestObjfileUnwinder, self).__init__("%s_obj_unwinder" % name)
 > +
 > +    def __call__(self, unwinder_info):
 > +        print "%s called" % self.name
 > +        return None
 > +
 > +
 > +
 > +gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder())
 > +saw_runtime_error = False
 > +try:
 > +    gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder(), replace=False)
 > +except RuntimeError:
 > +    saw_runtime_error = True
 > +if not saw_runtime_error:
 > +    raise RuntimeError("Missing runtime error from register_unwinder.")
 > +gdb.unwinder.register_unwinder(gdb, TestGlobalUnwinder(), replace=True)
 > +gdb.unwinder.register_unwinder(gdb.current_progspace(),
 > +                               TestProgspaceUnwinder("py_unwind_maint"))
 > +print "Python script imported"
 > diff --git a/gdb/testsuite/gdb.python/py-unwind.c b/gdb/testsuite/gdb.python/py-unwind.c
 > new file mode 100644
 > index 0000000..cf41d78
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.c
 > @@ -0,0 +1,81 @@
 > +/* This test program is part of GDB, the GNU debugger.
 > +
 > +   Copyright 2015 Free Software Foundation, Inc.
 > +
 > +   This program is free software; you can redistribute it and/or modify
 > +   it under the terms of the GNU General Public License as published by
 > +   the Free Software Foundation; either version 3 of the License, or
 > +   (at your option) any later version.
 > +
 > +   This program is distributed in the hope that it will be useful,
 > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +   GNU General Public License for more details.
 > +
 > +   You should have received a copy of the GNU General Public License
 > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 > +
 > +/* This is the test program loaded into GDB by the py-unwind test.  */
 > +
 > +#include <stdint.h>
 > +#include <stdio.h>
 > +#include <stdlib.h>
 > +
 > +static void *
 > +swap_value (void **location, void *new_value)
 > +{
 > +  void *old_value = *location;
 > +  *location = new_value;
 > +  return old_value;
 > +}
 > +
 > +static void
 > +bad_layout(void **variable_ptr, void *fp)
 > +{
 > +  fprintf (stderr, "First variable should be allocated one word below "
 > +           "the frame.  Got variable's address %p, frame at %p instead.\n",
 > +           variable_ptr, fp);
 > +  abort();
 > +}
 > +
 > +#define MY_FRAME (__builtin_frame_address (0))
 > +
 > +static void
 > +corrupt_frame_inner (void)
 > +{
 > +  /* Save outer frame address, then corrupt the unwind chain by
 > +     setting the outer frame address in it to self.  This is
 > +     ABI-specific: the first word of the frame contains previous frame
 > +     address in amd64.  */
 > +  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
 > +
 > +  /* Verify the compiler allocates the first local variable one word
 > +     below frame.  This is where the test unwinder expects to find the
 > +     correct outer frame address.  */
 > +  if (&previous_fp + 1 != (void **) MY_FRAME)
 > +    bad_layout (&previous_fp + 1, MY_FRAME);
 > +
 > +  /* Now restore it so that we can return.  The test sets the
 > +     breakpoint just before this happens, and GDB will not be able to
 > +     show the backtrace without JIT reader.  */
 > +  swap_value ((void **) MY_FRAME, previous_fp); /* break backtrace-broken */
 > +}
 > +
 > +static void
 > +corrupt_frame_outer (void)
 > +{
 > +  /* See above for the explanation of the code here.  This function
 > +     corrupts its frame, too, and then calls the inner one.  */
 > +  void *previous_fp = swap_value ((void **) MY_FRAME, MY_FRAME);
 > +  if (&previous_fp + 1 != (void **) MY_FRAME)
 > +    bad_layout (&previous_fp, MY_FRAME);
 > +  corrupt_frame_inner ();
 > +  swap_value ((void **) MY_FRAME, previous_fp);
 > +}
 > +
 > +int
 > +main ()
 > +{
 > +  corrupt_frame_outer ();
 > +  return 0;
 > +}
 > diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
 > new file mode 100644
 > index 0000000..53d6746
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.exp
 > @@ -0,0 +1,54 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +# This file is part of the GDB testsuite.  It verifies that frame
 > +# unwinders can be implemented in Python.
 > +
 > +load_lib gdb-python.exp
 > +
 > +standard_testfile
 > +
 > +if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
 > +    return -1
 > +}
 > +
 > +# Skip all tests if Python scripting is not enabled.
 > +if { [skip_python_tests] } { continue }
 > +
 > +# This test runs on a specific platform.
 > +if { ! [istarget x86_64-*]} { continue }
 > +
 > +# The following tests require execution.
 > +
 > +if ![runto_main] then {
 > +    fail "Can't run to main"
 > +    return 0
 > +}
 > +
 > +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
 > +
 > +gdb_breakpoint [gdb_get_line_number "break backtrace-broken"]
 > +
 > +gdb_test "source ${pyfile}" "Python script imported" \
 > +         "import python scripts"
 > +
 > +gdb_continue_to_breakpoint "break backtrace-broken"
 > +gdb_test_sequence "where"  "Backtrace restored by unwinder" {
 > +    "\\r\\n#0 .* corrupt_frame_inner \\(\\) at "
 > +    "\\r\\n#1 .* corrupt_frame_outer \\(\\) at "
 > +    "\\r\\n#2 .* main \\(.*\\) at"
 > +}
 > +
 > +
 > diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
 > new file mode 100644
 > index 0000000..8770578
 > --- /dev/null
 > +++ b/gdb/testsuite/gdb.python/py-unwind.py
 > @@ -0,0 +1,83 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +import gdb
 > +from gdb.unwinder import Unwinder
 > +
 > +class TestUnwinder(Unwinder):
 > +    AMD64_RBP = 6
 > +    AMD64_RSP = 7
 > +    AMD64_RIP = 16
 > +
 > +    def __init__(self):
 > +        Unwinder.__init__(self, "test unwinder")
 > +        self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
 > +        self.char_ptr_ptr_t = self.char_ptr_t.pointer()
 > +
 > +    def _read_word(self, address):
 > +        return address.cast(self.char_ptr_ptr_t).dereference()
 > +
 > +    def __call__(self, sniffer_info):
 > +        """Test unwinder written in Python.
 > +
 > +        This unwinder can unwind the frames that have been deliberately
 > +        corrupted in a specific way (functions in the accompanying
 > +        py-unwind.c file do that.)
 > +        This code is only on AMD64.
 > +        On AMD64 $RBP points to the innermost frame (unless the code
 > +        was compiled with -fomit-frame-pointer), which contains the
 > +        address of the previous frame at offset 0. The functions
 > +        deliberately corrupt their frames as follows:
 > +                     Before                 After
 > +                   Corruption:           Corruption:
 > +                +--------------+       +--------------+
 > +        RBP-8   |              |       | Previous RBP |
 > +                +--------------+       +--------------+
 > +        RBP     + Previous RBP |       |    RBP       |
 > +                +--------------+       +--------------+
 > +        RBP+8   | Return RIP   |       | Return  RIP  |
 > +                +--------------+       +--------------+
 > +        Old SP  |              |       |              |
 > +
 > +        This unwinder recognizes the corrupt frames by checking that
 > +        *RBP == RBP, and restores previous RBP from the word above it.
 > +        """
 > +        try:
 > +            # NOTE: the registers in Unwinder API can be referenced
 > +            # either by name or by number. The code below uses both
 > +            # to achieve more coverage.
 > +            bp = sniffer_info.read_register("rbp").cast(self.char_ptr_t)
 > +            if self._read_word(bp) != bp:
 > +                return None
 > +            # Found the frame that the test program has corrupted for us.
 > +            # The correct BP for the outer frame has been saved one word
 > +            # above, previous IP and SP are at the expected places.
 > +            previous_bp = self._read_word(bp - 8)
 > +            previous_ip = self._read_word(bp + 8)
 > +            previous_sp = bp + 16
 > +
 > +            sp = sniffer_info.read_register(TestUnwinder.AMD64_RSP)
 > +            ip = sniffer_info.read_register(TestUnwinder.AMD64_RIP)
 > +            unwind_info = sniffer_info.unwind_info_with_id(sp, ip)
 > +            unwind_info.set_previous_frame_register(TestUnwinder.AMD64_RBP,
 > +                                                    previous_bp)
 > +            unwind_info.set_previous_frame_register("rip", previous_ip)
 > +            unwind_info.set_previous_frame_register("rsp", previous_sp)
 > +            return unwind_info
 > +        except (gdb.error, RuntimeError):
 > +            return None
 > +
 > +gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
 > +print("Python script imported")

-- 
/dje

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-23 19:58                                       ` Doug Evans
@ 2015-03-24  9:06                                         ` Andy Wingo
  2015-03-26  3:31                                         ` Alexander Smundak
  1 sibling, 0 replies; 60+ messages in thread
From: Andy Wingo @ 2015-03-24  9:06 UTC (permalink / raw)
  To: Doug Evans; +Cc: Alexander Smundak, gdb-patches

Hi,

Replying for comments that affect the Guile patch.

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

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

Sure.

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

ACK

Andy

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-23 19:58                                       ` Doug Evans
  2015-03-24  9:06                                         ` Andy Wingo
@ 2015-03-26  3:31                                         ` Alexander Smundak
  2015-03-26 18:53                                           ` Eli Zaretskii
  2015-03-27 22:29                                           ` Doug Evans
  1 sibling, 2 replies; 60+ messages in thread
From: Alexander Smundak @ 2015-03-26  3:31 UTC (permalink / raw)
  To: Doug Evans; +Cc: Andy Wingo, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 2706 bytes --]

>  > diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
...
> Also, we need another class: gdb.FrameID.
> I'm not sure whether to implement it in C or Python.
The code now accepts any object having 'sp' and possibly 'pc' and
'special' attributes. IMHO there is no need to have a base class for this.

> There's some ambiguity on whether the Python error indicator will
> or will not be set on return.  Currently, sometimes it will and sometimes
> it won't.  I think the code is such that it won't matter today, but
> it would be good to either clear the error or at least document
> that the Python error indicator may or may not be set on return
> if the result is zero.
I have documented which internal functions are set Python error.

New revision:

gdb/ChangeLog

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

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

gdb/testsuite/ChangeLog

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

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

[-- Attachment #2: patch9.diff --]
[-- Type: text/plain, Size: 67404 bytes --]

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-26  3:31                                         ` Alexander Smundak
@ 2015-03-26 18:53                                           ` Eli Zaretskii
  2015-03-27 22:29                                           ` Doug Evans
  1 sibling, 0 replies; 60+ messages in thread
From: Eli Zaretskii @ 2015-03-26 18:53 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: dje, wingo, gdb-patches

> Date: Wed, 25 Mar 2015 20:31:09 -0700
> From: Alexander Smundak <asmundak@google.com>
> Cc: Andy Wingo <wingo@igalia.com>, gdb-patches <gdb-patches@sourceware.org>
> 
> --- 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.

This part is OK.

> +@node Unwinding Frames in Python
> +@subsubsection Unwinding Frames in Python
> +@cindex Unwinding frames in Python.

@cindex entries should generally begin with a lower-case letter.
That's because alphabetic sorting in various locales might produce
different orders when mixed-case letters are involved, so the order in
the Index nodes will be different depending on where the manual was
produced.  We want to avoid that.

> +In GDB terminology ``unwinding'' is the process of finding the
      ^^^
"@value{GDBN}" (here and elsewhere in the patch).

> +previous frame (that is, caller's) from the current one. An unwinder
                                                          ^^
Two spaces between sentences (here and elsewhere in the patch).

> +(for example, and application running Java Virtual Machine) sometimes
                 ^^^
"an"

> +returns an object (an instance of gdb.UnwindInfo class) describing
                                     ^^^^^^^^^^^^^^
This should be in @code, as it's a Python symbol.

> +usually found in the corresponding xxx-@code{tdep.h} file in the gdb
                                      ^^^^^^^^^^^^^^^^^
It is better to write @file{@var{platform}-tdep.h} here; this explains
better what is that "xxx" in the text.

> +It also provides a factory method to create a gdb.UnwindInfo instance
                                                 ^^^^^^^^^^^^^^
@code

> +using one of the @code{frame_id_build_xxx} functions.  The exact

Once again, please try a way to make "xxx" more clear, as I did above.

Thanks.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-26  3:31                                         ` Alexander Smundak
  2015-03-26 18:53                                           ` Eli Zaretskii
@ 2015-03-27 22:29                                           ` Doug Evans
  2015-03-28  1:10                                             ` Alexander Smundak
  1 sibling, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-03-27 22:29 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

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

Hi. Just a few more nits.

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

s/UnwindInfo/gdb.UnwindInfo/

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

    for objfile in _gdb.objfiles():

 > +        for unwinder in objfile.frame_unwinders:
 > +            if unwinder.enabled:
 > +                unwind_info = unwinder(pending_frame)
 > +                if unwind_info is not None:
 > +                    return unwind_info
 > +
 > +    current_progspace = _gdb.current_progspace()
 > +    for unwinder in current_progspace.frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder(pending_frame)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    for unwinder in frame_unwinders:
 > +        if unwinder.enabled:
 > +            unwind_info = unwinder(pending_frame)
 > +            if unwind_info is not None:
 > +                return unwind_info
 > +
 > +    return None
 > +
 >  
 >  # Convenience variable to GDB's python directory
 >  PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
 > diff --git a/gdb/python/lib/gdb/command/unwinders.py b/gdb/python/lib/gdb/command/unwinders.py
 > new file mode 100644
 > index 0000000..aa189fe
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/command/unwinders.py
 > @@ -0,0 +1,198 @@
 > +# Unwinder commands.
 > +# Copyright 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +import gdb
 > +import re
 > +
 > +
 > +def validate_regexp(exp, idstring):
 > +    try:
 > +        return re.compile(exp)
 > +    except SyntaxError:
 > +        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
 > +
 > +
 > +def parse_unwinder_command_args(arg):
 > +    """Internal utility to parse unwinder command argv.
 > +
 > +    Arguments:
 > +        arg: The arguments to the command. The format is:
 > +             [locus-regexp [name-regexp]]
 > +
 > +    Returns:
 > +        A 2-tuple of compiled regular expressions.
 > +
 > +    Raises:
 > +        SyntaxError: an error processing ARG
 > +    """
 > +
 > +    argv = gdb.string_to_argv(arg)
 > +    argc = len(argv)
 > +    if argc > 2:
 > +        raise SyntaxError("Too many arguments.")
 > +    locus_regexp = ""
 > +    name_regexp = ""
 > +    if argc >= 1:
 > +        locus_regexp = argv[0]
 > +        if argc >= 2:
 > +            name_regexp = argv[1]
 > +    return (validate_regexp(locus_regexp, "locus"),
 > +            validate_regexp(name_regexp, "unwinder"))
 > +
 > +
 > +class InfoUnwinder(gdb.Command):
 > +    """GDB command to list unwinders.
 > +
 > +    Usage: info unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression matching the location of the
 > +    unwinder.  If it is omitted, all registered unwinders from all
 > +    loci are listed.  A locus can be 'global', 'progspace' to list
 > +    the unwinders from the current progspace, or a regular expression
 > +    matching filenames of objfiles.
 > +
 > +    NAME-REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are listed.
 > +    """
 > +
 > +    def __init__(self):
 > +        super(InfoUnwinder, self).__init__("info unwinder",
 > +                                            gdb.COMMAND_DATA)

gdb.COMMAND_STACK

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

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

 > +
 > +    def invoke(self, arg, from_tty):
 > +        locus_re, name_re = parse_unwinder_command_args(arg)
 > +        if locus_re.match("global"):
 > +            self.list_unwinders("Global:", gdb.frame_unwinders,
 > +                                name_re)
 > +        if locus_re.match("progspace"):
 > +            cp = gdb.current_progspace()
 > +            self.list_unwinders("Progspace %s:" % cp.filename,
 > +                                cp.frame_unwinders, name_re)
 > +        for objfile in gdb.objfiles():
 > +            if locus_re.match(objfile.filename):
 > +                self.list_unwinders("Objfile %s:" % objfile.filename,
 > +                                    objfile.frame_unwinders, name_re)
 > +
 > +
 > +def do_enable_unwinder1(unwinders, name_re, flag):
 > +    """Enable/disable unwinders whose names match given regex.
 > +
 > +    Arguments:
 > +        unwinders: The list of unwinders.
 > +        name_re: Unwinder name filter.
 > +        flag: Enable/disable.
 > +
 > +    Returns:
 > +        The number of unwinders affected.
 > +    """
 > +    total = 0
 > +    for unwinder in unwinders:
 > +        if name_re.match(unwinder.name):
 > +            unwinder.enabled = flag
 > +            total += 1
 > +    return total
 > +
 > +
 > +def do_enable_unwinder(arg, flag):
 > +    """Enable/disable unwinder(s)."""
 > +    (locus_re, name_re) = parse_unwinder_command_args(arg)
 > +    total = 0
 > +    if locus_re.match("global"):
 > +        total += do_enable_unwinder1(gdb.frame_unwinders, name_re, flag)
 > +    if locus_re.match("progspace"):
 > +        total += do_enable_unwinder1(gdb.current_progspace().frame_unwinders,
 > +                                     name_re, flag)
 > +    for objfile in gdb.objfiles():
 > +        if locus_re.match(objfile.filename):
 > +            total += do_enable_unwinder1(objfile.frame_unwinders, name_re,
 > +                                         flag)
 > +    print("%d unwinder%s %s" % (total, "" if total == 1 else "s",
 > +                                "enabled" if flag else "disabled"))
 > +
 > +
 > +class EnableUnwinder(gdb.Command):
 > +    """GDB command to enable unwinders.
 > +
 > +    Usage: enable unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression specifying the unwinders to
 > +    enable.  It can 'global', 'progspace', or the name of an objfile
 > +    within that progspace.
 > +
 > +    NAME_REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are affected.
 > +
 > +    """
 > +
 > +    def __init__(self):
 > +        super(EnableUnwinder, self).__init__("enable unwinder",
 > +                                             gdb.COMMAND_STACK)
 > +
 > +    def invoke(self, arg, from_tty):
 > +        """GDB calls this to perform the command."""
 > +        do_enable_unwinder(arg, True)
 > +
 > +
 > +class DisableUnwinder(gdb.Command):
 > +    """GDB command to disable the specified unwinder.
 > +
 > +    Usage: disable unwinder [locus-regexp [name-regexp]]
 > +
 > +    LOCUS-REGEXP is a regular expression specifying the unwinders to
 > +    disable.  It can 'global', 'progspace', or the name of an objfile
 > +    within that progspace.
 > +
 > +    NAME_REGEXP is a regular expression to filter unwinder names.  If
 > +    this omitted for a specified locus, then all registered unwinders
 > +    in the locus are affected.
 > +
 > +    """
 > +
 > +    def __init__(self):
 > +        super(DisableUnwinder, self).__init__("disable unwinder",
 > +                                              gdb.COMMAND_STACK)
 > +
 > +    def invoke(self, arg, from_tty):
 > +        """GDB calls this to perform the command."""
 > +        do_enable_unwinder(arg, False)
 > +
 > +
 > +def register_unwinder_commands():
 > +    """Installs the unwinder commands."""
 > +    InfoUnwinder()
 > +    EnableUnwinder()
 > +    DisableUnwinder()
 > +
 > +
 > +register_unwinder_commands()
 > diff --git a/gdb/python/lib/gdb/unwinder.py b/gdb/python/lib/gdb/unwinder.py
 > new file mode 100644
 > index 0000000..0d15894
 > --- /dev/null
 > +++ b/gdb/python/lib/gdb/unwinder.py
 > @@ -0,0 +1,93 @@
 > +# Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +# This program is free software; you can redistribute it and/or modify
 > +# it under the terms of the GNU General Public License as published by
 > +# the Free Software Foundation; either version 3 of the License, or
 > +# (at your option) any later version.
 > +#
 > +# This program is distributed in the hope that it will be useful,
 > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +# GNU General Public License for more details.
 > +#
 > +# You should have received a copy of the GNU General Public License
 > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 > +
 > +"""Unwinder class and register_unwinder function."""
 > +
 > +import gdb
 > +
 > +
 > +class Unwinder(object):
 > +    """Base class (or a template) for frame unwinders written in Python.
 > +
 > +    An unwinder has a single method __call__ and the attributes
 > +    described below.
 > +
 > +    Attributes:
 > +        name: The name of the unwinder.
 > +        enabled: A boolean indicating whether the unwinder is enabled.
 > +    """
 > +
 > +    def __init__(self, name):
 > +        """Constructor.
 > +
 > +        Args:
 > +            name: An identifying name for the unwinder.
 > +        """
 > +        self.name = name
 > +        self.enabled = True
 > +
 > +    def __call__(self, pending_frame):
 > +        """GDB calls this method to unwind a frame.
 > +
 > +        Arguments:
 > +            pending_frame: gdb.PendingFrame instance.
 > +
 > +        Returns:
 > +            gdb.UnwindInfo instance.
 > +        """
 > +        raise NotImplementedError("Unwinder __call__.")
 > +
 > +
 > +def register_unwinder(locus, unwinder, replace=False):
 > +    """Register unwinder in given locus.
 > +
 > +    The unwinder is prepended to the locus's unwinders list. Unwinder
 > +    name should be unique.
 > +
 > +    Arguments:
 > +        locus: Either an objfile, progspace, or None (in which case
 > +               the unwinder is registered globally).
 > +        unwinder: An object of a gdb.Unwinder subclass
 > +        replace: If True, replaces existing unwinder with the same name.
 > +                 Otherwise, raises exception if unwinder with the same
 > +                 name already exists.
 > +
 > +    Returns:
 > +        Nothing.
 > +
 > +    Raises:
 > +        RuntimeError: Unwinder name is not unique.

Also raises TypeError for bad locus.

 > +

remove blank line

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

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

 > +
 > +    i = 0
 > +    for needle in locus.frame_unwinders:
 > +        if needle.name == unwinder.name:
 > +            if replace:
 > +                del locus.frame_unwinders[i]
 > +            else:
 > +                raise RuntimeError("Unwinder %s already exists." % unwinder.name)
 > +        i += 1
 > +    locus.frame_unwinders.insert(0, unwinder)
 > diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
 > index 157d200..c9528c3 100644
 > --- a/gdb/python/py-objfile.c
 > +++ b/gdb/python/py-objfile.c
 > @@ -42,6 +42,10 @@ typedef struct
 >  
 >    /* The frame filter list of functions.  */
 >    PyObject *frame_filters;
 > +
 > +  /* The list of frame unwinders.  */
 > +  PyObject *frame_unwinders;
 > +
 >    /* The type-printer list.  */
 >    PyObject *type_printers;
 >  
 > @@ -184,6 +188,7 @@ objfpy_dealloc (PyObject *o)
 >    Py_XDECREF (self->dict);
 >    Py_XDECREF (self->printers);
 >    Py_XDECREF (self->frame_filters);
 > +  Py_XDECREF (self->frame_unwinders);
 >    Py_XDECREF (self->type_printers);
 >    Py_XDECREF (self->xmethods);
 >    Py_TYPE (self)->tp_free (self);
 > @@ -206,6 +211,10 @@ objfpy_initialize (objfile_object *self)
 >    if (self->frame_filters == NULL)
 >      return 0;
 >  
 > +  self->frame_unwinders = PyList_New (0);
 > +  if (self->frame_unwinders == NULL)
 > +    return 0;
 > +
 >    self->type_printers = PyList_New (0);
 >    if (self->type_printers == NULL)
 >      return 0;
 > @@ -313,6 +322,48 @@ objfpy_set_frame_filters (PyObject *o, PyObject *filters, void *ignore)
 >    return 0;
 >  }
 >  
 > +/* Return the frame unwinders attribute for this object file.  */
 > +
 > +PyObject *
 > +objfpy_get_frame_unwinders (PyObject *o, void *ignore)
 > +{
 > +  objfile_object *self = (objfile_object *) o;
 > +
 > +  Py_INCREF (self->frame_unwinders);
 > +  return self->frame_unwinders;
 > +}
 > +
 > +/* Set this object file's frame unwinders list to UNWINDERS.  */
 > +
 > +static int
 > +objfpy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
 > +{
 > +  PyObject *tmp;
 > +  objfile_object *self = (objfile_object *) o;
 > +
 > +  if (!unwinders)
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       _("Cannot delete the frame unwinders attribute."));
 > +      return -1;
 > +    }
 > +
 > +  if (!PyList_Check (unwinders))
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       _("The frame_unwinders attribute must be a list."));
 > +      return -1;
 > +    }
 > +
 > +  /* Take care in case the LHS and RHS are related somehow.  */
 > +  tmp = self->frame_unwinders;
 > +  Py_INCREF (unwinders);
 > +  self->frame_unwinders = unwinders;
 > +  Py_XDECREF (tmp);
 > +
 > +  return 0;
 > +}
 > +
 >  /* Get the 'type_printers' attribute.  */
 >  
 >  static PyObject *
 > @@ -651,6 +702,8 @@ static PyGetSetDef objfile_getset[] =
 >      "Pretty printers.", NULL },
 >    { "frame_filters", objfpy_get_frame_filters,
 >      objfpy_set_frame_filters, "Frame Filters.", NULL },
 > +  { "frame_unwinders", objfpy_get_frame_unwinders,
 > +    objfpy_set_frame_unwinders, "Frame Unwinders", NULL },
 >    { "type_printers", objfpy_get_type_printers, objfpy_set_type_printers,
 >      "Type printers.", NULL },
 >    { "xmethods", objfpy_get_xmethods, NULL,
 > diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
 > index 93fbc14..17da3d1 100644
 > --- a/gdb/python/py-progspace.c
 > +++ b/gdb/python/py-progspace.c
 > @@ -41,6 +41,10 @@ typedef struct
 >  
 >    /* The frame filter list of functions.  */
 >    PyObject *frame_filters;
 > +
 > +  /* The frame unwinder list.  */
 > +  PyObject *frame_unwinders;
 > +
 >    /* The type-printer list.  */
 >    PyObject *type_printers;
 >  
 > @@ -82,6 +86,7 @@ pspy_dealloc (PyObject *self)
 >    Py_XDECREF (ps_self->dict);
 >    Py_XDECREF (ps_self->printers);
 >    Py_XDECREF (ps_self->frame_filters);
 > +  Py_XDECREF (ps_self->frame_unwinders);
 >    Py_XDECREF (ps_self->type_printers);
 >    Py_XDECREF (ps_self->xmethods);
 >    Py_TYPE (self)->tp_free (self);
 > @@ -104,6 +109,10 @@ pspy_initialize (pspace_object *self)
 >    if (self->frame_filters == NULL)
 >      return 0;
 >  
 > +  self->frame_unwinders = PyList_New (0);
 > +  if (self->frame_unwinders == NULL)
 > +    return 0;
 > +
 >    self->type_printers = PyList_New (0);
 >    if (self->type_printers == NULL)
 >      return 0;
 > @@ -211,6 +220,48 @@ pspy_set_frame_filters (PyObject *o, PyObject *frame, void *ignore)
 >    return 0;
 >  }
 >  
 > +/* Return the list of the frame unwinders for this program space.  */
 > +
 > +PyObject *
 > +pspy_get_frame_unwinders (PyObject *o, void *ignore)
 > +{
 > +  pspace_object *self = (pspace_object *) o;
 > +
 > +  Py_INCREF (self->frame_unwinders);
 > +  return self->frame_unwinders;
 > +}
 > +
 > +/* Set this program space's list of the unwinders to UNWINDERS.  */
 > +
 > +static int
 > +pspy_set_frame_unwinders (PyObject *o, PyObject *unwinders, void *ignore)
 > +{
 > +  PyObject *tmp;
 > +  pspace_object *self = (pspace_object *) o;
 > +
 > +  if (!unwinders)
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       "cannot delete the frame unwinders list");
 > +      return -1;
 > +    }
 > +
 > +  if (!PyList_Check (unwinders))
 > +    {
 > +      PyErr_SetString (PyExc_TypeError,
 > +		       "the frame unwinders attribute must be a list");
 > +      return -1;
 > +    }
 > +
 > +  /* Take care in case the LHS and RHS are related somehow.  */
 > +  tmp = self->frame_unwinders;
 > +  Py_INCREF (unwinders);
 > +  self->frame_unwinders = unwinders;
 > +  Py_XDECREF (tmp);
 > +
 > +  return 0;
 > +}
 > +
 >  /* Get the 'type_printers' attribute.  */
 >  
 >  static PyObject *
 > @@ -345,6 +396,8 @@ static PyGetSetDef pspace_getset[] =
 >      "Pretty printers.", NULL },
 >    { "frame_filters", pspy_get_frame_filters, pspy_set_frame_filters,
 >      "Frame filters.", NULL },
 > +  { "frame_unwinders", pspy_get_frame_unwinders, pspy_set_frame_unwinders,
 > +    "Frame unwinders.", NULL },
 >    { "type_printers", pspy_get_type_printers, pspy_set_type_printers,
 >      "Type printers.", NULL },
 >    { "xmethods", pspy_get_xmethods, NULL,
 > diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
 > new file mode 100644
 > index 0000000..3610871
 > --- /dev/null
 > +++ b/gdb/python/py-unwind.c
 > @@ -0,0 +1,782 @@
 > +/* Python frame unwinder interface.
 > +
 > +   Copyright (C) 2015 Free Software Foundation, Inc.
 > +
 > +   This file is part of GDB.
 > +
 > +   This program is free software; you can redistribute it and/or modify
 > +   it under the terms of the GNU General Public License as published by
 > +   the Free Software Foundation; either version 3 of the License, or
 > +   (at your option) any later version.
 > +
 > +   This program is distributed in the hope that it will be useful,
 > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
 > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 > +   GNU General Public License for more details.
 > +
 > +   You should have received a copy of the GNU General Public License
 > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 > +
 > +#include "defs.h"
 > +#include "arch-utils.h"
 > +#include "frame-unwind.h"
 > +#include "gdb_obstack.h"
 > +#include "gdbcmd.h"
 > +#include "language.h"
 > +#include "observer.h"
 > +#include "python-internal.h"
 > +#include "regcache.h"
 > +#include "valprint.h"
 > +#include "user-regs.h"
 > +
 > +#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
 > +  { fprintf_unfiltered (gdb_stdlog, args); }
 > +
 > +typedef struct
 > +{
 > +  PyObject_HEAD
 > +
 > +  /* Frame we are unwinding.  */
 > +  struct frame_info *frame_info;
 > +
 > +  /* Its architecture, passed by the sniffer caller.  */
 > +  struct gdbarch *gdbarch;
 > +} pending_frame_object;
 > +
 > +/* Saved registers array item.  */

blank line

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

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

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

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

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

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

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

unnecessary parens around TYPE_LENGTH().

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

Delete definition of except.

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

Delete definition of except.

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

Missing function comment.

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

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

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

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

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

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

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

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

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

-- 
/dje

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-27 22:29                                           ` Doug Evans
@ 2015-03-28  1:10                                             ` Alexander Smundak
  2015-03-30 17:45                                               ` Doug Evans
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-28  1:10 UTC (permalink / raw)
  To: Doug Evans; +Cc: Andy Wingo, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 1979 bytes --]

Addressed eliz@ and dje@ comments.

gdb/ChangeLog

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

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

gdb/testsuite/ChangeLog

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

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

[-- Attachment #2: patch10.diff --]
[-- Type: text/plain, Size: 67469 bytes --]

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-28  1:10                                             ` Alexander Smundak
@ 2015-03-30 17:45                                               ` Doug Evans
  2015-03-30 19:49                                                 ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-03-30 17:45 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

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

Hi.  Just a few more nits.

 > diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
 > index d725eb0..6b1878e 100644
 > --- a/gdb/doc/python.texi
 > +++ b/gdb/doc/python.texi
 > @@ -144,6 +144,7 @@ optional arguments while skipping others.  Example:
 >  * Frame Filter API::            Filtering Frames.
 >  * Frame Decorator API::         Decorating Frames.
 >  * Writing a Frame Filter::      Writing a Frame Filter.
 > +* Unwinding Frames in Python::  Writing frame unwinder.
 >  * Xmethods In Python::          Adding and replacing methods of C++ classes.
 >  * Xmethod API::                 Xmethod types.
 >  * Writing an Xmethod::          Writing an xmethod.
 > @@ -2178,6 +2179,148 @@ printed hierarchically.  Another approach would be to combine the
 >  marker in the inlined frame, and also show the hierarchical
 >  relationship.
 >  
 > +@node Unwinding Frames in Python
 > +@subsubsection Unwinding Frames in Python
 > +@cindex unwinding frames in Python
 > +
 > +In @value{GDBN} terminology ``unwinding'' is the process of finding
 > +the previous frame (that is, caller's) from the current one.  An
 > +unwinder has three methods.  The first one checks if it can handle
 > +given frame (``sniff'' it).  For the frames it can sniff an unwinder
 > +provides two additional methods: it can return frame's ID, and it can
 > +fetch registers from the previous frame.  A running @value{GDBN}
 > +mantains a list of the unwinders and calls each unwinder's sniffer in
 > +turn until it finds the one that recognizes the current frame.  There
 > +is an API to register an unwinder.
 > +
 > +The unwinders that come with @value{GDBN} handle standard frames.
 > +However, mixed language applications (for example, an application
 > +running Java Virtual Machine) sometimes use frame layouts that cannot
 > +be handled by the @value{GDBN} unwinders.  You can write Python code
 > +that can handle such custom frames.
 > +
 > +You implement a frame unwinder in Python as a class with which has two
 > +attributes, @code{name} and @code{enabled}, with obvious meanings, and
 > +a single method @code{__call__}, which examines a given frame and
 > +returns an object (an instance of @code{gdb.UnwindInfo class)}
 > +describing it.  If an unwinder does not recognize a frame, it should
 > +return @code{None}.  The code in @value{GDBN} that enables writing
 > +unwinders in Python uses this object to return frame's ID and previous
 > +frame registers when @value{GDBN} core asks for them.
 > +
 > +@subheading Unwinder Input
 > +
 > +An object passed to an unwinder (a @code{gdb.PendingFrame} instance)
 > +provides a method to read frame's registers:
 > +
 > +@defun PendingFrame.read_register (reg)
 > +This method returns the contents of the register @var{regn} in the
 > +frame as a @code{gdb.Value} object.  @var{reg} can be either a
 > +register number or a register name; the values are platform-specific.
 > +They are usually found in the corresponding
 > +@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree.
 > +@end defun
 > +
 > +It also provides a factory method to create a @code{gdb.UnwindInfo}
 > +instance to be returned to @value{GDBN}:
 > +
 > +@defun PendingFrame.create_unwind_info (frame_id)
 > +Returns a new @code{gdb.UnwindInfo} instance identified by given
 > +@var{frame_id}.  The argument is used to build @value{GDBN}'s frame ID
 > +using one of functions provided by @value{GDBN}.  @var{frame_id}'s attributes
 > +determine which function will be used, as follows:
 > +
 > +@table @code
 > +@item sp, pc, special
 > +@code{frame_id_build_special (@var{frame_id}.sp, @var{frame_id}.pc, @var{frame_id}.special)}
 > +
 > +@item sp, pc
 > +@code{frame_id_build (@var{frame_id}.sp, @var{frame_id}.pc)}
 > +
 > +This is the most common case.
 > +
 > +@item sp
 > +@code{frame_id_build_wild (@var{frame_id}.sp)}
 > +@end table
 > +The attribute values should be @code{gdb.Value}
 > +
 > +@end defun
 > +
 > +@subheading Unwinder Output: UnwindInfo
 > +
 > +A @code{gdb.UnwindInfo} object can be constructed by one of the
 > +methods described above.  Use the following method to set the caller

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

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

s/unwind_instance/unwind_info/

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

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

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

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

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-30 17:45                                               ` Doug Evans
@ 2015-03-30 19:49                                                 ` Alexander Smundak
  2015-03-31 22:36                                                   ` Doug Evans
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-03-30 19:49 UTC (permalink / raw)
  To: Doug Evans; +Cc: Andy Wingo, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 10574 bytes --]

Fixed documentation and added '_gdb.' prefix to 'objfiles()'.
PTAL.


On Mon, Mar 30, 2015 at 10:45 AM, Doug Evans <dje@google.com> wrote:
> Alexander Smundak writes:
>  > Addressed eliz@ and dje@ comments.
>  >
>  > gdb/ChangeLog
>  >
>  > 2015-03-28  Sasha Smundak  <asmundak@google.com>
>  >
>  >     * Makefile.in (SUBDIR_PYTHON_OBJS): Add py-unwind.o.
>  >     (SUBDIR_PYTHON_SRCS): Add py-unwind.c.
>  >     (py-unwind.o): New recipe.
>  >     * NEWS: mention Python frame unwinding.
>  >     * data-directory/Makefile.in (PYTHON_FILE_LIST): Add
>  >     gdb/unwinder.py and gdb/command/unwinder.py
>  >     * doc/python.texi (Writing a Frame Unwinder in Python): Add
>  >     section.
>  >     * python/lib/gdb/__init__.py (packages): Add frame_unwinders
>  >     list.
>  >     (execute_unwinders): New function.
>  >     * python/lib/gdb/command/unwinders.py: New file.
>  >     * python/lib/gdb/unwinder.py: New file.
>  >     * python/py-objfile.c (objfile_object): Add frame_unwinders field.
>  >     (objfpy_dealloc): Decrement frame_unwinders reference count.
>  >     (objfpy_initialize): Create frame_unwinders list.
>  >     (objfpy_get_frame_unwinders): New function.
>  >     (objfpy_set_frame_unwinders): Ditto.
>  >     (objfile_getset): Add frame_unwinders attribute to Objfile.
>  >     * python/py-progspace.c (pspace_object): Add frame_unwinders field.
>  >     (pspy_dealloc): Decrement frame_unwinders reference count.
>  >     (pspy_initialize): Create frame_unwinders list.
>  >     (pspy_get_frame_unwinders): New function.
>  >     (pspy_set_frame_unwinders): Ditto.
>  >     (pspy_getset): Add frame_unwinders attribute to gdb.Progspace.
>  >     * python/py-unwind.c: New file.
>  >     * python/python-internal.h (pspy_get_name_unwinders): New prototype.
>  >     (objpy_get_frame_unwinders): New prototype.
>  >     (gdbpy_initialize_unwind): New prototype.
>  >     * python/python.c (gdbpy_apply_type_printers): Call
>  >     gdbpy_initialize_unwind.
>  >
>  > gdb/testsuite/ChangeLog
>  >
>  > 2015-03-28  Sasha Smundak  <asmundak@google.com>
>  >
>  >     * gdb.python/py-unwind-maint.c: New file.
>  >     * gdb.python/py-unwind-maint.exp: New test.
>  >     * gdb.python/py-unwind-maint.py: New file.
>  >     * gdb.python/py-unwind.c: New file.
>  >     * gdb.python/py-unwind.exp: New test.
>  >     * gdb.python/py-unwind.py: New test.
>
> Hi.  Just a few more nits.
>
>  > diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
>  > index d725eb0..6b1878e 100644
>  > --- a/gdb/doc/python.texi
>  > +++ b/gdb/doc/python.texi
>  > @@ -144,6 +144,7 @@ optional arguments while skipping others.  Example:
>  >  * Frame Filter API::            Filtering Frames.
>  >  * Frame Decorator API::         Decorating Frames.
>  >  * Writing a Frame Filter::      Writing a Frame Filter.
>  > +* Unwinding Frames in Python::  Writing frame unwinder.
>  >  * Xmethods In Python::          Adding and replacing methods of C++ classes.
>  >  * Xmethod API::                 Xmethod types.
>  >  * Writing an Xmethod::          Writing an xmethod.
>  > @@ -2178,6 +2179,148 @@ printed hierarchically.  Another approach would be to combine the
>  >  marker in the inlined frame, and also show the hierarchical
>  >  relationship.
>  >
>  > +@node Unwinding Frames in Python
>  > +@subsubsection Unwinding Frames in Python
>  > +@cindex unwinding frames in Python
>  > +
>  > +In @value{GDBN} terminology ``unwinding'' is the process of finding
>  > +the previous frame (that is, caller's) from the current one.  An
>  > +unwinder has three methods.  The first one checks if it can handle
>  > +given frame (``sniff'' it).  For the frames it can sniff an unwinder
>  > +provides two additional methods: it can return frame's ID, and it can
>  > +fetch registers from the previous frame.  A running @value{GDBN}
>  > +mantains a list of the unwinders and calls each unwinder's sniffer in
>  > +turn until it finds the one that recognizes the current frame.  There
>  > +is an API to register an unwinder.
>  > +
>  > +The unwinders that come with @value{GDBN} handle standard frames.
>  > +However, mixed language applications (for example, an application
>  > +running Java Virtual Machine) sometimes use frame layouts that cannot
>  > +be handled by the @value{GDBN} unwinders.  You can write Python code
>  > +that can handle such custom frames.
>  > +
>  > +You implement a frame unwinder in Python as a class with which has two
>  > +attributes, @code{name} and @code{enabled}, with obvious meanings, and
>  > +a single method @code{__call__}, which examines a given frame and
>  > +returns an object (an instance of @code{gdb.UnwindInfo class)}
>  > +describing it.  If an unwinder does not recognize a frame, it should
>  > +return @code{None}.  The code in @value{GDBN} that enables writing
>  > +unwinders in Python uses this object to return frame's ID and previous
>  > +frame registers when @value{GDBN} core asks for them.
>  > +
>  > +@subheading Unwinder Input
>  > +
>  > +An object passed to an unwinder (a @code{gdb.PendingFrame} instance)
>  > +provides a method to read frame's registers:
>  > +
>  > +@defun PendingFrame.read_register (reg)
>  > +This method returns the contents of the register @var{regn} in the
>  > +frame as a @code{gdb.Value} object.  @var{reg} can be either a
>  > +register number or a register name; the values are platform-specific.
>  > +They are usually found in the corresponding
>  > +@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree.
>  > +@end defun
>  > +
>  > +It also provides a factory method to create a @code{gdb.UnwindInfo}
>  > +instance to be returned to @value{GDBN}:
>  > +
>  > +@defun PendingFrame.create_unwind_info (frame_id)
>  > +Returns a new @code{gdb.UnwindInfo} instance identified by given
>  > +@var{frame_id}.  The argument is used to build @value{GDBN}'s frame ID
>  > +using one of functions provided by @value{GDBN}.  @var{frame_id}'s attributes
>  > +determine which function will be used, as follows:
>  > +
>  > +@table @code
>  > +@item sp, pc, special
>  > +@code{frame_id_build_special (@var{frame_id}.sp, @var{frame_id}.pc, @var{frame_id}.special)}
>  > +
>  > +@item sp, pc
>  > +@code{frame_id_build (@var{frame_id}.sp, @var{frame_id}.pc)}
>  > +
>  > +This is the most common case.
>  > +
>  > +@item sp
>  > +@code{frame_id_build_wild (@var{frame_id}.sp)}
>  > +@end table
>  > +The attribute values should be @code{gdb.Value}
>  > +
>  > +@end defun
>  > +
>  > +@subheading Unwinder Output: UnwindInfo
>  > +
>  > +A @code{gdb.UnwindInfo} object can be constructed by one of the
>  > +methods described above.  Use the following method to set the caller
>
> I'd replace "can be constructed by one of the methods describe above." with
> "is constructed with the @code{PendingFrame.create_unwind_info}
> method described above."
>
>  > +frame's registers:
>  > +
>  > +@defun gdb.UnwindInfo.add_saved_register (reg, value)
>  > +@var{reg} identifies the register.  It can be a number or a name, just
>  > +as for the @code{PendingFrame.read_register} method above.
>  > +@var{value} is a register value (a @code{gdb.Value} object).
>  > +@end defun
>  > +
>  > +@subheading Unwinder Skeleton Code
>  > +
>  > +@value{GDBN} comes with the module containing the base @code{Unwinder}
>  > +class.  Derive your unwinder class from it and structure the code as
>  > +follows:
>  > +
>  > +@smallexample
>  > +from gdb.unwinders import Unwinder
>  > +
>  > +class FrameId(object):
>  > +    def __init__(self, sp, pc):
>  > +        self.sp = sp
>  > +        self.pc = pc
>  > +
>  > +
>  > +class MyUnwinder(Unwinder):
>  > +    def __init__(....):
>  > +        supe(MyUnwinder, self).__init___(<expects unwinder name argument>)
>  > +
>  > +    def __call__(pending_frame):
>  > +        if not <we recognize frame>:
>  > +            return None
>  > +        # Create UnwindInfo.  Usually the frame is identified by the stack
>  > +        # pointer and the program counter.
>  > +        sp = pending_frame.read_register(<SP number>)
>  > +        pc = pending_frame.read_register(<PC number>)
>  > +        unwind_info = pending_frame.create_unwind_info(FrameId(sp, pc))
>  > +
>  > +        # Find the values of the registers in the caller's frame and
>  > +        # save them in the result:
>  > +        unwind_info.add_saved_register(<register>, <value>)
>  > +        ....
>  > +
>  > +        # Return the result:
>  > +        return unwind_instance
>
> s/unwind_instance/unwind_info/
>
>  > diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
>  > index 92b06f2..0494959 100644
>  > --- a/gdb/python/lib/gdb/__init__.py
>  > +++ b/gdb/python/lib/gdb/__init__.py
>  > @@ -71,6 +71,42 @@ type_printers = []
>  >  xmethods = []
>  >  # Initial frame filters.
>  >  frame_filters = {}
>  > +# Initial frame unwinders.
>  > +frame_unwinders = []
>  > +
>  > +def execute_unwinders(pending_frame):
>  > +    """Internal function called from GDB to execute all unwinders.
>  > +
>  > +    Runs each currently enabled unwinder until it finds the one that
>  > +    can unwind given frame.
>  > +
>  > +    Arguments:
>  > +        pending_frame: gdb.PendingFrame instance.
>  > +    Returns:
>  > +        gdb.UnwindInfo instance or None.
>  > +    """
>  > +    for objfile in objfiles():
>
> It's odd to call objfiles() here and _gdb.current_progspace() below.
> Either the _gdb. prefix is necessary or it is not.
> At the least let's be consistent here.
> How about instead of adding _gdb. prefix here ...
>
>  > +        for unwinder in objfile.frame_unwinders:
>  > +            if unwinder.enabled:
>  > +                unwind_info = unwinder(pending_frame)
>  > +                if unwind_info is not None:
>  > +                    return unwind_info
>  > +
>  > +    current_progspace = _gdb.current_progspace()
>
> .... remove it here.
> [and rerun the testsuite to verify]
>
>  > +    for unwinder in current_progspace.frame_unwinders:
>  > +        if unwinder.enabled:
>  > +            unwind_info = unwinder(pending_frame)
>  > +            if unwind_info is not None:
>  > +                return unwind_info
>  > +
>  > +    for unwinder in frame_unwinders:
>  > +        if unwinder.enabled:
>  > +            unwind_info = unwinder(pending_frame)
>  > +            if unwind_info is not None:
>  > +                return unwind_info
>  > +
>  > +    return None
>  > +
>  >
>  >  # Convenience variable to GDB's python directory
>  >  PYTHONDIR = os.path.dirname(os.path.dirname(__file__))

[-- Attachment #2: patch11.diff --]
[-- Type: text/plain, Size: 67722 bytes --]

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

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-30 19:49                                                 ` Alexander Smundak
@ 2015-03-31 22:36                                                   ` Doug Evans
  2015-04-01  0:09                                                     ` Alexander Smundak
  0 siblings, 1 reply; 60+ messages in thread
From: Doug Evans @ 2015-03-31 22:36 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

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

Thanks.
LGTM.

And thanks for your patience!

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-03-31 22:36                                                   ` Doug Evans
@ 2015-04-01  0:09                                                     ` Alexander Smundak
  2015-04-01  0:28                                                       ` Doug Evans
  0 siblings, 1 reply; 60+ messages in thread
From: Alexander Smundak @ 2015-04-01  0:09 UTC (permalink / raw)
  To: Doug Evans; +Cc: Andy Wingo, gdb-patches

Thank you for reviewing this!

How will this change make it into the GDB repository?
I don't have the authority to commit changes.

^ permalink raw reply	[flat|nested] 60+ messages in thread

* Re: [RFC] [PATCH] Provide the ability to write the frame unwinder in Python
  2015-04-01  0:09                                                     ` Alexander Smundak
@ 2015-04-01  0:28                                                       ` Doug Evans
  0 siblings, 0 replies; 60+ messages in thread
From: Doug Evans @ 2015-04-01  0:28 UTC (permalink / raw)
  To: Alexander Smundak; +Cc: Andy Wingo, gdb-patches

On Tue, Mar 31, 2015 at 5:09 PM, Alexander Smundak <asmundak@google.com> wrote:
> Thank you for reviewing this!
>
> How will this change make it into the GDB repository?
> I don't have the authority to commit changes.

I'll check it in tomorrow.

^ permalink raw reply	[flat|nested] 60+ messages in thread

end of thread, other threads:[~2015-04-01  0:28 UTC | newest]

Thread overview: 60+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-12-15 18:14 [RFC] [PATCH] Provide the ability to write the frame unwinder in Python Alexander Smundak
2014-12-22 19:24 ` Alexander Smundak
2014-12-29 18:02   ` Alexander Smundak
2015-01-05 17:53     ` Alexander Smundak
2015-01-12 20:03       ` Alexander Smundak
2015-01-22  3:31         ` Alexander Smundak
2015-01-29  1:36           ` Alexander Smundak
2015-01-12 21:00 ` Simon Marchi
2015-01-12 21:22   ` Doug Evans
2015-02-04 22:36 ` Doug Evans
2015-02-12 17:58   ` Alexander Smundak
2015-02-19  2:32     ` Alexander Smundak
2015-02-20 11:12     ` Phil Muldoon
2015-02-26  3:09       ` Alexander Smundak
2015-03-02 22:56         ` Alexander Smundak
2015-03-03  8:46           ` Andy Wingo
2015-03-04  2:36             ` Alexander Smundak
2015-03-04  7:49               ` Andy Wingo
2015-03-09 11:02                 ` Phil Muldoon
2015-03-11  2:22                   ` Alexander Smundak
2015-03-11  8:49                     ` Andy Wingo
2015-03-11 17:34                       ` Doug Evans
2015-03-11 18:48                       ` Alexander Smundak
2015-03-16 11:29                         ` Andy Wingo
2015-03-16 12:01                           ` Andy Wingo
2015-03-16 17:25                           ` Alexander Smundak
2015-03-17  8:57                             ` Andy Wingo
2015-03-17 19:48                               ` Alexander Smundak
2015-03-17 21:37                                 ` Alexander Smundak
2015-03-18  8:54                                   ` Andy Wingo
2015-03-18 22:57                                     ` Alexander Smundak
2015-03-23 19:58                                       ` Doug Evans
2015-03-24  9:06                                         ` Andy Wingo
2015-03-26  3:31                                         ` Alexander Smundak
2015-03-26 18:53                                           ` Eli Zaretskii
2015-03-27 22:29                                           ` Doug Evans
2015-03-28  1:10                                             ` Alexander Smundak
2015-03-30 17:45                                               ` Doug Evans
2015-03-30 19:49                                                 ` Alexander Smundak
2015-03-31 22:36                                                   ` Doug Evans
2015-04-01  0:09                                                     ` Alexander Smundak
2015-04-01  0:28                                                       ` Doug Evans
2015-03-18 23:25                                 ` Doug Evans
2015-03-19  0:36                                   ` Alexander Smundak
2015-03-19  8:12                                     ` Andy Wingo
2015-03-20  0:15                                       ` Doug Evans
2015-03-20  2:27                                         ` Alexander Smundak
2015-03-20 17:48                                           ` Doug Evans
2015-03-20  8:26                                         ` Andy Wingo
2015-03-20 18:32                                           ` Doug Evans
2015-03-17 22:21                               ` Doug Evans
2015-03-18  8:57                                 ` Andy Wingo
2015-03-18 16:48                                   ` Doug Evans
2015-03-19  8:04                                     ` Andy Wingo
2015-03-09  9:42           ` Andy Wingo
2015-03-03  0:49         ` Alexander Smundak
2015-03-03 14:38           ` Andy Wingo
2015-03-04  2:52             ` Alexander Smundak
2015-02-20  9:42 ` Phil Muldoon
2015-02-20  9:59   ` Phil Muldoon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).