From 9a84753aa7f8b8939cf4eea9c7f1db4b42e171e1 Mon Sep 17 00:00:00 2001 From: Matthieu Longo Date: Thu, 17 Jul 2025 18:36:41 +0100 Subject: gdb: new setters and getters for __dict__, and attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GDB is currently using the Python unlimited API. Migrating the codebase to the Python limited API would have for benefit to make a GDB build artifacts compatible with older and newer versions of Python that they were built with. This patch prepares the ground for migrating the existing C extension types from static types to heap-allocated ones, by removing the dependency on tp_dictoffset, which is unavailable when using the limited API. One of the most common incompatibilities in the current static type declarations is the tp_dictoffset slot, which specifies the dictionary offset within the instance structure. Historically, the unlimited API has provided two approaches to supply a dictionary for __dict__: * A managed dictionary. Setting Py_TPFLAGS_MANAGED_DICT in tp_flags indicates that the instances of the type have a __dict__ attribute, and that the dictionary is managed by Python. According to the Python documentation, this is the recommended approach. However, this flag was introduced in 3.12, together with PyObject_VisitManagedDict() and PyObject_ClearManagedDict(), neither of which is part of the limited API (at least for now). As a result, this recommended approach is not viable in the context of the limited API. * An instance dictionary, for which the offset in the instance is provided via tp_dictoffset. According to the Python documentation, this "tp slot" is on the deprecation path, and Py_TPFLAGS_MANAGED_DICT should be used instead. Given the age of the GDB codebase and the requirement to support older Python versions (>= 3.4), no need to argue that today, the implementation of __dict__ relies on tp_dictoffset. However, in the context of the limited API, PyType_Slot does not provide a Py_tp_dictoffset member, so another approach is needed to provide __dict__ to instances of C extension types. Given the constraints of the limited API, the proposed solution consists in providing a dictionary through a common base class, gdbpy__dict__wrapper. This helper class owns a dictionary member corresponding to __dict__, and any C extension type requiring a __dict__ must inherit from it. Since extension object must also be convertible to PyObject, this wrapper class publicly inherits from PyObject as well. Access to the dictionary is provided via a custom getter defined in a PyGetSetDef, similarily to what was previously done with gdb_py_generic_dict(). Because __dict__ participates in attribute look-up, and since this dictionary is neither managed by Python nor exposed via tp_dictoffset, custom implementations of tp_getattro and tp_setattro are required to correctly redirect attribute look-ups to the dictionary. These custom implementations — equivalent to PyObject_GenericGetAttr() and PyObject_GenericSetAttr() — must be installed via tp_getattro / tp_setattro for static types, or Py_tp_getattro / Py_tp_setattro for heap-allocated types. - gdbpy__dict__wrapper: a base class for C extension objects that own a __dict__. - gdb_py_generic_dict_getter: a __dict__ getter for extension types derived from gdbpy__dict__wrapper. - gdb_py_generic_getattro: equivalent of PyObject_GenericGetAttr, but fixes the look-up of __dict__. - gdb_py_generic_setattro: equivalent of PyObject_GenericSetAttr, but fixes the look-up of __dict__. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830 Approved-By: Tom Tromey --- gdb/python/python-internal.h | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) (limited to 'gdb/python/python-internal.h') diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 95619bf775e..f6915a62b7a 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -107,6 +107,15 @@ typedef unsigned long gdb_py_ulongest; #endif /* HAVE_LONG_LONG */ +#if PY_VERSION_HEX < 0x030a0000 +static inline PyObject * +Py_NewRef (PyObject *obj) +{ + Py_INCREF (obj); + return obj; +} +#endif + /* A template variable holding the format character (as for Py_BuildValue) for a given type. */ template @@ -384,22 +393,27 @@ struct gdbpy_breakpoint_object extern gdbpy_breakpoint_object *bppy_pending_object; -struct thread_object +struct thread_object : public gdbpy_dict_wrapper { - PyObject_HEAD - /* The thread we represent. */ struct thread_info *thread; /* The Inferior object to which this thread belongs. */ PyObject *inf_obj; - - /* Dictionary holding user-added attributes. This is the __dict__ - attribute of the object. */ - PyObject *dict; }; -struct inferior_object; +using thread_map_t + = gdb::unordered_map>; + +struct inferior_object : public gdbpy_dict_wrapper +{ + /* The inferior we represent. */ + struct inferior *inferior; + + /* thread_object instances under this inferior. This owns a + reference to each object it contains. */ + thread_map_t *threads; +}; extern struct cmd_list_element *set_python_list; extern struct cmd_list_element *show_python_list; @@ -989,7 +1003,9 @@ gdbpy_ref<> gdb_py_object_from_longest (LONGEST l); gdbpy_ref<> gdb_py_object_from_ulongest (ULONGEST l); int gdb_py_int_as_long (PyObject *, long *); -PyObject *gdb_py_generic_dict (PyObject *self, void *closure); +PyObject *gdb_py_generic_dict_getter (PyObject *self, void *closure); +PyObject *gdb_py_generic_getattro (PyObject *self, PyObject *attr); +int gdb_py_generic_setattro (PyObject *self, PyObject *attr, PyObject *value); int gdb_pymodule_addobject (PyObject *mod, const char *name, PyObject *object); -- cgit v1.2.3