This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[rfc] expose gdb values to python


Hi folks,

This is the patch exposing struct value to Python. The object it defines
is used quite a lot in other patches in the series, so it is central to
the Python scripting experience (so to say).

It implements a lot of Python __magic__ functions in order to make the
gdb.Value objects behave as much as possible as Python native objects
corresponding to the struct value they (the gdb.Value objects)
represent. Also, it makes structure elements available using dictionary
syntax (e.g., baz = foo['bar'] to access foo.bar).

Because of that, the documentation is short. There's not much new stuff
to talk about.

The testcase is complete. I believe it tests every feature of gdb.Value
objects. All tests pass.

There's only one thing that bothers me with this patch (I know, there
are two FIXMEs, but one of them doesn't bother me much. Hang on.): the
implementation of __str__ uses current_language in its call to
common_val_print. I believe it's better to avoid using current_language,
right? I don't think there's a way to get a sensible language_defn to
use here, so my only idea is to add an element to struct value which
holds the language associated with the value. This element would be
filled at the moment the value is created. Sounds like a lot of work...
WDYT?

The other FIXME is for the bogus implementation of valpy_length, which
is supposed to return the number of elements in the gdb.Value fake
dictionary. I had a quick look at how I'd enumerate all elements in a
struct/class to find a sensible answer, but I got scared by the code in
value_struct_elt and friends. Since this functionality doesn't seem to
impact basic use of gdb.Value objects, I thought I could get away for
now without implementing this... :-) Mmmm... it just occurred to me that
I should take a look at varobj code to see how it lists children
varobjs.
-- 
[]'s
Thiago Jung Bauermann
IBM Linux Technology Center

:ADDPATCH python:

2008-09-12  Thiago Jung Bauermann  <bauerman@br.ibm.com>

gdb/
	* Makefile.in (SUBDIR_PYTHON_OBS): Add python-value.o.
	(SUBDIR_PYTHON_SRCS): Add python-value.c.
	(python-value.o): New target.
	* configure.ac (CONFIG_OBS): Add python-value.o.
	(CONFIG_SRCS): Add python/python-value.c
	* python-internal.h (value_object_type): Add external declaration.
	(gdbpy_get_value_from_history, value_to_value_object,
	convert_value_from_python, gdbpy_initialize_values): Add function
	prototype.
	* python/python-value.c: New file.
	* python/python.c (GdbMethods): Add gdbpy_get_value_from_history.
	(_initialize_python): Call gdbpy_initialize_values.
	* python/python.h (values_in_python): Add external declaration.
	* value.c (value_prepend_to_list, value_remove_from_list): New
	functions.
	(preserve_values): Iterate over values_in_python list as well.
	* value.h (value_prepend_to_list, value_remove_from_list): Add
	function prototypes.

gdb/doc/
	* gdb.texinfo. (Values From Inferior): New subsubsection.

gdb/testsuite/
	* gdb.python/python-values.c: New file.
	* gdb.python/python-values.exp: New file.

Index: gdb.git/gdb/Makefile.in
===================================================================
--- gdb.git.orig/gdb/Makefile.in	2008-09-12 02:24:27.000000000 -0300
+++ gdb.git/gdb/Makefile.in	2008-09-12 02:28:11.000000000 -0300
@@ -271,10 +271,12 @@ SUBDIR_TUI_CFLAGS= \
 #
 SUBDIR_PYTHON_OBS = \
 	python.o \
-	python-utils.o
+	python-utils.o \
+	python-value.o
 SUBDIR_PYTHON_SRCS = \
 	python/python.c \
-	python/python-utils.c
+	python/python-utils.c \
+	python/python-value.c
 SUBDIR_PYTHON_DEPS =
 SUBDIR_PYTHON_LDFLAGS=
 SUBDIR_PYTHON_CFLAGS=
@@ -1846,6 +1848,10 @@ python-utils.o: $(srcdir)/python/python-
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/python-utils.c
 	$(POSTCOMPILE)
 
+python-value.o: $(srcdir)/python/python-value.c
+	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/python-value.c
+	$(POSTCOMPILE)
+
 #
 # Dependency tracking.  Most of this is conditional on GNU Make being
 # found by configure; if GNU Make is not found, we fall back to a
Index: gdb.git/gdb/configure.ac
===================================================================
--- gdb.git.orig/gdb/configure.ac	2008-08-08 01:47:05.000000000 -0300
+++ gdb.git/gdb/configure.ac	2008-09-12 02:26:42.000000000 -0300
@@ -624,10 +624,10 @@ if test "${have_libpython}" = yes; then
     AC_MSG_RESULT(${PYTHON_CFLAGS})
   fi
 else
-  # Even if Python support is not compiled in, we need to have this file
+  # Even if Python support is not compiled in, we need to have these files
   # included in order to recognize the GDB command "python".
-  CONFIG_OBS="$CONFIG_OBS python.o"
-  CONFIG_SRCS="$CONFIG_SRCS python/python.c"
+  CONFIG_OBS="$CONFIG_OBS python.o python-value.o"
+  CONFIG_SRCS="$CONFIG_SRCS python/python.c python/python-value.c"
 fi
 AC_SUBST(PYTHON_CFLAGS)
 
