From patchwork Mon Mar 31 18:11:55 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Doug Evans X-Patchwork-Id: 359 Return-Path: X-Original-To: siddhesh@wilcox.dreamhost.com Delivered-To: siddhesh@wilcox.dreamhost.com Received: from homiemail-mx22.g.dreamhost.com (mx2.sub5.homie.mail.dreamhost.com [208.113.200.128]) by wilcox.dreamhost.com (Postfix) with ESMTP id 09FC4360361 for ; Mon, 31 Mar 2014 11:12:05 -0700 (PDT) Received: by homiemail-mx22.g.dreamhost.com (Postfix, from userid 14314964) id AC35E4515598; Mon, 31 Mar 2014 11:12:05 -0700 (PDT) X-Original-To: gdb@patchwork.siddhesh.in Delivered-To: x14314964@homiemail-mx22.g.dreamhost.com Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by homiemail-mx22.g.dreamhost.com (Postfix) with ESMTPS id 7EE4D45437AB for ; Mon, 31 Mar 2014 11:12:05 -0700 (PDT) DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:from:mime-version:content-type :content-transfer-encoding:message-id:date:to:subject :in-reply-to:references; q=dns; s=default; b=K8/FyqfzywiP7rZnUcG NooCO41YQ0W/tB0+pGM6MpfOAw2uB+6zUZ06TgPYR6MX4ARRlLkQfUDDI/P4Rbm2 CUThv4wAii/Dobz04iFnJfnuFCQUpej1VPQF9S5nRvpLWf0MMVUgedtcNzYQzlDo hrC7JPuQ0YLKAwX1Bgx74Egs= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:from:mime-version:content-type :content-transfer-encoding:message-id:date:to:subject :in-reply-to:references; s=default; bh=KcgZOygUadurLH0RgrRytv+ZR 0c=; b=ZPRZfWxRACJrZ+ayfaSezhub66vdj7Jh006CGFgt9GFXSUw/457lYy38o r4wONhkpTAe8YHnpV1PlmuQOb5/H43PXvqurvI/zIlP83ApqqL7CF+/p4eUNtZiV tt3ngXgHNKcuN1wv8nSgcok31hvtdKnl6ope4uw+uo1nIx10xI= Received: (qmail 7626 invoked by alias); 31 Mar 2014 18:12:02 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 7614 invoked by uid 89); 31 Mar 2014 18:12:01 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-3.2 required=5.0 tests=AWL, BAYES_00, RCVD_IN_DNSWL_LOW, RP_MATCHES_RCVD, SPF_PASS autolearn=ham version=3.3.2 X-HELO: mail-yk0-f201.google.com Received: from mail-yk0-f201.google.com (HELO mail-yk0-f201.google.com) (209.85.160.201) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES128-SHA encrypted) ESMTPS; Mon, 31 Mar 2014 18:11:58 +0000 Received: by mail-yk0-f201.google.com with SMTP id 19so441612ykq.2 for ; Mon, 31 Mar 2014 11:11:56 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:mime-version:content-type :content-transfer-encoding:message-id:date:to:subject:in-reply-to :references; bh=8Ad2qd2EU1H/GerYBkSetGXRGerpkQpDzxBBi6SJyx8=; b=TG8HFMZlIrTAeE/6AtGj3H9/K9qzZgj7JvvsLRvO4dmo75UurKLloQlgUa/NgGfntj qV73jAVi7VHGZwKCtiNoEn9OlYT9+sxlASchRs2nh/K5I/oPyezbJOXGvUdhWzStAOmt pcMN7FM/n55uUJg7hlMV2weSFYsRoXMEnc9Oy6kPIdFbfAXT4lhaf+7964dgymPXvDbO KRKP5w7tt8Iz89ssiz6NesfLb6rqaPzlFU8x6BHXb0/PbYRSC8YO8M63CSTBvLGHoZtI MbnQ6yKoFVEA85UaW5SVuy6ehjug8MGiAZKIozkw8TacJrN92GJ7Y7NQlk1omPu020tH demg== X-Gm-Message-State: ALoCoQkpDUvbb7WqjEq6VEysqNpL/Tl05NYzMrXLz7AUKrs6DgmVwCoIUwMkgo68yG/rEIoThqDa9JgoICGhxRjcgQyQyAYynx+FivMSahC3zNPhzXzqU1IQG3WWssotSU/dCoBWkMfECGsj2RDrcmKVXnxcQv35hBq/qeA4kSy3v2qsDlfQTNAYojAw8+ytAD23t19q0Lti8B5W/YcgwYAb26C43WO0RRYVj/lGBBFI+Py5weA1TNI= X-Received: by 10.58.198.3 with SMTP id iy3mr9634260vec.39.1396289516322; Mon, 31 Mar 2014 11:11:56 -0700 (PDT) Received: from corp2gmr1-2.hot.corp.google.com (corp2gmr1-2.hot.corp.google.com [172.24.189.93]) by gmr-mx.google.com with ESMTPS id s65si2785576yhc.2.2014.03.31.11.11.56 for (version=TLSv1.1 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 31 Mar 2014 11:11:56 -0700 (PDT) Received: from ruffy.mtv.corp.google.com (ruffy.mtv.corp.google.com [172.17.128.44]) by corp2gmr1-2.hot.corp.google.com (Postfix) with ESMTP id D263E5A421E for ; Mon, 31 Mar 2014 11:11:55 -0700 (PDT) From: Doug Evans MIME-Version: 1.0 Message-ID: <21305.45035.310308.253836@ruffy.mtv.corp.google.com> Date: Mon, 31 Mar 2014 11:11:55 -0700 To: gdb-patches@sourceware.org Subject: Re: [PATCH, doc RFA]: Fix pr 14236 In-Reply-To: References: X-IsSubscribed: yes X-DH-Original-To: gdb@patchwork.siddhesh.in Doug Evans writes: > Hi. > > This patch is a proposed(!) fix to bug 14236. > The problem here is that the "interrupt" command (figuratively speaking) > takes an implicit & as an argument (it is always asynchronous) and > users aren't expecting this. > > This patch fixes the problem by changing the behaviour by making > "interrupt" synchronous by default and supporting a new option &, > to be consistent with every other gdb command (which is what I think > every user would expect, including this user). > > Alas, while I think this is the correct fix (*1), if one could set aside > breaking existing code, the latter, obviously, is important too. > I had to update a few testcases that expected the old behaviour, > but I don't, in this particular case, have a feel for whether the > change in behaviour would break anything outside of gdb's testsuite. > Without a "wait" command the current behaviour is completely useless > in scripts, for example. > > I have patches in the works to fix this in other ways. > E.g., regardless of how "interrupt" is fixed I think we also want a > "wait" command. > > But for now I'd like to know what the community thinks of this approach. > > (*1): gdb's infrun state machine doesn't support waiting for all > threads to stop in non-stop mode, AFAICT, and I'm loathe to make it more > complicated just to handle this. Therefore the continuation that handles > completing the interrupt command waits for any remaining threads itself. > Alas, I'm not sure if there's a technical reason why this patch > can't work. It is derived, in part, from the attach support, and it > too waits for threads outside of the main event loop. Hi. Sorry for the followup. Gotta love test-driven-development. I had a TODO of another testcase and sure enough ... This patch replaces the previous one. No doc changes, just a new testcase and a change to wait for the current thread to stop in non-stop mode. 2014-03-30 Doug Evans PR gdb/14236 * NEWS: Mention change in default behaviour of "interrupt" command. * infcmd.c (struct interrupt_command_continuation_args): New struct. (interrupt_command_continuation_free_args): New function. (wait_thread_stopped): New function. (interrupt_command_post_wait): New function. (interrupt_command_continuation): New function. (add_interrupt_continuation): New function. (interrupt_command): Rewrite. doc/ * gdb.texinfo (Background Execution): Document new option "&" for "interrupt" command. testsuite/ * gdb.base/async-shell.exp: Add "&" to "interrupt" command. * gdb.base/dprintf-non-stop.exp: Ditto. * gdb.base/interrupt-noterm.exp: Ditto. * gdb.threads/interrupt-command.c: New file. * gdb.threads/interrupt-command.exp: New file. diff --git a/gdb/NEWS b/gdb/NEWS index 2a384ba..01d8094 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -3,6 +3,9 @@ *** Changes since GDB 7.7 +* The "interrupt" command now waits for the inferior to stop by default. + To avoid waiting for the inferior to stop pass "&". + * Guile scripting GDB now has support for scripting using Guile. Whether this is diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 0bf33b7..f3747fd 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -5871,13 +5871,15 @@ using the @code{interrupt} command. @table @code @kindex interrupt -@item interrupt -@itemx interrupt -a +@item interrupt @r{[}-a@r{]} @r{[}&@r{]} Suspend execution of the running program. In all-stop mode, @code{interrupt} stops the whole process, but in non-stop mode, it stops only the current thread. To stop the whole program in non-stop mode, -use @code{interrupt -a}. +use @code{interrupt -a}. The @code{-a} option is ignored in all-stop mode. +By default, the @code{interrupt} command waits for the thread to stop, +or if the @code{-a} option is specified all threads. +To not wait for the thread to stop pass @code{&}. @end table @node Thread-Specific Breakpoints diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 920169d..b5535bb 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -2700,29 +2700,178 @@ interrupt_target_1 (int all_threads) set_stop_requested (ptid, 1); } -/* interrupt [-a] - Stop the execution of the target while running in async mode, in - the backgound. In all-stop, stop the whole process. In non-stop - mode, stop the current thread only by default, or stop all threads - if the `-a' switch is used. */ +/* Data associated with an "interrupt" command continuation. */ + +struct interrupt_command_continuation_args +{ + /* This is the thread to wait for or minus_one_ptid to wait for all + threads. */ + ptid_t wait_for_ptid; +}; + +/* continuation_free_arg_ftype function for the interrupt command. */ + +static void +interrupt_command_continuation_free_args (void *args) +{ + struct interrupt_command_continuation_args *a = args; + + xfree (a); +} + +/* Wait for thread PTID to stop, or all threads if it is minus_one_ptid. + This is used in non-stop mode to implement "interrupt -a" without + complicating the infrun state machine more than it already is. */ + +static void +wait_thread_stopped (ptid_t ptid) +{ + struct cleanup* cleanups = make_cleanup_restore_current_thread (); + int all_threads = ptid_equal (ptid, minus_one_ptid); + + gdb_assert (non_stop); + + while (all_threads + ? any_running () + : is_running (ptid)) + { + struct target_waitstatus last; + ptid_t last_ptid; + + QUIT; + wait_for_inferior (); + get_last_target_status (&last_ptid, &last); + finish_thread_state (last_ptid); + /* Leave printing of the stop event of PTID to normal_stop. */ + if (!all_threads && !ptid_equal (last_ptid, ptid)) + print_stop_event (&last); + } + + do_cleanups (cleanups); + + if (!all_threads && !target_can_async_p ()) + normal_stop (); +} + +/* Finish up waiting for thread WAIT_FOR_PTID to stop, or continue waiting + for all threads to stop if it is minus_one_ptid. + Note: This function is named the way it is as it's derived from + attach_command_post_wait. */ + +static void +interrupt_command_post_wait (ptid_t wait_for_ptid) +{ + struct inferior *inferior; + + inferior = current_inferior (); + inferior->control.stop_soon = NO_STOP_QUIETLY; + + /* If this is all-stop we don't have to do anything special to wait for all + threads to stop. */ + if (non_stop) + wait_thread_stopped (wait_for_ptid); +} + +/* continuation_ftype function for the interrupt command. */ + +static void +interrupt_command_continuation (void *args, int err) +{ + struct interrupt_command_continuation_args *a = args; + + if (err) + return; + + interrupt_command_post_wait (a->wait_for_ptid); +} + +/* Queue a continuation to complete the interrupt command. + WAIT_FOR_PTID is minus_one_ptid if waiting for all threads to stop. */ + +static void +add_interrupt_continuation (ptid_t wait_for_ptid) +{ + struct interrupt_command_continuation_args *a; + + a = xmalloc (sizeof (*a)); + a->wait_for_ptid = wait_for_ptid; + + /* We add the continuation to the inferior, even if we're only waiting for + a single thread, because we don't know which thread the event loop will + see first. */ + add_inferior_continuation (interrupt_command_continuation, a, + interrupt_command_continuation_free_args); +} + +/* interrupt [-a] [&] + Stop the execution of the target. + In all-stop, stop the whole process. In non-stop mode, stop only + the current thread by default, or stop all threads if the "-a" + switch is used. "-a" is ignored in all-stop mode. */ static void interrupt_command (char *args, int from_tty) { - if (target_can_async_p ()) + int async_exec = 0; + int all_threads = 0; + + /* Otherwise subsequent invocations will just throw an error. */ + dont_repeat (); + + ERROR_NO_INFERIOR; + ensure_not_tfind_mode (); + + if (args != NULL) + async_exec = strip_bg_char (&args); + + if (args != NULL + && strncmp (args, "-a", sizeof ("-a") - 1) == 0) + all_threads = 1; + + if (all_threads) { - int all_threads = 0; + if (!any_running ()) + error (_("All threads are already stopped.")); + } + else + { + if (!is_running (inferior_ptid)) + error (_("Current thread is already stopped.")); + } - dont_repeat (); /* Not for the faint of heart. */ + /* If we're in synchronous all-stop mode, the above error checking for + whether threads are already stopped should have fired. */ + gdb_assert (non_stop || target_can_async_p ()); - if (args != NULL - && strncmp (args, "-a", sizeof ("-a") - 1) == 0) - all_threads = 1; + /* This will flag an error if & is given in non-async mode. + This isn't an execution command per se, but this performs what we + need. */ + prepare_execution_command (¤t_target, async_exec); - if (!non_stop && all_threads) - error (_("-a is meaningless in all-stop mode.")); + interrupt_target_1 (all_threads); - interrupt_target_1 (all_threads); + if (async_exec) + return; + + /* Set the terminal to the inferior while we're waiting for it to stop. + One reason to do this is because if we don't in target-async mode, + after we return rl_linefunc will get set to NULL. If we leave the + terminal as ours then if the user types a command while gdb is + waiting for the inferior, readline will get called to process the + command and will abort because rl_linefunc is NULL. This is + fragile, but apparently exists as a workaround for a readline + issue. Grep for "trick readline" in display_gdb_prompt. + This must be done after calling async_disable_stdin (which is called + by prepare_execution_command), or it will "early exit" without doing + anything. */ + target_terminal_inferior (); + + if (target_can_async_p ()) + add_interrupt_continuation (all_threads ? minus_one_ptid : inferior_ptid); + else + { + gdb_assert (non_stop); + wait_thread_stopped (all_threads ? minus_one_ptid : inferior_ptid); } } diff --git a/gdb/testsuite/gdb.base/async-shell.exp b/gdb/testsuite/gdb.base/async-shell.exp index 4890a59..d23eb4e 100644 --- a/gdb/testsuite/gdb.base/async-shell.exp +++ b/gdb/testsuite/gdb.base/async-shell.exp @@ -40,9 +40,9 @@ gdb_test "run &" "Starting program: \[^\r\n\]*(\r\n$gdbindex_warning_re)?" gdb_test "shell echo foo" "foo" -set test "interrupt" +set test "interrupt &" gdb_test_multiple $test $test { - -re "interrupt\r\n$gdb_prompt " { + -re "interrupt &\r\n$gdb_prompt " { pass $test } } diff --git a/gdb/testsuite/gdb.base/dprintf-non-stop.exp b/gdb/testsuite/gdb.base/dprintf-non-stop.exp index fdaa5c1..5aa4463 100644 --- a/gdb/testsuite/gdb.base/dprintf-non-stop.exp +++ b/gdb/testsuite/gdb.base/dprintf-non-stop.exp @@ -52,9 +52,9 @@ gdb_expect { # Now test that we're still able to issue commands. GDB used to # implement re-resuming from dprintfs with a synchronous "continue" in # the dprintf's command list, which stole the prompt from the user. -set test "interrupt" +set test "interrupt &" gdb_test_multiple $test $test { - -re "interrupt\r\n$gdb_prompt " { + -re "interrupt &\r\n$gdb_prompt " { pass $test } } diff --git a/gdb/testsuite/gdb.base/interrupt-noterm.exp b/gdb/testsuite/gdb.base/interrupt-noterm.exp index a22acd2..323f5cc 100644 --- a/gdb/testsuite/gdb.base/interrupt-noterm.exp +++ b/gdb/testsuite/gdb.base/interrupt-noterm.exp @@ -59,9 +59,9 @@ if { $async_supported < 0 } { # With native debugging, and no terminal (emulated by interactive-mode # off, above), GDB had a bug where "interrupt" would send SIGINT to # its own process group, instead of the inferior's. -set test "interrupt" +set test "interrupt &" gdb_test_multiple $test $test { - -re "interrupt\r\n$gdb_prompt " { + -re "interrupt &\r\n$gdb_prompt " { pass $test } } diff --git a/gdb/testsuite/gdb.threads/interrupt-command.c b/gdb/testsuite/gdb.threads/interrupt-command.c new file mode 100644 index 0000000..fdeea09 --- /dev/null +++ b/gdb/testsuite/gdb.threads/interrupt-command.c @@ -0,0 +1,63 @@ +/* Test the "interrupt" command. + + Copyright 2014 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 + +#ifndef NR_THREADS +#define NR_THREADS 4 +#endif + +pthread_t threads[NR_THREADS]; + +static void * +thread_entry (void *unused) +{ + while (1) + sleep (1); +} + +static void +all_threads_running (void) +{ +} + +static void +do_something (void) +{ +} + +int +main (int argc, char *argv[]) +{ + int i; + + alarm (60); + + for (i = 0; i < NR_THREADS; ++i) + pthread_create (&threads[i], NULL, thread_entry, NULL); + + all_threads_running (); + + while (1) + do_something (); + + return 0; +} diff --git a/gdb/testsuite/gdb.threads/interrupt-command.exp b/gdb/testsuite/gdb.threads/interrupt-command.exp new file mode 100644 index 0000000..c97a99c --- /dev/null +++ b/gdb/testsuite/gdb.threads/interrupt-command.exp @@ -0,0 +1,465 @@ +# Copyright (C) 2014 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 . + +set NR_THREADS 4 + +standard_testfile + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable [list debug "additional_flags=-DNR_THREADS=$NR_THREADS"]] != "" } { + return -1 +} + +proc prepare_interrupt_test { target_async non_stop } { + global binfile + clean_restart ${binfile} + + gdb_test_no_output "set target-async ${target_async}" "set async mode" + gdb_test_no_output "set non-stop ${non_stop}" "set non-stop mode" + + if ![runto_main] { + untested "could not run to main" + return -1 + } + + gdb_breakpoint "all_threads_running" + gdb_continue_to_breakpoint "all_threads_running" +} + +proc interrupt_all_stop { command } { + global gdb_prompt + + set test_name $command + gdb_test_multiple $command "" { + -re "Program received signal SIGINT.*$gdb_prompt $" { + pass $test_name + } + timeout { + fail "$test_name (timeout)" + } + } +} + +# Apparently the state of a thread isn't necessarily "running" immediately +# after doing "c &". This feels like a bug, but for robustness sake it's a +# good thing to verify anyway. +# THREAD_NR is either a thread number or "all". + +proc wait_thread_running { thread_nr } { + global gdb_prompt + set nr_attempts 4 + + set test_name "wait thread $thread_nr running" + for { set i 0 } { $i < $nr_attempts } { incr i } { + if { "$thread_nr" == "all" } { + set running -1 + } else { + set running -1 + send_gdb "info thread $thread_nr\n" + gdb_expect { + -re "\n\[ \]*$thread_nr +Thread \[^\r\]+\\(running\\)\[\r\n\]+$gdb_prompt $" { + set running 1 + } + -re "\n\[ \]*$thread_nr +Thread \[^\r\]+\[\r\n\]+$gdb_prompt $" { + set running 0 + } + -re "$gdb_prompt $" { + verbose -log "unexpected output processing info thread output" + } + timeout { + verbose -log "unexpected timeout processing info thread output" + } + } + if { $running < 0 } { + fail "$test_name (error processing info threads output)" + return + } + if { $running } { + pass $test_name + return + } + } + } + + fail "$test_name (thread still not running after $nr_attempts attempts)" +} + +proc verify_current_thread { thread_nr } { + global gdb_prompt + + set test_name "verify thread $thread_nr current" + set correct_current_thread 0 + gdb_test_multiple "info threads" $test_name { + -re "\n\[*\]\[ \]+$thread_nr +Thread \[^\r\]+\r" { + set correct_current_thread 1 + exp_continue + } + -re "\n\[*\]?\[ \]+\[\[:digit:\]\]+ +Thread \[^\r\]+\r" { + exp_continue + } + -re "$gdb_prompt $" { + if { $correct_current_thread } { + pass $test_name + } else { + fail $test_name + } + } + timeout { + fail "$test_name (timeout)" + } + } +} + +proc interrupt_all_threads_all_stop_background { command } { + global gdb_prompt + + set command "$command &" + set test_name $command + # This has to watch for gdb_prompt separately from SIGINT because + # other cases added will catch gdb_prompt before we recognize SIGINT. + gdb_test_multiple $command $test_name { + -re "$gdb_prompt " { + exp_continue + } + -re "\[\r\n\]+Program received signal SIGINT" { + pass $test_name + } + timeout { + fail "$test_name (timeout)" + } + } +} + +proc interrupt_and_verify_one_thread_non_stop { ampersand } { + global gdb_prompt NR_THREADS + + for { set i 0 } { $i < $NR_THREADS } { incr i } { + set thread_nr [expr 2 + $i] + + gdb_test "thread $thread_nr" \ + "Switching to thread $thread_nr.*running.*" + + set test_name [string trim "interrupt $ampersand"] + set test_name "$test_name, thread $thread_nr" + gdb_test_multiple "interrupt $ampersand" $test_name { + -re "$gdb_prompt " { + if { "$ampersand" == "&" } { + exp_continue + } + pass $test_name + } + -re "\n\[^\r\]+Thread \[^\r\]+ stopped\[^\r\]+\r" { + if { "$ampersand" != "&" } { + exp_continue + } + pass $test_name + } + timeout { + fail "$test_name (timeout)" + } + } + + set test_name "thread $thread_nr stopped" + gdb_test_multiple "info thread $thread_nr" $test_name { + -re "running.*$gdb_prompt $" { + fail $test_name + } + -re "$gdb_prompt $" { + pass $test_name + } + timeout { + fail "$test_name (timeout)" + set i $NR_THREADS + } + } + } +} + +proc interrupt_all_threads_non_stop { command ampersand nr_threads_running } { + global gdb_prompt NR_THREADS + + set thread_stopped_count 0 + set test_name [string trim "$command $ampersand"] + + gdb_test_multiple "$command $ampersand" $test_name { + -re "\n\[^\r\]+Thread \[^\r\]+ stopped\[^\r\]+\r" { + verbose -log "got stopped thread" + incr thread_stopped_count + if { "$ampersand" == "&" } { + if { $thread_stopped_count == $nr_threads_running } { + pass $test_name + } else { + exp_continue + } + } else { + exp_continue + } + } + -re "\nNo unwaited-for children left.\[\r\n\]+$gdb_prompt $" { + # This appeared during development. Ensure it doesn't come back. + fail "$test_name (No unwaited-for children left)" + } + -re "$gdb_prompt " { + if { "$ampersand" == "&" } { + exp_continue + } + if { $thread_stopped_count != $nr_threads_running } { + fail "$test_name (missing thread)" + } else { + pass $test_name + } + } + timeout { + fail "$test_name (timeout)" + } + } +} + +proc verify_all_threads_stopped { } { + global gdb_prompt NR_THREADS + + set test_name "info threads" + set running_count 0 + set thread_count 0 + + gdb_test_multiple "info threads" $test_name { + -re "\n\[ \]*\[\[:digit:\]\]+ +Thread \[^\r\]+\\(running\\)\r" { + verbose -log "got running thread" + incr running_count + incr thread_count + exp_continue + } + -re "\n\[*\]?\[ \]*\[\[:digit:\]\]+ +Thread \[^\r\]+\r" { + verbose -log "got stopped thread" + incr thread_count + exp_continue + } + -re "$gdb_prompt $" { + if { $thread_count != [expr $NR_THREADS + 1] } { + fail "$test_name (missing thread)" + } elseif { $running_count != 0 } { + fail "$test_name (running thread)" + } else { + pass $test_name + } + } + timeout { + fail "$test_name (timeout)" + } + } +} + +proc interrupt_and_verify_all_threads_non_stop { ampersand } { + global gdb_prompt NR_THREADS + + # These tests are repeated just for stress testing purposes. + + for { set i 0 } { $i < $NR_THREADS } { incr i } { + set thread_nr [expr 2 + $i] + with_test_prefix "thread ${thread_nr}" { + gdb_test "c -a &" "Continuing." + gdb_test "thread $thread_nr" \ + "Switching to thread $thread_nr.*running.*" + interrupt_all_threads_non_stop "interrupt -a" $ampersand [expr $NR_THREADS + 1] + verify_all_threads_stopped + } + } +} + +proc test_sync_all_stop { } { + with_test_prefix "sync, all-stop" { + prepare_interrupt_test off off + + gdb_test "interrupt" "Current thread is already stopped." + gdb_test "interrupt -a" "All threads are already stopped." + } +} + +proc test_async_all_stop { } { + global NR_THREADS + + with_test_prefix "async, all-stop" { + prepare_interrupt_test on off + + # Do these tests several times because there was a bug during + # development when doing two in a row. + + for { set i 0 } { $i < $NR_THREADS } { incr i } { + set thread_nr [expr 2 + $i] + with_test_prefix "interrupt, thread ${thread_nr}" { + gdb_test "c &" "Continuing." + wait_thread_running $thread_nr + gdb_test "thread ${thread_nr}" \ + "Switching to thread ${thread_nr}.*running.*" + interrupt_all_stop "interrupt" + verify_all_threads_stopped + } + } + + for { set i 0 } { $i < $NR_THREADS } { incr i } { + set thread_nr [expr 2 + $i] + with_test_prefix "interrupt -a, thread ${thread_nr}" { + gdb_test "c &" "Continuing." + wait_thread_running $thread_nr + gdb_test "thread ${thread_nr}" \ + "Switching to thread ${thread_nr}.*running.*" + interrupt_all_stop "interrupt -a" + verify_all_threads_stopped + } + } + } + + with_test_prefix "async, all-stop, &" { + prepare_interrupt_test on off + + # These tests are repeated just for stress testing purposes. + + for { set i 0 } { $i < $NR_THREADS } { incr i } { + set thread_nr [expr 2 + $i] + with_test_prefix "interrupt, thread ${thread_nr}" { + gdb_test "c &" "Continuing." + wait_thread_running $thread_nr + gdb_test "thread ${thread_nr}" \ + "Switching to thread ${thread_nr}.*running.*" + interrupt_all_threads_all_stop_background "interrupt" + verify_all_threads_stopped + } + } + + for { set i 0 } { $i < $NR_THREADS } { incr i } { + set thread_nr [expr 2 + $i] + with_test_prefix "interrupt -a, thread ${thread_nr}" { + gdb_test "c &" "Continuing." + wait_thread_running $thread_nr + gdb_test "thread ${thread_nr}" \ + "Switching to thread ${thread_nr}.*running.*" + interrupt_all_threads_all_stop_background "interrupt -a" + verify_all_threads_stopped + } + } + } +} + +proc test_sync_non_stop { } { + global NR_THREADS + + with_test_prefix "sync, non-stop, one thread at a time" { + prepare_interrupt_test off on + + interrupt_and_verify_one_thread_non_stop "" + } + + with_test_prefix "sync, non-stop, all threads" { + prepare_interrupt_test off on + + # IWBN to use interrupt_and_verify_all_threads_non_stop here, + # but it uses & to continually resume the threads, which we can't do. + # So we just test once, until we know the extra coverage is needed + # here. One suggestions is to put a breakpoint on do_something. + interrupt_all_threads_non_stop "interrupt -a" "" $NR_THREADS + verify_all_threads_stopped + } +} + +proc test_async_non_stop { } { + global NR_THREADS + + with_test_prefix "async, non-stop, one thread at a time" { + prepare_interrupt_test on on + + interrupt_and_verify_one_thread_non_stop "" + } + + with_test_prefix "async, non-stop, one thread at a time, &" { + prepare_interrupt_test on on + + interrupt_and_verify_one_thread_non_stop "&" + } + + with_test_prefix "async, non-stop, all threads" { + prepare_interrupt_test on on + + interrupt_and_verify_all_threads_non_stop "" + } + + with_test_prefix "async, non-stop, all threads, &" { + prepare_interrupt_test on on + + interrupt_and_verify_all_threads_non_stop "&" + } +} + +test_sync_all_stop +test_async_all_stop +test_sync_non_stop +test_async_non_stop + +# That's it for standard testing. +# The rest of the file is specialized testing. + +# Verify "interrupt" waits for the right thread in non-stop. +# This test is inheritantly racy, even if it succeeds we're not testing +# the right things to verify success wasn't a fluke. But it has been +# quite useful in practice. + +proc test_right_thread_stopped { } { + global gdb_prompt + + with_test_prefix "test_right_thread_stopped" { + prepare_interrupt_test on on + # At this point thread 1 is stopped, threads 2-5 are running. + gdb_test_multiple "define interrupt2" "define user command: interrupt2" { + -re "Type commands for definition of \"interrupt2\".\r\nEnd with a line saying just \"end\".\r\n>$" { + gdb_test "thread 2\ninterrupt &\nthread 3\ninterrupt\nend" "" \ + "define user command: interrupt2" + } + } + set test_name "interrupt2" + set prompt_seen 0 + set thread2_stopped 0 + set thread3_stopped 0 + # Turn on command tracing, makes it easier to match output to the + # commands in the interrupt2 macro. + gdb_test_no_output "set trace-commands on" + gdb_test_multiple "interrupt2" $test_name { + -re "\[\r\n\]+\[^\r\]+Thread \[^\r\]+ #3 stopped\[^\r\]+\[\r\n\]+" { + # Note: There may also be a notification that thread 2 stopped + # before we see the gdb prompt. However, it can also occur + # after the prompt: be careful not to add a regexp for thread 2 + # here lest it swallow the prompt! + verbose -log "saw thread 3" + set thread3_stopped 1 + exp_continue + } + -re "$gdb_prompt " { + # Don't put a trailing $ on the regexp here. + # If expect buffers the thread 2 stopped output, we lose. + if { $thread3_stopped } { + pass $test_name + } else { + fail "$test_name (thread 3 not stopped before prompt)" + } + } + timeout { + fail "$test_name (timeout)" + } + } + # We can't use gdb_test_no_output here because the command is traced. + gdb_test "set trace-commands off" ".*" + # One last thing, verify thread 3 is still the current thread. + verify_current_thread 3 + } +} + +test_right_thread_stopped