[1/4] gdb/python: have UnwindInfo.add_saved_register accept named args

Message ID 0d08cca8737eff64400b5bb973bed259e4affa7e.1680177890.git.aburgess@redhat.com
State New
Headers
Series Python API: Accept named arguments in a few more places |

Commit Message

Andrew Burgess March 30, 2023, 12:10 p.m. UTC
  Update gdb.UnwindInfo.add_saved_register to accept named keyword
arguments.

As part of this update we now use gdb_PyArg_ParseTupleAndKeywords
instead of PyArg_UnpackTuple to parse the function arguments.

By switching to gdb_PyArg_ParseTupleAndKeywords, we can now use 'O!'
as the argument format for the function's value argument.  This means
that we can check the argument type (is gdb.Value) as part of the
argument processing rather than manually performing the check later in
the function.  One result of this is that we now get a better error
message (at least, I think so).  Previously we would get something
like:

  ValueError: Bad register value

Now we get:

  TypeError: argument 2 must be gdb.Value, not XXXX

It's unfortunate that the exception type changed, but I think the new
exception type actually makes more sense.

For existing unwinder code that doesn't throw any exceptions nothing
should change with this commit.  It is possible that a user has some
code that throws and catches the ValueError, and this code will break
after this commit, but I think this is going to be sufficiently rare
that we can take the risk here.
---
 gdb/doc/python.texi                    |  4 +-
 gdb/python/py-unwind.c                 | 84 +++++++++++++-------------
 gdb/testsuite/gdb.python/py-unwind.exp | 15 +++--
 gdb/testsuite/gdb.python/py-unwind.py  | 28 ++++++---
 4 files changed, 75 insertions(+), 56 deletions(-)
  

Comments

Eli Zaretskii March 30, 2023, 2:08 p.m. UTC | #1
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Thu, 30 Mar 2023 13:10:20 +0100
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
> --- a/gdb/doc/python.texi
> +++ b/gdb/doc/python.texi
> @@ -2882,8 +2882,8 @@
>  create a @code{gdb.UnwindInfo} instance.  Use the following method to
>  specify caller registers that have been saved in this frame:
>  
> -@defun gdb.UnwindInfo.add_saved_register (reg, value)
> -@var{reg} identifies the register, for a description of the acceptable
> +@defun gdb.UnwindInfo.add_saved_register (@var{register}, @var{value})
> +@var{register} identifies the register, for a description of the acceptable
>  values see @ref{gdbpy_frame_read_register,,Frame.read_register}.
>  @var{value} is a register value (a @code{gdb.Value} object).
>  @end defun

Why did you use @var inside the argument list on a @defun line?  That
shouldn't be necessary, and the original text didn't use that.

