diff --git a/gdb/NEWS b/gdb/NEWS index 92e7f32ab8c..bab300e36b8 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -104,6 +104,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. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 2fd8a9b5af8..674ec565b0d 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -4184,6 +4184,41 @@ the Python @code{bytes} representation of the handle and @var{type} is 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 +@group +(@value{GDBP}) 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) +@end group +@group +(@value{GDBP}) file /tmp/hello +Reading symbols from /tmp/hello... +(@value{GDBP}) 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"); +(@value{GDBP}) python print(gdb.selected_thread()._last_stop_time) +2024-01-04 14:48:41.347036 +@end group +@end smallexample + @node Recordings In Python @subsubsection Recordings In Python @cindex recordings in python diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c index b5887c7942d..421158455e7 100644 --- a/gdb/python/py-infthread.c +++ b/gdb/python/py-infthread.c @@ -51,6 +51,9 @@ create_thread_object (struct thread_info *tp) thread_obj->thread = tp; thread_obj->inf_obj = (PyObject *) inf_obj.release (); + thread_obj->dict = PyDict_New (); + if (thread_obj->dict == nullptr) + return nullptr; return thread_obj; } @@ -58,7 +61,13 @@ 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); + + Py_DECREF (thr_obj->inf_obj); + Py_XDECREF (thr_obj->dict); + Py_TYPE (self)->tp_free (self); } @@ -418,6 +427,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, @@ -498,7 +509,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 */ }; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 8ff9af650c2..e01557edeb7 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -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; diff --git a/gdb/testsuite/gdb.python/py-inferior.exp b/gdb/testsuite/gdb.python/py-inferior.exp index 0e00636fa1c..d1cd29f734b 100644 --- a/gdb/testsuite/gdb.python/py-inferior.exp +++ b/gdb/testsuite/gdb.python/py-inferior.exp @@ -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')" \