Index: gdb.git/gdb/doc/gdb.texinfo
===================================================================
--- gdb.git.orig/gdb/doc/gdb.texinfo	2008-09-12 02:24:28.000000000 -0300
+++ gdb.git/gdb/doc/gdb.texinfo	2008-09-12 02:26:42.000000000 -0300
@@ -17631,6 +17631,7 @@ situation, a Python @code{KeyboardInterr
 @menu
 * Basic Python::                Basic Python Functions.
 * Exception Handling::
+* Values From Inferior::
 @end menu
 
 @node Basic Python
@@ -17709,6 +17710,36 @@ message as its value, and the Python cal
 Python statement closest to where the @value{GDBN} error occured as the
 traceback.
 
+@node Values From Inferior
+@subsubsection Values From Inferior
+
+@value{GDBN} provides values it obtains from the inferior program in an
+object of type @code{gdb.Value}.  This object keeps track of information
+related to the value such as its type, the address where it is kept in
+the inferior, and so on.
+
+You can directly use @code{gdb.Value} objects in places which make sense
+for the type of the value they contain.  For instance, you can use the
+value of an integer variable in the inferior as if it were a Python
+integer variable.
+
+If the @code{gdb.Value} object is of a structure type or an instance of a
+class, its elements and methods are provided using dictionary syntax.
+For example, if @code{some_val} is a @code{gdb.Value} instance holding
+a structure, you can access its @code{foo} element with:
+
+@smallexample
+bar = some_val['foo']
+@end smallexample
+
+The following method is provided:
+
+@defmethod Value dereference
+If the @code{gdb.Value} object is of a pointer type, you can dereference
+it using the @code{dereference} method.  This gives you a new
+@code{gdb.Value} object for the pointed-to contents.
+@end defmethod
+
 @node Interpreters
 @chapter Command Interpreters
 @cindex command interpreters
Index: gdb.git/gdb/python/python-internal.h
===================================================================
--- gdb.git.orig/gdb/python/python-internal.h	2008-08-07 01:22:14.000000000 -0300
+++ gdb.git/gdb/python/python-internal.h	2008-09-12 02:26:42.000000000 -0300
@@ -43,11 +43,18 @@ typedef Py_intptr_t Py_ssize_t;
 #error "Unable to find usable Python.h"
 #endif
 
-struct block;
-struct symbol;
-struct symtab_and_line;
+struct value;
 
 extern PyObject *gdb_module;
+extern PyTypeObject value_object_type;
+
+PyObject *gdbpy_get_value_from_history (PyObject *self, PyObject *args);
+
+PyObject *value_to_value_object (struct value *v);
+
+struct value *convert_value_from_python (PyObject *obj);
+
+void gdbpy_initialize_values (void);
 
 struct cleanup *make_cleanup_py_decref (PyObject *py);
 
Index: gdb.git/gdb/python/python-value.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb.git/gdb/python/python-value.c	2008-09-12 02:26:42.000000000 -0300
@@ -0,0 +1,663 @@
+/* Python interface to values.
+
+   Copyright (C) 2008 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 "charset.h"
+#include "value.h"
+#include "exceptions.h"
+#include "language.h"
+#include "dfp.h"
+
+/* List of all values which are currently exposed to Python. It is
+   maintained so that when an objfile is discarded, preserve_values
+   can copy the values' types if needed.  This is declared
+   unconditionally to reduce the number of uses of HAVE_PYTHON in the
+   generic code.  */
+struct value *values_in_python;
+
+#ifdef HAVE_PYTHON
+
+#include "python-internal.h"
+
+typedef struct {
+  PyObject_HEAD
+  struct value *value;
+  int owned_by_gdb;
+} value_object;
+
+static void valpy_dealloc (PyObject *obj);
+static PyObject *valpy_new (PyTypeObject *subtype, PyObject *args,
+			    PyObject *keywords);
+static Py_ssize_t valpy_length (PyObject *self);
+static PyObject *valpy_getitem (PyObject *self, PyObject *key);
+static int valpy_setitem (PyObject *self, PyObject *key, PyObject *value);
+static PyObject *valpy_str (PyObject *self);
+static PyObject *valpy_add (PyObject *self, PyObject *other);
+static PyObject *valpy_subtract (PyObject *self, PyObject *other);
+static PyObject *valpy_multiply (PyObject *self, PyObject *other);
+static PyObject *valpy_divide (PyObject *self, PyObject *other);
+static PyObject *valpy_remainder (PyObject *self, PyObject *other);
+static PyObject *valpy_power (PyObject *self, PyObject *other, PyObject *unused);
+static PyObject *valpy_negative (PyObject *self);
+static PyObject *valpy_positive (PyObject *self);
+static PyObject *valpy_absolute (PyObject *self);
+static int valpy_nonzero (PyObject *self);
+static PyObject *valpy_richcompare (PyObject *self, PyObject *other, int op);
+static PyObject *valpy_dereference (PyObject *self, PyObject *args);
+
+static PyMethodDef value_object_methods[] = {
+  { "dereference", valpy_dereference, METH_NOARGS, "Dereferences the value." },
+  {NULL}  /* Sentinel */
+};
+
+static PyNumberMethods value_object_as_number = {
+  valpy_add,
+  valpy_subtract,
+  valpy_multiply,
+  valpy_divide,
+  valpy_remainder,
+  NULL,			      /* nb_divmod */
+  valpy_power,		      /* nb_power */
+  valpy_negative,	      /* nb_negative */
+  valpy_positive,	      /* nb_positive */
+  valpy_absolute,	      /* nb_absolute */
+  valpy_nonzero		      /* nb_nonzero */
+};
+
+static PyMappingMethods value_object_as_mapping = {
+  valpy_length,
+  valpy_getitem,
+  valpy_setitem
+};
+
+PyTypeObject value_object_type = {
+  PyObject_HEAD_INIT (NULL)
+  0,				  /*ob_size*/
+  "gdb.Value",			  /*tp_name*/
+  sizeof (value_object),	  /*tp_basicsize*/
+  0,				  /*tp_itemsize*/
+  valpy_dealloc,		  /*tp_dealloc*/
+  0,				  /*tp_print*/
+  0,				  /*tp_getattr*/
+  0,				  /*tp_setattr*/
+  0,				  /*tp_compare*/
+  0,				  /*tp_repr*/
+  &value_object_as_number,	  /*tp_as_number*/
+  0,				  /*tp_as_sequence*/
+  &value_object_as_mapping,	  /*tp_as_mapping*/
+  0,				  /*tp_hash */
+  0,				  /*tp_call*/
+  valpy_str,			  /*tp_str*/
+  0,				  /*tp_getattro*/
+  0,				  /*tp_setattro*/
+  0,				  /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,	/*tp_flags*/
+  "GDB value object",		  /* tp_doc */
+  0,				  /* tp_traverse */
+  0,				  /* tp_clear */
+  valpy_richcompare,		  /* tp_richcompare */
+  0,				  /* tp_weaklistoffset */
+  0,				  /* tp_iter */
+  0,				  /* tp_iternext */
+  value_object_methods		  /* tp_methods */
+};
+
+
+/* Called by the Python interpreter when deallocating a value object.  */
+static void
+valpy_dealloc (PyObject *obj)
+{
+  value_object *self = (value_object *) obj;
+
+  value_remove_from_list (&values_in_python, self->value);
+
+  if (!self->owned_by_gdb)
+    value_free (self->value);
+  self->ob_type->tp_free (self);
+}
+
+/* Called when a new gdb.Value object needs to be allocated.  */
+static PyObject *
+valpy_new (PyTypeObject *subtype, PyObject *args, PyObject *keywords)
+{
+  struct value *value = NULL;   /* Initialize to appease gcc warning.  */
+  value_object *value_obj;
+  volatile struct gdb_exception except;
+
+  if (PyTuple_Size (args) != 1)
+    {
+      PyErr_SetString (PyExc_TypeError, _("Value object creation takes only " \
+					  "1 argument"));
+      return NULL;
+    }
+
+  value_obj = (value_object *) subtype->tp_alloc (subtype, 1);
+  if (value_obj == NULL)
+    {
+      PyErr_SetString (PyExc_MemoryError, _("Could not allocate memory to " \
+					    "create Value object."));
+      return NULL;
+    }
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      value = convert_value_from_python (PyTuple_GetItem (args, 0));
+    }
+  if (except.reason < 0)
+    {
+      subtype->tp_free (value_obj);
+      return PyErr_Format (except.reason == RETURN_QUIT
+			     ? PyExc_KeyboardInterrupt : PyExc_TypeError,
+			     "%s", except.message);
+    }
+
+  value_obj->value = value;
+  release_value (value);
+  value_prepend_to_list (&values_in_python, value);
+
+  return (PyObject *) value_obj;
+}
+
+/* Given a value of a pointer type, apply the C unary * operator to it.  */
+static PyObject *
+valpy_dereference (PyObject *self, PyObject *args)
+{
+  struct value *res_val = NULL;	  /* Initialize to appease gcc warning.  */
+  volatile struct gdb_exception except;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      res_val = value_ind (((value_object *) self)->value);
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return value_to_value_object (res_val);
+}
+
+static Py_ssize_t
+valpy_length (PyObject *self)
+{
+  return 0;  /* FIXME: dummy.  */
+}
+
+/* Given string name of an element inside structure, return its value
+   object.  */
+static PyObject *
+valpy_getitem (PyObject *self, PyObject *key)
+{
+  value_object *self_value = (value_object *) self;
+  char *field;
+  struct value *res_val = NULL;	  /* Initialize to appease gcc warning.  */
+  struct cleanup *old;
+  volatile struct gdb_exception except;
+
+  field = python_string_to_target_string (key);
+  if (field == NULL)
+    return NULL;
+
+  old = make_cleanup (xfree, field);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      res_val = value_struct_elt (&self_value->value, NULL, field, 0, NULL);
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  do_cleanups (old);
+
+  return value_to_value_object (res_val);
+}
+
+static int
+valpy_setitem (PyObject *self, PyObject *key, PyObject *value)
+{
+  PyErr_Format (PyExc_NotImplementedError,
+		_("Setting of struct elements is not currently supported."));
+  return 1;
+}
+
+/* Called by the Python interpreter to obtain string representation
+   of the object.  */
+static PyObject *
+valpy_str (PyObject *self)
+{
+  char *s = NULL;
+  long dummy;
+  struct ui_file *stb;
+  struct cleanup *old_chain;
+  PyObject *result;
+  volatile struct gdb_exception except;
+
+  stb = mem_fileopen ();
+  old_chain = make_cleanup_ui_file_delete (stb);
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      /* FIXME: use current_language?  */
+      common_val_print (((value_object *) self)->value, stb, 0, 0, 0,
+			Val_pretty_default, current_language);
+      s = ui_file_xstrdup (stb, &dummy);
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  do_cleanups (old_chain);
+
+  result = PyUnicode_Decode (s, strlen (s), host_charset (), NULL);
+  xfree (s);
+
+  return result;
+}
+
+enum valpy_opcode
+{
+  VALPY_ADD,
+  VALPY_SUB,
+  VALPY_MUL,
+  VALPY_DIV,
+  VALPY_REM,
+  VALPY_POW
+};
+
+/* Returns a value object which is the sum of this value with the given
+   integer argument.  */
+static PyObject *
+valpy_binop (enum valpy_opcode opcode, PyObject *self, PyObject *other)
+{
+  long l;
+  double d;
+  struct value *res_val = NULL;	  /* Initialize to appease gcc warning.  */
+  struct value *other_val;
+  value_object *self_value;
+  volatile struct gdb_exception except;
+
+  /* If the gdb.Value object is the second operand, then it will be passed
+     to us as the OTHER argument, and SELF will be an entirely different
+     kind of object, altogether.  Swap them to avoid surprises.  */
+  if (!PyObject_TypeCheck (self, &value_object_type))
+    {
+      PyObject *tmp;
+
+      tmp = self;
+      self = other;
+      other = tmp;
+    }
+
+  self_value = (value_object *) self;
+
+  if (PyObject_TypeCheck (other, &value_object_type))
+    other_val = ((value_object *) other)->value;
+  else if (PyInt_Check (other))
+    {
+      l = PyInt_AsLong (other);
+      if (PyErr_Occurred ())
+	return Py_NotImplemented;
+
+      other_val = value_from_longest (builtin_type_int, l);
+    }
+  else if (PyFloat_Check (other))
+    {
+      d = PyFloat_AsDouble (other);
+      if (PyErr_Occurred ())
+	return Py_NotImplemented;
+
+      other_val = value_from_double (builtin_type_double, d);
+    }
+  else
+    /* If the types cannot be added, Python documentation says to return
+       NotImplemented (http://docs.python.org/ref/numeric-types.html).  */
+    return Py_NotImplemented;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      switch (opcode)
+	{
+	case VALPY_ADD:
+	  res_val = value_add (self_value->value, other_val);
+	  break;
+	case VALPY_SUB:
+	  res_val = value_sub (self_value->value, other_val);
+	  break;
+	case VALPY_MUL:
+	  res_val = value_binop (self_value->value, other_val, BINOP_MUL);
+	  break;
+	case VALPY_DIV:
+	  res_val = value_binop (self_value->value, other_val, BINOP_DIV);
+	  break;
+	case VALPY_REM:
+	  res_val = value_binop (self_value->value, other_val, BINOP_REM);
+	  break;
+	case VALPY_POW:
+	  res_val = value_binop (self_value->value, other_val, BINOP_EXP);
+	  break;
+	}
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return value_to_value_object (res_val);
+}
+
+static PyObject *
+valpy_add (PyObject *self, PyObject *other)
+{
+  return valpy_binop (VALPY_ADD, self, other);
+}
+
+static PyObject *
+valpy_subtract (PyObject *self, PyObject *other)
+{
+  return valpy_binop (VALPY_SUB, self, other);
+}
+
+static PyObject *
+valpy_multiply (PyObject *self, PyObject *other)
+{
+  return valpy_binop (VALPY_MUL, self, other);
+}
+
+static PyObject *
+valpy_divide (PyObject *self, PyObject *other)
+{
+  return valpy_binop (VALPY_DIV, self, other);
+}
+
+static PyObject *
+valpy_remainder (PyObject *self, PyObject *other)
+{
+  return valpy_binop (VALPY_REM, self, other);
+}
+
+static PyObject *
+valpy_power (PyObject *self, PyObject *other, PyObject *unused)
+{
+  /* We don't support the ternary form of pow.  I don't know how to express
+     that, so let's just throw NotImplementedError to at least do something
+     about it.  */
+  if (unused != Py_None)
+    {
+      PyErr_SetString (PyExc_NotImplementedError,
+		       "Invalid operation on gdb.Value.");
+      return NULL;
+    }
+
+  return valpy_binop (VALPY_POW, self, other);
+}
+
+static PyObject *
+valpy_negative (PyObject *self)
+{
+  struct value *val = NULL;
+  volatile struct gdb_exception except;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      val = value_neg (((value_object *) self)->value);
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return value_to_value_object (val);
+}
+
+static PyObject *
+valpy_positive (PyObject *self)
+{
+  struct value *copy = value_copy (((value_object *) self)->value);
+
+  return value_to_value_object (copy);
+}
+
+static PyObject *
+valpy_absolute (PyObject *self)
+{
+  if (value_less (((value_object *) self)->value,
+		  value_from_longest (builtin_type_int, 0)))
+    return valpy_negative (self);
+  else
+    return valpy_positive (self);
+}
+
+/* Implements boolean evaluation of gdb.Value.  */
+static int
+valpy_nonzero (PyObject *self)
+{
+  value_object *self_value = (value_object *) self;
+  struct type *type;
+
+  type = check_typedef (value_type (self_value->value));
+
+  if (is_integral_type (type))
+    return !!value_as_long (self_value->value);
+  else if (TYPE_CODE (type) == TYPE_CODE_FLT)
+    return value_as_double (self_value->value) != 0;
+  else if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
+    return !decimal_is_zero (value_contents (self_value->value),
+			     TYPE_LENGTH (type));
+  else
+    {
+      PyErr_SetString (PyExc_TypeError, _("Attempted truth testing on invalid"
+					  "gdb.Value type."));
+      return 0;
+    }
+}
+
+/* Implements comparison operations for value objects.  */
+static PyObject *
+valpy_richcompare (PyObject *self, PyObject *other, int op)
+{
+  int result = 0;
+  struct value *value_self, *value_other;
+  volatile struct gdb_exception except;
+
+  if (PyObject_TypeCheck (other, &value_object_type))
+    value_other = ((value_object *) other)->value;
+  else if (PyInt_Check (other))
+    {
+      LONGEST l;
+
+      l = PyInt_AsLong (other);
+      if (PyErr_Occurred ())
+	return NULL;
+
+      value_other = value_from_longest (builtin_type_int, l);
+    }
+  else if (PyFloat_Check (other))
+    {
+      DOUBLEST d;
+
+      d = PyFloat_AsDouble (other);
+      if (PyErr_Occurred ())
+	return NULL;
+
+      value_other = value_from_double (builtin_type_double, d);
+    }
+  else if (PyString_Check (other) || PyUnicode_Check (other))
+    {
+      char *str;
+
+      str = python_string_to_target_string (other);
+      value_other = value_from_string (str);
+      xfree (str);
+    }
+  else if (other == Py_None)
+    /* Comparing with None is special.  From what I can tell, in Python
+       None is smaller than anything else.  */
+    switch (op) {
+      case Py_LT:
+      case Py_LE:
+      case Py_EQ:
+	Py_RETURN_FALSE;
+      case Py_NE:
+      case Py_GT:
+      case Py_GE:
+	Py_RETURN_TRUE;
+      default:
+	/* Can't happen.  */
+	PyErr_SetString (PyExc_NotImplementedError,
+			 "Invalid operation on gdb.Value.");
+	return NULL;
+    }
+  else
+    {
+      PyErr_SetString (PyExc_NotImplementedError,
+		       "Operation not supported on gdb.Value of this type.");
+      return NULL;
+    }
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      switch (op) {
+        case Py_LT:
+	  result = value_less (((value_object *) self)->value, value_other);
+	  break;
+	case Py_LE:
+	  result = value_less (((value_object *) self)->value, value_other)
+	    || value_equal (((value_object *) self)->value, value_other);
+	  break;
+	case Py_EQ:
+	  result = value_equal (((value_object *) self)->value, value_other);
+	  break;
+	case Py_NE:
+	  result = !value_equal (((value_object *) self)->value, value_other);
+	  break;
+        case Py_GT:
+	  result = value_less (value_other, ((value_object *) self)->value);
+	  break;
+	case Py_GE:
+	  result = value_less (value_other, ((value_object *) self)->value)
+	    || value_equal (((value_object *) self)->value, value_other);
+	  break;
+	default:
+	  /* Can't happen.  */
+	  PyErr_SetString (PyExc_NotImplementedError,
+			   "Invalid operation on gdb.Value.");
+	  return NULL;
+      }
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  if (result == 1)
+    Py_RETURN_TRUE;
+
+  Py_RETURN_FALSE;
+}
+
+/* Returns an object for a value which is released from the all_values chain,
+   so its lifetime is not bound to the execution of a command.  */
+PyObject *
+value_to_value_object (struct value *val)
+{
+  value_object *val_obj;
+
+  val_obj = PyObject_New (value_object, &value_object_type);
+  if (val_obj != NULL)
+    {
+      val_obj->value = val;
+      release_value (val);
+      value_prepend_to_list (&values_in_python, val);
+    }
+
+  return (PyObject *) val_obj;
+}
+
+/* Try to convert a Python value to a gdb value.  If the value cannot
+   be converted, throw a gdb exception.  */
+
+struct value *
+convert_value_from_python (PyObject *obj)
+{
+  struct value *value = NULL; /* -Wall */
+  PyObject *target_str, *unicode_str;
+  struct cleanup *old;
+
+  if (! obj)
+    error (_("Internal error while converting Python value."));
+
+  if (PyBool_Check (obj))
+    value = value_from_longest (builtin_type_bool, obj == Py_True);
+  else if (PyInt_Check (obj))
+    value = value_from_longest (builtin_type_int, PyInt_AsLong (obj));
+  else if (PyLong_Check (obj))
+    {
+      LONGEST l = PyLong_AsLongLong (obj);
+      if (! PyErr_Occurred ())
+	value = value_from_longest (builtin_type_long, l);
+    }
+  else if (PyFloat_Check (obj))
+    {
+      double d = PyFloat_AsDouble (obj);
+      if (! PyErr_Occurred ())
+	value = value_from_double (builtin_type_double, d);
+    }
+  else if (PyString_Check (obj) || PyUnicode_Check (obj))
+    {
+      char *s;
+
+      s = python_string_to_target_string (obj);
+      if (s == NULL)
+	return NULL;
+
+      old = make_cleanup (xfree, s);
+      value = value_from_string (s);
+      do_cleanups (old);
+    }
+  else if (PyObject_TypeCheck (obj, &value_object_type))
+    value = ((value_object *) obj)->value;
+  else
+    error (_("Could not convert Python object: %s"),
+	   PyString_AsString (PyObject_Str (obj)));
+
+  if (PyErr_Occurred ())
+    error (_("Error converting Python value."));
+
+  return value;
+}
+
+/* Returns value object in the ARGth position in GDB's history.  */
+PyObject *
+gdbpy_get_value_from_history (PyObject *self, PyObject *args)
+{
+  int i;
+  struct value *res_val = NULL;	  /* Initialize to appease gcc warning.  */
+  volatile struct gdb_exception except;
+
+  if (!PyArg_ParseTuple (args, "i", &i))
+    return NULL;
+
+  TRY_CATCH (except, RETURN_MASK_ALL)
+    {
+      res_val = access_value_history (i);
+    }
+  GDB_PY_HANDLE_EXCEPTION (except);
+
+  return value_to_value_object (res_val);
+}
+
+void
+gdbpy_initialize_values (void)
+{
+  value_object_type.tp_new = valpy_new;
+  if (PyType_Ready (&value_object_type) < 0)
+    return;
+
+  Py_INCREF (&value_object_type);
+  PyModule_AddObject (gdb_module, "Value", (PyObject *) &value_object_type);
+
+  values_in_python = NULL;
+}
+
+#endif /* HAVE_PYTHON */
Index: gdb.git/gdb/python/python.c
===================================================================
--- gdb.git.orig/gdb/python/python.c	2008-09-12 02:24:28.000000000 -0300
+++ gdb.git/gdb/python/python.c	2008-09-12 02:26:42.000000000 -0300
@@ -52,6 +52,8 @@ static PyObject *gdbpy_flush (PyObject *
 
 static PyMethodDef GdbMethods[] =
 {
+  { "get_value_from_history", gdbpy_get_value_from_history, METH_VARARGS,
+    "Get a value from history" },
   { "execute", execute_gdb_command, METH_VARARGS,
     "Execute a gdb command" },
   { "get_parameter", get_parameter, METH_VARARGS,
@@ -398,6 +400,8 @@ Enables or disables printing of Python s
   PyModule_AddStringConstant (gdb_module, "HOST_CONFIG", (char*) host_name);
   PyModule_AddStringConstant (gdb_module, "TARGET_CONFIG", (char*) target_name);
 
+  gdbpy_initialize_values ();
+
   PyRun_SimpleString ("import gdb");
 
   /* Create a couple objects which are used for Python's stdout and
Index: gdb.git/gdb/python/python.h
===================================================================
--- gdb.git.orig/gdb/python/python.h	2008-08-07 01:22:14.000000000 -0300
+++ gdb.git/gdb/python/python.h	2008-09-12 02:26:42.000000000 -0300
@@ -22,6 +22,8 @@
 
 #include "value.h"
 
+extern struct value *values_in_python;
+
 void eval_python_from_control_command (struct command_line *);
 
 #endif /* GDB_PYTHON_H */
Index: gdb.git/gdb/testsuite/gdb.python/python-values.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb.git/gdb/testsuite/gdb.python/python-values.c	2008-09-12 02:26:42.000000000 -0300
@@ -0,0 +1,41 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2008 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/>.  */
+
+struct s
+{
+  int a;
+  int b;
+};
+
+union u
+{
+  int a;
+  float b;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct s s;
+  union u u;
+
+  s.a = 3;
+  s.b = 5;
+  u.a = 7;
+
+  return 0;      /* break to inspect struct and union */
+}
Index: gdb.git/gdb/testsuite/gdb.python/python-values.exp
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ gdb.git/gdb/testsuite/gdb.python/python-values.exp	2008-09-12 02:26:42.000000000 -0300
@@ -0,0 +1,278 @@
+# Copyright (C) 2008 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 the mechanism
+# exposing values to Python.
+
+if $tracelevel then {
+    strace $tracelevel
+}
+
+set testfile "python-values"
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    untested "Couldn't compile ${srcfile}"
+    return -1
+}
+
+# Usage: gdb_py_test_multiple NAME INPUT RESULT {INPUT RESULT}...
+# Run a test named NAME, consisting of multiple lines of input.
+# After each input line INPUT, search for result line RESULT.
+# Succeed if all results are seen; fail otherwise.
+proc gdb_py_test_multiple {name args} {
+    global gdb_prompt
+    foreach {input result} $args {
+	if {[gdb_test_multiple $input "$name - $input" {
+	    -re "\[\r\n\]*($result)\[\r\n\]+($gdb_prompt | *>)$" {
+		pass "$name - $input"
+	    }
+	}]} {
+	    return 1
+	}
+    }
+    return 0
+}
+
+proc test_value_creation {} {
+  global gdb_prompt
+
+  gdb_test_multiple "python i = gdb.Value (True)" "create boolean value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create boolean value"}
+      -re "$gdb_prompt $"	      {pass "create boolean value"}
+  }
+
+  gdb_test_multiple "python i = gdb.Value (5)" "create integer value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create integer value"}
+      -re "$gdb_prompt $"	      {pass "create integer value"}
+  }
+
+  gdb_test_multiple "python i = gdb.Value (5L)" "create long value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create long value"}
+      -re "$gdb_prompt $"	      {pass "create long value"}
+  }
+
+  gdb_test_multiple "python f = gdb.Value (1.25)" "create double value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create double value"}
+      -re "$gdb_prompt $"	      {pass "create double value"}
+  }
+
+  gdb_test_multiple "python a = gdb.Value ('string test')" "create 8-bit string value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create 8-bit string value"}
+      -re "$gdb_prompt $"	      {pass "create 8-bit string value"}
+  }
+
+  gdb_test "python print a" "\"string test\"" "print 8-bit string"
+  gdb_test "python print a.__class__" "<type 'gdb.Value'>" "verify type of 8-bit string"
+
+  gdb_test_multiple "python a = gdb.Value (u'unicode test')" "create unicode value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create unicode value"}
+      -re "$gdb_prompt $"	      {pass "create unicode value"}
+  }
+
+  gdb_test "python print a" "\"unicode test\"" "print Unicode string"
+  gdb_test "python print a.__class__" "<type 'gdb.Value'>" "verify type of unicode string"
+}
+
+proc test_value_numeric_ops {} {
+  global gdb_prompt
+
+  gdb_test_multiple "python i = gdb.Value (5)" "create first integer value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create first integer value"}
+      -re "$gdb_prompt $"	      {}
+  }
+
+  gdb_test_multiple "python j = gdb.Value (2)" "create second integer value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create second integer value"}
+      -re "$gdb_prompt $"	      {}
+  }
+
+  gdb_test_multiple "python f = gdb.Value (1.25)" "create first double value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create first double value"}
+      -re "$gdb_prompt $"	      {}
+  }
+
+  gdb_test_multiple "python g = gdb.Value (2.5)" "create second double value" {
+      -re "Traceback.*$gdb_prompt $"  {fail "create second double value"}
+      -re "$gdb_prompt $"	      {}
+  }
+
+  gdb_test "python print 'result = ' + str(i+j)" " = 7" "add two integer values"
+  gdb_test "python print (i+j).__class__" "<type 'gdb.Value'>" "verify type of integer add result"
+
+  gdb_test "python print 'result = ' + str(f+g)" " = 3.75" "add two double values"
+  gdb_test "python print 'result = ' + str(i-j)" " = 3" "subtract two integer values"
+  gdb_test "python print 'result = ' + str(f-g)" " = -1.25" "subtract two double values"
+  gdb_test "python print 'result = ' + str(i*j)" " = 10" "multiply two integer values"
+  gdb_test "python print 'result = ' + str(f*g)" " = 3.125" "multiply two double values"
+  gdb_test "python print 'result = ' + str(i/j)" " = 2" "divide two integer values"
+  gdb_test "python print 'result = ' + str(f/g)" " = 0.5" "divide two double values"
+  gdb_test "python print 'result = ' + str(i%j)" " = 1" "take remainder of two integer values"
+  # Remainder of float is implemented in Python but not in GDB's value system.
+
+  gdb_test "python print 'result = ' + str(i**j)" " = 25" "integer value raised to the power of another integer value"
+  gdb_test "python print 'result = ' + str(g**j)" " = 6.25" "double value raised to the power of integer value"
+
+  gdb_test "python print 'result = ' + str(-i)" " = -5" "negated integer value"
+  gdb_test "python print 'result = ' + str(+i)" " = 5" "positive integer value"
+  gdb_test "python print 'result = ' + str(-f)" " = -1.25" "negated double value"
+  gdb_test "python print 'result = ' + str(+f)" " = 1.25" "positive double value"
+  gdb_test "python print 'result = ' + str(abs(j-i))" " = 3" "absolute of  integer value"
+  gdb_test "python print 'result = ' + str(abs(f-g))" " = 1.25" "absolute of double value"
+
+  # Test gdb.Value mixed with Python types.
+
+  gdb_test "python print 'result = ' + str(i+1)" " = 6" "add integer value with python integer"
+  gdb_test "python print (i+1).__class__" "<type 'gdb.Value'>" "verify type of mixed integer add result"
+  gdb_test "python print 'result = ' + str(f+1.5)" " = 2.75" "add double value with python float"
+
+  gdb_test "python print 'result = ' + str(1+i)" " = 6" "add python integer with integer value"
+  gdb_test "python print 'result = ' + str(1.5+f)" " = 2.75" "add python float with double value"
+
+  # Test some invalid operations.
+
+  gdb_test_multiple "python print 'result = ' + str(i+'foo')" "catch error in python type conversion" {
+      -re "unsupported operand type.*$gdb_prompt $"   {pass "catch error in python type conversion"}
+      -re "result = .*$gdb_prompt $"		      {fail "catch error in python type conversion"}
+      -re "$gdb_prompt $"			      {fail "catch error in python type conversion"}
+  }
+
+  gdb_test_multiple "python print 'result = ' + str(i+gdb.Value('foo'))" "catch throw of GDB error" {
+      -re "Traceback.*$gdb_prompt $"  {pass "catch throw of GDB error"}
+      -re "result = .*$gdb_prompt $"  {fail "catch throw of GDB error"}
+      -re "$gdb_prompt $"	      {fail "catch throw of GDB error"}
+  }
+}
+
+proc test_value_boolean {} {
+  # First, define a useful function to test booleans.
+  gdb_py_test_multiple "define function to test booleans" \
+    "python" "" \
+    "def test_bool (val):" "" \
+    "  if val:" "" \
+    "    print 'yay'" "" \
+    "  else:" "" \
+    "    print 'nay'" "" \
+    "end" ""
+
+  gdb_test "py test_bool (gdb.Value (True))" "yay" "check evaluation of true boolean value in expression"
+
+  gdb_test "py test_bool (gdb.Value (False))" "nay" "check evaluation of false boolean value in expression"
+
+  gdb_test "py test_bool (gdb.Value (5))" "yay" "check evaluation of true integer value in expression"
+
+  gdb_test "py test_bool (gdb.Value (0))" "nay" "check evaluation of false integer value in expression"
+
+  gdb_test "py test_bool (gdb.Value (5.2))" "yay" "check evaluation of true integer value in expression"
+
+  gdb_test "py test_bool (gdb.Value (0.0))" "nay" "check evaluation of false integer value in expression"
+}
+
+proc test_value_compare {} {
+  gdb_test "py print gdb.Value (1) < gdb.Value (1)" "False" "less than, equal"
+  gdb_test "py print gdb.Value (1) < gdb.Value (2)" "True" "less than, less"
+  gdb_test "py print gdb.Value (2) < gdb.Value (1)" "False" "less than, greater"
+  gdb_test "py print gdb.Value (2) < None" "False" "less than, None"
+
+  gdb_test "py print gdb.Value (1) <= gdb.Value (1)" "True" "less or equal, equal"
+  gdb_test "py print gdb.Value (1) <= gdb.Value (2)" "True" "less or equal, less"
+  gdb_test "py print gdb.Value (2) <= gdb.Value (1)" "False" "less or equal, greater"
+  gdb_test "py print gdb.Value (2) <= None" "False" "less or equal, None"
+
+  gdb_test "py print gdb.Value (1) == gdb.Value (1)" "True" "equality of gdb.Values"
+  gdb_test "py print gdb.Value (1) == gdb.Value (2)" "False" "inequality of gdb.Values"
+  gdb_test "py print gdb.Value (1) == 1.0" "True" "equality of gdb.Value with Python value"
+  gdb_test "py print gdb.Value (1) == 2" "False" "inequality of gdb.Value with Python value"
+  gdb_test "py print gdb.Value (1) == None" "False" "inequality of gdb.Value with None"
+
+  gdb_test "py print gdb.Value (1) != gdb.Value (1)" "False" "inequality, false"
+  gdb_test "py print gdb.Value (1) != gdb.Value (2)" "True" "inequality, true"
+  gdb_test "py print gdb.Value (1) != None" "True" "inequality, None"
+
+  gdb_test "py print gdb.Value (1) > gdb.Value (1)" "False" "greater than, equal"
+  gdb_test "py print gdb.Value (1) > gdb.Value (2)" "False" "greater than, less"
+  gdb_test "py print gdb.Value (2) > gdb.Value (1)" "True" "greater than, greater"
+  gdb_test "py print gdb.Value (2) > None" "True" "greater than, None"
+
+  gdb_test "py print gdb.Value (1) >= gdb.Value (1)" "True" "greater or equal, equal"
+  gdb_test "py print gdb.Value (1) >= gdb.Value (2)" "False" "greater or equal, less"
+  gdb_test "py print gdb.Value (2) >= gdb.Value (1)" "True" "greater or equal, greater"
+  gdb_test "py print gdb.Value (2) >= None" "True" "greater or equal, None"
+}
+
+proc test_value_in_inferior {} {
+  global gdb_prompt
+  global testfile
+
+  gdb_breakpoint [gdb_get_line_number "break to inspect struct and union"]
+  gdb_start_cmd
+
+  # Avoid race condition where a continue command in gdb_continue_to_breakpoint
+  # is issued too early.
+  gdb_test "" "$gdb_prompt"
+
+  gdb_continue_to_breakpoint "break to inspect struct and union"
+
+  # Just get inferior variable s in the value history, available to python.
+  gdb_test "print s" " = {a = 3, b = 5}" ""
+
+  gdb_test_multiple "python s = gdb.get_value_from_history (0)" "get value from history" {
+      -re "Traceback.*$gdb_prompt $"  {fail "get value from history"}
+      -re "$gdb_prompt $"	      {pass "get value from history"}
+  }
+
+  gdb_test "python print 'result = ' + str(s\['a'\])" " = 3" "acess element inside struct using 8-bit string name"
+  gdb_test "python print 'result = ' + str(s\[u'a'\])" " = 3" "acess element inside struct using unicode name"
+
+  # Test dereferencing the argv pointer
+
+  # Just get inferior variable argv the value history, available to python.
+  gdb_test "print argv" " = \\(char \\*\\*\\) 0x.*" ""
+
+  gdb_test_multiple "python argv = gdb.get_value_from_history (0)" "" {
+      -re "Traceback.*$gdb_prompt $"  {fail "get value from history"}
+      -re "$gdb_prompt $"	      {}
+  }
+
+  gdb_test_multiple "python arg0 = argv.dereference ()" "" {
+      -re "Traceback.*$gdb_prompt $"  {fail "dereference value"}
+      -re "$gdb_prompt $"	      {pass "dereference value"}
+  }
+
+  # Check that the dereferenced value is sane
+  gdb_test "python print arg0" "0x.*$testfile\"" "verify dereferenced value"
+}
+
+
+# Start with a fresh gdb.
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+gdb_test_multiple "python print 'hello, world!'" "verify python support" {
+    -re "not supported.*$gdb_prompt $"	{
+      unsupported "python support is disabled"
+      return -1
+    }
+    -re "$gdb_prompt $"	{}
+}
+
+test_value_creation
+test_value_numeric_ops
+test_value_boolean
+test_value_compare
+test_value_in_inferior
Index: gdb.git/gdb/value.c
===================================================================
--- gdb.git.orig/gdb/value.c	2008-09-12 02:24:28.000000000 -0300
+++ gdb.git/gdb/value.c	2008-09-12 02:26:42.000000000 -0300
@@ -37,6 +37,8 @@
 #include "dfp.h"
 #include "objfiles.h"
 
