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.129.124]) by sourceware.org (Postfix) with ESMTPS id B5DFA38323F4 for ; Mon, 5 Jun 2023 13:53:07 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B5DFA38323F4 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=1685973187; 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: in-reply-to:in-reply-to:references:references; bh=xi1vidHFUPkrcXZWbhTeaG+O1ei8M/9qJGBHSJ2+wYc=; b=Aw1LqmsnX3Xiix3pLP/reNGUPXmlXJjaMCMZiGwaab0g+ZMs8ZStUYR+swTy2D+29cULmI pb+oXqxAcQIhKu6Ktp3OIB5DgSJaUGL6b27MU5hEGhiXBa6fgL4DNEjWy7/DRO8nF6VN5/ T5lgqcduzEz3BxlVncfYlWnurNmlQfI= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-45-Zk7WqmwpO0GkdN26F7JJOA-1; Mon, 05 Jun 2023 09:53:05 -0400 X-MC-Unique: Zk7WqmwpO0GkdN26F7JJOA-1 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-3f6045853c1so23775605e9.3 for ; Mon, 05 Jun 2023 06:53:05 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685973184; x=1688565184; h=mime-version:message-id:date:references:in-reply-to:subject:cc:to :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=xi1vidHFUPkrcXZWbhTeaG+O1ei8M/9qJGBHSJ2+wYc=; b=SN487vCoqbjswh22sadPzrgIGWAN91H4+VH40SCP0cvxxne8aktn7+YtbAP0KCvxuT S0XM3BfZ2u39qLTyUK+qPKStvVOUYqAorod99Bjl4ISLpE8M+iNpaX759hpgidcXCrfi ysWmr3j8USp4z+rIhuAilpKsL6jyYa7l6klAJRgSys4tW2t67euvKZhL0uBg8l7agSqr Ql4h3GV43zh9syyOcHSEJ+tTXXflFISV95O5C13APAH9YhZ8ODCdwS09ME29oA/X9Vzs FCjDCxwEpfipS0aVZgN/m7QO++rrJiYhi8K9lUiSRUFbxLseE59Lbr9xegBGidpC8h/w ts1w== X-Gm-Message-State: AC+VfDzOG0rUTsl4MVQkkkJtIerKPQCHWCBZg1/2Zs2K4OYYiDwlgTHn o1iDjQhfvYobHQa1MY4P8QkjwKeUBUp4fmnO5AHBFfR/OeMhJb5bWdqqKOp8+NvGe8B8UPsdRfU PKC4PAp6JO0tLKJtApH+dCVy68I+H5A== X-Received: by 2002:a05:600c:229a:b0:3f7:38e2:d87a with SMTP id 26-20020a05600c229a00b003f738e2d87amr2534801wmf.37.1685973184116; Mon, 05 Jun 2023 06:53:04 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ4sMGVYzROWjq3mLvmmACZP4xX5rOIlhakJJKgXXxokKZK9gR5GA5Udn1RKhA1Z76JY7jZhiQ== X-Received: by 2002:a05:600c:229a:b0:3f7:38e2:d87a with SMTP id 26-20020a05600c229a00b003f738e2d87amr2534776wmf.37.1685973183296; Mon, 05 Jun 2023 06:53:03 -0700 (PDT) Received: from localhost (11.72.115.87.dyn.plus.net. [87.115.72.11]) by smtp.gmail.com with ESMTPSA id w11-20020a1cf60b000000b003f423f5b659sm10882740wmc.10.2023.06.05.06.53.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 05 Jun 2023 06:53:02 -0700 (PDT) From: Andrew Burgess To: "Aktemur, Tankut Baris" , "gdb-patches@sourceware.org" Cc: "Saiapova, Natalia" Subject: RE: [PATCHv7 2/6] gdb: fix b/p conditions with infcalls in multi-threaded inferiors In-Reply-To: References: Date: Mon, 05 Jun 2023 14:53:01 +0100 Message-ID: <875y823tj6.fsf@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain X-Spam-Status: No, score=-11.7 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,SPF_HELO_NONE,SPF_NONE,TXREP,T_SCC_BODY_TEXT_LINE 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: "Aktemur, Tankut Baris" writes: > On Monday, May 15, 2023 9:22 PM, Andrew Burgess wrote: >> This commit fixes bug PR 28942, that is, creating a conditional >> breakpoint in a multi-threaded inferior, where the breakpoint >> condition includes an inferior function call. >> >> Currently, when a user tries to create such a breakpoint, then GDB >> will fail with: >> >> (gdb) break infcall-from-bp-cond-single.c:61 if (return_true ()) >> Breakpoint 2 at 0x4011fa: file >> /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/infcall-from-bp-cond- >> single.c, line 61. >> (gdb) continue >> Continuing. >> [New Thread 0x7ffff7c5d700 (LWP 2460150)] >> [New Thread 0x7ffff745c700 (LWP 2460151)] >> [New Thread 0x7ffff6c5b700 (LWP 2460152)] >> [New Thread 0x7ffff645a700 (LWP 2460153)] >> [New Thread 0x7ffff5c59700 (LWP 2460154)] >> Error in testing breakpoint condition: >> Couldn't get registers: No such process. >> An error occurred while in a function called from GDB. >> Evaluation of the expression containing the function >> (return_true) will be abandoned. >> When the function is done executing, GDB will silently stop. >> Selected thread is running. >> (gdb) >> >> Or, in some cases, like this: >> >> (gdb) break infcall-from-bp-cond-simple.c:56 if (is_matching_tid (arg, 1)) >> Breakpoint 2 at 0x401194: file >> /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/infcall-from-bp-cond- >> simple.c, line 56. >> (gdb) continue >> Continuing. >> [New Thread 0x7ffff7c5d700 (LWP 2461106)] >> [New Thread 0x7ffff745c700 (LWP 2461107)] >> ../../src.release/gdb/nat/x86-linux-dregs.c:146: internal-error: >> x86_linux_update_debug_registers: Assertion `lwp_is_stopped (lwp)' failed. >> A problem internal to GDB has been detected, >> further debugging may prove unreliable. >> >> The precise error depends on the exact thread state; so there's race >> conditions depending on which threads have fully started, and which >> have not. But the underlying problem is always the same; when GDB >> tries to execute the inferior function call from within the breakpoint >> condition, GDB will, incorrectly, try to resume threads that are >> already running - GDB doesn't realise that some threads might already >> be running. >> >> The solution proposed in this patch requires an additional member >> variable thread_info::in_cond_eval. This flag is set to true (in >> breakpoint.c) when GDB is evaluating a breakpoint condition. >> >> In user_visible_resume_ptid (infrun.c), when the in_cond_eval flag is >> true, then GDB will only try to resume the current thread, that is, >> the thread for which the breakpoint condition is being evaluated. >> This solves the problem of GDB trying to resume threads that are >> already running. >> >> The next problem is that inferior function calls are assumed to be >> synchronous, that is, GDB doesn't expect to start an inferior function >> call in thread #1, then receive a stop from thread #2 for some other, >> unrelated reason. To prevent GDB responding to an event from another >> thread, we update fetch_inferior_event and do_target_wait in infrun.c, >> so that, when an inferior function call (on behalf of a breakpoint >> condition) is in progress, we only wait for events from the current >> thread (the one evaluating the condition). >> >> In do_target_wait I had to change the inferior_matches lambda >> function, which is used to select which inferior to wait on. >> Previously the logic was this: >> >> auto inferior_matches = [&wait_ptid] (inferior *inf) >> { >> return (inf->process_target () != nullptr >> && ptid_t (inf->pid).matches (wait_ptid)); >> }; >> >> This compares the pid of the inferior against the complete ptid we >> want to wait on. Before this commit wait_ptid was only ever >> minus_one_ptid (which is special, and means any process), and so every >> inferior would match. >> >> After this commit though wait_ptid might represent a specific thread >> in a specific inferior. If we compare the pid of the inferior to a >> specific ptid then these will not match. The fix is to compare >> against the pid extracted from the wait_ptid, not against the complete >> wait_ptid itself. >> >> In fetch_inferior_event, after receiving the event, we only want to >> stop all the other threads, and call inferior_event_handler with >> INF_EXEC_COMPLETE, if we are not evaluating a conditional breakpoint. >> If we are, then all the other threads should be left doing whatever >> they were before. The inferior_event_handler call will be performed >> once the breakpoint condition has finished being evaluated, and GDB >> decides to stop or not. >> >> The final problem that needs solving relates to GDB's commit-resume >> mechanism, which allows GDB to collect resume requests into a single >> packet in order to reduce traffic to a remote target. >> >> The problem is that the commit-resume mechanism will not send any >> resume requests for an inferior if there are already events pending on >> the GDB side. >> >> Imagine an inferior with two threads. Both threads hit a breakpoint, >> maybe the same conditional breakpoint. At this point there are two >> pending events, one for each thread. >> >> GDB selects one of the events and spots that this is a conditional >> breakpoint, GDB evaluates the condition. >> >> The condition includes an inferior function call, so GDB sets up for >> the call and resumes the one thread, the resume request is added to >> the commit-resume queue. >> >> When the commit-resume queue is committed GDB sees that there is a >> pending event from another thread, and so doesn't send any resume >> requests to the actual target, GDB is assuming that when we wait we >> will select the event from the other thread. >> >> However, as this is an inferior function call for a condition >> evaluation, we will not select the event from the other thread, we >> only care about events from the thread that is evaluating the >> condition - and the resume for this thread was never sent to the >> target. >> >> And so, GDB hangs, waiting for an event from a thread that was never >> fully resumed. >> >> To fix this issue I have added the concept of "forcing" the >> commit-resume queue. When enabling commit resume, if the force flag >> is true, then any resumes will be committed to the target, even if >> there are other threads with pending events. >> >> A note on authorship: this patch was based on some work done by >> Natalia Saiapova and Tankut Baris Aktemur from Intel[1]. I have made >> some changes to their work in this version. >> >> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28942 >> >> [1] https://sourceware.org/pipermail/gdb-patches/2020-October/172454.html >> >> Co-authored-by: Natalia Saiapova >> Co-authored-by: Tankut Baris Aktemur > > Thanks again for this. > > I have a few minor comments inlined below. Except those, it looks good to me. > There is already Co-authored-by above, but in case you'd like to also add: > Reviewed-By: Tankut Baris Aktemur I fixed this issues you highlighted and added your reviewed-by tag. Thanks, Andrew > > Regards > -Baris > > >> --- >> gdb/breakpoint.c | 2 + >> gdb/gdbthread.h | 3 + >> gdb/infcall.c | 6 + >> gdb/infrun.c | 64 +++-- >> gdb/infrun.h | 3 +- >> .../infcall-from-bp-cond-other-thread-event.c | 135 ++++++++++ >> ...nfcall-from-bp-cond-other-thread-event.exp | 174 +++++++++++++ >> .../gdb.threads/infcall-from-bp-cond-simple.c | 89 +++++++ >> .../infcall-from-bp-cond-simple.exp | 235 ++++++++++++++++++ >> .../gdb.threads/infcall-from-bp-cond-single.c | 139 +++++++++++ >> .../infcall-from-bp-cond-single.exp | 117 +++++++++ >> 11 files changed, 952 insertions(+), 15 deletions(-) >> create mode 100644 gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c >> create mode 100644 gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp >> create mode 100644 gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c >> create mode 100644 gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp >> create mode 100644 gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c >> create mode 100644 gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp >> >> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c >> index fdb184ae81f..6fceed8f408 100644 >> --- a/gdb/breakpoint.c >> +++ b/gdb/breakpoint.c >> @@ -5548,6 +5548,8 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread) >> { >> try >> { >> + scoped_restore reset_in_cond_eval >> + = make_scoped_restore (&thread->control.in_cond_eval, true); >> condition_result = breakpoint_cond_eval (cond); >> } >> catch (const gdb_exception_error &ex) >> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h >> index 7135515bf45..897752f2691 100644 >> --- a/gdb/gdbthread.h >> +++ b/gdb/gdbthread.h >> @@ -171,6 +171,9 @@ struct thread_control_state >> command. This is used to decide whether "set scheduler-locking >> step" behaves like "on" or "off". */ >> int stepping_command = 0; >> + >> + /* True if the thread is evaluating a BP condition. */ >> + bool in_cond_eval = false; >> }; >> >> /* Inferior thread specific part of `struct infcall_suspend_state'. */ >> diff --git a/gdb/infcall.c b/gdb/infcall.c >> index 233ef5f29e9..49c88add394 100644 >> --- a/gdb/infcall.c >> +++ b/gdb/infcall.c >> @@ -642,6 +642,12 @@ run_inferior_call (std::unique_ptr sm, >> >> proceed (real_pc, GDB_SIGNAL_0); >> >> + /* Enable commit resume, but pass true for the force flag. This >> + ensures any thread we set running in proceed will actually be >> + committed to the target, even if some other thread in the current >> + target has a pending event. */ >> + scoped_enable_commit_resumed enable ("infcall", true); >> + >> infrun_debug_show_threads ("non-exited threads after proceed for inferior-call", >> all_non_exited_threads ()); >> >> diff --git a/gdb/infrun.c b/gdb/infrun.c >> index 07ef3c7c187..ea7ab6187ee 100644 >> --- a/gdb/infrun.c >> +++ b/gdb/infrun.c >> @@ -2275,6 +2275,14 @@ user_visible_resume_ptid (int step) >> mode. */ >> resume_ptid = inferior_ptid; >> } >> + else if (inferior_ptid != null_ptid >> + && inferior_thread ()->control.in_cond_eval) >> + { >> + /* The inferior thread is evaluating a BP condition. Other threads >> + might be stopped or running and we do not want to change their >> + state, thus, resume only the current thread. */ >> + resume_ptid = inferior_ptid; >> + } >> else if (!sched_multi && target_supports_multi_process ()) >> { >> /* Resume all threads of the current process (and none of other >> @@ -2992,12 +3000,24 @@ schedlock_applies (struct thread_info *tp) >> execution_direction))); >> } >> >> -/* Set process_stratum_target::COMMIT_RESUMED_STATE in all target >> - stacks that have threads executing and don't have threads with >> - pending events. */ >> +/* When FORCE_P is false, set process_stratum_target::COMMIT_RESUMED_STATE >> + in all target stacks that have threads executing and don't have threads >> + with pending events. >> + >> + When FORCE_P is true, set process_stratum_target::COMMIT_RESUMED_STATE >> + in all target stacks that have threads executing regardless of whether >> + there are pending events or not. >> + >> + Passing FORCE_P as false makes sense when GDB is going to wait for >> + events from all threads and will therefore spot the pending events. >> + However, if GDB is only going to wait for events from select threads >> + (i.e. when performing an inferior call) then a pending event on some >> + other thread will not be spotted, and if we fail to commit the resume >> + state for the thread performing the inferior call, then the inferior >> + call will never complete (or even start). */ > > There is one extra space of indentation above. Is that intentional? > >> static void >> -maybe_set_commit_resumed_all_targets () >> +maybe_set_commit_resumed_all_targets (bool force_p) >> { >> scoped_restore_current_thread restore_thread; >> >> @@ -3026,7 +3046,7 @@ maybe_set_commit_resumed_all_targets () >> status to report, handle it before requiring the target to >> commit its resumed threads: handling the status might lead to >> resuming more threads. */ >> - if (proc_target->has_resumed_with_pending_wait_status ()) >> + if (!force_p && proc_target->has_resumed_with_pending_wait_status ()) >> { >> infrun_debug_printf ("not requesting commit-resumed for target %s, a" >> " thread has a pending waitstatus", >> @@ -3036,7 +3056,7 @@ maybe_set_commit_resumed_all_targets () >> >> switch_to_inferior_no_thread (inf); >> >> - if (target_has_pending_events ()) >> + if (!force_p && target_has_pending_events ()) >> { >> infrun_debug_printf ("not requesting commit-resumed for target %s, " >> "target has pending events", >> @@ -3129,7 +3149,7 @@ scoped_disable_commit_resumed::reset () >> { >> /* This is the outermost instance, re-enable >> COMMIT_RESUMED_STATE on the targets where it's possible. */ >> - maybe_set_commit_resumed_all_targets (); >> + maybe_set_commit_resumed_all_targets (false); >> } >> else >> { >> @@ -3162,7 +3182,7 @@ scoped_disable_commit_resumed::reset_and_commit () >> /* See infrun.h. */ >> >> scoped_enable_commit_resumed::scoped_enable_commit_resumed >> - (const char *reason) >> + (const char *reason, bool force_p) >> : m_reason (reason), >> m_prev_enable_commit_resumed (enable_commit_resumed) >> { >> @@ -3174,7 +3194,7 @@ scoped_enable_commit_resumed::scoped_enable_commit_resumed >> >> /* Re-enable COMMIT_RESUMED_STATE on the targets where it's >> possible. */ >> - maybe_set_commit_resumed_all_targets (); >> + maybe_set_commit_resumed_all_targets (force_p); >> >> maybe_call_commit_resumed_all_targets (); >> } >> @@ -3880,10 +3900,11 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs, >> polling the rest of the inferior list starting from that one in a >> circular fashion until the whole list is polled once. */ >> >> - auto inferior_matches = [&wait_ptid] (inferior *inf) >> + ptid_t wait_ptid_pid {wait_ptid.pid ()}; >> + auto inferior_matches = [&wait_ptid_pid] (inferior *inf) >> { >> return (inf->process_target () != nullptr >> - && ptid_t (inf->pid).matches (wait_ptid)); >> + && ptid_t (inf->pid).matches (wait_ptid_pid)); >> }; >> >> /* First see how many matching inferiors we have. */ >> @@ -4352,7 +4373,17 @@ fetch_inferior_event () >> the event. */ >> scoped_disable_commit_resumed disable_commit_resumed ("handling event"); >> >> - if (!do_target_wait (minus_one_ptid, &ecs, TARGET_WNOHANG)) >> + /* Is the current thread performing an inferior function call as part >> + of a breakpoint condition evaluation? */ >> + bool in_cond_eval = (inferior_ptid != null_ptid >> + && inferior_thread ()->control.in_cond_eval); >> + >> + /* If the thread is in the middle of the condition evaluation, wait for >> + an event from the current thread. Otherwise, wait for an event from >> + any thread. */ >> + ptid_t waiton_ptid = in_cond_eval ? inferior_ptid : minus_one_ptid; >> + >> + if (!do_target_wait (waiton_ptid, &ecs, TARGET_WNOHANG)) >> { >> infrun_debug_printf ("do_target_wait returned no event"); >> disable_commit_resumed.reset_and_commit (); >> @@ -4408,7 +4439,12 @@ fetch_inferior_event () >> bool should_notify_stop = true; >> bool proceeded = false; >> >> - stop_all_threads_if_all_stop_mode (); >> + /* If the thread that stopped just completed an inferior >> + function call as part of a condition evaluation, then we >> + don't want to stop all the other threads. */ >> + if (ecs.event_thread == nullptr >> + || !ecs.event_thread->control.in_cond_eval) >> + stop_all_threads_if_all_stop_mode (); >> >> clean_up_just_stopped_threads_fsms (&ecs); >> >> @@ -4423,7 +4459,7 @@ fetch_inferior_event () >> proceeded = normal_stop (); >> } >> >> - if (!proceeded) >> + if (!proceeded && !in_cond_eval) >> { >> inferior_event_handler (INF_EXEC_COMPLETE); >> cmd_done = 1; >> diff --git a/gdb/infrun.h b/gdb/infrun.h >> index 9513bc570e4..1a6743aa45e 100644 >> --- a/gdb/infrun.h >> +++ b/gdb/infrun.h >> @@ -395,7 +395,8 @@ extern void maybe_call_commit_resumed_all_targets (); >> >> struct scoped_enable_commit_resumed >> { >> - explicit scoped_enable_commit_resumed (const char *reason); >> + explicit scoped_enable_commit_resumed (const char *reason, >> + bool force_p = false); >> ~scoped_enable_commit_resumed (); >> >> DISABLE_COPY_AND_ASSIGN (scoped_enable_commit_resumed); >> diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c >> b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c >> new file mode 100644 >> index 00000000000..e2a8ccb4ebe >> --- /dev/null >> +++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c >> @@ -0,0 +1,135 @@ >> +/* Copyright 2022-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 . */ >> + >> +#include >> +#include >> +#include >> +#include >> + >> +#define NUM_THREADS 2 >> + >> +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; >> + >> +/* Some global variables to poke, just for something to do. */ >> +volatile int global_var_0 = 0; >> +volatile int global_var_1 = 0; >> + >> +/* This flag is updated from GDB. */ >> +volatile int raise_signal = 0; >> + >> +/* Implement the breakpoint condition function. Release the other thread >> + and try to give the other thread a chance to run. Then return ANSWER. */ >> +int >> +condition_core_func (int answer) >> +{ >> + /* This unlock should release the other thread. */ >> + if (pthread_mutex_unlock (&mutex) != 0) >> + abort (); >> + >> + /* And this yield and sleep should (hopefully) give the other thread a >> + chance to run. This isn't guaranteed of course, but once the other >> + thread does run it should hit a breakpoint, which GDB should >> + (temporarily) ignore, so there's no easy way for us to know the other >> + thread has done what it needs to, thus, yielding and sleeping is the >> + best we can do. */ >> + sched_yield (); >> + sleep (2); >> + >> + return answer; >> +} >> + >> +void >> +stop_marker () >> +{ >> + int a = 100; /* Final breakpoint here. */ >> +} >> + >> +/* A breakpoint condition function that always returns true. */ >> +int >> +condition_true_func () >> +{ >> + return condition_core_func (1); >> +} >> + >> +/* A breakpoint condition function that always returns false. */ >> +int >> +condition_false_func () >> +{ >> + return condition_core_func (0); >> +} >> + >> +void * >> +worker_func (void *arg) >> +{ >> + volatile int *ptr = 0; >> + int tid = *((int *) arg); >> + >> + switch (tid) >> + { >> + case 0: >> + global_var_0 = 11; /* First thread breakpoint. */ >> + break; >> + >> + case 1: >> + if (pthread_mutex_lock (&mutex) != 0) >> + abort (); >> + if (raise_signal) >> + global_var_1 = *ptr; /* Signal here. */ >> + else >> + global_var_1 = 99; /* Other thread breakpoint. */ >> + break; >> + >> + default: >> + abort (); >> + } >> + >> + return NULL; >> +} >> + >> +int >> +main () >> +{ >> + pthread_t threads[NUM_THREADS]; >> + int args[NUM_THREADS]; >> + >> + /* Set an alarm, just in case the test deadlocks. */ >> + alarm (300); >> + >> + /* We want the mutex to start locked. */ >> + if (pthread_mutex_lock (&mutex) != 0) >> + abort (); >> + >> + for (int i = 0; i < NUM_THREADS; i++) >> + { >> + args[i] = i; >> + pthread_create (&threads[i], NULL, worker_func, &args[i]); >> + } >> + >> + for (int i = 0; i < NUM_THREADS; i++) >> + { >> + void *retval; >> + pthread_join (threads[i], &retval); >> + } >> + >> + /* Unlock once we're done, just for cleanliness. */ >> + if (pthread_mutex_unlock (&mutex) != 0) >> + abort (); >> + >> + stop_marker (); >> + >> + return 0; >> +} >> diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp >> b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp >> new file mode 100644 >> index 00000000000..6d4e1e13ab2 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp >> @@ -0,0 +1,174 @@ >> +# Copyright 2022-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 . >> + >> +# Test for conditional breakpoints where the breakpoint condition includes >> +# an inferior function call. >> +# >> +# The tests in this script are testing what happens when an event arrives in >> +# another thread while GDB is waiting for the inferior function call (in the >> +# breakpoint condition) to finish. >> +# >> +# The expectation is that GDB will queue events for other threads and wait >> +# for the inferior function call to complete, if the condition is true, then >> +# the conditional breakpoint should be reported first. The other thread >> +# event should of course, not get lost, and should be reported as soon as >> +# the user tries to continue the inferior. >> +# >> +# If the conditional breakpoint ends up not being taken (the condition is >> +# false), then the other thread event should be reported immediately. >> +# >> +# This script tests what happens when the other thread event is (a) the >> +# other thread hitting a breakpoint, and (b) the other thread taking a >> +# signal (SIGSEGV in this case). >> + >> +standard_testfile >> + >> +if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \ >> + {debug pthreads}] == -1 } { >> + return >> +} >> + >> +set cond_bp_line [gdb_get_line_number "First thread breakpoint"] >> +set other_bp_line [gdb_get_line_number "Other thread breakpoint"] >> +set final_bp_line [gdb_get_line_number "Final breakpoint here"] >> +set signal_line [gdb_get_line_number "Signal here"] >> + >> +# Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main. >> +proc start_gdb_and_runto_main { target_async target_non_stop } { >> + save_vars { ::GDBFLAGS } { >> + append ::GDBFLAGS \ >> + " -ex \"maint set target-non-stop $target_non_stop\"" >> + append ::GDBFLAGS \ >> + " -ex \"maintenance set target-async ${target_async}\"" >> + >> + clean_restart ${::binfile} >> + } >> + >> + if { ![runto_main] } { >> + return -1 >> + } >> + >> + return 0 >> +} >> + >> +# Run a test of GDB's conditional breakpoints, where the conditions include >> +# inferior function calls. While the inferior function call is executing >> +# another thread will hit a breakpoint (when OTHER_THREAD_SIGNAL is false), >> +# or receive a signal (when OTHER_THREAD_SIGNAL is true). GDB should report >> +# the conditional breakpoint first (if the condition is true), and then >> +# report the second thread event once the inferior is continued again. >> +# >> +# When STOP_AT_COND is true then the conditional breakpoint will have a >> +# condition that evaluates to true (and the GDB will stop at the > > Nit: No "the" before "GDB". > >> +# breakpoint), otherwise, the condition will evaluate to false (and GDB will >> +# not stop at the breakpoint). >> +proc run_condition_test { stop_at_cond other_thread_signal \ >> + target_async target_non_stop } { >> + if { [start_gdb_and_runto_main $target_async \ >> + $target_non_stop] == -1 } { >> + return >> + } >> + >> + # Setup the conditional breakpoint. >> + if { $stop_at_cond } { >> + set cond_func "condition_true_func" >> + } else { >> + set cond_func "condition_false_func" >> + } >> + gdb_breakpoint \ >> + "${::srcfile}:${::cond_bp_line} if (${cond_func} ())" >> + set cond_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number for conditional breakpoint"] >> + >> + if { $other_thread_signal } { >> + # Arrange for the other thread to raise a signal while GDB is >> + # evaluating the breakpoint condition. >> + gdb_test_no_output "set raise_signal = 1" >> + } else { >> + # And a breakpoint that will be hit by another thread only once the >> + # breakpoint condition starts to be evaluated. >> + gdb_breakpoint "${::srcfile}:${::other_bp_line}" >> + set other_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number for other breakpoint"] >> + } >> + >> + # A final breakpoint once the test has completed. >> + gdb_breakpoint "${::srcfile}:${::final_bp_line}" >> + set final_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number for final breakpoint"] >> + >> + if { $stop_at_cond } { >> + # Continue. The first breakpoint we hit should be the conditional >> + # breakpoint. The other thread will have hit its breakpoint, but >> + # that will have been deferred until the conditional breakpoint is >> + # reported. >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "" \ >> + "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${cond_bp_num}, >> worker_func \[^\r\n\]+:${::cond_bp_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+First thread breakpoint\[^\r\n\]+"] \ >> + "hit the conditional breakpoint" >> + } >> + >> + if { $other_thread_signal } { >> + # Now continue again, the other thread will now report that it >> + # received a signal. >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "Thread ${::decimal} \"\[^\"\r\n\]+\" received signal SIGSEGV, Segmentation >> fault\\." \ >> + "\\\[Switching to Thread \[^\r\n\]+\\\]" \ >> + "${::hex} in worker_func \[^\r\n\]+:${::signal_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Signal here\[^\r\n\]+"] \ >> + "received signal in other thread" >> + } else { >> + # Now continue again, the other thread will now report its >> + # breakpoint. >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "" \ >> + "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${other_bp_num}, >> worker_func \[^\r\n\]+:${::other_bp_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Other thread breakpoint\[^\r\n\]+"] \ >> + "hit the breakpoint in other thread" >> + >> + # Run to the stop marker. >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "" \ >> + "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${final_bp_num}, >> stop_marker \[^\r\n\]+:${::final_bp_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Final breakpoint here\[^\r\n\]+"] \ >> + "hit the final breakpoint" >> + } >> + >> + gdb_exit > > Is this really necessary? We do clean_restart at the beginning of a test, > which exits GDB as the first thing. > >> +} >> + >> +foreach_with_prefix target_async { "on" "off" } { >> + foreach_with_prefix target_non_stop { "on" "off" } { >> + foreach_with_prefix other_thread_signal { true false } { >> + foreach_with_prefix stop_at_cond { true false } { >> + run_condition_test $stop_at_cond $other_thread_signal \ >> + $target_async $target_non_stop >> + } >> + } >> + } >> +} >> diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c >> b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c >> new file mode 100644 >> index 00000000000..9d746d8be49 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c >> @@ -0,0 +1,89 @@ >> +/* Copyright 2022-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 . */ >> + >> +#include >> +#include >> + >> +#define NUM_THREADS 3 >> + >> +int >> +is_matching_tid (int *tid_ptr, int tid_value) >> +{ >> + return *tid_ptr == tid_value; >> +} >> + >> +int >> +return_true () >> +{ >> + return 1; >> +} >> + >> +int >> +return_false () >> +{ >> + return 0; >> +} >> + >> +int >> +function_that_segfaults () >> +{ >> + int *p = 0; >> + *p = 1; /* Segfault happens here. */ >> +} >> + >> +int >> +function_with_breakpoint () >> +{ >> + return 1; /* Nested breakpoint. */ >> +} >> + >> +void * >> +worker_func (void *arg) >> +{ >> + int a = 42; /* Breakpoint here. */ >> +} >> + >> +void >> +stop_marker () >> +{ >> + int b = 99; /* Stop marker. */ >> +} >> + >> +int >> +main () >> +{ >> + pthread_t threads[NUM_THREADS]; >> + int args[NUM_THREADS]; >> + >> + alarm (300); >> + >> + for (int i = 0; i < NUM_THREADS; i++) >> + { >> + args[i] = i; >> + pthread_create (&threads[i], NULL, worker_func, &args[i]); >> + } >> + >> + for (int i = 0; i < NUM_THREADS; i++) >> + { >> + void *retval; >> + pthread_join (threads[i], &retval); >> + } >> + >> + stop_marker (); >> + >> + return 0; >> +} >> diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp >> b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp >> new file mode 100644 >> index 00000000000..37e1b64d9a4 >> --- /dev/null >> +++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp >> @@ -0,0 +1,235 @@ >> +# Copyright 2022-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 . >> + >> +# Some simple tests of inferior function calls from breakpoint >> +# conditions, in multi-threaded inferiors. >> +# >> +# This test sets up a multi-threaded inferior, and places a breakpoint >> +# at a location that many of the threads will reach. We repeat the >> +# test with different conditions, sometimes a single thread should >> +# stop at the breakpoint, sometimes multiple threads should stop, and >> +# sometime no threads should stop. > > Typo: sometime -> sometimes > >> + >> +standard_testfile >> + >> +if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \ >> + {debug pthreads}] == -1 } { >> + return >> +} >> + >> +set cond_bp_line [gdb_get_line_number "Breakpoint here"] >> +set stop_bp_line [gdb_get_line_number "Stop marker"] >> +set nested_bp_line [gdb_get_line_number "Nested breakpoint"] >> +set segv_line [gdb_get_line_number "Segfault happens here"] >> + >> +# Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main. >> +proc start_gdb_and_runto_main { target_async target_non_stop } { >> + save_vars { ::GDBFLAGS } { >> + append ::GDBFLAGS \ >> + " -ex \"maint set target-non-stop $target_non_stop\"" >> + append ::GDBFLAGS \ >> + " -ex \"maintenance set target-async ${target_async}\"" >> + >> + clean_restart ${::binfile} >> + } >> + >> + if { ![runto_main] } { >> + return -1 >> + } >> + >> + return 0 >> +} >> + >> +# Run a test of GDB's conditional breakpoints, where the conditions include >> +# inferior function calls. >> +# >> +# CONDITION is the expression to be used as the breakpoint condition. >> +# >> +# N_EXPECTED_HITS is the number of threads that we expect to stop due to >> +# CONDITON. >> +# >> +# MESSAGE is used as a test name prefix. >> +proc run_condition_test { message n_expected_hits condition \ >> + target_async target_non_stop } { >> + with_test_prefix $message { >> + >> + if { [start_gdb_and_runto_main $target_async \ >> + $target_non_stop] == -1 } { >> + return >> + } >> + >> + # Use this convenience variable to track how often the >> + # breakpoint condition has been evaluated. This should be >> + # once per thread. >> + gdb_test "set \$n_cond_eval = 0" >> + >> + # Setup the conditional breakpoint. >> + gdb_breakpoint \ >> + "${::srcfile}:${::cond_bp_line} if ((++\$n_cond_eval) && (${condition}))" >> + >> + # And a breakpoint that we hit when the test is over, this one is >> + # not conditional. Only the main thread gets here once all the >> + # other threads have finished. >> + gdb_breakpoint "${::srcfile}:${::stop_bp_line}" >> + >> + # The number of times we stop at the conditional breakpoint. >> + set n_hit_condition 0 >> + >> + # Now keep 'continue'-ing GDB until all the threads have finished >> + # and we reach the stop_marker breakpoint. >> + gdb_test_multiple "continue" "spot all breakpoint hits" { >> + -re " worker_func >> \[^\r\n\]+${::srcfile}:${::cond_bp_line}\r\n${::decimal}\\s+\[^\r\n\]+Breakpoint >> here\[^\r\n\]+\r\n${::gdb_prompt} $" { >> + incr n_hit_condition >> + send_gdb "continue\n" >> + exp_continue >> + } >> + >> + -re " stop_marker >> \[^\r\n\]+${::srcfile}:${::stop_bp_line}\r\n${::decimal}\\s+\[^\r\n\]+Stop >> marker\[^\r\n\]+\r\n${::gdb_prompt} $" { >> + pass $gdb_test_name >> + } >> + } >> + >> + gdb_assert { $n_hit_condition == $n_expected_hits } \ >> + "stopped at breakpoint the expected number of times" >> + >> + # Ensure the breakpoint condition was evaluated once per thread. >> + gdb_test "print \$n_cond_eval" "= 3" \ >> + "condition was evaluated in each thread" >> + } >> +} >> + >> +# Check that after handling a conditional breakpoint (where the condition >> +# includes an inferior call), it is still possible to kill the running >> +# inferior, and then restart the inferior. >> +# >> +# At once point doing this would result in GDB giving an assertion error. >> +proc_with_prefix run_kill_and_restart_test { target_async target_non_stop } { >> + # This test relies on the 'start' command, which is not possible with >> + # the plain 'remote' target. >> + if { [target_info gdb_protocol] == "remote" } { >> + return >> + } >> + >> + if { [start_gdb_and_runto_main $target_async \ >> + $target_non_stop] == -1 } { >> + return >> + } >> + >> + # Setup the conditional breakpoint. >> + gdb_breakpoint \ >> + "${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 1))" >> + gdb_continue_to_breakpoint "worker_func" >> + >> + # Now kill the program being debugged. >> + gdb_test "kill" "" "kill process" \ >> + "Kill the program being debugged.*y or n. $" "y" >> + >> + # Check we can restart the inferior. At one point this would trigger an >> + # assertion. >> + gdb_test "start" ".*" > > I believe using 'gdb_start_cmd' is preferred. > >> +} >> + >> +# Create a conditional breakpoint which includes a call to a function that >> +# segfaults. Run GDB and check what happens when the inferior segfaults >> +# during the inferior call. >> +proc_with_prefix run_bp_cond_segfaults { target_async target_non_stop } { >> + if { [start_gdb_and_runto_main $target_async \ >> + $target_non_stop] == -1 } { >> + return >> + } >> + >> + # This test relies on the inferior segfaulting when trying to >> + # access address zero. >> + if { [is_address_zero_readable] } { >> + return >> + } >> + >> + # Setup the conditional breakpoint, include a call to >> + # 'function_that_segfaults', which triggers the segfault. >> + gdb_breakpoint \ >> + "${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 0) && >> function_that_segfaults ())" >> + set bp_1_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number of conditional breakpoint"] >> + >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "Thread ${::decimal} \"infcall-from-bp\" received signal SIGSEGV, Segmentation >> fault\\." \ >> + "${::hex} in function_that_segfaults \\(\\) at \[^\r\n\]+:${::segv_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Segfault happens here\[^\r\n\]+" \ >> + "Error in testing condition for breakpoint ${bp_1_num}:" \ >> + "The program being debugged was signaled while in a function called from GDB\\." >> \ >> + "GDB remains in the frame where the signal was received\\." \ >> + "To change this behavior use \"set unwindonsignal on\"\\." \ >> + "Evaluation of the expression containing the function" \ >> + "\\(function_that_segfaults\\) will be abandoned\\." \ >> + "When the function is done executing, GDB will silently stop\\."] >> +} >> + >> +# Create a conditional breakpoint which includes a call to a function that >> +# itself has a breakpoint set within it. Run GDB and check what happens >> +# when GDB hits the nested breakpoint. >> +proc_with_prefix run_bp_cond_hits_breakpoint { target_async target_non_stop } { >> + if { [start_gdb_and_runto_main $target_async \ >> + $target_non_stop] == -1 } { >> + return >> + } >> + >> + # Setup the conditional breakpoint, include a call to >> + # 'function_with_breakpoint' in which we will shortly place a >> + # breakpoint. >> + gdb_breakpoint \ >> + "${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 0) && >> function_with_breakpoint ())" >> + set bp_1_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number of conditional breakpoint"] >> + >> + gdb_breakpoint "${::srcfile}:${::nested_bp_line}" >> + set bp_2_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number of nested breakpoint"] >> + >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "Thread ${::decimal} \"infcall-from-bp\" hit Breakpoint ${bp_2_num}, >> function_with_breakpoint \\(\\) at \[^\r\n\]+:${::nested_bp_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Nested breakpoint\[^\r\n\]+" \ >> + "Error in testing condition for breakpoint ${bp_1_num}:" \ >> + "The program being debugged stopped while in a function called from GDB\\." \ >> + "Evaluation of the expression containing the function" \ >> + "\\(function_with_breakpoint\\) will be abandoned\\." \ >> + "When the function is done executing, GDB will silently stop\\."] >> +} >> + >> +foreach_with_prefix target_async { "on" "off" } { >> + foreach_with_prefix target_non_stop { "on" "off" } { >> + run_condition_test "exactly one thread is hit" \ >> + 1 "is_matching_tid (arg, 1)" \ >> + $target_async $target_non_stop >> + run_condition_test "exactly two threads are hit" \ >> + 2 "(is_matching_tid (arg, 0) || is_matching_tid (arg, 2))" \ >> + $target_async $target_non_stop >> + run_condition_test "all three threads are hit" \ >> + 3 "return_true ()" \ >> + $target_async $target_non_stop >> + run_condition_test "no thread is hit" \ >> + 0 "return_false ()" \ >> + $target_async $target_non_stop >> + >> + run_kill_and_restart_test $target_async $target_non_stop >> + run_bp_cond_segfaults $target_async $target_non_stop >> + run_bp_cond_hits_breakpoint $target_async $target_non_stop >> + } >> +} >> diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c >> b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c >> new file mode 100644 >> index 00000000000..835c72f03cf >> --- /dev/null >> +++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c >> @@ -0,0 +1,139 @@ >> +/* Copyright 2022-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 . */ >> + >> +#include >> +#include >> +#include >> +#include >> + >> +#define NUM_THREADS 5 >> + >> +/* Semaphores, used to track when threads have started, and to control >> + when the threads finish. */ >> +sem_t startup_semaphore; >> +sem_t finish_semaphore; >> + >> +/* Mutex to control when the first worker thread hit a breakpoint >> + location. */ >> +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; >> + >> +/* Global variable to poke, just so threads have something to do. */ >> +volatile int global_var = 0; >> + >> +int >> +return_true () >> +{ >> + return 1; >> +} >> + >> +int >> +return_false () >> +{ >> + return 0; >> +} >> + >> +void * >> +worker_func (void *arg) >> +{ >> + int tid = *((int *) arg); >> + >> + switch (tid) >> + { >> + case 0: >> + /* Wait for MUTEX to become available, then pass through the >> + conditional breakpoint location. */ >> + if (pthread_mutex_lock (&mutex) != 0) >> + abort (); >> + global_var = 99; /* Conditional breakpoint here. */ >> + if (pthread_mutex_unlock (&mutex) != 0) >> + abort (); >> + break; >> + >> + default: >> + /* Notify the main thread that the thread has started, then wait for >> + the main thread to tell us to finish. */ >> + sem_post (&startup_semaphore); >> + if (sem_wait (&finish_semaphore) != 0) >> + abort (); >> + break; >> + } >> +} >> + >> +void >> +stop_marker () >> +{ >> + global_var = 99; /* Stop marker. */ >> +} >> + >> +int >> +main () >> +{ >> + pthread_t threads[NUM_THREADS]; >> + int args[NUM_THREADS]; >> + void *retval; >> + >> + /* An alarm, just in case the thread deadlocks. */ >> + alarm (300); >> + >> + /* Semaphore initialization. */ >> + if (sem_init (&startup_semaphore, 0, 0) != 0) >> + abort (); >> + if (sem_init (&finish_semaphore, 0, 0) != 0) >> + abort (); >> + >> + /* Lock MUTEX, this prevents the first worker thread from rushing ahead. */ >> + if (pthread_mutex_lock (&mutex) != 0) >> + abort (); >> + >> + /* Worker thread creation. */ >> + for (int i = 0; i < NUM_THREADS; i++) >> + { >> + args[i] = i; >> + pthread_create (&threads[i], NULL, worker_func, &args[i]); >> + } >> + >> + /* Wait for every thread (other than the first) to tell us it has started >> + up. */ >> + for (int i = 1; i < NUM_THREADS; i++) >> + { >> + if (sem_wait (&startup_semaphore) != 0) >> + abort (); >> + } >> + >> + /* Unlock the first thread so it can proceed. */ >> + if (pthread_mutex_unlock (&mutex) != 0) >> + abort (); >> + >> + /* Wait for the first thread only. */ >> + pthread_join (threads[0], &retval); >> + >> + /* Now post FINISH_SEMAPHORE to allow all the other threads to finish. */ >> + for (int i = 1; i < NUM_THREADS; i++) >> + sem_post (&finish_semaphore); >> + >> + /* Now wait for the remaining threads to complete. */ >> + for (int i = 1; i < NUM_THREADS; i++) >> + pthread_join (threads[i], &retval); >> + >> + /* Semaphore cleanup. */ >> + sem_destroy (&finish_semaphore); >> + sem_destroy (&startup_semaphore); >> + >> + stop_marker (); >> + >> + return 0; >> +} >> diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp >> b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp >> new file mode 100644 >> index 00000000000..787dee3aa8e >> --- /dev/null >> +++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp >> @@ -0,0 +1,117 @@ >> +# Copyright 2022-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 . >> + >> +# This test reprocuces bug gdb/28942, performing an inferior function >> +# call from a breakpoint condition in a multi-threaded inferior. >> +# >> +# The important part of this test is that, when the conditional >> +# breakpoint is hit, and the condition (which includes an inferior >> +# function call) is evaluated, the other threads are running. >> + >> +standard_testfile >> + >> +if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \ >> + {debug pthreads}] == -1 } { >> + return >> +} >> + >> +set cond_bp_line [gdb_get_line_number "Conditional breakpoint here"] >> +set final_bp_line [gdb_get_line_number "Stop marker"] >> + >> +# Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main. >> +proc start_gdb_and_runto_main { target_async target_non_stop } { >> + save_vars { ::GDBFLAGS } { >> + append ::GDBFLAGS \ >> + " -ex \"maint set target-non-stop $target_non_stop\"" >> + append ::GDBFLAGS \ >> + " -ex \"maintenance set target-async ${target_async}\"" >> + >> + clean_restart ${::binfile} >> + } >> + >> + if { ![runto_main] } { >> + return -1 >> + } >> + >> + return 0 >> +} >> + >> +# Run a test of GDB's conditional breakpoints, where the conditions include >> +# inferior function calls. >> +# >> +# TARGET_ASYNC and TARGET_NON_STOP are used when starting up GDB. >> +# >> +# When STOP_AT_COND is true the breakpoint condtion will evaluate to >> +# true, and GDB will stop at the breakpoint. Otherwise, the >> +# breakpoint condition will evaluate to false and GDB will not stop at >> +# the breakpoint. >> +proc run_condition_test { stop_at_cond \ >> + target_async target_non_stop } { >> + if { [start_gdb_and_runto_main $target_async \ >> + $target_non_stop] == -1 } { >> + return >> + } >> + >> + # Setup the conditional breakpoint. >> + if { $stop_at_cond } { >> + set cond_func "return_true" >> + } else { >> + set cond_func "return_false" >> + } >> + gdb_breakpoint \ >> + "${::srcfile}:${::cond_bp_line} if (${cond_func} ())" >> + set cond_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number for conditional breakpoint"] >> + >> + # And a breakpoint that we hit when the test is over, this one is >> + # not conditional. >> + gdb_breakpoint "${::srcfile}:${::final_bp_line}" >> + set final_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ >> + "get number for final breakpoint"] >> + >> + if { $stop_at_cond } { >> + # Continue. The first breakpoint we hit should be the conditional >> + # breakpoint. The other thread will have hit its breakpoint, but >> + # that will have been deferred until the conditional breakpoint is >> + # reported. >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "" \ >> + "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${cond_bp_num}, >> worker_func \[^\r\n\]+:${::cond_bp_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Conditional breakpoint here\[^\r\n\]+"] \ >> + "hit the conditional breakpoint" >> + } >> + >> + # Run to the stop marker. >> + gdb_test "continue" \ >> + [multi_line \ >> + "Continuing\\." \ >> + ".*" \ >> + "" \ >> + "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${final_bp_num}, >> stop_marker \[^\r\n\]+:${::final_bp_line}" \ >> + "${::decimal}\\s+\[^\r\n\]+Stop marker\[^\r\n\]+"] \ >> + "hit the final breakpoint" >> +} >> + >> +foreach_with_prefix target_async { "on" "off" } { >> + foreach_with_prefix target_non_stop { "on" "off" } { >> + foreach_with_prefix stop_at_cond { true false } { >> + run_condition_test $stop_at_cond \ >> + $target_async $target_non_stop >> + } >> + } >> +} >> -- >> 2.25.4 > > Intel Deutschland GmbH > Registered Address: Am Campeon 10, 85579 Neubiberg, Germany > Tel: +49 89 99 8853-0, www.intel.de > Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva > Chairperson of the Supervisory Board: Nicole Lau > Registered Office: Munich > Commercial Register: Amtsgericht Muenchen HRB 186928