gdb/python: make more use of RegisterDescriptors
authorAndrew Burgess <andrew.burgess@embecosm.com>
Wed, 22 Jul 2020 11:13:11 +0000 (12:13 +0100)
committerAndrew Burgess <andrew.burgess@embecosm.com>
Tue, 28 Jul 2020 09:27:54 +0000 (10:27 +0100)
This commit unifies all of the Python register lookup code (used by
Frame.read_register, PendingFrame.read_register, and
gdb.UnwindInfo.add_saved_register), and adds support for using a
gdb.RegisterDescriptor for register lookup.

Currently the register unwind code (PendingFrame and UnwindInfo) allow
registers to be looked up either by name, or by GDB's internal
number.  I suspect the number was added for performance reasons, when
unwinding we don't want to repeatedly map from name to number for
every unwind.  However, this kind-of sucks, it means Python scripts
could include GDB's internal register numbers, and if we ever change
this numbering in the future users scripts will break in unexpected
ways.

Meanwhile, the Frame.read_register method only supports accessing
registers using a string, the register name.

This commit unifies all of the register to register-number lookup code
in our Python bindings, and adds a third choice into the mix, the use
of gdb.RegisterDescriptor.

The register descriptors can be looked up by name, but once looked up,
they contain GDB's register number, and so provide all of the
performance benefits of using a register number directly.  However, as
they are looked up by name we are no longer tightly binding the Python
API to GDB's internal numbering scheme.

As we may already have scripts in the wild that are using the register
numbers directly I have kept support for this in the API, but I have
listed this method last in the manual, and I have tried to stress that
this is NOT a good method to use and that users should use either a
string or register descriptor approach.

After this commit all existing Python code should function as before,
but users now have new options for how to identify registers.

gdb/ChangeLog:

* python/py-frame.c: Remove 'user-regs.h' include.
(frapy_read_register): Rewrite to make use of
gdbpy_parse_register_id.
* python/py-registers.c (gdbpy_parse_register_id): New function,
moved here from python/py-unwind.c.  Updated the return type, and
also accepts register descriptor objects.
* python/py-unwind.c: Remove 'user-regs.h' include.
(pyuw_parse_register_id): Moved to python/py-registers.c.
(unwind_infopy_add_saved_register): Update to use
gdbpy_parse_register_id.
(pending_framepy_read_register): Likewise.
* python/python-internal.h (gdbpy_parse_register_id): Declare.

gdb/testsuite/ChangeLog:

* gdb.python/py-unwind.py: Update to make use of a register
descriptor.

gdb/doc/ChangeLog:

* python.texi (Unwinding Frames in Python): Update descriptions
for PendingFrame.read_register and
gdb.UnwindInfo.add_saved_register.
(Frames In Python): Update description of Frame.read_register.

gdb/ChangeLog
gdb/doc/ChangeLog
gdb/doc/python.texi
gdb/python/py-frame.c
gdb/python/py-registers.c
gdb/python/py-unwind.c
gdb/python/python-internal.h
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.python/py-unwind.py

index 106dfcdac816d58969e07d4e40c571b7c1458cf3..234dc2407864a73bec9dae65e4aedf79c50d764b 100644 (file)
@@ -1,3 +1,18 @@
+2020-07-28  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * python/py-frame.c: Remove 'user-regs.h' include.
+       (frapy_read_register): Rewrite to make use of
+       gdbpy_parse_register_id.
+       * python/py-registers.c (gdbpy_parse_register_id): New function,
+       moved here from python/py-unwind.c.  Updated the return type, and
+       also accepts register descriptor objects.
+       * python/py-unwind.c: Remove 'user-regs.h' include.
+       (pyuw_parse_register_id): Moved to python/py-registers.c.
+       (unwind_infopy_add_saved_register): Update to use
+       gdbpy_parse_register_id.
+       (pending_framepy_read_register): Likewise.
+       * python/python-internal.h (gdbpy_parse_register_id): Declare.
+
 2020-07-28  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * python/py-registers.c: Add 'user-regs.h' include.