+#include "python/python.h"
+
 /* Prototypes for exported functions. */
 
 void _initialize_values (void);
@@ -130,8 +132,8 @@ struct value
 
   /* Values are stored in a chain, so that they can be deleted easily
      over calls to the inferior.  Values assigned to internal
-     variables or put into the value history are taken off this
-     list.  */
+     variables, put into the value history or exposed to Python are
+     taken off this list.  */
   struct value *next;
 
   /* Register number if the value is from a register.  */
@@ -257,6 +259,31 @@ allocate_repeat_value (struct type *type
 					    type, range_type));
 }
 
+/* Needed if another module needs to maintain its on list of values.  */
+void
+value_prepend_to_list (struct value **head, struct value *val)
+{
+  val->next = *head;
+  *head = val;
+}
+
+/* Needed if another module needs to maintain its on list of values.  */
+void
+value_remove_from_list (struct value **head, struct value *val)
+{
+  struct value *prev;
+
+  if (*head == val)
+    *head = (*head)->next;
+  else
+    for (prev = *head; prev->next; prev = prev->next)
+      if (prev->next == val)
+      {
+	prev->next = val->next;
+	break;
+      }
+}
+
 /* Accessor methods.  */
 
 struct value *
@@ -916,6 +943,7 @@ preserve_values (struct objfile *objfile
   htab_t copied_types;
   struct value_history_chunk *cur;
   struct internalvar *var;
+  struct value *val;
   int i;
 
   /* Create the hash table.  We allocate on the objfile's obstack, since
@@ -930,6 +958,9 @@ preserve_values (struct objfile *objfile
   for (var = internalvars; var; var = var->next)
     preserve_one_value (var->value, objfile, copied_types);
 
+  for (val = values_in_python; val; val = val->next)
+    preserve_one_value (val, objfile, copied_types);
+
   htab_delete (copied_types);
 }
 
Index: gdb.git/gdb/value.h
===================================================================
--- gdb.git.orig/gdb/value.h	2008-09-12 02:24:28.000000000 -0300
+++ gdb.git/gdb/value.h	2008-09-12 02:26:42.000000000 -0300
@@ -40,9 +40,15 @@ struct language_defn;
 
 struct value;
 
+/* Needed if another module needs to maintain its on list of values.  */
+
+void value_prepend_to_list (struct value **head, struct value *val);
+void value_remove_from_list (struct value **head, struct value *val);
+
 /* Values are stored in a chain, so that they can be deleted easily
-   over calls to the inferior.  Values assigned to internal variables
-   or put into the value history are taken off this list.  */
+   over calls to the inferior.  Values assigned to internal variables,
+   put into the value history or exposed to Python are taken off this
+   list.  */
 
 struct value *value_next (struct value *);
 



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]