[RFC] Debug Operators with GDB Python

Siva Chandra sivachandra@google.com
Thu Nov 29 21:44:00 GMT 2012


Hello,

We are proposing to add the ability to define (what I call as) 'Debug
Operators' using the GDB Python API. The idea behind this is that a
user should be able to define operators for the types in his program
using the GDB Python API. When the user uses these operators (for
example, at the GDB command line to evaluate expressions), GDB
performs the operations using the Python implementation of the
operators if they exist. For languages like C++, which can have
operators defined in the source, debug operators enable performing an
operation without running the inferior. In general, it can be a
convenient debug aid (think unary * for a smart pointer, or a
subscript [] operator for a container class).

The work is at a state wherein I have a patch as a proof of concept of
my design. We would like to get feedback on the idea as well as the
design before me move ahead further. I have the patch attached. Also
attached are a simple C++ program and a Python file with operator
definitions so that you can try the patch.

As I mentioned earlier, the patch is definitely not complete. What I
am looking for is a feedback on the way I am intercepting (in
value_x_binop and value_x_unop) to perform the operation using the
Python implementation of the operator. Dynamic types and inheritance
are handled on the Python side using the currently available API. One
problem I see with this approach is resolution of typedef equivalence
when looking for appropriate operator functions. If we have a class A
and its typedef TA, then using the Python API we can figure that TA is
same as A, but I do not at this point know of a way for the other way
round. Other approaches we have considered do not cater well to
template types.

The Changelog for the patch is as follows:

2012-11-29  Siva Chandra Reddy  <sivachandra@google.com>

        * Makefile.in: Add entries for new files py-operator.c/o.
        * valarith.c (value_x_binop, value_x_unop): Use Python operators
        if present.
        * data-directory/Makefile.in: Add entries for new files
        gdb/operator.py and gdb/command/debug-operator.py
        * python/py-operator.c: C side implementation of the debug
        operators support.
        * python/python-internal.h: Add new declarations.
        * python/python.h: Add new declarations.
        * python/python.c: Add initializations for the debug operator
        support.
        * python/lib/gdb/operator.py: Python side implementation of the
        support for debug operators.
        * python/lib/gdb/command/debug-operator.py: Implementation of
        the command 'enable debug-operators'.

Thanks,
Siva Chandra
-------------- next part --------------
Index: Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/Makefile.in,v
retrieving revision 1.1218
diff -u -p -r1.1218 Makefile.in
--- Makefile.in	26 Nov 2012 19:23:50 -0000	1.1218
+++ Makefile.in	28 Nov 2012 14:04:16 -0000
@@ -289,6 +289,7 @@ SUBDIR_PYTHON_OBS = \
 	py-lazy-string.o \
 	py-newobjfileevent.o \
 	py-objfile.o \
+	py-operator.o \
 	py-param.o \
 	py-prettyprint.o \
 	py-progspace.o \
@@ -322,6 +323,7 @@ SUBDIR_PYTHON_SRCS = \
 	python/py-lazy-string.c \
 	python/py-newobjfileevent.c \
 	python/py-objfile.c \
+	python/py-operator.c \
 	python/py-param.c \
 	python/py-prettyprint.c \
 	python/py-progspace.c \
@@ -2113,6 +2115,10 @@ py-objfile.o: $(srcdir)/python/py-objfil
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-objfile.c
 	$(POSTCOMPILE)
 
+py-operator.o: $(srcdir)/python/py-operator.c
+	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-operator.c
+	$(POSTCOMPILE)
+
 py-param.o: $(srcdir)/python/py-param.c
 	$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-param.c
 	$(POSTCOMPILE)
Index: valarith.c
===================================================================
RCS file: /cvs/src/src/gdb/valarith.c,v
retrieving revision 1.106
diff -u -p -r1.106 valarith.c
--- valarith.c	14 Sep 2012 12:46:55 -0000	1.106
+++ valarith.c	28 Nov 2012 14:04:16 -0000
@@ -31,6 +31,7 @@
 #include <math.h>
 #include "infcall.h"
 #include "exceptions.h"
+#include "python/python.h"
 
 /* Define whether or not the C operator '/' truncates towards zero for
    differently signed operands (truncation direction is undefined in C).  */