index 1074511bcbb4e99bcb40180b9edb2cd9105299d0..76a2d9e724a58c47e19913c6f59a0722b9bb94d3 100644 (file)
@@ -1,3 +1,10 @@
+2020-07-28  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * python.texi (Unwinding Frames in Python): Update descriptions
+       for PendingFrame.read_register and
+       gdb.UnwindInfo.add_saved_register.
+       (Frames In Python): Update description of Frame.read_register.
+
 2020-07-28  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * python.texi (Registers In Python): Document new find function.
index c9dc1ff3b7145c181143ffb2d3215a9698c80309..9bb9f3c2a6b74a299f77027b57978cf853f1545d 100644 (file)
@@ -2458,12 +2458,11 @@ provides a method to read frame's registers:
 
 @defun PendingFrame.read_register (reg)
 This method returns the contents of the register @var{reg} in the
-frame as a @code{gdb.Value} object.  @var{reg} can be either a
-register number or a register name; the values are platform-specific.
-They are usually found in the corresponding
-@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree.  If
-@var{reg} does not name a register for the current architecture, this
-method will throw an exception.
+frame as a @code{gdb.Value} object.  For a description of the
+acceptable values of @var{reg} see
+@ref{gdbpy_frame_read_register,,Frame.read_register}.  If @var{reg}
+does not name a register for the current architecture, this method
+will throw an exception.
 
 Note that this method will always return a @code{gdb.Value} for a
 valid register name.  This does not mean that the value will be valid.
@@ -2532,8 +2531,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.  It can be a number or a name, just
-as for the @code{PendingFrame.read_register} method above.
+@var{reg} 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
 
@@ -4687,10 +4686,29 @@ Return the frame's symtab and line object.
 
 @anchor{gdbpy_frame_read_register}
 @defun Frame.read_register (register)
-Return the value of @var{register} in this frame.  The @var{register}
-argument must be a string (e.g., @code{'sp'} or @code{'rax'}).
-Returns a @code{Gdb.Value} object.  Throws an exception if @var{register}
-does not exist.
+Return the value of @var{register} in this frame.  Returns a
+@code{Gdb.Value} object.  Throws an exception if @var{register} does
+not exist.  The @var{register} argument must be one of the following:
+@enumerate
+@item
+A string that is the name of a valid register (e.g., @code{'sp'} or
+@code{'rax'}).
+@item
+A @code{gdb.RegisterDescriptor} object (@pxref{Registers In Python}).
+@item
+A @value{GDBN} internal, platform specific number.  Using these
+numbers is supported for historic reasons, but is not recommended as
+future changes to @value{GDBN} could change the mapping between
+numbers and the registers they represent, breaking any Python code
+that uses the platform-specific numbers.  The numbers are usually
+found in the corresponding @file{@var{platform}-tdep.h} file in the
+@value{GDBN} source tree.
+@end enumerate
+Using a string to access registers will be slightly slower than the
+other two methods as @value{GDBN} must look up the mapping between
+name and internal register number.  If performance is critical
+consider looking up and caching a @code{gdb.RegisterDescriptor}
+object.
 @end defun
 
 @defun Frame.read_var (variable @r{[}, block@r{]})
index 3fd1e7fabcc4499ebd79465dd924c04ea56be705..e121afb222da46092eb9d7652458d69a5f19817f 100644 (file)
@@ -27,7 +27,6 @@
 #include "python-internal.h"
 #include "symfile.h"
 #include "objfiles.h"
