gdb/python: make more use of RegisterDescriptors

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.
This commit is contained in:
Andrew Burgess
2020-07-22 12:13:11 +01:00
parent 14fa8fb307
commit 43d5901ded
9 changed files with 153 additions and 57 deletions

View 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> 2020-07-28 Andrew Burgess <andrew.burgess@embecosm.com>
* python/py-registers.c: Add 'user-regs.h' include. * python/py-registers.c: Add 'user-regs.h' include.

View 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> 2020-07-28 Andrew Burgess <andrew.burgess@embecosm.com>
* python.texi (Registers In Python): Document new find function. * python.texi (Registers In Python): Document new find function.

View File

@ -2458,12 +2458,11 @@ provides a method to read frame's registers:
@defun PendingFrame.read_register (reg) @defun PendingFrame.read_register (reg)
This method returns the contents of the register @var{reg} in the 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 frame as a @code{gdb.Value} object. For a description of the
register number or a register name; the values are platform-specific. acceptable values of @var{reg} see
They are usually found in the corresponding @ref{gdbpy_frame_read_register,,Frame.read_register}. If @var{reg}
@file{@var{platform}-tdep.h} file in the @value{GDBN} source tree. If does not name a register for the current architecture, this method
@var{reg} does not name a register for the current architecture, this will throw an exception.
method will throw an exception.
Note that this method will always return a @code{gdb.Value} for a 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. 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: specify caller registers that have been saved in this frame:
@defun gdb.UnwindInfo.add_saved_register (reg, value) @defun gdb.UnwindInfo.add_saved_register (reg, value)
@var{reg} identifies the register. It can be a number or a name, just @var{reg} identifies the register, for a description of the acceptable
as for the @code{PendingFrame.read_register} method above. values see @ref{gdbpy_frame_read_register,,Frame.read_register}.
@var{value} is a register value (a @code{gdb.Value} object). @var{value} is a register value (a @code{gdb.Value} object).
@end defun @end defun
@ -4687,10 +4686,29 @@ Return the frame's symtab and line object.
@anchor{gdbpy_frame_read_register} @anchor{gdbpy_frame_read_register}
@defun Frame.read_register (register) @defun Frame.read_register (register)
Return the value of @var{register} in this frame. The @var{register} Return the value of @var{register} in this frame. Returns a
argument must be a string (e.g., @code{'sp'} or @code{'rax'}). @code{Gdb.Value} object. Throws an exception if @var{register} does
Returns a @code{Gdb.Value} object. Throws an exception if @var{register} not exist. The @var{register} argument must be one of the following:
does not exist. @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 @end defun
@defun Frame.read_var (variable @r{[}, block@r{]}) @defun Frame.read_var (variable @r{[}, block@r{]})

View File