Otherwise, this is okay (although the change just modifies the name of
a single argument, so I'm not sure why we are doing this).  Thanks.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Andrew Burgess April 6, 2023, 2:10 p.m. UTC | #2
Eli Zaretskii <eliz@gnu.org> writes:

>> Cc: Andrew Burgess <aburgess@redhat.com>
>> Date: Thu, 30 Mar 2023 13:10:20 +0100
>> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
>> 
>> --- a/gdb/doc/python.texi
>> +++ b/gdb/doc/python.texi
>> @@ -2882,8 +2882,8 @@
>>  create a @code{gdb.UnwindInfo} instance.  Use the following method to
>>  specify caller registers that have been saved in this frame:
>>  
>> -@defun gdb.UnwindInfo.add_saved_register (reg, value)
>> -@var{reg} identifies the register, for a description of the acceptable
>> +@defun gdb.UnwindInfo.add_saved_register (@var{register}, @var{value})
>> +@var{register} identifies the register, for a description of the acceptable
>>  values see @ref{gdbpy_frame_read_register,,Frame.read_register}.
>>  @var{value} is a register value (a @code{gdb.Value} object).
>>  @end defun
>
> Why did you use @var inside the argument list on a @defun line?  That
> shouldn't be necessary, and the original text didn't use that.

I've removed the use of @var from this and all the patches in this
series.  This completely removes all doc changes from patches #3 and #4.

> Otherwise, this is okay (although the change just modifies the name of
> a single argument, so I'm not sure why we are doing this).  Thanks.

I've added a paragraph to the commit message that explains this change,
here's the summary:

This commit is adding support for Python named arguments, so a user can
do:

  obj.add_saved_register(register="...", value="...")

if they want.  I prefer 'register' rather than 'reg' in situations like
this.  The argument names GDB accepts has to match what's in the docs,
hence the change in patches #1 and #2.

Thanks,
Andrew

>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  

Patch

diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index c74d586ef39..ef50e6dbe7b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2882,8 +2882,8 @@ 
 create a @code{gdb.UnwindInfo} instance.  Use the following method to
 specify caller registers that have been saved in this frame:
 
-@defun gdb.UnwindInfo.add_saved_register (reg, value)
-@var{reg} identifies the register, for a description of the acceptable
+@defun gdb.UnwindInfo.add_saved_register (@var{register}, @var{value})
+@var{register} identifies the register, for a description of the acceptable
 values see @ref{gdbpy_frame_read_register,,Frame.read_register}.
 @var{value} is a register value (a @code{gdb.Value} object).
 @end defun
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index 409dbd3a470..2e13b84eb12 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -292,7 +292,7 @@  pyuw_create_unwind_info (PyObject *pyo_pending_frame,
    gdb.UnwindInfo.add_saved_register (REG, VALUE) -> None.  */
 
 static PyObject *
-unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
+unwind_infopy_add_saved_register (PyObject *self, PyObject *args, PyObject *kw)
 {
   unwind_info_object *unwind_info = (unwind_info_object *) self;
   pending_frame_object *pending_frame
@@ -305,11 +305,15 @@  unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
     {
       PyErr_SetString (PyExc_ValueError,
 		       "UnwindInfo instance refers to a stale PendingFrame");
-      return NULL;
+      return nullptr;
     }
-  if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
-			  &pyo_reg_id, &pyo_reg_value))
-    return NULL;
+
+  static const char *keywords[] = { "register", "value", nullptr };
+  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "OO!", keywords,
+					&pyo_reg_id, &value_object_type,
+					&pyo_reg_value))
+    return nullptr;
+
   if (!gdbpy_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
     return nullptr;
 
@@ -332,43 +336,38 @@  unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
 	}
     }
 
-  {
-    struct value *value;
-    size_t data_size;
+  /* The argument parsing above guarantees that PYO_REG_VALUE will be a
+     gdb.Value object, as a result the value_object_to_value call should
+     succeed.  */
+  gdb_assert (pyo_reg_value != nullptr);
+  struct value *value = value_object_to_value (pyo_reg_value);
+  gdb_assert (value != nullptr);
+
+  ULONGEST reg_size = register_size (pending_frame->gdbarch, regnum);
+  if (reg_size != value->type ()->length ())
+    {
+      PyErr_Format (PyExc_ValueError,
+		    "The value of the register returned by the Python "
+		    "sniffer has unexpected size: %s instead of %s.",
+		    pulongest (value->type ()->length ()),
+		    pulongest (reg_size));
+      return nullptr;
+    }
+
+  gdbpy_ref<> new_value = gdbpy_ref<>::new_reference (pyo_reg_value);
+  bool found = false;
+  for (saved_reg &reg : *unwind_info->saved_regs)
+    {
+      if (regnum == reg.number)
+	{
+	  found = true;
+	  reg.value = std::move (new_value);
+	  break;
+	}
+    }
+  if (!found)
+    unwind_info->saved_regs->emplace_back (regnum, std::move (new_value));
 
-    if (pyo_reg_value == NULL
-      || (value = value_object_to_value (pyo_reg_value)) == NULL)
-      {
-	PyErr_SetString (PyExc_ValueError, "Bad register value");
-	return NULL;
-      }
-    data_size = register_size (pending_frame->gdbarch, regnum);
-    if (data_size != value->type ()->length ())
-      {
-	PyErr_Format (
-	    PyExc_ValueError,
-	    "The value of the register returned by the Python "
-	    "sniffer has unexpected size: %u instead of %u.",
-	    (unsigned) value->type ()->length (),
-	    (unsigned) data_size);
-	return NULL;
-      }
-  }
-  {
-    gdbpy_ref<> new_value = gdbpy_ref<>::new_reference (pyo_reg_value);
-    bool found = false;
-    for (saved_reg &reg : *unwind_info->saved_regs)
-      {
-	if (regnum == reg.number)
-	  {
-	    found = true;
-	    reg.value = std::move (new_value);
-	    break;
-	  }
-      }
-    if (!found)
-      unwind_info->saved_regs->emplace_back (regnum, std::move (new_value));
-  }
   Py_RETURN_NONE;
 }
 
