[6/6] gdb/python: Add gdb.InferiorThread.__dict__ attribute
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_check--master-arm |
fail
|
Testing failed
|
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
fail
|
Testing failed
|
Commit Message
The gdb.Objfile, gdb.Progspace, gdb.Type, and gdb.Inferior Python
types already have a __dict__ attribute, which allows users to create
user defined attributes within the objects. This is useful if the
user wants to cache information within an object.
This commit adds the same functionality to the gdb.InferiorThread
type.
After this commit there is a new gdb.InferiorThread.__dict__
attribute, which is a dictionary. A user can, for example, do this:
(gdb) pi
>>> t = gdb.selected_thread()
>>> t._user_attribute = 123
>>> t._user_attribute
123
>>>
There's a new test included.
---
gdb/NEWS | 4 +++
gdb/doc/python.texi | 31 ++++++++++++++++++++++++
gdb/python/py-infthread.c | 17 +++++++++++--
gdb/python/python-internal.h | 4 +++
gdb/testsuite/gdb.python/py-inferior.exp | 17 +++++++++++++
5 files changed, 71 insertions(+), 2 deletions(-)
Comments
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Fri, 5 Jan 2024 11:48:35 +0000
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 500d5ab7160..c4862a8beb6 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -96,6 +96,10 @@ show remote thread-options-packet
> these will be stored in the object's new Inferior.__dict__
> attribute.
>
> + ** User defined attributes can be added to a gdb.InferiorThread
> + object, these will be stored in the object's new
> + InferiorThread.__dict__ attribute.
This part is OK.
> +@smallexample
> +(gdb) python
> +import datetime
> +
> +def thread_stopped(event):
> + if event.inferior_thread is not None:
> + thread = event.inferior_thread
> + else:
> + thread = gdb.selected_thread()
> + thread._last_stop_time = datetime.datetime.today()
> +
> +gdb.events.stop.connect(thread_stopped)
> +(gdb) file /tmp/hello
> +Reading symbols from /tmp/hello...
> +(gdb) start
> +Temporary breakpoint 1 at 0x401198: file /tmp/hello.c, line 18.
> +Starting program: /tmp/hello
> +
> +Temporary breakpoint 1, main () at /tmp/hello.c:18
> +18 printf ("Hello World\n");
> +(gdb) python print(gdb.selected_thread()._last_stop_time)
> +2024-01-04 14:48:41.347036
> +@end smallexample
When you have an @example that consists of more than just a couple of
lines, and has empty lines in it, it is a good idea to divide it into
separate parts using @group..@end group. This will make sure that in
the printed manual such @group's will never be split across pages,
thus making it easier to read the example.
Thanks.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
Andrew> + thread_obj->dict = PyDict_New ();
Andrew> + if (thread_obj->dict == nullptr)
Andrew> + return nullptr;
If this fails...
Andrew> static void
Andrew> thpy_dealloc (PyObject *self)
Andrew> {
Andrew> - Py_DECREF (((thread_object *) self)->inf_obj);
Andrew> + thread_object *thr_obj = (thread_object *) self;
Andrew> +
Andrew> + gdb_assert (thr_obj->inf_obj != nullptr);
Andrew> + gdb_assert (thr_obj->dict != nullptr);
Andrew> +
Andrew> + Py_DECREF (thr_obj->inf_obj);
Andrew> + Py_DECREF (thr_obj->dict);
... won't this be called and then crash because dict is NULL?
I am not really sure TBH but other code, like your change in
py-inferior.c, seems to think this.
I suspect Py_XDECREF should be used instead.
The current call to Py_DECREF isn't a bug because create_thread_object
avoids creating the thread object when 'inf_obj' would be null. But,
the new patch doesn't do this.
Tom
Tom Tromey <tom@tromey.com> writes:
>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> + thread_obj->dict = PyDict_New ();
> Andrew> + if (thread_obj->dict == nullptr)
> Andrew> + return nullptr;
>
> If this fails...
>
> Andrew> static void
> Andrew> thpy_dealloc (PyObject *self)
> Andrew> {
> Andrew> - Py_DECREF (((thread_object *) self)->inf_obj);
> Andrew> + thread_object *thr_obj = (thread_object *) self;
> Andrew> +
> Andrew> + gdb_assert (thr_obj->inf_obj != nullptr);
> Andrew> + gdb_assert (thr_obj->dict != nullptr);
> Andrew> +
> Andrew> + Py_DECREF (thr_obj->inf_obj);
> Andrew> + Py_DECREF (thr_obj->dict);
>
> ... won't this be called and then crash because dict is NULL?
>
> I am not really sure TBH but other code, like your change in
> py-inferior.c, seems to think this.
>
> I suspect Py_XDECREF should be used instead.
>
> The current call to Py_DECREF isn't a bug because create_thread_object
> avoids creating the thread object when 'inf_obj' would be null. But,
> the new patch doesn't do this.
Thanks for spotting this. I'll get this fixed up.
Andrew
@@ -96,6 +96,10 @@ show remote thread-options-packet
these will be stored in the object's new Inferior.__dict__
attribute.
+ ** User defined attributes can be added to a gdb.InferiorThread
+ object, these will be stored in the object's new
+ InferiorThread.__dict__ attribute.
+
* Debugger Adapter Protocol changes
** GDB now emits the "process" event.
@@ -4173,6 +4173,37 @@
a @code{gdb.Type} for the handle type.
@end defun
+One may add arbitrary attributes to @code{gdb.InferiorThread} objects
+in the usual Python way. This is useful if, for example, one needs to
+do some extra record keeping associated with the thread.
+
+In this contrived example we record the time when a thread last
+stopped:
+
+@smallexample
+(gdb) python
+import datetime
+
+def thread_stopped(event):
+ if event.inferior_thread is not None:
+ thread = event.inferior_thread
+ else:
+ thread = gdb.selected_thread()
+ thread._last_stop_time = datetime.datetime.today()
+
+gdb.events.stop.connect(thread_stopped)
+(gdb) file /tmp/hello
+Reading symbols from /tmp/hello...
+(gdb) start
+Temporary breakpoint 1 at 0x401198: file /tmp/hello.c, line 18.
+Starting program: /tmp/hello
+
+Temporary breakpoint 1, main () at /tmp/hello.c:18
+18 printf ("Hello World\n");
+(gdb) python print(gdb.selected_thread()._last_stop_time)
+2024-01-04 14:48:41.347036
+@end smallexample
+
@node Recordings In Python
@subsubsection Recordings In Python
@cindex recordings in python
@@ -49,6 +49,10 @@ create_thread_object (struct thread_info *tp)
if (thread_obj == NULL)
return NULL;
+ thread_obj->dict = PyDict_New ();
+ if (thread_obj->dict == nullptr)
+ return nullptr;
+
thread_obj->thread = tp;
thread_obj->inf_obj = (PyObject *) inf_obj.release ();
@@ -58,7 +62,14 @@ create_thread_object (struct thread_info *tp)
static void
thpy_dealloc (PyObject *self)
{
- Py_DECREF (((thread_object *) self)->inf_obj);
+ thread_object *thr_obj = (thread_object *) self;
+
+ gdb_assert (thr_obj->inf_obj != nullptr);
+ gdb_assert (thr_obj->dict != nullptr);
+
+ Py_DECREF (thr_obj->inf_obj);
+ Py_DECREF (thr_obj->dict);
+
Py_TYPE (self)->tp_free (self);
}
@@ -394,6 +405,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 },
{ "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,
@@ -471,7 +484,7 @@ PyTypeObject thread_object_type =
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
- 0, /* tp_dictoffset */
+ offsetof (thread_object, dict), /* tp_dictoffset */
0, /* tp_init */
0 /* tp_alloc */
};
@@ -356,6 +356,10 @@ struct thread_object
/* 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;
@@ -107,6 +107,19 @@ gdb_test "python print(last_thread)" \
"<gdb.InferiorThread id=${decimal}\\.${decimal} target-id=\"\[^\r\n\]*\">" \
"test repr of a valid thread"
+# Add a user defined attribute to this thread, check the attribute can
+# be read back, and check the attribute is not present on other
+# threads.
+gdb_test_no_output "python last_thread._user_attribute = 123" \
+ "add user defined attribute to InferiorThread object"
+gdb_test "python print(last_thread._user_attribute)" "123" \
+ "read back user defined attribute"
+gdb_test "python print(i0.threads ()\[0\]._user_attribute)" \
+ [multi_line \
+ "AttributeError: 'gdb\\.InferiorThread' object has no attribute '_user_attribute'" \
+ "Error while executing Python code\\."] \
+ "attempt to read non-existent user defined attribute"
+
# Proceed to the next test.
gdb_breakpoint [gdb_get_line_number "Break here."]
@@ -117,6 +130,10 @@ gdb_test "python print(last_thread)" \
"<gdb.InferiorThread \\(invalid\\)>" \
"test repr of an invalid thread"
+# Check the user defined attribute is still present on the invalid thread object.
+gdb_test "python print(last_thread._user_attribute)" "123" \
+ "check user defined attribute on an invalid InferiorThread object"
+
# Test memory read and write operations.
gdb_py_test_silent_cmd "python addr = gdb.selected_frame ().read_var ('str')" \