From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 1EC10385840C for ; Wed, 11 Oct 2023 12:53:25 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1EC10385840C Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1697028804; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4BH3Z6vuex7yBzKVHTsZhRvQm3oBrL76RK6f+GkJJUU=; b=Z4xgVbep4pvl6A/iIpbWBYFxZzwIMeSWfquULivSXKRlgTBB2o8YTGfD+/bdVkMDpeEgqY kWnYyjFDFt06QZFnKEk2qLsZUSSdX+Gp7dI1IEcg7Z8rGC7jdnml6jSY2LPx0NKgi0mqrG x+lJKf1tMR79EL86crYY/I9GoWILNg4= Received: from mail-ej1-f71.google.com (mail-ej1-f71.google.com [209.85.218.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-196-4lKR11CPM0Wj2cB9pMZZmw-1; Wed, 11 Oct 2023 08:53:23 -0400 X-MC-Unique: 4lKR11CPM0Wj2cB9pMZZmw-1 Received: by mail-ej1-f71.google.com with SMTP id a640c23a62f3a-9ae0bf9c0a9so488202766b.3 for ; Wed, 11 Oct 2023 05:53:23 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1697028802; x=1697633602; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=4BH3Z6vuex7yBzKVHTsZhRvQm3oBrL76RK6f+GkJJUU=; b=UNeTbo0ffRz7Qgb08JnWprspVI2+urudiHHGwUlPSLpgbOROslrPpBejYqTeG485pe W1ywEyoWCM1d46KIeMkvgwx0J6qlnPMGgpxQ95xdGe5PCfZdnAwMl13zy3ExHtGVkDxr UCHBRMHYyLqr+yFqg9GfVZwxzcQdR3hJYwNs5+cdNoVH8AIs013qq0H3NutihWrOc/Ze I6CDR7ER2PJwNaVppe1kMUS76Qa4HA3E8V4LCVRZIePc0yPXBdFb3Id5KWA0otXhGeuO D6XvC5O/RcgQfbLh+KXVMMeBJc9q+Jf6XhnIX2yzgnOcpsXz4/4g3jGViX4EbZwngOV3 /oeg== X-Gm-Message-State: AOJu0YySQaAmNFNMY3koYjhww3BMljLL/ouGgiPPoULtIxLtcR+KvCmR vhYSledSNuaxN9fd9GiDuJcdF3wg+LinR6+vJZoPCSu1BXEv+IFZAQxfK8x93P95cDcDN6w6vQC IxRpMOLwya+F48Kg/XHT2Xd9XLlTeZdKx60Ds/qEJ+QW1HubssGGD3GkCjOwRzYrK0LHR36rIOZ sXNVMjWQ== X-Received: by 2002:a17:907:75da:b0:9ad:a46c:2936 with SMTP id jl26-20020a17090775da00b009ada46c2936mr19808052ejc.8.1697028802077; Wed, 11 Oct 2023 05:53:22 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEdPeRUtN4LK9ptJC90/qTdEVyNLb/OcS7Bv94ThQTm3wZpRRwVDLxWomtPONsV9fc2q4M8lQ== X-Received: by 2002:a17:907:75da:b0:9ad:a46c:2936 with SMTP id jl26-20020a17090775da00b009ada46c2936mr19808036ejc.8.1697028801627; Wed, 11 Oct 2023 05:53:21 -0700 (PDT) Received: from localhost ([31.111.84.209]) by smtp.gmail.com with ESMTPSA id e27-20020a170906045b00b00989828a42e8sm9730548eja.154.2023.10.11.05.53.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 11 Oct 2023 05:53:21 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 2/2] gdb: call update_thread_list after completing an inferior call Date: Wed, 11 Oct 2023 13:53:13 +0100 Message-Id: X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.6 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: I noticed that if GDB is using a remote or extended-remote target, then, if an inferior call caused a new thread to appear, or for an existing thread to exit, then these events are not reported to the user. The problem is that for these targets GDB relies on a call to update_thread_list to learn about changes to the inferior's thread list. If GDB doesn't pass through the normal stop code then GDB will not call update_thread_list, and so will not report changes in the thread list. This commit adds an additional update_thread_list call, after which thread events are correctly reported. --- gdb/infcall.c | 7 + .../gdb.threads/infcall-thread-announce.c | 192 ++++++++++++++++++ .../gdb.threads/infcall-thread-announce.exp | 71 +++++++ 3 files changed, 270 insertions(+) create mode 100644 gdb/testsuite/gdb.threads/infcall-thread-announce.c create mode 100644 gdb/testsuite/gdb.threads/infcall-thread-announce.exp diff --git a/gdb/infcall.c b/gdb/infcall.c index 0f9ad34bbb4..e62c2205808 100644 --- a/gdb/infcall.c +++ b/gdb/infcall.c @@ -1388,6 +1388,13 @@ call_function_by_hand_dummy (struct value *function, gdb::observers::inferior_call_post.notify (call_thread_ptid, funaddr); + + /* As the inferior call failed, we are about to throw an error, which + will be caught and printed somewhere else in GDB. We want new threads + to be printed before the error message, otherwise it looks odd; the + threads appear after GDB has reported a stop. */ + update_thread_list (); + if (call_thread->state != THREAD_EXITED) { /* The FSM should still be the same. */ diff --git a/gdb/testsuite/gdb.threads/infcall-thread-announce.c b/gdb/testsuite/gdb.threads/infcall-thread-announce.c new file mode 100644 index 00000000000..2ed246c7662 --- /dev/null +++ b/gdb/testsuite/gdb.threads/infcall-thread-announce.c @@ -0,0 +1,192 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023 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 + +/* Test can create at most this number of extra threads. */ +#define MAX_THREADS 3 + +/* For convenience. */ +#define FALSE 0 +#define TRUE (!FALSE) + +/* Controls a thread created by this test. */ +struct thread_descriptor +{ + /* The pthread handle. Not valid unless STARTED is true. */ + pthread_t thr; + + /* This field is set to TRUE when a thread has been created, otherwise, + is false. */ + int started; + + /* A condition variable and mutex, used for synchronising between the + worker thread and the main thread. */ + pthread_cond_t cond; + pthread_mutex_t mutex; +}; + +/* Keep track of worker threads. */ +struct thread_descriptor threads[MAX_THREADS]; + +/* Worker thread function. Doesn't do much. Synchronise with the main + thread, mark the thread as started, and then block waiting for the main + thread. Once the main thread wakes us, this thread exits. + + ARG is a thread_descriptor shared with the main thread. */ + +void * +thread_function (void *arg) +{ + int res; + struct thread_descriptor *thread = (struct thread_descriptor *) arg; + + /* Acquire the thread's lock. Initially the main thread holds this lock, + but releases it when the main thread enters a pthread_cond_wait. */ + res = pthread_mutex_lock (&thread->mutex); + assert (res == 0); + + /* Mark the thread as started. */ + thread->started = TRUE; + + /* Signal the main thread to tell it we are started. The main thread + will still be blocked though as we hold the thread's lock. */ + res = pthread_cond_signal (&thread->cond); + assert (res == 0); + + /* Now wait until the main thread tells us to exit. By entering this + pthread_cond_wait we release the lock, which allows the main thread to + resume. */ + res = pthread_cond_wait (&thread->cond, &thread->mutex); + assert (res == 0); + + /* The main thread woke us up. We reacquired the thread lock as we left + the pthread_cond_wait, so release the lock now. */ + res = pthread_mutex_unlock (&thread->mutex); + assert (res == 0); + + return NULL; +} + +/* Start a new thread within the global THREADS array. Return true if a + new thread was started, otherwise return false. */ + +int +start_thread () +{ + int idx, res; + + for (idx = 0; idx < MAX_THREADS; ++idx) + if (!threads[idx].started) + break; + + if (idx == MAX_THREADS) + return FALSE; + + /* Acquire the thread lock before starting the new thread. */ + res = pthread_mutex_lock (&threads[idx].mutex); + assert (res == 0); + + /* Start the new thread. */ + res = pthread_create (&threads[idx].thr, NULL, + thread_function, &threads[idx]); + assert (res == 0); + + /* Unlock and wait. The thread signals us once it is ready. */ + res = pthread_cond_wait (&threads[idx].cond, &threads[idx].mutex); + assert (res == 0); + + /* The worker thread is now blocked in a pthread_cond_wait and we + reacquired the lock as we left our own pthread_cond_wait above. */ + res = pthread_mutex_unlock (&threads[idx].mutex); + assert (res == 0); + + return TRUE; +} + +/* Stop a thread from within the global THREADS array. Return true if a + thread was stopped, otherwise return false. */ +int +stop_thread () +{ + /* Look for a thread that is started. */ + for (int idx = 0; idx < MAX_THREADS; ++idx) + if (threads[idx].started) + { + int res; + + /* Grab the thread lock. */ + res = pthread_mutex_lock (&threads[idx].mutex); + assert (res == 0); + + /* Signal the worker thread, this wakes it up, but it can't exit + until it acquires the thread lock, which we currently hold. */ + res = pthread_cond_signal (&threads[idx].cond); + assert (res == 0); + + /* Release the thread lock, this allows the worker thread to exit. */ + res = pthread_mutex_unlock (&threads[idx].mutex); + assert (res == 0); + + /* Now wait for the thread to exit. */ + void *retval; + res = pthread_join (threads[idx].thr, &retval); + assert (res == 0); + assert (retval == NULL); + + /* Now the thread has exited, mark it as no longer started. */ + assert (threads[idx].started); + threads[idx].started = FALSE; + + return TRUE; + } + + return FALSE; +} + +void +init_descriptor_array () +{ + for (int i = 0; i < MAX_THREADS; ++i) + { + int res; + + threads[i].started = FALSE; + res = pthread_cond_init (&threads[i].cond, NULL); + assert (res == 0); + res = pthread_mutex_init (&threads[i].mutex, NULL); + assert (res == 0); + } +} + +void +breakpt () +{ + /* Nothing. */ +} + +int +main () +{ + init_descriptor_array (); + breakpt (); + start_thread (); + stop_thread (); + breakpt (); + return 0; +} diff --git a/gdb/testsuite/gdb.threads/infcall-thread-announce.exp b/gdb/testsuite/gdb.threads/infcall-thread-announce.exp new file mode 100644 index 00000000000..f480b435f21 --- /dev/null +++ b/gdb/testsuite/gdb.threads/infcall-thread-announce.exp @@ -0,0 +1,71 @@ +# Copyright 2023 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 . + +# Check that thread creation and thread exit events are correctly +# announced when a thread starts, or exits, as a result of an inferior +# function call from GDB. + +standard_testfile + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] == -1} { + return -1 +} + +if ![runto_main] { + return -1 +} + +gdb_breakpoint breakpt +gdb_continue_to_breakpoint "first breakpt call" + +set thr_count 1 + +proc check_thread_count { adjustment } { + incr ::thr_count $adjustment + + gdb_test "p \$_inferior_thread_count" \ + "^\\\$$::decimal = $::thr_count" +} + +with_test_prefix "starting threads" { + gdb_test "call (void) start_thread()" \ + "\\\[New Thread \[^\r\n\]+\\\]" \ + "start a new thread, return value discarded" + check_thread_count +1 + + foreach_with_prefix call_type { print call } { + gdb_test "$call_type start_thread()" \ + "\\\[New Thread \[^\r\n\]+\\\]\r\n\\\$$decimal = 1" \ + "start another new thread" + check_thread_count +1 + } +} + +with_test_prefix "stopping threads" { + gdb_test "call (void) stop_thread()" \ + "\\\[Thread \[^\r\n\]+ exited\\\]" \ + "stop a thread, return value discarded" + check_thread_count -1 + + foreach_with_prefix call_type { print call } { + gdb_test "$call_type stop_thread()" \ + "\\\[Thread \[^\r\n\]+ exited\\\]\r\n\\\$$decimal = 1" \ + "stop another thread" + check_thread_count -1 + } +} + +gdb_continue_to_breakpoint "second breakpt call" +gdb_continue_to_end -- 2.25.4