-#include "user-regs.h"
 
 typedef struct {
   PyObject_HEAD
@@ -242,12 +241,11 @@ frapy_pc (PyObject *self, PyObject *args)
 static PyObject *
 frapy_read_register (PyObject *self, PyObject *args)
 {
-  const char *regnum_str;
+  PyObject *pyo_reg_id;
   struct value *val = NULL;
 
-  if (!PyArg_ParseTuple (args, "s", &regnum_str))
+  if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
     return NULL;
-
   try
     {
       struct frame_info *frame;
@@ -255,14 +253,18 @@ frapy_read_register (PyObject *self, PyObject *args)
 
       FRAPY_REQUIRE_VALID (self, frame);
 
-      regnum = user_reg_map_name_to_regnum (get_frame_arch (frame),
-                                            regnum_str,
-                                            strlen (regnum_str));
-      if (regnum >= 0)
-        val = value_of_register (regnum, frame);
+      if (!gdbpy_parse_register_id (get_frame_arch (frame), pyo_reg_id,
+                                   &regnum))
+       {
+         PyErr_SetString (PyExc_ValueError, "Bad register");
+         return NULL;
+       }
+
+      gdb_assert (regnum >= 0);
+      val = value_of_register (regnum, frame);
 
       if (val == NULL)
-        PyErr_SetString (PyExc_ValueError, _("Unknown register."));
+        PyErr_SetString (PyExc_ValueError, _("Can't read register."));
     }
   catch (const gdb_exception &except)
     {
index fffe3ecb1e6476232596326f678b9093e30abd0a..516d43d7116fd10abeaaf5491198d9439d860432 100644 (file)
@@ -370,6 +370,59 @@ register_descriptor_iter_find (PyObject *self, PyObject *args, PyObject *kw)
   Py_RETURN_NONE;
 }
 
+/* See python-internal.h.  */
+
+bool
+gdbpy_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
+                        int *reg_num)
+{
+  gdb_assert (pyo_reg_id != NULL);
+
+  /* The register could be a string, its name.  */
+  if (gdbpy_is_string (pyo_reg_id))
+    {
+      gdb::unique_xmalloc_ptr<char> reg_name (gdbpy_obj_to_string (pyo_reg_id));
+
+      if (reg_name != NULL)
+       {
+         *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name.get (),
+                                                 strlen (reg_name.get ()));
+         return *reg_num >= 0;
+       }
+    }
+  /* The register could be its internal GDB register number.  */
+  else if (PyInt_Check (pyo_reg_id))
+    {
+      long value;
+      if (gdb_py_int_as_long (pyo_reg_id, &value) && (int) value == value)
+        {
+         if (user_reg_map_regnum_to_name (gdbarch, value) != NULL)
+           {
+             *reg_num = (int) value;
+             return true;
+           }
+        }
+    }
+  /* The register could be a gdb.RegisterDescriptor object.  */
+  else if (PyObject_IsInstance (pyo_reg_id,
+                          (PyObject *) &register_descriptor_object_type))
+    {
+      register_descriptor_object *reg
+       = (register_descriptor_object *) pyo_reg_id;
+      if (reg->gdbarch == gdbarch)
+       {
+         *reg_num = reg->regnum;
+         return true;
+       }
+      else
+       PyErr_SetString (PyExc_ValueError,
+                        _("Invalid Architecture in RegisterDescriptor"));
+    }
+
+  gdb_assert (PyErr_Occurred ());
+  return false;
+}
+
 /* Initializes the new Python classes from this file in the gdb module.  */
 
 int
index 1cef491cedf5b7495ee9d4a15c1d61789cc67d6f..55d6a310c93573e66d92802f434f86b4fc33ce9e 100644 (file)
@@ -27,7 +27,6 @@
 #include "python-internal.h"
 #include "regcache.h"
 #include "valprint.h"
-#include "user-regs.h"
 
 #define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level)  \
   { fprintf_unfiltered (gdb_stdlog, args); }
@@ -101,37 +100,6 @@ static unsigned int pyuw_debug = 0;
 
 static struct gdbarch_data *pyuw_gdbarch_data;
 
-/* Parses register id, which can be either a number or a name.
-   Returns 1 on success, 0 otherwise.  */
-
-static int
-pyuw_parse_register_id (struct gdbarch *gdbarch, PyObject *pyo_reg_id,
-                        int *reg_num)
-{
-  if (pyo_reg_id == NULL)
-    return 0;
-  if (gdbpy_is_string (pyo_reg_id))
-    {
-      gdb::unique_xmalloc_ptr<char> reg_name (gdbpy_obj_to_string (pyo_reg_id));
-
-      if (reg_name == NULL)
-        return 0;
-      *reg_num = user_reg_map_name_to_regnum (gdbarch, reg_name.get (),
-                                              strlen (reg_name.get ()));
-      return *reg_num >= 0;
-    }
-  else if (PyInt_Check (pyo_reg_id))
-    {
-      long value;
-      if (gdb_py_int_as_long (pyo_reg_id, &value) && (int) value == value)
-        {
-          *reg_num = (int) value;
-          return user_reg_map_regnum_to_name (gdbarch, *reg_num) != NULL;
-        }
-    }
-  return 0;
-}
-
 /* Convert gdb.Value instance to inferior's pointer.  Return 1 on success,
    0 on failure.  */
 
