binutils-gdb/gdb/python/py-infthread.c
Matthieu Longo e3998113f9 gdb: add new helpers for retrieving a type's fully qualified name
Py_TYPE (self)->tp_name is the traditional idiomatic way to get a Python
type's fully qualified name. However, in the context of the Python
limited API, PyTypeObject is opaque, so the 'tp_name' attribute is no
longer accessible. Additionally, retrieving the type of a Python object
requires Py_TYPE, which is only available as part of the stable API
starting with Python 3.14.

This patch increases minimal Python limited API version from 3.11 to 3.14.
It also introduces two new helpers to retrieve a type's fully qualified
name: gdb_py_tp_name() and gdbpy_py_obj_tp_name(), which extract the fully
qualified name from a PyTypeObject and a PyObject, respectively. Ifdefery
allows these wrappers to select the appropriate API depending on the Python
version and whether the Python limited API is enabled. For any Python
version less than 3.13, gdb_py_tp_name() fallbacks using __qualname__
instead. However, the result may differ slightly in some cases, e.g. the
module name may be missing.

Finally, this patch adapts the existing code to use these wrappers, and
adjusts some test expectations to use the fully qualified name (or
__qualname__ for versions <= 3.13) where it was not previously used.
Note that the corner case where the module name would be missing does not
appear in the testsuite.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830

Approved-By: Tom Tromey <tom@tromey.com>
2026-04-08 11:49:30 +01:00

