gdb/python: Add gdb.Architecture.registers method
authorAndrew Burgess <andrew.burgess@embecosm.com>
Fri, 5 Jun 2020 16:52:10 +0000 (17:52 +0100)
committerAndrew Burgess <andrew.burgess@embecosm.com>
Mon, 6 Jul 2020 14:06:06 +0000 (15:06 +0100)
This commit adds a new method gdb.Architecture.registers that returns
an object of the new type gdb.RegisterDescriptorIterator.  This
iterator returns objects of the new type gdb.RegisterDescriptor.

A RegisterDescriptor is not a way to read the value of a register,
this is already covered by Frame.read_register, a RegisterDescriptor
is simply a way to discover from Python, which registers are
available for a given architecture.

I did consider just returning a string, the name of each register,
instead of a RegisterDescriptor, however, I'm aware that it we don't
want to break the existing Python API in any way, so if I return just
a string now, but in the future we want more information about a
register then we would have to add a second API to get that
information.  By going straight to a descriptor object now, it is easy
to add additional properties in the future should we wish to.

Right now the only property of a register that a user can access is
the name of the register.

In future we might want to be able to ask the register about is
register groups, or its type.

gdb/ChangeLog:

* Makefile.in (SUBDIR_PYTHON_SRCS): Add py-registers.c
* python/py-arch.c (archpy_registers): New function.
(arch_object_methods): Add 'registers' method.
* python/py-registers.c: New file.
* python/python-internal.h
(gdbpy_new_register_descriptor_iterator): Declare.
(gdbpy_initialize_registers): Declare.
* python/python.c (do_start_initialization): Call
gdbpy_initialize_registers.
* NEWS: Mention additions to the Python API.

gdb/testsuite/ChangeLog:

* gdb.python/py-arch-reg-names.exp: New file.

gdb/doc/ChangeLog:

* python.texi (Python API): Add new section the menu.
(Frames In Python): Add new @anchor.
(Architectures In Python): Document new registers method.
(Registers In Python): New section.

gdb/ChangeLog
gdb/Makefile.in
gdb/NEWS
gdb/doc/ChangeLog
gdb/doc/python.texi
gdb/python/py-arch.c
gdb/python/py-registers.c [new file with mode: 0644]
gdb/python/python-internal.h
gdb/python/python.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.python/py-arch-reg-names.exp [new file with mode: 0644]

index 1fac9a516faab3493777abc39b321dc7d7c9a37a..e3abc3d651f75f82ca2ea0f8bbc967f93e6f26bb 100644 (file)
@@ -1,3 +1,16 @@
+2020-07-06  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * Makefile.in (SUBDIR_PYTHON_SRCS): Add py-registers.c
+       * python/py-arch.c (archpy_registers): New function.
+       (arch_object_methods): Add 'registers' method.
+       * python/py-registers.c: New file.
+       * python/python-internal.h
+       (gdbpy_new_register_descriptor_iterator): Declare.
+       (gdbpy_initialize_registers): Declare.
+       * python/python.c (do_start_initialization): Call
+       gdbpy_initialize_registers.
+       * NEWS: Mention additions to the Python API.
+
 2020-07-06  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * NEWS: Mention new Python API method.
index 9ae9fe2d1e146611325806283e78025d488458e3..9d4844573974c653b94713cc8f4a445202524b17 100644 (file)
@@ -401,6 +401,7 @@ SUBDIR_PYTHON_SRCS = \
        python/py-record.c \
        python/py-record-btrace.c \
        python/py-record-full.c \
+       python/py-registers.c \
        python/py-signalevent.c \
        python/py-stopevent.c \
        python/py-symbol.c \
index 29db0734f87451b20b3aafe040ea67d6dc0793fc..84019a6036e443f55de9e900fbc6093ccb698b95 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -120,6 +120,11 @@ GNU/Linux/RISC-V (gdbserver)       riscv*-*-linux*
   ** New method gdb.PendingFrame.architecture () to retrieve the
      architecture of the pending frame.
 
+  ** New gdb.Architecture.registers method that returns a
+     gdb.RegisterDescriptorIterator object, an iterator that returns
+     gdb.RegisterDescriptor objects.  The new RegisterDescriptor is a
+     way to query the registers available for an architecture.
+
 *** Changes in GDB 9
 
 * 'thread-exited' event is now available in the annotations interface.