@@ -353,6 +354,14 @@ value_x_binop (struct value *arg1, struc
   arg1 = coerce_ref (arg1);
   arg2 = coerce_ref (arg2);
 
+  if (py_use_debug_operators ())
+    {
+      struct value *result = py_perform_binop (arg1, arg2, op);
+
+      if (result)
+        return result;
+    }
+
   /* now we know that what we have to do is construct our
      arg vector and find the right function to call it with.  */
 
@@ -517,6 +526,14 @@ value_x_unop (struct value *arg1, enum e
 
   arg1 = coerce_ref (arg1);
 
+  if (py_use_debug_operators ())
+    {
+      struct value *result = py_perform_unop (arg1, op);
+
+      if (result)
+        return result;
+    }
+
   /* now we know that what we have to do is construct our
      arg vector and find the right function to call it with.  */
 
Index: data-directory/Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/data-directory/Makefile.in,v
retrieving revision 1.13
diff -u -p -r1.13 Makefile.in
--- data-directory/Makefile.in	12 Nov 2012 17:41:56 -0000	1.13
+++ data-directory/Makefile.in	28 Nov 2012 14:04:16 -0000
@@ -56,11 +56,13 @@ PYTHON_FILES = \
 	gdb/types.py \
 	gdb/printing.py \
 	gdb/prompt.py \
+	gdb/operator.py \
 	gdb/command/__init__.py \
 	gdb/command/type_printers.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/explore.py \
+	gdb/command/debug-operator.py \
 	gdb/function/__init__.py \
 	gdb/function/strfns.py
 
Index: python/py-operator.c
===================================================================
RCS file: python/py-operator.c
diff -N python/py-operator.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ python/py-operator.c	28 Nov 2012 14:04:16 -0000
@@ -0,0 +1,201 @@
+/* Implementation of the support for Python debug operators.
+
+   Copyright (C) 2012 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 "symtab.h"
+#include "language.h"
+#include "python-internal.h"
+#include "python.h"
+#include "expression.h"
+
+static PyObject *binop_executor = NULL;
+static PyObject *unop_executor = NULL;
+
+#define OPCODE(code) { code, #code }
+
+struct py_opcode_desc
+{
+  enum exp_opcode opcode;
+  const char *name;
+};
+
+static struct py_opcode_desc py_opcodes[] =
+{
+  OPCODE (BINOP_ADD),                        /* + */
+  OPCODE (BINOP_SUB),                        /* - */
+  OPCODE (BINOP_MUL),                        /* * */
+  OPCODE (BINOP_DIV),                        /* / */
+  OPCODE (BINOP_REM),                        /* % */
+  OPCODE (BINOP_MOD),                        /* mod (Knuth 1.2.4) */
+  OPCODE (BINOP_LSH),                        /* << */
+  OPCODE (BINOP_RSH),                        /* >> */
+  OPCODE (BINOP_LOGICAL_AND),                /* && */
+  OPCODE (BINOP_LOGICAL_OR),                 /* || */
+  OPCODE (BINOP_BITWISE_AND),                /* & */
+  OPCODE (BINOP_BITWISE_IOR),                /* | */
+  OPCODE (BINOP_BITWISE_XOR),                /* ^ */
+  OPCODE (BINOP_EQUAL),                      /* == */
+  OPCODE (BINOP_NOTEQUAL),                   /* != */
+  OPCODE (BINOP_LESS),                       /* < */
+  OPCODE (BINOP_GTR),                        /* > */
+  OPCODE (BINOP_LEQ),                        /* <= */
+  OPCODE (BINOP_GEQ),                        /* >= */
+  OPCODE (BINOP_REPEAT),                     /* @ */
+  OPCODE (BINOP_ASSIGN),                     /* = */
+  OPCODE (BINOP_COMMA),                      /* , */
+  OPCODE (BINOP_SUBSCRIPT),                  /* x[y] */
+  OPCODE (BINOP_EXP),                        /* Exponentiation */
+
+  OPCODE (UNOP_NEG),                         /* Unary - */
+  OPCODE (UNOP_LOGICAL_NOT),                 /* Unary ! */
+  OPCODE (UNOP_COMPLEMENT),                  /* Unary ~ */
+  OPCODE (UNOP_IND),                         /* Unary * */
+  OPCODE (UNOP_ADDR),                        /* Unary & */
+  OPCODE (UNOP_PREINCREMENT),                /* ++ before an expression */
+  OPCODE (UNOP_POSTINCREMENT),               /* ++ after an expression */
+  OPCODE (UNOP_PREDECREMENT),                /* -- before an expression */
+  OPCODE (UNOP_POSTDECREMENT),               /* -- after an expression */
+
+  { OP_NULL, NULL }                          /* Delimiter */
+};
+
+PyObject *
+gdbpy_enable_debug_operators (PyObject *self, PyObject *args)
+{
+  PyObject *result = NULL;
+  PyObject *temp1, *temp2;
+
+  if (PyArg_ParseTuple (args, "OO", &temp1, &temp2))
+    {
+      if (!PyCallable_Check (temp1) || !PyCallable_Check (temp2))
+        {
+          printf_filtered ("ERROR: Unable to use debug operators!\n");
+          return NULL;
+        }
+
+        Py_XINCREF (temp1);
+        Py_XINCREF (temp2);
+
+        Py_XDECREF (binop_executor);
+        Py_XDECREF (unop_executor);
+
+        binop_executor = temp1;
+        unop_executor = temp2;
+
+        Py_INCREF (Py_None);
+        result = Py_None;
+    }
+
+   return result;
+}
+
+struct value *
+py_perform_binop (struct value *opr1, struct value *opr2, enum exp_opcode op)
+{
+  struct value *res_value = NULL;
+  PyObject *opr1_obj = value_to_value_object (opr1);
+  PyObject *opr2_obj = value_to_value_object (opr2);
+  PyObject *res_obj, *arg_tuple;
+  struct cleanup *cleanup;
+
+  cleanup = ensure_python_env (get_current_arch (), current_language);
+
+  arg_tuple = Py_BuildValue ("(iOO)", op, opr1_obj, opr2_obj);
+  if (!arg_tuple)
+    {
+      printf_filtered ("ERROR: Python error internal to GDB.\n");
+      Py_XDECREF (opr1_obj);
+      Py_XDECREF (opr2_obj);
+      do_cleanups (cleanup);
+      return NULL;
+    }
+
+  res_obj = PyObject_Call (binop_executor, arg_tuple, NULL);
+
+  Py_DECREF (arg_tuple);
+  Py_XDECREF (opr1_obj);
+  Py_XDECREF (opr2_obj);
+
+  if (!PyObject_TypeCheck (res_obj, Py_None->ob_type))
+    res_value = convert_value_from_python (res_obj);
+
+  Py_DECREF (res_obj);
+
+  do_cleanups (cleanup);
+  return res_value;
+}
+
+struct value *
+py_perform_unop (struct value *opr, enum exp_opcode op)
+{
+  struct value *res_value = NULL;
+  PyObject *opr_obj = value_to_value_object (opr);
+  PyObject *res_obj, *arg_tuple;
+  struct cleanup *cleanup;
+
+  cleanup = ensure_python_env (get_current_arch (), current_language);
+
+  arg_tuple = Py_BuildValue ("(iO)", op, opr_obj);
+  if (!arg_tuple)
+    {
+      printf_filtered ("ERROR: Python error internal to GDB.\n");
+      Py_XDECREF (opr_obj);
+      do_cleanups (cleanup);
+      return NULL;
+    }
+
+  res_obj = PyObject_Call (unop_executor, arg_tuple, NULL);
+
+  Py_DECREF (arg_tuple);
+  Py_XDECREF (opr_obj);
+
+  if (!PyObject_TypeCheck (res_obj, Py_None->ob_type))
+    res_value = convert_value_from_python (res_obj);
+
+  Py_DECREF (res_obj);
+
+  do_cleanups (cleanup);
+  return res_value;
+}
+
+void
+gdbpy_initialize_debug_operators (void)
+{
+  int i;
+
+  for (i = 0; py_opcodes[i].opcode != OP_NULL; ++i)
+    {
+      if (PyModule_AddIntConstant (gdb_module,
+				   /* Cast needed for Python 2.4.  */
+				   (char *) py_opcodes[i].name,
+				   py_opcodes[i].opcode) < 0)
+	return;
+    }
+}
+
+int
+py_use_debug_operators (void)
+{
+  if (binop_executor)
+    return 1;
+  else
+    return 0;
+}
Index: python/python-internal.h
===================================================================
RCS file: /cvs/src/src/gdb/python/python-internal.h,v
retrieving revision 1.59
diff -u -p -r1.59 python-internal.h
--- python/python-internal.h	13 Sep 2012 21:49:31 -0000	1.59
+++ python/python-internal.h	28 Nov 2012 14:04:16 -0000
@@ -203,6 +203,7 @@ PyObject *gdbpy_parameter_value (enum va
 char *gdbpy_parse_command_name (const char *name,
 				struct cmd_list_element ***base_list,
 				struct cmd_list_element **start_list);
+PyObject *gdbpy_enable_debug_operators (PyObject *self, PyObject *args);
 
 PyObject *symtab_and_line_to_sal_object (struct symtab_and_line sal);
 PyObject *symtab_to_symtab_object (struct symtab *symtab);
@@ -262,6 +263,7 @@ void gdbpy_initialize_continue_event (vo
 void gdbpy_initialize_exited_event (void);
 void gdbpy_initialize_thread_event (void);
 void gdbpy_initialize_new_objfile_event (void);
+void gdbpy_initialize_debug_operators (void);
 
 struct cleanup *make_cleanup_py_decref (PyObject *py);
 
Index: python/python.c
===================================================================
RCS file: /cvs/src/src/gdb/python/python.c,v
retrieving revision 1.101
diff -u -p -r1.101 python.c
--- python/python.c	12 Nov 2012 19:24:14 -0000	1.101
+++ python/python.c	28 Nov 2012 14:04:16 -0000
@@ -1571,6 +1571,8 @@ message == an error message without a st
   gdbpy_initialize_thread_event ();
   gdbpy_initialize_new_objfile_event () ;
 
+  gdbpy_initialize_debug_operators ();
+
   observer_attach_before_prompt (before_prompt_hook);
 
   gdbpy_to_string_cst = PyString_FromString ("to_string");
@@ -1729,6 +1731,10 @@ The first element contains any unparsed 
 (or None if the string was fully parsed).  The second element contains\n\
 a tuple that contains all the locations that match, represented as\n\
 gdb.Symtab_and_line objects (or None)."},
+  { "enable_debug_operators", gdbpy_enable_debug_operators, METH_VARARGS,
+    "enable_debug_operators (Function, Function).  Enables debug oeprators\n\
+implemented in python registering a binary operator executor and a unary\n\
+operator executor.  NOTE: This is an internal function!"},
   { "parse_and_eval", gdbpy_parse_and_eval, METH_VARARGS,
     "parse_and_eval (String) -> Value.\n\
 Parse String as an expression, evaluate it, and return the result as a Value."
Index: python/python.h
===================================================================
RCS file: /cvs/src/src/gdb/python/python.h,v
retrieving revision 1.20
diff -u -p -r1.20 python.h
--- python/python.h	12 Nov 2012 17:41:57 -0000	1.20
+++ python/python.h	28 Nov 2012 14:04:16 -0000
@@ -49,6 +49,13 @@ int gdbpy_should_stop (struct breakpoint
 
 int gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj);
 
+int py_use_debug_operators (void);
+
+struct value *py_perform_binop (struct value *opr1, struct value *opr2,
+                                enum exp_opcode op);
+
+struct value *py_perform_unop (struct value *opr, enum exp_opcode opcode);
+
 void *start_type_printers (void);
 
 char *apply_type_printers (void *, struct type *type);
Index: python/lib/gdb/operator.py
===================================================================
RCS file: python/lib/gdb/operator.py
diff -N python/lib/gdb/operator.py
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ python/lib/gdb/operator.py	28 Nov 2012 14:04:16 -0000
@@ -0,0 +1,101 @@
+# Support for debug operators in GDB.
+# Copyright (C) 2012 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/>.
+
+"""Debug operator API."""
+
+import gdb
+import re
+
+BINARY_OPCODE_TO_OPERATOR_MAP = { }
+UNARY_OPCODE_TO_OPERATOR_MAP = { }
+
+def register_binop(opcode, opr1_matcher, opr2_matcher, function):
+    if opcode in BINARY_OPCODE_TO_OPERATOR_MAP:
+        BINARY_OPCODE_TO_OPERATOR_MAP[opcode].append((opr1_matcher,
+                                                      opr2_matcher,
+                                                      function))
+    else:
+        BINARY_OPCODE_TO_OPERATOR_MAP[opcode] = [(opr1_matcher,
+                                                  opr2_matcher,
+                                                  function)]
+
+def register_unop(opcode, opr_matcher, function):
+    if opcode in UNARY_OPCODE_TO_OPERATOR_MAP:
+        UNARY_OPCODE_TO_OPERATOR_MAP[opcode].append((opr_matcher,
+                                                     function))
+    else:
+        UNARY_OPCODE_TO_OPERATOR_MAP[opcode] = [(opr_matcher,
+                                                 function)]
+
+def _get_dynamic_value(opr):
+    if opr.type != opr.dynamic_type:
+        return opr.cast(opr.dynamic_type)
+    else:
+        return opr
+
+def _get_matching_binop_function(opr1_type, opr2_type, opcode_matcher_list):
+    for opr1_matcher, opr2_matcher, function in opcode_matcher_list:
+        if (re.match(opr1_matcher, str(opr1_type)) and
+            re.match(opr2_matcher, str(opr2_type))):
+            return function
+
+def _def_matching_unop_function(opr_type, opcode_matcher_list):
+    for opr_matcher, function in opcode_matcher_list:
+        if re.match(opr_matcher, str(opr_type)):
+            return function
+
+def _get_is_a_types(gdbtype):
+    is_a_types = [gdbtype]
+    typedefee = gdbtype.strip_typedefs()
+    if str(typedefee) != str(gdbtype):
+        is_a_types.append(typedefee)
+    if typedefee.code != gdb.TYPE_CODE_STRUCT:
+        return is_a_types
+    for field in typedefee.fields():
+        if field.is_base_class:
+            is_a_types.extend(_get_is_a_types(field.type))
+    return is_a_types
+
+def _execute_binop(opcode, opr1, opr2):
+    opr1 = _get_dynamic_value(opr1) 
+    opr2 = _get_dynamic_value(opr2) 
+    opcode_matcher_list = BINARY_OPCODE_TO_OPERATOR_MAP.get(opcode)
+    if not opcode_matcher_list:
+        return None
+    opr1_is_a_list = _get_is_a_types(opr1.type)
+    opr2_is_a_list = _get_is_a_types(opr2.type)
+    for opr1_type in opr1_is_a_list:
+        for opr2_type in opr2_is_a_list:
+            op_function = _get_matching_binop_function(opr1_type, opr2_type,
+                                                       opcode_matcher_list)
+            if op_function:
+                return op_function(opr1.cast(opr1_type), opr2.cast(opr2_type))
+
+def _execute_unop(opcode, opr):
+    opr = _get_dynamic_value(opr) 
+    opcode_matcher_list = UNARY_OPCODE_TO_OPERATOR_MAP.get(opcode)
+    if not opcode_matcher_list:
+        return None
+    opr_is_a_list = _get_is_a_types(opr.type)
+    for opr_type in opr_is_a_list:
+        op_function = _get_matching_unop_function(opr_type,
+                                                  opcode_matcher_list)
+        if op_function:
+            return op_function(opr.cast(opr_type))
+
+def enable_debug_operators():
+    gdb.enable_debug_operators(gdb.operator._execute_binop,
+                               gdb.operator._execute_unop)
Index: python/lib/gdb/command/debug-operator.py
===================================================================
RCS file: python/lib/gdb/command/debug-operator.py
diff -N python/lib/gdb/command/debug-operator.py
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ python/lib/gdb/command/debug-operator.py	28 Nov 2012 14:04:16 -0000
@@ -0,0 +1,31 @@
+# Type utilities.
+# Copyright (C) 2010-2012 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/>.
+
+"""Implementation of the command 'enable debug-operators'."""
+
+import gdb
+import gdb.operator
+
+class EnableDebugOperators(gdb.Command):
+    def __init__(self):
+        super(EnableDebugOperators, self).__init__("enable debug-operators",
+                                                   gdb.COMMAND_SUPPORT)
+
+    def invoke(self, arg, from_tty):
+        gdb.operator.enable_debug_operators()
+
+
+EnableDebugOperators()
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dop.cc
Type: application/octet-stream
Size: 500 bytes
Desc: not available
URL: <http://sourceware.org/pipermail/gdb-patches/attachments/20121129/75bec2e7/attachment.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dop.py
Type: application/octet-stream
Size: 670 bytes
Desc: not available
URL: <http://sourceware.org/pipermail/gdb-patches/attachments/20121129/75bec2e7/attachment-0001.obj>


More information about the Gdb-patches mailing list