@@ -275,7 +243,7 @@ unwind_infopy_add_saved_register (PyObject *self, PyObject *args)
   if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
                           &pyo_reg_id, &pyo_reg_value))
     return NULL;
-  if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
+  if (!gdbpy_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
     {
       PyErr_SetString (PyExc_ValueError, "Bad register");
       return NULL;
@@ -376,7 +344,7 @@ pending_framepy_read_register (PyObject *self, PyObject *args)
     }
   if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
     return NULL;
-  if (!pyuw_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
+  if (!gdbpy_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
     {
       PyErr_SetString (PyExc_ValueError, "Bad register");
       return NULL;
index 1e6dcf3dbb0f4c654c283bff332fbadf90395518..6874543441bf548b4d9be9c7b77c659162fb41a9 100644 (file)
@@ -776,4 +776,23 @@ struct Py_buffer_deleter
 /* A unique_ptr specialization for Py_buffer.  */
 typedef std::unique_ptr<Py_buffer, Py_buffer_deleter> Py_buffer_up;
 
+/* Parse a register number from PYO_REG_ID and place the register number
+   into *REG_NUM.  The register is a register for GDBARCH.
+
+   If a register is parsed successfully then *REG_NUM will have been
+   updated, and true is returned.  Otherwise the contents of *REG_NUM are
+   undefined, and false is returned.
+
+   The PYO_REG_ID object can be a string, the name of the register.  This
+   is the slowest approach as GDB has to map the name to a number for each
+   call.  Alternatively PYO_REG_ID can be an internal GDB register
+   number.  This is quick but should not be encouraged as this means
+   Python scripts are now dependent on GDB's internal register numbering.
+   Final PYO_REG_ID can be a gdb.RegisterDescriptor object, these objects
+   can be looked up by name once, and then cache the register number so
+   should be as quick as using a register number.  */
+
+extern bool gdbpy_parse_register_id (struct gdbarch *gdbarch,
+                                    PyObject *pyo_reg_id, int *reg_num);
+
 #endif /* PYTHON_PYTHON_INTERNAL_H */
index a4b8dfeb8da2d5ba72a19048ad22f07de989ca87..5ae258820e64f745eb3750f9f293cf2798384777 100644 (file)
@@ -1,3 +1,8 @@
+2020-07-28  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * gdb.python/py-unwind.py: Update to make use of a register
+       descriptor.
+
 2020-07-28  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * gdb.python/py-arch-reg-names.exp: Add additional tests.
index d01da80f25ba509975cc64abbc756c7d88199e84..59602d69028293f3433d462a82eb1f21eda885d2 100644 (file)
@@ -33,12 +33,19 @@ class FrameId(object):
 class TestUnwinder(Unwinder):
     AMD64_RBP = 6
     AMD64_RSP = 7
-    AMD64_RIP = 16
+    AMD64_RIP = None
 
     def __init__(self):
         Unwinder.__init__(self, "test unwinder")
         self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
         self.char_ptr_ptr_t = self.char_ptr_t.pointer()
+        self._last_arch = None
+
+    # Update the register descriptor AMD64_RIP based on ARCH.
+    def _update_register_descriptors (self, arch):
+        if (self._last_arch != arch):
+            TestUnwinder.AMD64_RIP = arch.registers ().find ("rip")
+            self._last_arch = arch
 
     def _read_word(self, address):
         return address.cast(self.char_ptr_ptr_t).dereference()
@@ -77,6 +84,8 @@ class TestUnwinder(Unwinder):
         if (inf_arch != frame_arch):
             raise gdb.GdbError ("architecture mismatch")
 
+        self._update_register_descriptors (frame_arch)
+
         try:
             # NOTE: the registers in Unwinder API can be referenced
             # either by name or by number. The code below uses both