index 82ed257fb8401bf62c484e587c1cf4561e9912d1..d513d7486a5b83024ff8deb2e4c9c8274d1a3ebd 100644 (file)
@@ -1,3 +1,10 @@
+2020-07-06  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * python.texi (Python API): Add new section to the menu.
+       (Frames In Python): Add new @anchor.
+       (Architectures In Python): Document new registers method.
+       (Registers In Python): New section.
+
 2020-07-06  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * python.texi (Unwinding Frames in Python): Document
index fff7e5b0128af1540f07cff1fd829a997224f6b2..0ed2764b102bf6f2c1dc4bb7edd049bb7f834bb1 100644 (file)
@@ -163,6 +163,7 @@ optional arguments while skipping others.  Example:
                                 using Python.
 * Lazy Strings In Python::      Python representation of lazy strings.
 * Architectures In Python::     Python representation of architectures.
+* Registers In Python::         Python representation of registers.
 * TUI Windows In Python::       Implementing new TUI windows.
 @end menu
 
@@ -4684,6 +4685,7 @@ Return the frame's symtab and line object.
 @xref{Symbol Tables In Python}.
 @end defun
 
+@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'}).
@@ -5715,6 +5717,37 @@ instruction in bytes.
 @end table
 @end defun
 
+@anchor{gdbpy_architecture_registers}
+@defun Architecture.registers (@r{[} @var{reggroup} @r{]})
+Return a @code{gdb.RegisterDescriptorIterator} (@pxref{Registers In
+Python}) for all of the registers in @var{reggroup}, a string that is
+the name of a register group.  If @var{reggroup} is omitted, or is the
+empty string, then the register group @samp{all} is assumed.
+@end defun
+
+@node Registers In Python
+@subsubsection Registers In Python
+@cindex Registers In Python
+
+Python code can request from a @code{gdb.Architecture} information
+about the set of registers available
+(@pxref{gdbpy_architecture_registers,,@code{Architecture.registers}}).
+The register information is returned as a
+@code{gdb.RegisterDescriptorIterator}, which is an iterator that in
+turn returns @code{gdb.RegisterDescriptor} objects.
+
+A @code{gdb.RegisterDescriptor} does not provide the value of a
+register (@pxref{gdbpy_frame_read_register,,@code{Frame.read_register}}
+for reading a register's value), instead the @code{RegisterDescriptor}
+is a way to discover which registers are available for a particular
+architecture.
+
+A @code{gdb.RegisterDescriptor} has the following read-only properties:
+
+@defvar RegisterDescriptor.name
+The name of this register.
+@end defvar
+
 @node TUI Windows In Python
 @subsubsection Implementing new TUI windows
 @cindex Python TUI Windows
index 853c7a9e7ee31261c92aa19fded9e98ef2f43878..15f9f50d7d7e2f25c790c5ed6aefe10360160d3b 100644 (file)
@@ -226,6 +226,28 @@ archpy_disassemble (PyObject *self, PyObject *args, PyObject *kw)
   return result_list.release ();
 }
 
+/* Implementation of gdb.Architecture.registers (self, reggroup) -> Iterator.
+   Returns an iterator over register descriptors for registers in GROUP
+   within the architecture SELF.  */
+
+static PyObject *
+archpy_registers (PyObject *self, PyObject *args, PyObject *kw)
+{
+  static const char *keywords[] = { "reggroup", NULL };
+  struct gdbarch *gdbarch = NULL;
+  const char *group_name = NULL;
+
+  /* Parse method arguments.  */
+  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "|s", keywords,
+                                       &group_name))
+    return NULL;
+
+  /* Extract the gdbarch from the self object.  */
+  ARCHPY_REQUIRE_VALID (self, gdbarch);
+
+  return gdbpy_new_register_descriptor_iterator (gdbarch, group_name);
+}
+
 /* Initializes the Architecture class in the gdb module.  */
 
 int
@@ -249,6 +271,11 @@ Return the name of the architecture as a string value." },
     "disassemble (start_pc [, end_pc [, count]]) -> List.\n\
 Return a list of at most COUNT disassembled instructions from START_PC to\n\
 END_PC." },
