From: Matthieu Longo Date: Thu, 17 Jul 2025 17:36:41 +0000 (+0100) Subject: gdb: new setters and getters for __dict__, and attributes X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9a84753aa7f8b8939cf4eea9c7f1db4b42e171e1;p=thirdparty%2Fbinutils-gdb.git gdb: new setters and getters for __dict__, and attributes 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 --- diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c index 6847722628f..24b573b2dbd 100644 --- a/gdb/python/py-corefile.c +++ b/gdb/python/py-corefile.c @@ -26,20 +26,14 @@ /* A gdb.Corefile object. */ -struct corefile_object +struct corefile_object : public gdbpy_dict_wrapper { - PyObject_HEAD - /* The inferior this core file is attached to. This will be set to NULL when the inferior is deleted, or if a different core file is loaded for the inferior. When this is NULL the gdb.Corefile object is considered invalid.*/ struct inferior *inferior; - /* Dictionary holding user-added attributes. This is the __dict__ - attribute of the object. This is an owning reference. */ - PyObject *dict; - /* A Tuple of gdb.CorefileMappedFile objects. This tuple is only created the first time the user calls gdb.Corefile.mapped_files(), the result is cached here. If this pointer is not NULL then this is an owning @@ -511,8 +505,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_corefile); static gdb_PyGetSetDef corefile_getset[] = { - { "__dict__", gdb_py_generic_dict, nullptr, - "The __dict__ for the gdb.Corefile.", &corefile_object_type }, + { "__dict__", gdb_py_generic_dict_getter, nullptr, + "The __dict__ for the gdb.Corefile.", nullptr }, { "filename", cfpy_get_filename, nullptr, "The filename of a valid Corefile object.", nullptr }, { nullptr } @@ -548,8 +542,8 @@ PyTypeObject corefile_object_type = 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ + gdb_py_generic_getattro, /*tp_getattro*/ + gdb_py_generic_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB corefile object", /* tp_doc */ @@ -566,7 +560,7 @@ PyTypeObject corefile_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (corefile_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ diff --git a/gdb/python/py-event.c b/gdb/python/py-event.c index c985159a6f7..8fb21b7fdad 100644 --- a/gdb/python/py-event.c +++ b/gdb/python/py-event.c @@ -101,8 +101,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_event); static gdb_PyGetSetDef event_object_getset[] = { - { "__dict__", gdb_py_generic_dict, NULL, - "The __dict__ for this event.", &event_object_type }, + { "__dict__", gdb_py_generic_dict_getter, NULL, + "The __dict__ for this event.", NULL }, { NULL } }; @@ -124,8 +124,8 @@ PyTypeObject event_object_type = 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ + gdb_py_generic_getattro, /* tp_getattro */ + gdb_py_generic_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ "GDB event object", /* tp_doc */ @@ -142,7 +142,7 @@ PyTypeObject event_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (event_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0 /* tp_alloc */ }; diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h index 6c81d64eb4f..ec2e7bc03c5 100644 --- a/gdb/python/py-event.h +++ b/gdb/python/py-event.h @@ -31,12 +31,8 @@ #include "py-event-types.def" #undef GDB_PY_DEFINE_EVENT_TYPE -struct event_object -{ - PyObject_HEAD - - PyObject *dict; -}; +struct event_object : public gdbpy_dict_wrapper +{}; extern int emit_continue_event (ptid_t ptid); extern int emit_exited_event (const LONGEST *exit_code, struct inferior *inf); diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c index 8230f9d3943..69a7bfa3c37 100644 --- a/gdb/python/py-inferior.c +++ b/gdb/python/py-inferior.c @@ -32,25 +32,6 @@ #include "progspace-and-thread.h" #include "gdbsupport/unordered_map.h" -using thread_map_t - = gdb::unordered_map>; - -struct inferior_object -{ - PyObject_HEAD - - /* 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; - - /* Dictionary holding user-added attributes. - This is the __dict__ attribute of the object. */ - PyObject *dict; -}; - extern PyTypeObject inferior_object_type; /* Deleter to clean up when an inferior is removed. */ @@ -1061,8 +1042,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_inferior); static gdb_PyGetSetDef inferior_object_getset[] = { - { "__dict__", gdb_py_generic_dict, nullptr, - "The __dict__ for this inferior.", &inferior_object_type }, + { "__dict__", gdb_py_generic_dict_getter, nullptr, + "The __dict__ for this inferior.", nullptr }, { "arguments", infpy_get_args, infpy_set_args, "Arguments to this program.", nullptr }, { "num", infpy_get_num, NULL, "ID of inferior, as assigned by GDB.", NULL }, @@ -1144,8 +1125,8 @@ PyTypeObject inferior_object_type = 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ + gdb_py_generic_getattro, /* tp_getattro */ + gdb_py_generic_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ "GDB inferior object", /* tp_doc */ @@ -1162,7 +1143,7 @@ PyTypeObject inferior_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (inferior_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0 /* tp_alloc */ }; diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c index e5d3222f9ae..d75742360d4 100644 --- a/gdb/python/py-infthread.c +++ b/gdb/python/py-infthread.c @@ -415,8 +415,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_thread); static gdb_PyGetSetDef thread_object_getset[] = { - { "__dict__", gdb_py_generic_dict, nullptr, - "The __dict__ for this thread.", &thread_object_type }, + { "__dict__", gdb_py_generic_dict_getter, nullptr, + "The __dict__ for this thread.", nullptr }, { "name", thpy_get_name, thpy_set_name, "The name of the thread, as set by the user or the OS.", NULL }, { "details", thpy_get_details, NULL, @@ -479,8 +479,8 @@ PyTypeObject thread_object_type = 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ + gdb_py_generic_getattro, /*tp_getattro*/ + gdb_py_generic_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB thread object", /* tp_doc */ @@ -497,7 +497,7 @@ PyTypeObject thread_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (thread_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0 /* tp_alloc */ }; diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c index 8cf365a27dc..36acdb06a25 100644 --- a/gdb/python/py-objfile.c +++ b/gdb/python/py-objfile.c @@ -26,17 +26,11 @@ #include "python.h" #include "inferior.h" -struct objfile_object +struct objfile_object : public gdbpy_dict_wrapper { - PyObject_HEAD - /* The corresponding objfile. */ struct objfile *objfile; - /* Dictionary holding user-added attributes. - This is the __dict__ attribute of the object. */ - PyObject *dict; - /* The pretty-printer list of functions. */ PyObject *printers; @@ -739,8 +733,8 @@ Look up a static-linkage global symbol in this objfile and return it." }, static gdb_PyGetSetDef objfile_getset[] = { - { "__dict__", gdb_py_generic_dict, NULL, - "The __dict__ for this objfile.", &objfile_object_type }, + { "__dict__", gdb_py_generic_dict_getter, NULL, + "The __dict__ for this objfile.", NULL }, { "filename", objfpy_get_filename, NULL, "The objfile's filename, or None.", NULL }, { "username", objfpy_get_username, NULL, @@ -785,8 +779,8 @@ PyTypeObject objfile_object_type = 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ + gdb_py_generic_getattro, /*tp_getattro*/ + gdb_py_generic_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB objfile object", /* tp_doc */ @@ -803,7 +797,7 @@ PyTypeObject objfile_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (objfile_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ objfpy_new, /* tp_new */ diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c index ee26b761adb..19f5e533b0a 100644 --- a/gdb/python/py-progspace.c +++ b/gdb/python/py-progspace.c @@ -29,17 +29,11 @@ #include "observable.h" #include "inferior.h" -struct pspace_object +struct pspace_object : public gdbpy_dict_wrapper { - PyObject_HEAD - /* The corresponding pspace. */ struct program_space *pspace; - /* Dictionary holding user-added attributes. - This is the __dict__ attribute of the object. */ - PyObject *dict; - /* The pretty-printer list of functions. */ PyObject *printers; @@ -758,8 +752,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_pspace); static gdb_PyGetSetDef pspace_getset[] = { - { "__dict__", gdb_py_generic_dict, NULL, - "The __dict__ for this progspace.", &pspace_object_type }, + { "__dict__", gdb_py_generic_dict_getter, NULL, + "The __dict__ for this progspace.", NULL }, { "filename", pspy_get_filename, NULL, "The filename of the progspace's main symbol file, or None.", nullptr }, { "symbol_file", pspy_get_symbol_file, nullptr, @@ -821,8 +815,8 @@ PyTypeObject pspace_object_type = 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ + gdb_py_generic_getattro, /*tp_getattro*/ + gdb_py_generic_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB progspace object", /* tp_doc */ @@ -839,7 +833,7 @@ PyTypeObject pspace_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (pspace_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h index b09d88dc30d..4d44ca90e21 100644 --- a/gdb/python/py-ref.h +++ b/gdb/python/py-ref.h @@ -42,4 +42,42 @@ struct gdbpy_ref_policy template using gdbpy_ref = gdb::ref_ptr>; +/* A wrapper class for Python extension objects that have a __dict__ attribute. + + Any Python C object extension needing __dict__ should inherit from this + class. Given that the C extension object must also be convertible to + PyObject, this wrapper class publicly inherits from PyObject as well. + + Access to the dict requires a custom getter defined via PyGetSetDef. + gdb_PyGetSetDef my_object_getset[] = + { + { "__dict__", gdb_py_generic_dict_getter, nullptr, + "The __dict__ for this object.", nullptr }, + ... + { nullptr } + }; + + It is also important to note that __dict__ is used during the attribute + look-up. Since this dictionary is not managed by Python and is not exposed + via tp_dictoffset, custom attribute getter (tp_getattro) and setter + (tp_setattro) are required to correctly redirect attribute access to the + dictionary: + - gdb_py_generic_getattro (), assigned to tp_getattro for static types, + or Py_tp_getattro for heap-allocated types. + - gdb_py_generic_setattro (), assigned to tp_setattro for static types, + or Py_tp_setattro for heap-allocated types. */ +struct gdbpy_dict_wrapper : public PyObject +{ + /* Dictionary holding user-added attributes. + This is the __dict__ attribute of the object. */ + PyObject *dict; + + /* Compute the address of the __dict__ attribute for the given PyObject. */ + static PyObject **compute_addr (PyObject *self) + { + auto *wrapper = reinterpret_cast (self); + return &wrapper->dict; + } +}; + #endif /* GDB_PYTHON_PY_REF_H */ diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c index f39cb0240c8..76f001b99da 100644 --- a/gdb/python/py-type.c +++ b/gdb/python/py-type.c @@ -36,13 +36,8 @@ struct type_object : public PyObject extern PyTypeObject type_object_type; /* A Field object. */ -struct field_object -{ - PyObject_HEAD - - /* Dictionary holding our attributes. */ - PyObject *dict; -}; +struct field_object : public gdbpy_dict_wrapper +{}; extern PyTypeObject field_object_type; @@ -1707,8 +1702,8 @@ PyTypeObject type_object_type = static gdb_PyGetSetDef field_object_getset[] = { - { "__dict__", gdb_py_generic_dict, NULL, - "The __dict__ for this field.", &field_object_type }, + { "__dict__", gdb_py_generic_dict_getter, NULL, + "The __dict__ for this field.", NULL }, { NULL } }; @@ -1730,8 +1725,8 @@ PyTypeObject field_object_type = 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ + gdb_py_generic_getattro, /*tp_getattro*/ + gdb_py_generic_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "GDB field object", /* tp_doc */ @@ -1748,7 +1743,7 @@ PyTypeObject field_object_type = 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ - offsetof (field_object, dict), /* tp_dictoffset */ + 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ diff --git a/gdb/python/py-utils.c b/gdb/python/py-utils.c index 131230f80b3..8283b30db04 100644 --- a/gdb/python/py-utils.c +++ b/gdb/python/py-utils.c @@ -309,24 +309,92 @@ gdb_py_int_as_long (PyObject *obj, long *result) -/* Generic implementation of the __dict__ attribute for objects that - have a dictionary. The CLOSURE argument should be the type object. - This only handles positive values for tp_dictoffset. */ +/* Generic implementation of the getter for the __dict__ attribute for objects + having a dictionary. The CLOSURE argument is unused. */ PyObject * -gdb_py_generic_dict (PyObject *self, void *closure) +gdb_py_generic_dict_getter (PyObject *self, + void *closure ATTRIBUTE_UNUSED) { - PyObject *result; - PyTypeObject *type_obj = (PyTypeObject *) closure; - char *raw_ptr; + PyObject **py_dict_ptr = gdbpy_dict_wrapper::compute_addr (self); + PyObject *py_dict = *py_dict_ptr; + if (py_dict == nullptr) + { + PyErr_SetString (PyExc_AttributeError, + "This object has no __dict__"); + return nullptr; + } + return Py_NewRef (py_dict); +} - raw_ptr = (char *) self + type_obj->tp_dictoffset; - result = * (PyObject **) raw_ptr; +/* Generic attribute getter function similar to PyObject_GenericGetAttr () but + that should be used when the object has a dictionary __dict__. */ +PyObject * +gdb_py_generic_getattro (PyObject *self, PyObject *attr) +{ + PyObject *value = PyObject_GenericGetAttr (self, attr); + if (value != nullptr) + return value; + + if (! PyErr_ExceptionMatches (PyExc_AttributeError)) + return nullptr; + + gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr)); + if (dict == nullptr) + return nullptr; + + /* Clear previous AttributeError set by PyObject_GenericGetAttr when it + did not find the attribute, and try to get the attribute from __dict__. */ + PyErr_Clear(); + + value = PyDict_GetItemWithError (dict.get (), attr); + if (value != nullptr) + return Py_NewRef (value); + + /* If PyDict_GetItemWithError() returns NULL because an error occurred, it + sets an exception. Propagate it by returning NULL. */ + if (PyErr_Occurred () != nullptr) + return nullptr; + + /* If the key is not found, PyDict_GetItemWithError() returns NULL without + setting an exception. Failing to set one here would later result in: + : error return without exception set + Therefore, we must explicitly raise an AttributeError in this case. */ + PyErr_Format (PyExc_AttributeError, + "'%s' object has no attribute '%s'", + Py_TYPE (self)->tp_name, + PyUnicode_AsUTF8AndSize (attr, nullptr)); + return nullptr; +} - Py_INCREF (result); - return result; +/* Generic attribute setter function similar to PyObject_GenericSetAttr () but + that should be used when the object has a dictionary __dict__. */ +int +gdb_py_generic_setattro (PyObject *self, PyObject *attr, PyObject *value) +{ + if (PyObject_GenericSetAttr (self, attr, value) == 0) + return 0; + + if (! PyErr_ExceptionMatches (PyExc_AttributeError)) + return -1; + + gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr)); + if (dict == nullptr) + return -1; + + /* Clear previous AttributeError set by PyObject_GenericGetAttr() when it + did not find the attribute, and try to set the attribute into __dict__. */ + PyErr_Clear(); + + /* Set the new value. + Note: the old value is managed by PyDict_SetItem(), so no need to get + a borrowed reference on it and decrement its reference counter before + setting a new value. */ + return PyDict_SetItem (dict.get (), attr, value); } + + /* Like PyModule_AddObject, but does not steal a reference to OBJECT. */ 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);