501 lines
13 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Python interface to inferior threads.
Copyright (C) 2009-2026 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 "gdbthread.h"
#include "inferior.h"
#include "python-internal.h"
extern PyTypeObject thread_object_type;
/* Require that INFERIOR be a valid inferior ID. */
#define THPY_REQUIRE_VALID(Thread) \
do { \
if (!Thread->thread) \
{ \
PyErr_SetString (PyExc_RuntimeError, \
_("Thread no longer exists.")); \
return NULL; \
} \
} while (0)
gdbpy_ref<thread_object>
create_thread_object (struct thread_info *tp)
{
gdbpy_ref<thread_object> thread_obj;
gdbpy_ref<inferior_object> inf_obj = inferior_to_inferior_object (tp->inf);
if (inf_obj == NULL)
return nullptr;
thread_obj.reset (PyObject_New (thread_object, &thread_object_type));
if (thread_obj == NULL)
return nullptr;
thread_obj->thread = tp;
thread_obj->inf_obj = (PyObject *) inf_obj.release ();
if (!thread_obj->allocate_dict ())
return nullptr;
return thread_obj;
}
static void
thpy_dealloc (PyObject *self)
{
thread_object *thr_obj = (thread_object *) self;
gdb_assert (thr_obj->inf_obj != nullptr);
Py_DECREF (thr_obj->inf_obj);
Py_XDECREF (thr_obj->dict);
Py_TYPE (self)->tp_free (self);
}
static PyObject *
thpy_get_name (PyObject *self, void *ignore)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
const char *name = thread_name (thread_obj->thread);
if (name == NULL)
Py_RETURN_NONE;
return PyUnicode_FromString (name);
}
/* Return a string containing target specific additional information about
the state of the thread, or None, if there is no such additional
information. */
static PyObject *
thpy_get_details (PyObject *self, void *ignore)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
/* GCC can't tell that extra_info will always be assigned after the
'catch', so initialize it. */
const char *extra_info = nullptr;
try
{
extra_info = target_extra_thread_info (thread_obj->thread);
}
catch (const gdb_exception &except)
{
return gdbpy_handle_gdb_exception (nullptr, except);
}
if (extra_info == nullptr)
Py_RETURN_NONE;
return PyUnicode_FromString (extra_info);
}
static int
thpy_set_name (PyObject *self, PyObject *newvalue, void *ignore)
{
thread_object *thread_obj = (thread_object *) self;
gdb::unique_xmalloc_ptr<char> name;
if (! thread_obj->thread)
{
PyErr_SetString (PyExc_RuntimeError, _("Thread no longer exists."));
return -1;
}
if (newvalue == NULL)
{
PyErr_SetString (PyExc_TypeError,
_("Cannot delete `name' attribute."));
return -1;
}
else if (newvalue == Py_None)
{
/* Nothing. */
}
else if (! gdbpy_is_string (newvalue))
{
PyErr_SetString (PyExc_TypeError,
_("The value of `name' must be a string."));
return -1;
}
else
{
name = python_string_to_host_string (newvalue);
if (! name)
return -1;
}
thread_obj->thread->set_name (std::move (name));
return 0;
}
/* Getter for InferiorThread.num. */
static PyObject *
thpy_get_num (PyObject *self, void *closure)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
gdbpy_ref<> result
= gdb_py_object_from_longest (thread_obj->thread->per_inf_num);
return result.release ();
}
/* Getter for InferiorThread.global_num. */
static PyObject *
thpy_get_global_num (PyObject *self, void *closure)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
gdbpy_ref<> result
= gdb_py_object_from_longest (thread_obj->thread->global_num);
return result.release ();
}
/* Getter for InferiorThread.ptid -> (pid, lwp, tid).
Returns a tuple with the thread's ptid components. */
static PyObject *
thpy_get_ptid (PyObject *self, void *closure)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
return gdbpy_create_ptid_object (thread_obj->thread->ptid).release ();
}
/* Implement gdb.InferiorThread.ptid_string attribute. */
static PyObject *
thpy_get_ptid_string (PyObject *self, void *closure)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
ptid_t ptid = thread_obj->thread->ptid;
try
{
/* Select the correct inferior before calling a target_* function. */
scoped_restore_current_thread restore_thread;
switch_to_inferior_no_thread (thread_obj->thread->inf);
std::string ptid_str = target_pid_to_str (ptid);
return PyUnicode_FromString (ptid_str.c_str ());
}
catch (const gdb_exception &except)
{
return gdbpy_handle_gdb_exception (nullptr, except);
}
}
/* Getter for InferiorThread.inferior -> Inferior. */
static PyObject *
thpy_get_inferior (PyObject *self, void *ignore)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
Py_INCREF (thread_obj->inf_obj);
return thread_obj->inf_obj;
}
/* Implementation of InferiorThread.switch ().
Makes this the GDB selected thread. */
static PyObject *
thpy_switch (PyObject *self, PyObject *args)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
try
{
switch_to_thread (thread_obj->thread);
}
catch (const gdb_exception &except)
{
return gdbpy_handle_gdb_exception (nullptr, except);
}
Py_RETURN_NONE;
}
/* Implementation of InferiorThread.is_stopped () -> Boolean.
Return whether the thread is stopped. */
static PyObject *
thpy_is_stopped (PyObject *self, PyObject *args)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
if (thread_obj->thread->state () == THREAD_STOPPED)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
/* Implementation of InferiorThread.is_running () -> Boolean.
Return whether the thread is running. */
static PyObject *
thpy_is_running (PyObject *self, PyObject *args)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
if (thread_obj->thread->state () == THREAD_RUNNING)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
/* Implementation of InferiorThread.is_exited () -> Boolean.
Return whether the thread is exited. */
static PyObject *
thpy_is_exited (PyObject *self, PyObject *args)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
if (thread_obj->thread->state () == THREAD_EXITED)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
/* Implementation of gdb.InfThread.is_valid (self) -> Boolean.
Returns True if this inferior Thread object still exists
in GDB. */
static PyObject *
thpy_is_valid (PyObject *self, PyObject *args)
{
thread_object *thread_obj = (thread_object *) self;
if (! thread_obj->thread)
Py_RETURN_FALSE;
Py_RETURN_TRUE;
}
/* Implementation of gdb.InferiorThread.handle (self) -> handle. */
static PyObject *
thpy_thread_handle (PyObject *self, PyObject *args)
{
thread_object *thread_obj = (thread_object *) self;
THPY_REQUIRE_VALID (thread_obj);
gdb::array_view<const gdb_byte> hv;
try
{
hv = target_thread_info_to_thread_handle (thread_obj->thread);
}
catch (const gdb_exception &except)
{
return gdbpy_handle_gdb_exception (nullptr, except);
}
if (hv.size () == 0)
{
PyErr_SetString (PyExc_RuntimeError, _("Thread handle not found."));
return NULL;
}
PyObject *object = PyBytes_FromStringAndSize ((const char *) hv.data (),
hv.size());
return object;
}
/* Implement repr() for gdb.InferiorThread. */
static PyObject *
thpy_repr (PyObject *self)
{
thread_object *thread_obj = (thread_object *) self;
if (thread_obj->thread == nullptr)
return gdb_py_invalid_object_repr (self);
thread_info *thr = thread_obj->thread;
return PyUnicode_FromFormat ("<%s id=%s target-id=\"%s\">",
gdbpy_py_obj_tp_name (self),
print_full_thread_id (thr),
target_pid_to_str (thr->ptid).c_str ());
}
/* See python-internal.h. */
gdbpy_ref<>
gdbpy_create_ptid_object (ptid_t ptid)
{
int pid = ptid.pid ();
long lwp = ptid.lwp ();
ULONGEST tid = ptid.tid ();
gdbpy_ref<> pid_obj = gdb_py_object_from_longest (pid);
if (pid_obj == nullptr)
return nullptr;
gdbpy_ref<> lwp_obj = gdb_py_object_from_longest (lwp);
if (lwp_obj == nullptr)
return nullptr;
gdbpy_ref<> tid_obj = gdb_py_object_from_ulongest (tid);
if (tid_obj == nullptr)
return nullptr;
gdbpy_ref<> ret (PyTuple_New (3));
if (ret == nullptr)
return nullptr;
/* Note that these steal references, hence the use of 'release'. */
if (PyTuple_SetItem (ret.get (), 0, pid_obj.release ()) < 0
|| PyTuple_SetItem (ret.get (), 1, lwp_obj.release ()) < 0
|| PyTuple_SetItem (ret.get (), 2, tid_obj.release ()) < 0)
return nullptr;
return ret;
}
/* Implementation of gdb.selected_thread () -> gdb.InferiorThread.
Returns the selected thread object. */
PyObject *
gdbpy_selected_thread (PyObject *self, PyObject *args)
{
if (inferior_ptid != null_ptid)
return thread_to_thread_object (inferior_thread ()).release ();
Py_RETURN_NONE;
}
static int
gdbpy_initialize_thread ()
{
return gdbpy_type_ready (&thread_object_type);
}
GDBPY_INITIALIZE_FILE (gdbpy_initialize_thread);
static gdb_PyGetSetDef thread_object_getset[] =
{
gdbpy_dict_wrapper_cfg_dict_getter ("thread"),
{ "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,
"A target specific string containing extra thread state details.",
NULL },
{ "num", thpy_get_num, NULL,
"Per-inferior number of the thread, as assigned by GDB.", NULL },
{ "global_num", thpy_get_global_num, NULL,
"Global number of the thread, as assigned by GDB.", NULL },
{ "ptid", thpy_get_ptid, NULL, "ID of the thread, as assigned by the OS.",
NULL },
{ "ptid_string", thpy_get_ptid_string, nullptr,
"A string representing ptid, as used by, for example, 'info threads'.",
nullptr },
{ "inferior", thpy_get_inferior, NULL,
"The Inferior object this thread belongs to.", NULL },
{ NULL }
};
static PyMethodDef thread_object_methods[] =
{
{ "is_valid", thpy_is_valid, METH_NOARGS,
"is_valid () -> Boolean.\n\
Return true if this inferior thread is valid, false if not." },
{ "switch", thpy_switch, METH_NOARGS,
"switch ()\n\
Makes this the GDB selected thread." },
{ "is_stopped", thpy_is_stopped, METH_NOARGS,
"is_stopped () -> Boolean\n\
Return whether the thread is stopped." },
{ "is_running", thpy_is_running, METH_NOARGS,
"is_running () -> Boolean\n\
Return whether the thread is running." },
{ "is_exited", thpy_is_exited, METH_NOARGS,
"is_exited () -> Boolean\n\
Return whether the thread is exited." },
{ "handle", thpy_thread_handle, METH_NOARGS,
"handle () -> handle\n\
Return thread library specific handle for thread." },
{ NULL }
};
PyTypeObject thread_object_type =
{
PyVarObject_HEAD_INIT (NULL, 0)
"gdb.InferiorThread", /*tp_name*/
sizeof (thread_object), /*tp_basicsize*/
0, /*tp_itemsize*/
thpy_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
thpy_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
gdbpy_dict_wrapper_getsetattro,
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"GDB thread object", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
thread_object_methods, /* tp_methods */
0, /* tp_members */
thread_object_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0 /* tp_alloc */
};