@@ -1087,7 +1086,8 @@  PyTypeObject pending_frame_object_type =
 static PyMethodDef unwind_info_object_methods[] =
 {
   { "add_saved_register",
-    unwind_infopy_add_saved_register, METH_VARARGS,
+    (PyCFunction) unwind_infopy_add_saved_register,
+    METH_VARARGS | METH_KEYWORDS,
     "add_saved_register (REG, VALUE) -> None\n"
     "Set the value of the REG in the previous frame to VALUE." },
   { NULL }  /* Sentinel */
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
index fddf4f15393..37c8fd8fbb1 100644
--- a/gdb/testsuite/gdb.python/py-unwind.exp
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -131,10 +131,17 @@  gdb_test "disable unwinder global \"test unwinder\"" \
 check_for_broken_backtrace "stack is broken after command disabling"
 check_info_unwinder "info unwinder after command disabling" off
 
-# Check that invalid register names cause errors.
-gdb_test "python print(add_saved_register_error)" "True" \
-    "add_saved_register error"
-gdb_test "python print(read_register_error)" "True" \
+# Check that invalid register names and values cause errors.
+gdb_test "python print(add_saved_register_errors\[\"unknown_name\"\])" \
+    "Bad register" \
+    "add_saved_register error when an unknown register name is used"
+gdb_test "python print(add_saved_register_errors\[\"unknown_number\"\])" \
+    "Bad register" \
+    "add_saved_register error when an unknown register number is used"
+gdb_test "python print(add_saved_register_errors\[\"bad_value\"\])" \
+    "argument 2 must be gdb.Value, not int" \
+    "add_saved_register error when invalid register value is used"
+gdb_test "python print(read_register_error)" "Bad register" \
     "read_register error"
 
 # Try to create an unwinder object with a non-string name.
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
index 4e110c51e3b..5853abc7486 100644
--- a/gdb/testsuite/gdb.python/py-unwind.py
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -18,7 +18,7 @@  from gdb.unwinder import Unwinder, FrameId
 
 
 # These are set to test whether invalid register names cause an error.
-add_saved_register_error = False
+add_saved_register_errors = {}
 read_register_error = False
 
 
@@ -94,9 +94,9 @@  class TestUnwinder(Unwinder):
 
             try:
                 pending_frame.read_register("nosuchregister")
-            except ValueError:
+            except ValueError as ve:
                 global read_register_error
-                read_register_error = True
+                read_register_error = str(ve)
 
             frame_id = FrameId(
                 pending_frame.read_register(TestUnwinder.AMD64_RSP),
@@ -104,13 +104,25 @@  class TestUnwinder(Unwinder):
             )
             unwind_info = pending_frame.create_unwind_info(frame_id)
             unwind_info.add_saved_register(TestUnwinder.AMD64_RBP, previous_bp)
-            unwind_info.add_saved_register("rip", previous_ip)
-            unwind_info.add_saved_register("rsp", previous_sp)
+            unwind_info.add_saved_register(value=previous_ip, register="rip")
+            unwind_info.add_saved_register(register="rsp", value=previous_sp)
+
+            global add_saved_register_errors
             try:
                 unwind_info.add_saved_register("nosuchregister", previous_sp)
-            except ValueError:
-                global add_saved_register_error
-                add_saved_register_error = True
+            except ValueError as ve:
+                add_saved_register_errors["unknown_name"] = str(ve)
+
+            try:
+                unwind_info.add_saved_register(999, previous_sp)
+            except ValueError as ve:
+                add_saved_register_errors["unknown_number"] = str(ve)
+
+            try:
+                unwind_info.add_saved_register("rsp", 1234)
+            except TypeError as ve:
+                add_saved_register_errors["bad_value"] = str(ve)
+
             return unwind_info
         except (gdb.error, RuntimeError):
             return None