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

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