From patchwork Mon Jun 5 11:41:51 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Farre X-Patchwork-Id: 70601 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 19586384644B for ; Mon, 5 Jun 2023 11:42:23 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 19586384644B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1685965343; bh=ynejirRUlUCZKSQVtxNJu4jWBLlHVBoF8IJRtX+GdrI=; h=To:Cc:Subject:Date:List-Id:List-Unsubscribe:List-Archive: List-Post:List-Help:List-Subscribe:From:Reply-To:From; b=fA1SXjxvMrIRegYSO64omDMpnzcpVIZtFPnT+NE6nfVagbOYW68WWRbBCiWq0AnfU TOT3xCtolspO27BxuUutVnCZvhq5Wpt3KnXA46uhE+9ppq4Tb/nQNEx3MrQbjVdFlU +OLwFmhlf8CIxVZO1b6knWb6Ql4DDB/7PmA5PK5Q= X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mail-lf1-x129.google.com (mail-lf1-x129.google.com [IPv6:2a00:1450:4864:20::129]) by sourceware.org (Postfix) with ESMTPS id 673E13854E47 for ; Mon, 5 Jun 2023 11:41:58 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 673E13854E47 Received: by mail-lf1-x129.google.com with SMTP id 2adb3069b0e04-4f004cc54f4so5870315e87.3 for ; Mon, 05 Jun 2023 04:41:58 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685965316; x=1688557316; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=ynejirRUlUCZKSQVtxNJu4jWBLlHVBoF8IJRtX+GdrI=; b=MdVW8MJqmFUMW6npNM+Os5q0dWVfxOjrGT+44AArmUWE56PL7LRJoV9GkFAT6j7uas 300CY3+lJGc+KHA9juD7ZPvsJM3xbi6WWaL0NT6eeTjJtQhk03dcu/D08KWU21G0GkNJ m+IlYb8Ea0ApVI/4Y8D9ot6dAK+WlYmR9Z3LO8yyz+5ZaptDz7+V2fsKW1gEh1xapJw8 myRrK+aI0cdeR/Z4s7L9t1ZN7epioQzyPpLpL+35nN5o5500sjMROSPvEtXYxMUslpw/ GplL2i/8ubJ/ammBeQvz+k1i9rbDyzxnbBv1pvJgO/CJOtkXKH5OBUGuNxYnFFw/+Cxp +BKw== X-Gm-Message-State: AC+VfDxSTYhQdXzHuV/GnG4qNFZk4KnjoAEMt91yzp5w4ZEJ7LdoPtz1 BH8gTUuqpIOD+XTtSOSfvAHaKS+vcXN0BQ== X-Google-Smtp-Source: ACHHUZ4RDo9g8I28kBn937XQWqVo6DwkMIm/jP6B/24o88vxpnK/OEDBNiZZ4n/5FYAPwPmjtf3zbw== X-Received: by 2002:ac2:539c:0:b0:4f4:d383:4d6b with SMTP id g28-20020ac2539c000000b004f4d3834d6bmr5163087lfh.6.1685965315963; Mon, 05 Jun 2023 04:41:55 -0700 (PDT) Received: from fedora.. (78-73-77-63-no2450.tbcn.telia.com. [78.73.77.63]) by smtp.gmail.com with ESMTPSA id j12-20020a19f50c000000b004f4c2f62246sm1088500lfb.277.2023.06.05.04.41.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 05 Jun 2023 04:41:55 -0700 (PDT) To: gdb-patches@sourceware.org Cc: Simon Farre Subject: [PATCH v5] gdb/Python: Added ThreadExitedEvent Date: Mon, 5 Jun 2023 13:41:51 +0200 Message-Id: <20230605114151.61322-1-simon.farre.cx@gmail.com> X-Mailer: git-send-email 2.40.1 MIME-Version: 1.0 X-Spam-Status: No, score=-11.0 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP, T_SCC_BODY_TEXT_LINE, URI_LONG_REPEAT autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Simon Farre via Gdb-patches From: Simon Farre Reply-To: Simon Farre Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" v5: Did what Tom Tromey requested in v4; which can be found here: https://pi.simark.ca/gdb-patches/87pmjm0xar.fsf@tromey.com/ v4: Doc formatting fixed. v3: Eli: Updated docs & NEWS to reflect new changes. Added a reference from the .ptid attribute of the ThreadExitedEvent to the ptid attribute of InferiorThread. To do this, I've added an anchor to that attribute. Tom: Tom requested that I should probably just emit the thread object; I ran into two issues for this, which I could not resolve in this patch; 1 - The Thread Object (the python type) checks it's own validity by doing a comparison of it's `thread_info* thread` to nullptr. This means that any access of it's attributes may (probably, since we are in "async" land) throw Python exceptions because the thread has been removed from the thread object. Therefore I've decided in v3 of this patch to just emit most of the same fields that gdb.InferiorThread has, namely global_num, name, num and ptid (the 3-attribute tuple provided by gdb.InferiorThread.ptid). 2 - A python user can hold a global reference to an exiting thread. Thus in order to have a ThreadExit event that can provide attribute access reliably (both as a global reference, but also inside the thread exit handler, as we can never guarantee that it's executed _before_ the thread_info pointer is removed from the gdbpy thread object), the `thread_info *` thread pointer must not be null. However, this comes at the cost of gdb.InferiorThread believing it is "valid" - which means, that if a user holds takes a global reference to that exiting event thread object, they can some time later do `t.switch()` at which point GDB will 'explode' so to speak. v2: Fixed white space issues and NULL/nullptr stuff, as requested by Tom Tromey. v1: Currently no event is emitted for a thread exit. This adds this functionality by emitting a new gdb.ThreadExitedEvent. It currently provides four attributes: - global_num: The GDB assigned global thread number - num: the per-inferior thread number - name: name of the thread or none if not set - ptid: the PTID of the thread, a 3-attribute tuple, identical to InferiorThread.ptid attribute Added info to docs & the NEWS file as well. Added test to test suite. Fixed formatting. Feedback wanted and appreciated. Reviewed-By: Eli Zaretskii --- gdb/NEWS | 2 + gdb/doc/python.texi | 10 ++++ gdb/python/py-all-events.def | 1 + gdb/python/py-event-types.def | 5 ++ gdb/python/py-event.h | 4 ++ gdb/python/py-inferior.c | 3 ++ gdb/python/py-threadevent.c | 22 +++++++++ gdb/testsuite/gdb.python/py-thread-exited.c | 37 ++++++++++++++ gdb/testsuite/gdb.python/py-thread-exited.exp | 49 +++++++++++++++++++ gdb/testsuite/gdb.python/py-thread-exited.py | 31 ++++++++++++ 10 files changed, 164 insertions(+) create mode 100644 gdb/testsuite/gdb.python/py-thread-exited.c create mode 100644 gdb/testsuite/gdb.python/py-thread-exited.exp create mode 100644 gdb/testsuite/gdb.python/py-thread-exited.py diff --git a/gdb/NEWS b/gdb/NEWS index 649a3a9824a..a98dd5cedbc 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -140,6 +140,8 @@ info main * Python API + ** gdb.ThreadExitedEvent added. Emits a ThreadEvent. + ** The gdb.unwinder.Unwinder.name attribute is now read-only. ** The name argument passed to gdb.unwinder.Unwinder.__init__ must diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 02cb9ad4bc0..10997a2ce2f 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -3829,6 +3829,15 @@ This has a single attribute: The new thread. @end defvar +@item events.thread_exited +This is emitted when @value{GDBN} notices a thread has exited. The event +is of type @code{gdb.ThreadExitedEvent} which extends @code{gdb.ThreadEvent}. +This has a single attribute: + +@defvar ThreadExitedEvent.inferior_thread +The exiting thread. +@end defvar + @item events.gdb_exiting This is emitted when @value{GDBN} exits. This event is not emitted if @value{GDBN} exits as a result of an internal error, or after an @@ -3893,6 +3902,7 @@ make Python breakpoints thread-specific, for example (@pxref{python_breakpoint_thread,,The Breakpoint.thread attribute}). @end defvar +@anchor{inferior_thread_ptid} @defvar InferiorThread.ptid ID of the thread, as assigned by the operating system. This attribute is a tuple containing three integers. The first is the Process ID (PID); the second diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def index 4d711f7fbd1..aa28f2c6f8b 100644 --- a/gdb/python/py-all-events.def +++ b/gdb/python/py-all-events.def @@ -32,6 +32,7 @@ GDB_PY_DEFINE_EVENT(clear_objfiles) GDB_PY_DEFINE_EVENT(new_inferior) GDB_PY_DEFINE_EVENT(inferior_deleted) GDB_PY_DEFINE_EVENT(new_thread) +GDB_PY_DEFINE_EVENT(thread_exited) GDB_PY_DEFINE_EVENT(inferior_call) GDB_PY_DEFINE_EVENT(memory_changed) GDB_PY_DEFINE_EVENT(register_changed) diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def index 2215c5ebf94..395d6c07447 100644 --- a/gdb/python/py-event-types.def +++ b/gdb/python/py-event-types.def @@ -51,6 +51,11 @@ GDB_PY_DEFINE_EVENT_TYPE (new_thread, "GDB new thread event object", thread_event_object_type); +GDB_PY_DEFINE_EVENT_TYPE (thread_exited, + "ThreadExitedEvent", + "GDB thread exited event object", + event_object_type); + GDB_PY_DEFINE_EVENT_TYPE (new_inferior, "NewInferiorEvent", "GDB new inferior event object", diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h index 0a7d31d02e9..01574aa20dc 100644 --- a/gdb/python/py-event.h +++ b/gdb/python/py-event.h @@ -61,6 +61,10 @@ extern int emit_memory_changed_event (CORE_ADDR addr, ssize_t len); extern int evpy_emit_event (PyObject *event, eventregistry_object *registry); +/* Emits a thread exit event for THREAD containing the attributes + global_num, name, num and ptid */ +extern int emit_thread_exit_event (thread_info * thread); + extern gdbpy_ref<> create_event_object (PyTypeObject *py_type); /* thread events can either be thread specific or process wide. If gdb is diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c index 8c4871dfa89..af8bd8855a3 100644 --- a/gdb/python/py-inferior.c +++ b/gdb/python/py-inferior.c @@ -371,6 +371,9 @@ delete_thread_object (struct thread_info *tp, int ignore) if (inf_obj == NULL) return; + if (emit_thread_exit_event (tp) < 0) + gdbpy_print_stack (); + auto it = inf_obj->threads->find (tp); if (it != inf_obj->threads->end ()) { diff --git a/gdb/python/py-threadevent.c b/gdb/python/py-threadevent.c index 05a833def25..7633f375996 100644 --- a/gdb/python/py-threadevent.c +++ b/gdb/python/py-threadevent.c @@ -53,3 +53,25 @@ create_thread_event_object (PyTypeObject *py_type, PyObject *thread) return thread_event_obj; } + +/* Emits a thread exit event for THREAD containing the attributes + global_num, name, num and ptid */ + +int +emit_thread_exit_event (thread_info * thread) +{ + if (evregpy_no_listeners_p (gdb_py_events.thread_exited)) + return 0; + + auto py_thr = thread_to_thread_object (thread); + + if (py_thr == nullptr) + return -1; + + auto inf_thr = create_thread_event_object (&thread_exited_event_object_type, + py_thr.get ()); + if (inf_thr == nullptr) + return -1; + + return evpy_emit_event (inf_thr.get (), gdb_py_events.thread_exited); +} diff --git a/gdb/testsuite/gdb.python/py-thread-exited.c b/gdb/testsuite/gdb.python/py-thread-exited.c new file mode 100644 index 00000000000..d4ff55b1b44 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-thread-exited.c @@ -0,0 +1,37 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2010-2022 Free Software Foundation, Inc. + + 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 . */ + +#include +#include +#include +#include +pthread_t thread2_id; +pthread_t thread3_id; + +void* do_thread (void* d) +{ + return NULL; +} + +int main (void) +{ + pthread_create (&thread2_id, NULL, do_thread, NULL); + pthread_join (thread2_id, NULL); + pthread_create (&thread3_id, NULL, do_thread, NULL); + pthread_join (thread3_id, NULL); + return 12; +} diff --git a/gdb/testsuite/gdb.python/py-thread-exited.exp b/gdb/testsuite/gdb.python/py-thread-exited.exp new file mode 100644 index 00000000000..ec552fc8b6e --- /dev/null +++ b/gdb/testsuite/gdb.python/py-thread-exited.exp @@ -0,0 +1,49 @@ +# Copyright (C) 2022-2022 Free Software Foundation, Inc. + +# 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 . + +if { ![support_displaced_stepping] } { + unsupported "displaced stepping" + return -1 +} + +load_lib gdb-python.exp + +standard_testfile + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } { + return -1 +} + +save_vars { GDBFLAGS } { + clean_restart $testfile +} + +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/py-thread-exited.py] +gdb_test_no_output "source ${pyfile}" "load python file" + +gdb_test "test-events" "Event testers registered." + +if ![runto_main] { + return -1 +} + +gdb_breakpoint 37 "last of main" + +gdb_continue_to_breakpoint "continue to breakpoint" + +gdb_test "python print(threadOneExit)" \ + ".*event type: thread-exited. global num: 2.*" +gdb_test "python print(threadTwoExit)" \ + ".*event type: thread-exited. global num: 3.*" diff --git a/gdb/testsuite/gdb.python/py-thread-exited.py b/gdb/testsuite/gdb.python/py-thread-exited.py new file mode 100644 index 00000000000..51dfeb05ae4 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-thread-exited.py @@ -0,0 +1,31 @@ +import gdb + +threadOneExit = "" +threadTwoExit = "" +# we don't want to overwrite the 2nd thread's exit event, thus +# store it here. we don't care about it though. +mainThreadExit = "" + +def thread_exited_handler(event): + global threadOneExit, threadTwoExit, mainThreadExit + print("{}".format(event)) + assert isinstance(event, gdb.ThreadExitedEvent) + if threadOneExit == "": + threadOneExit = "event type: thread-exited. global num: {}".format(event.inferior_thread.global_num) + else: + if threadTwoExit == "": + threadTwoExit = "event type: thread-exited. global num: {}".format(event.inferior_thread.global_num) + else: + mainThreadExit = "event type: thread-exited. global num: {}".format(event.inferior_thread.global_num) + +class test_events(gdb.Command): + """Test events.""" + + def __init__(self): + gdb.Command.__init__(self, "test-events", gdb.COMMAND_STACK) + + def invoke(self, arg, from_tty): + gdb.events.thread_exited.connect(thread_exited_handler) + print("Event testers registered.") + +test_events()