@ -27,7 +27,6 @@
#include "python-internal.h" #include "python-internal.h"
#include "symfile.h" #include "symfile.h"
#include "objfiles.h" #include "objfiles.h"
#include "user-regs.h"
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
@ -242,12 +241,11 @@ frapy_pc (PyObject *self, PyObject *args)
static PyObject * static PyObject *
frapy_read_register (PyObject *self, PyObject *args) frapy_read_register (PyObject *self, PyObject *args)
{ {
const char *regnum_str; PyObject *pyo_reg_id;
struct value *val = NULL; struct value *val = NULL;
if (!PyArg_ParseTuple (args, "s", &regnum_str)) if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
return NULL; return NULL;
try try
{ {
struct frame_info *frame; struct frame_info *frame;
@ -255,14 +253,18 @@ frapy_read_register (PyObject *self, PyObject *args)
FRAPY_REQUIRE_VALID (self, frame); FRAPY_REQUIRE_VALID (self, frame);
regnum = user_reg_map_name_to_regnum (get_frame_arch (frame), if (!gdbpy_parse_register_id (get_frame_arch (frame), pyo_reg_id,
regnum_str, &regnum))
strlen (regnum_str)); {
if (regnum >= 0) PyErr_SetString (PyExc_ValueError, "Bad register");
val = value_of_register (regnum, frame); return NULL;
}
gdb_assert (regnum >= 0);
val = value_of_register (regnum, frame);
if (val == NULL) if (val == NULL)
PyErr_SetString (PyExc_ValueError, _("Unknown register.")); PyErr_SetString (PyExc_ValueError, _("Can't read register."));
} }
catch (const gdb_exception &except) catch (const gdb_exception &except)
{ {

View File

@ -370,6 +370,59 @@ register_descriptor_iter_find (PyObject *self, PyObject *args, PyObject *kw)
Py_RETURN_NONE; 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. */ /* Initializes the new Python classes from this file in the gdb module. */
int int

View File

@ -27,7 +27,6 @@
#include "python-internal.h" #include "python-internal.h"
#include "regcache.h" #include "regcache.h"
#include "valprint.h" #include "valprint.h"
#include "user-regs.h"
#define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level) \ #define TRACE_PY_UNWIND(level, args...) if (pyuw_debug >= level) \
{ fprintf_unfiltered (gdb_stdlog, args); } { fprintf_unfiltered (gdb_stdlog, args); }
@ -101,37 +100,6 @@ static unsigned int pyuw_debug = 0;
static struct gdbarch_data *pyuw_gdbarch_data; 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, /* Convert gdb.Value instance to inferior's pointer. Return 1 on success,
0 on failure. */ 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, if (!PyArg_UnpackTuple (args, "previous_frame_register", 2, 2,
&pyo_reg_id, &pyo_reg_value)) &pyo_reg_id, &pyo_reg_value))
return NULL; 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"); PyErr_SetString (PyExc_ValueError, "Bad register");
return NULL; 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)) if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
return NULL; 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"); PyErr_SetString (PyExc_ValueError, "Bad register");
return NULL; return NULL;

View File

@ -776,4 +776,23 @@ struct Py_buffer_deleter
/* A unique_ptr specialization for Py_buffer. */ /* A unique_ptr specialization for Py_buffer. */
typedef std::unique_ptr<Py_buffer, Py_buffer_deleter> Py_buffer_up; 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 */ #endif /* PYTHON_PYTHON_INTERNAL_H */

View 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> 2020-07-28 Andrew Burgess <andrew.burgess@embecosm.com>
* gdb.python/py-arch-reg-names.exp: Add additional tests. * gdb.python/py-arch-reg-names.exp: Add additional tests.

View File

@ -33,12 +33,19 @@ class FrameId(object):
class TestUnwinder(Unwinder): class TestUnwinder(Unwinder):
AMD64_RBP = 6 AMD64_RBP = 6
AMD64_RSP = 7 AMD64_RSP = 7
AMD64_RIP = 16 AMD64_RIP = None
def __init__(self): def __init__(self):
Unwinder.__init__(self, "test unwinder") Unwinder.__init__(self, "test unwinder")
self.char_ptr_t = gdb.lookup_type("unsigned char").pointer() self.char_ptr_t = gdb.lookup_type("unsigned char").pointer()
self.char_ptr_ptr_t = self.char_ptr_t.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): def _read_word(self, address):
return address.cast(self.char_ptr_ptr_t).dereference() return address.cast(self.char_ptr_ptr_t).dereference()
@ -77,6 +84,8 @@ class TestUnwinder(Unwinder):
if (inf_arch != frame_arch): if (inf_arch != frame_arch):
raise gdb.GdbError ("architecture mismatch") raise gdb.GdbError ("architecture mismatch")
self._update_register_descriptors (frame_arch)
try: try:
# NOTE: the registers in Unwinder API can be referenced # NOTE: the registers in Unwinder API can be referenced
# either by name or by number. The code below uses both # either by name or by number. The code below uses both