mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-24 12:35:55 +08:00
3965bff5b9
Currently, when we add a new python sub-system to GDB, e.g. py-inferior.c, we end up having to create a new function like gdbpy_initialize_inferior, which then has to be called from the function do_start_initialization in python.c. In some cases (py-micmd.c and py-tui.c), we have two functions gdbpy_initialize_*, and gdbpy_finalize_*, with the second being called from finalize_python which is also in python.c. This commit proposes a mechanism to manage these initialization and finalization calls, this means that adding a new Python subsystem will no longer require changes to python.c or python-internal.h, instead, the initialization and finalization functions will be registered directly from the sub-system file, e.g. py-inferior.c, or py-micmd.c. The initialization and finalization functions are managed through a new class gdbpy_initialize_file in python-internal.h. This class contains a single global vector of all the initialization and finalization functions. In each Python sub-system we create a new gdbpy_initialize_file object, the object constructor takes care of registering the two callback functions. Now from python.c we can call static functions on the gdbpy_initialize_file class which take care of walking the callback list and invoking each callback in turn. To slightly simplify the Python sub-system files I added a new macro GDBPY_INITIALIZE_FILE, which hides the need to create an object. We can now just do this: GDBPY_INITIALIZE_FILE (gdbpy_initialize_registers); One possible problem with this change is that there is now no guaranteed ordering of how the various sub-systems are initialized (or finalized). To try and avoid dependencies creeping in I have added a use of the environment variable GDB_REVERSE_INIT_FUNCTIONS, this is the same environment variable used in the generated init.c file. Just like with init.c, when this environment variable is set we reverse the list of Python initialization (and finalization) functions. As there is already a test that starts GDB with the environment variable set then this should offer some level of protection against dependencies creeping in - though for full protection I guess we'd need to run all gdb.python/*.exp tests with the variable set. I have tested this patch with the environment variable set, and saw no regressions, so I think we are fine right now. One other change of note was for gdbpy_initialize_gdb_readline, this function previously returned void. In order to make this function have the correct signature I've updated its return type to int, and we now return 0 to indicate success. All of the other initialize (and finalize) functions have been made static within their respective sub-system files. There should be no user visible changes after this commit.
461 lines
11 KiB
C
461 lines
11 KiB
C
/* Python interface to inferior threads.
|
||
|
||
Copyright (C) 2009-2023 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;
|
||
|
||
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)
|
||
{
|
||
GDB_PY_HANDLE_EXCEPTION (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);
|
||
}
|
||
|
||
/* 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 lwp;
|
||
ULONGEST tid;
|
||
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_ulongest (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;
|
||
}
|
||
|
||
static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
|
||
gdbpy_initialize_thread (void)
|
||
{
|
||
if (PyType_Ready (&thread_object_type) < 0)
|
||
return -1;
|
||
|
||
return gdb_pymodule_addobject (gdb_module, "InferiorThread",
|
||
(PyObject *) &thread_object_type);
|
||
}
|
||
|
||
GDBPY_INITIALIZE_FILE (gdbpy_initialize_thread);
|
||
|
||
|
||
|
||
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 },
|
||
{ "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 },
|
||
{ "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 */
|
||
};
|