+  { "registers", (PyCFunction) archpy_registers,
+    METH_VARARGS | METH_KEYWORDS,
+    "registers ([ group-name ]) -> Iterator.\n\
+Return an iterator of register descriptors for the registers in register\n\
+group GROUP-NAME." },
   {NULL}  /* Sentinel */
 };
 
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
new file mode 100644 (file)
index 0000000..6ccd17e
--- /dev/null
@@ -0,0 +1,269 @@
+/* Python interface to register, and register group information.
+
+   Copyright (C) 2020 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 "gdbarch.h"
+#include "arch-utils.h"
+#include "disasm.h"
+#include "reggroups.h"
+#include "python-internal.h"
+
+/* Structure for iterator over register descriptors.  */
+typedef struct {
+  PyObject_HEAD
+
+  /* The register group that the user is iterating over.  This will never
+     be NULL.  */
+  struct reggroup *reggroup;
+
+  /* The next register number to lookup.  Starts at 0 and counts up.  */
+  int regnum;
+
+  /* Pointer back to the architecture we're finding registers for.  */
+  struct gdbarch *gdbarch;
+} register_descriptor_iterator_object;
+
+extern PyTypeObject register_descriptor_iterator_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("register_descriptor_iterator_object");
+
+/* A register descriptor.  */
+typedef struct {
+  PyObject_HEAD
+
+  /* The register this is a descriptor for.  */
+  int regnum;
+
+  /* The architecture this is a register for.  */
+  struct gdbarch *gdbarch;
+} register_descriptor_object;
+
+extern PyTypeObject register_descriptor_object_type
+    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("register_descriptor_object");
+
+/* Create an return a new gdb.RegisterDescriptor object.  */
+static PyObject *
+gdbpy_new_register_descriptor (struct gdbarch *gdbarch,
+                              int regnum)
+{
+  /* Create a new object and fill in its details.  */
+  register_descriptor_object *reg
+    = PyObject_New (register_descriptor_object,
+                   &register_descriptor_object_type);
+  if (reg == NULL)
+    return NULL;
+  reg->regnum = regnum;
+  reg->gdbarch = gdbarch;
+  return (PyObject *) reg;
+}
+
+/* Convert the register descriptor to a string.  */
+
+static PyObject *
+gdbpy_register_descriptor_to_string (PyObject *self)
+{
+  register_descriptor_object *reg
+    = (register_descriptor_object *) self;
+  struct gdbarch *gdbarch = reg->gdbarch;
+  int regnum = reg->regnum;
+
+  const char *name = gdbarch_register_name (gdbarch, regnum);
+  return PyString_FromString (name);
+}
+
+/* Implement gdb.RegisterDescriptor.name attribute get function.  Return a
+   string that is the name of this register.  Due to checking when register
+   descriptors are created the name will never by the empty string.  */
+
+static PyObject *
+gdbpy_register_descriptor_name (PyObject *self, void *closure)
+{
+  return gdbpy_register_descriptor_to_string (self);
+}
+
+/* Create and return a new gdb.RegisterDescriptorIterator object which
+   will iterate over all registers in GROUP_NAME for GDBARCH.  If
+   GROUP_NAME is either NULL or the empty string then the ALL_REGGROUP is
+   used, otherwise lookup the register group matching GROUP_NAME and use
+   that.
+
+   This function can return NULL if GROUP_NAME isn't found.  */
+
+PyObject *
+gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
+                                       const char *group_name)
+{
+  struct reggroup *grp = NULL;
+
+  /* Lookup the requested register group, or find the default.  */
+  if (group_name == NULL || *group_name == '\0')
+    grp = all_reggroup;
+  else
+    {
+      grp = reggroup_find (gdbarch, group_name);
+      if (grp == NULL)
+       {
+         PyErr_SetString (PyExc_ValueError,
+                          _("Unknown register group name."));
+         return NULL;
+       }
+    }
+  /* Create a new iterator object initialised for this architecture and
+     fill in all of the details.  */
+  register_descriptor_iterator_object *iter
+    = PyObject_New (register_descriptor_iterator_object,
+                   &register_descriptor_iterator_object_type);
+  if (iter == NULL)
+    return NULL;
+  iter->regnum = 0;
+  iter->gdbarch = gdbarch;
+  gdb_assert (grp != NULL);
+  iter->reggroup = grp;
+
+  return (PyObject *) iter;
+}
+
+/* Return a reference to the gdb.RegisterDescriptorIterator object.  */
+
+static PyObject *
+gdbpy_register_descriptor_iter (PyObject *self)
+{
+  Py_INCREF (self);
+  return self;
+}
+
+/* Return the next register name.  */
+
+static PyObject *
+gdbpy_register_descriptor_iter_next (PyObject *self)
+{
+  register_descriptor_iterator_object *iter_obj
+    = (register_descriptor_iterator_object *) self;
+  struct gdbarch *gdbarch = iter_obj->gdbarch;
+
+  do
+    {
+      if (iter_obj->regnum >= gdbarch_num_cooked_regs (gdbarch))
+       {
+         PyErr_SetString (PyExc_StopIteration, _("No more registers"));
+         return NULL;
+       }
+
+      const char *name = nullptr;
+      int regnum = iter_obj->regnum;
+      if (gdbarch_register_reggroup_p (gdbarch, regnum,
+                                      iter_obj->reggroup))
+       name = gdbarch_register_name (gdbarch, regnum);
+      iter_obj->regnum++;
+
+      if (name != nullptr && *name != '\0')
+       return gdbpy_new_register_descriptor (gdbarch, regnum);
+    }
+  while (true);
+}
+
+/* Initializes the new Python classes from this file in the gdb module.  */
+
+int
+gdbpy_initialize_registers ()
+{
+  register_descriptor_object_type.tp_new = PyType_GenericNew;
+  if (PyType_Ready (&register_descriptor_object_type) < 0)
+    return -1;
+  if (gdb_pymodule_addobject
+      (gdb_module, "RegisterDescriptor",
+       (PyObject *) &register_descriptor_object_type) < 0)
+    return -1;
+
+  register_descriptor_iterator_object_type.tp_new = PyType_GenericNew;
+  if (PyType_Ready (&register_descriptor_iterator_object_type) < 0)
+    return -1;
+  return (gdb_pymodule_addobject
+         (gdb_module, "RegisterDescriptorIterator",
+          (PyObject *) &register_descriptor_iterator_object_type));
+}
+
+PyTypeObject register_descriptor_iterator_object_type = {
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.RegisterDescriptorIterator",            /*tp_name*/
+  sizeof (register_descriptor_iterator_object),        /*tp_basicsize*/
+  0,                             /*tp_itemsize*/
+  0,                             /*tp_dealloc*/
+  0,                             /*tp_print*/
+  0,                             /*tp_getattr*/
+  0,                             /*tp_setattr*/
+  0,                             /*tp_compare*/
+  0,                             /*tp_repr*/
+  0,                             /*tp_as_number*/
+  0,                             /*tp_as_sequence*/
+  0,                             /*tp_as_mapping*/
+  0,                             /*tp_hash */
+  0,                             /*tp_call*/
+  0,                             /*tp_str*/
+  0,                             /*tp_getattro*/
+  0,                             /*tp_setattro*/
+  0,                             /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER,                   /*tp_flags*/
+  "GDB architecture register descriptor iterator object",      /*tp_doc */
+  0,                             /*tp_traverse */
+  0,                             /*tp_clear */
+  0,                             /*tp_richcompare */
+  0,                             /*tp_weaklistoffset */
+  gdbpy_register_descriptor_iter,        /*tp_iter */
+  gdbpy_register_descriptor_iter_next,  /*tp_iternext */
+  0                              /*tp_methods */
+};
+
+static gdb_PyGetSetDef gdbpy_register_descriptor_getset[] = {
+  { "name", gdbpy_register_descriptor_name, NULL,
+    "The name of this register.", NULL },
+  { NULL }  /* Sentinel */
+};
+
+PyTypeObject register_descriptor_object_type = {
+  PyVarObject_HEAD_INIT (NULL, 0)
+  "gdb.RegisterDescriptor",      /*tp_name*/
+  sizeof (register_descriptor_object), /*tp_basicsize*/
+  0,                             /*tp_itemsize*/
+  0,                             /*tp_dealloc*/
+  0,                             /*tp_print*/
+  0,                             /*tp_getattr*/
+  0,                             /*tp_setattr*/
+  0,                             /*tp_compare*/
+  0,                             /*tp_repr*/
+  0,                             /*tp_as_number*/
+  0,                             /*tp_as_sequence*/
+  0,                             /*tp_as_mapping*/
+  0,                             /*tp_hash */
+  0,                             /*tp_call*/
+  gdbpy_register_descriptor_to_string,                 /*tp_str*/
+  0,                             /*tp_getattro*/
+  0,                             /*tp_setattro*/
+  0,                             /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT,            /*tp_flags*/
+  "GDB architecture register descriptor object",       /*tp_doc */
+  0,                             /*tp_traverse */
+  0,                             /*tp_clear */
+  0,                             /*tp_richcompare */
+  0,                             /*tp_weaklistoffset */
+  0,                             /*tp_iter */
+  0,                             /*tp_iternext */
+  0,                             /*tp_methods */
+  0,                             /*tp_members */
+  gdbpy_register_descriptor_getset                     /*tp_getset */
+};
index e352b30382804cfc293f065c5c5a70de4cb25268..758dc553ce118de6dd69be340bb92d537ed3b9d0 100644 (file)
@@ -473,6 +473,9 @@ PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
 PyObject *gdbarch_to_arch_object (struct gdbarch *gdbarch);
 
