From 0f767f942b027df6de60c42ed0e4a1dac7d0fd4b Mon Sep 17 00:00:00 2001 From: Andrew Burgess Date: Fri, 5 Jun 2020 17:52:10 +0100 Subject: [PATCH] gdb/python: Add gdb.Architecture.registers method 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 | 13 + gdb/Makefile.in | 1 + gdb/NEWS | 5 + gdb/doc/ChangeLog | 7 + gdb/doc/python.texi | 33 +++ gdb/python/py-arch.c | 27 ++ gdb/python/py-registers.c | 269 ++++++++++++++++++ gdb/python/python-internal.h | 5 + gdb/python/python.c | 1 + gdb/testsuite/ChangeLog | 4 + .../gdb.python/py-arch-reg-names.exp | 87 ++++++ 11 files changed, 452 insertions(+) create mode 100644 gdb/python/py-registers.c create mode 100644 gdb/testsuite/gdb.python/py-arch-reg-names.exp diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 1fac9a516f..e3abc3d651 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,16 @@ +2020-07-06 Andrew Burgess + + * 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 * NEWS: Mention new Python API method. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 9ae9fe2d1e..9d48445739 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -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 \ diff --git a/gdb/NEWS b/gdb/NEWS index 29db0734f8..84019a6036 100644 --- 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. diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 82ed257fb8..d513d7486a 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,10 @@ +2020-07-06 Andrew Burgess + + * 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 * python.texi (Unwinding Frames in Python): Document diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index fff7e5b012..0ed2764b10 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -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 diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c index 853c7a9e7e..15f9f50d7d 100644 --- a/gdb/python/py-arch.c +++ b/gdb/python/py-arch.c @@ -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 index 0000000000..6ccd17ef86 --- /dev/null +++ b/gdb/python/py-registers.c @@ -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 . */ + +#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, + ®ister_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, + ®ister_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 (®ister_descriptor_object_type) < 0) + return -1; + if (gdb_pymodule_addobject + (gdb_module, "RegisterDescriptor", + (PyObject *) ®ister_descriptor_object_type) < 0) + return -1; + + register_descriptor_iterator_object_type.tp_new = PyType_GenericNew; + if (PyType_Ready (®ister_descriptor_iterator_object_type) < 0) + return -1; + return (gdb_pymodule_addobject + (gdb_module, "RegisterDescriptorIterator", + (PyObject *) ®ister_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 */ +}; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index e352b30382..758dc553ce 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -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 create_thread_object (struct thread_info *tp); gdbpy_ref<> thread_to_thread_object (thread_info *thr);; gdbpy_ref 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) diff --git a/gdb/python/python.c b/gdb/python/python.c index 4bdd2201ab..7787dce4b4 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -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) diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index bf5b89bd0f..9562c2cffa 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2020-07-06 Andrew Burgess + + * gdb.python/py-arch-reg-names.exp: New file. + 2020-07-06 Andrew Burgess * 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 index 0000000000..14bc0a822a --- /dev/null +++ b/gdb/testsuite/gdb.python/py-arch-reg-names.exp @@ -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 . + +# 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" -- 2.34.1