
Python 2 has a bit flag Py_TPFLAGS_HAVE_ITER which can be passed as part of the tp_flags field when defining a new object type. This flag is not defined in Python 3 and so we define it to 0 in python-internal.h (when IS_PY3K is defined). The meaning of this flag is that the object has the fields tp_iter and tp_iternext. Note the use of "has" here, the flag says nothing about the values in those fields, just that the type object has the fields. In early versions of Python 2 these fields were no part of the PyTypeObject struct, they were added in version 2.2 (see https://docs.python.org/release/2.3/api/type-structs.html). And so, there could be a some code compiled out there which has a PyTypeObject structure within it that doesn't even have the tp_iter and tp_iternext fields, attempting to access these fields would be undefined behaviour. And so Python added the Py_TPFLAGS_HAVE_ITER flag. If the flag is present then Python is free to access the tp_iter and tp_iternext fields. If we consider GDB then we always assume that the tp_iter and tp_iternext fields are part of PyTypeObject. If someone was crazy enough to try and compile GDB against Python 2.1 then we'd get lots of build errors saying that we were passing too many fields when initializing PyTypeObject structures. And so, I claim, we can be sure that GDB will always be compiled with a version of Python that has the tp_iter and tp_iternext fields in PyTypeObject. Next we can look at the Py_TPFLAGS_DEFAULT flag. In Python 2, each time additional fields are added to PyTypeObject a new Py_TPFLAGS_* flag would be defined to indicate whether those flags are present or not. And, those new flags would be added to Py_TPFLAGS_DEFAULT. And so, in the latest version of Python 2 the Py_TPFLAGS_DEFAULT flag includes Py_TPFLAGS_HAVE_ITER (see https://docs.python.org/2.7/c-api/typeobj.html). In GDB we pass Py_TPFLAGS_DEFAULT as part of the tp_flags for all objects we define. And so, in this commit, I propose to remove all uses of Py_TPFLAGS_HAVE_ITER from GDB, it's simply not needed. There should be no user visible changes after this commit.
429 lines
10 KiB
C
429 lines
10 KiB
C
/* Python interface to inferior threads.
|
|
|
|
Copyright (C) 2009-2021 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 "defs.h"
|
|
#include "gdbthread.h"
|
|
#include "inferior.h"
|
|
#include "python-internal.h"
|
|
|
|
extern PyTypeObject thread_object_type
|
|
CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("thread_object");
|
|
|
|
/* 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 NULL;
|
|
|
|
thread_obj.reset (PyObject_New (thread_object, &thread_object_type));
|
|
if (thread_obj == NULL)
|
|
return NULL;
|
|
|
|
thread_obj->thread = tp;
|
|
thread_obj->inf_obj = (PyObject *) inf_obj.release ();
|
|
|
|
return thread_obj;
|
|
}
|
|
|
|
static void
|
|
thpy_dealloc (PyObject *self)
|
|
{
|
|
Py_DECREF (((thread_object *) self)->inf_obj);
|
|
Py_TYPE (self)->tp_free (self);
|
|
}
|
|
|
|
static PyObject *
|
|
thpy_get_name (PyObject *self, void *ignore)
|
|
{
|
|
thread_object *thread_obj = (thread_object *) self;
|
|
const char *name;
|
|
|
|
THPY_REQUIRE_VALID (thread_obj);
|
|
|
|
name = thread_obj->thread->name;
|
|
if (name == NULL)
|
|
name = target_thread_name (thread_obj->thread);
|
|
|
|
if (name == NULL)
|
|
Py_RETURN_NONE;
|
|
|
|
return PyString_FromString (name);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
xfree (thread_obj->thread->name);
|
|
thread_obj->thread->name = name.release ();
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
GDB_PY_HANDLE_EXCEPTION (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::byte_vector hv;
|
|
|
|
try
|
|
{
|
|
hv = target_thread_info_to_thread_handle (thread_obj->thread);
|
|
}
|
|
catch (const gdb_exception &except)
|
|
{
|
|
GDB_PY_HANDLE_EXCEPTION (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;
|
|
}
|
|
|
|
/* Return a reference to a new Python object representing a ptid_t.
|
|
The object is a tuple containing (pid, lwp, tid). */
|
|
PyObject *
|
|
gdbpy_create_ptid_object (ptid_t ptid)
|
|
{
|
|
int pid;
|
|
long tid, lwp;
|
|
PyObject *ret;
|
|
|
|
ret = PyTuple_New (3);
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
pid = ptid.pid ();
|
|
lwp = ptid.lwp ();
|
|
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_longest (tid);
|
|
if (tid_obj == nullptr)
|
|
return nullptr;
|
|
|
|
/* Note that these steal references, hence the use of 'release'. */
|
|
PyTuple_SET_ITEM (ret, 0, pid_obj.release ());
|
|
PyTuple_SET_ITEM (ret, 1, lwp_obj.release ());
|
|
PyTuple_SET_ITEM (ret, 2, tid_obj.release ());
|
|
|
|
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;
|
|
}
|
|
|
|
int
|
|
gdbpy_initialize_thread (void)
|
|
{
|
|
if (PyType_Ready (&thread_object_type) < 0)
|
|
return -1;
|
|
|
|
return gdb_pymodule_addobject (gdb_module, "InferiorThread",
|
|
(PyObject *) &thread_object_type);
|
|
}
|
|
|
|
static gdb_PyGetSetDef thread_object_getset[] =
|
|
{
|
|
{ "name", thpy_get_name, thpy_set_name,
|
|
"The name of the thread, as set by the user or the OS.", 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 },
|
|
{ "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*/
|
|
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, /*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 */
|
|
};
|