+PyObject *gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
+                                                 const char *group_name);
+
 gdbpy_ref<thread_object> create_thread_object (struct thread_info *tp);
 gdbpy_ref<> thread_to_thread_object (thread_info *thr);;
 gdbpy_ref<inferior_object> inferior_to_inferior_object (inferior *inf);
@@ -540,6 +543,8 @@ int gdbpy_initialize_py_events (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_arch (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
+int gdbpy_initialize_registers ()
+  CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_xmethods (void)
   CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION;
 int gdbpy_initialize_unwind (void)
index 4bdd2201abc37f54f797938bc5b56e5c462793ad..7787dce4b4cc7d04fb84920c697ab0d8c665b33e 100644 (file)
@@ -1759,6 +1759,7 @@ do_start_initialization ()
       || gdbpy_initialize_py_events () < 0
       || gdbpy_initialize_event () < 0
       || gdbpy_initialize_arch () < 0
+      || gdbpy_initialize_registers () < 0
       || gdbpy_initialize_xmethods () < 0
       || gdbpy_initialize_unwind () < 0
       || gdbpy_initialize_tui () < 0)
index bf5b89bd0f787dc03eb3543cd52cc3e47e2936ff..9562c2cffaaf40aecd83c0fd2654d491377c0fa8 100644 (file)
@@ -1,3 +1,7 @@
+2020-07-06  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * gdb.python/py-arch-reg-names.exp: New file.
+
 2020-07-06  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * gdb.python/py-unwind.py (TestUnwinder::__call__): Add test for
diff --git a/gdb/testsuite/gdb.python/py-arch-reg-names.exp b/gdb/testsuite/gdb.python/py-arch-reg-names.exp
new file mode 100644 (file)
index 0000000..14bc0a8
--- /dev/null
@@ -0,0 +1,87 @@
+# Copyright 2020 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/>.
+
+# Check the gdb.Architecture.registers functionality.
+
+load_lib gdb-python.exp
+standard_testfile py-arch.c
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+if ![runto_main] {
+   return -1
+}
+
+# First, use 'info registers' to get a list of register names.
+set regs {}
+gdb_test_multiple "info registers general" "info registers general" {
+    -re "^info registers general\r\n" {
+       exp_continue
+    }
+    -re "^(\[^ \t\]+)\[ \t\]+\[^\r\n\]+\r\n" {
+       set reg $expect_out(1,string)
+       lappend regs $reg
+       exp_continue
+    }
+    -re "^$gdb_prompt " {
+    }
+}
+gdb_assert {[llength $regs] > 0} \
+    "Found at least one register"
+
+# Now get the same register names using Python API.
+gdb_py_test_silent_cmd \
+    "python frame = gdb.selected_frame()" "get frame" 0
+gdb_py_test_silent_cmd \
+    "python arch = frame.architecture()" "get arch" 0
+gdb_py_test_silent_cmd \
+    "python regs = list (arch.registers (\"general\"))" \
+    "get general registers" 0
+gdb_py_test_silent_cmd \
+    "python regs = map (lambda r : r.name, regs)" \
+    "get names of general registers" 0
+
+set py_regs {}
+gdb_test_multiple "python print (\"\\n\".join (regs))" \
+    "general register from python" {
+       -re "^python print \[^\r\n\]+\r\n" {
+           exp_continue
+       }
+       -re "^(\[^\r\n\]+)\r\n" {
+           set reg $expect_out(1,string)
+           lappend py_regs $reg
+           exp_continue
+       }
+       -re "^$gdb_prompt " {
+       }
+    }
+
+gdb_assert {[llength $py_regs] > 0} \
+    "Found at least one register from python"
+gdb_assert {[llength $py_regs] == [llength $regs]} \
+    "Same numnber of registers found"
+
+set found_non_match 0
+for { set i 0 } { $i < [llength $regs] } { incr i } {
+    if {[lindex $regs $i] != [lindex $py_regs $i]} {
+       set found_non_match 1
+    }
+}
+gdb_assert { $found_non_match == 0 } "all registers match"