This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
Re: [PATCH] PR15693 - Fix spurious *running events, thread state, dprintf-style call
- From: Hui Zhu <teawater at gmail dot com>
- To: Pedro Alves <palves at redhat dot com>
- Cc: Tom Tromey <tromey at redhat dot com>, gdb-patches ml <gdb-patches at sourceware dot org>, Marc Khouzam <marc dot khouzam at ericsson dot com>
- Date: Fri, 16 May 2014 16:28:02 +0800
- Subject: Re: [PATCH] PR15693 - Fix spurious *running events, thread state, dprintf-style call
- Authentication-results: sourceware.org; auth=none
- References: <CANFwon35XMxAGHxgW5ubhome5oLgxGNt7MCRYSb41nn2xh5dzQ at mail dot gmail dot com> <87ehahnp93 dot fsf at fleche dot redhat dot com> <CANFwon3vYcDDYsw99JtggMVD=kmHyyz9Jqby=Kikq03PdWzU0w at mail dot gmail dot com> <53756234 dot 7070007 at redhat dot com>
Hi Pedro,
Thanks for your work.
Best,
Hui
On Fri, May 16, 2014 at 8:56 AM, Pedro Alves <palves@redhat.com> wrote:
> On 07/31/2013 09:15 AM, Hui Zhu wrote:
>> On Tue, Jul 30, 2013 at 3:24 AM, Tom Tromey <tromey@redhat.com> wrote:
>>>>>>>> "Hui" == Hui Zhu <teawater@gmail.com> writes:
>>>
>>> Hui> 2013-07-08 Hui Zhu <hui@codesourcery.com>
>>> Hui> PR gdb/15693
>>> Hui> infcall.c (run_inferior_call): Save value of call_thread->state
>>> Hui> and set it back.
>>>
>>> I didn't see a response to this.
>>> I think this needs a test case.
>>>
>>> Tom
>>
>> Hi Tom,
>>
>> I make a test for it.
>
> Sorry that it took _so_ long to get this reviewed/fixed.
>
> But better late than never...
>
> I was going to approve this this morning, with a few tweaks
> (the test as is is racy and doesn't actually fail with
> an unfixed GDB), but then I realized that the fix isn't right
> in case the inferior has multiple threads. If I run the test
> in the patch below against your patch, I get this ...
>
> -exec-continue
> ^running
> *running,thread-id="all"
> (gdb)
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> *running,thread-id="all"
> =breakpoint-modified,bkpt={number="3",type="breakpoint",disp="keep",enabled="y",addr="0x000000000040086a",func="test",file="../../../src/gdb/testsuite/gdb.mi/mi-condbreak-c
> all-thr-state.c",fullname="/home/pedro/gdb/mygit/src/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c",line="32",thread-groups=["i1"],times="1",original-location="mi-cond
> break-call-thr-state.c:32"}
> *stopped,reason="breakpoint-hit",disp="keep",bkptno="3",frame={addr="0x000000000040086a",func="test",args=[],file="../../../src/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-s
> tate.c",fullname="/home/pedro/gdb/mygit/src/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c",line="32"},thread-id="1",stopped-threads="all",core="2"
> FAIL: gdb.mi/mi-condbreak-call-thr-state.exp: mt: no spurious *running notifications
>
>> 2013-07-31 Hui Zhu <hui@codesourcery.com>
>>
>> PR gdb/15693
>> * infcall.c (run_inferior_call): Save value of call_thread->state
>> and set it back.
>
> ... because this only saves/restores the state of _one_ thread.
> So even though the current thread's state is reset to "running", the
> other threads end up marked as stopped, and so the next resume
> (because the breakpoint doesn't cause a stop) emits *running events
> anyway...
>
> So I thought about this a lot today, and come up with the
> patch below.
>
> Let me know what you think.
>
> 8<-------
> Subject: [PATCH] PR15693 - Fix spurious *running events, thread state,
> dprintf-style call
>
> If one sets a breakpoint with a condition that involves calling a
> function in the inferior, and then the condition evaluates false, GDB
> outputs one *running event for each time the program hits the
> breakpoint. E.g.,
>
> $ gdb return-false -i=mi
>
> (gdb)
> start
> ...
> (gdb)
> b 14 if return_false ()
> &"b 14 if return_false ()\n"
> ~"Breakpoint 2 at 0x4004eb: file return-false.c, line 14.\n"
> ...
> ^done
> (gdb)
> c
> &"c\n"
> ~"Continuing.\n"
> ^running
> *running,thread-id="1"
> (gdb)
> *running,thread-id="1"
> *running,thread-id="1"
> *running,thread-id="1"
> *running,thread-id="1"
> *running,thread-id="1"
> *running,thread-id="1"
> ... repeat forever ...
>
> An easy way a user can trip on this is with a dprintf with
> dprintf-style set to call. In that case, dprintf calls a function in
> the inferior, and the resumes, just like the case above.
>
> If the breakpoint/dprintf is set in a loop, then these spurious events
> can potentially slows down a frontend much, if it decides to refresh
> its GUI whenever it sees this event.
>
> When we run an infcall, we pretend we don't actually run the inferior.
> This is already handled for the usual case of calling a function
> directly from the CLI:
>
> (gdb)
> p return_false ()
> &"p return_false ()\n"
> ~"$1 = 0"
> ~"\n"
> ^done
> (gdb)
>
> Note no *running, not *stopped events. That's handled by:
>
> static void
> mi_on_resume (ptid_t ptid)
> {
> ...
> /* Suppress output while calling an inferior function. */
> if (tp->control.in_infcall)
> return;
>
> and equivalent code on normal_stop.
>
> However, in the cases of the PR, after finishing the infcall there's
> one more resume, and mi_on_resume doesn't know that that should be
> suppressed too, somehow.
>
> The "running/stopped" state is a high level user/frontend state.
> Internal stops are invisible to the frontend. If follows from that
> that we should be setting the thread to running at a higher level
> where we still know the set of threads the user _intends_ to resume.
>
> Currently we mark a thread as running from within target_resume, a low
> level target operation. As consequence, today, if we resume a
> multi-threaded program while stopped at a breakpoint, we see this:
>
> -exec-continue
> ^running
> *running,thread-id="1"
> (gdb)
> *running,thread-id="all"
>
> The first *running was GDB stepping over the breakpoint, and the
> second is GDB finally resuming everything.
>
> Between those two *running's, threads other than "1" have bogus still
> have their state set to stopped. That bogus -- in async mode, this
> opens a tiny window between both resumes where the user might try to
> run another execution command to threads other than thread 1, and very
> much confuse GDB.
>
> That is, the "step" below should fail the "step", complaining that the
> thread is running:
>
> (gdb) c -a &
> (gdb) thread 2
> (gdb) step
>
> IOW, threads that GDB happens to not resume immediately (say, because
> it needs to step over a breakpoint) shall still be marked as running.
>
> Then, if we move marking threads as running to a higher layer,
> decoupled from target_resume , and also skip marking threads as
> running when running an infcall, the spurious *running events
> disappear.
>
> I think we might end up adding a new thread state -- THREAD_INFCALL or
> some such, however since infcalls are always synchronous today, I
> didn't find a need. There's no way to execute a CLI/MI command
> directly from the prompt if some thread is running an infcall.
>
> Tested on x86_64 Fedora 20.
>
> gdb/
> 2014-05-16 Pedro Alves <palves@redhat.com>
>
> PR PR15693
> * infrun.c (resume): Determine how much to resume depending on
> whether the caller wanted a step, not whether we can hardware step
> the target. Mark all threads that we intend to run as running,
> unless we're calling an inferior function.
> (normal_stop): If the thread is running an infcall, don't finish
> thread state.
> * target.c (target_resume): Don't mark threads as running here.
>
> gdb/testsuite/
> 2014-05-16 Pedro Alves <palves@redhat.com>
> Hui Zhu <hui@codesourcery.com>
>
> PR PR15693
> * gdb.mi/mi-condbreak-call-thr-state-mt.c: New file.
> * gdb.mi/mi-condbreak-call-thr-state-st.c: New file.
> * gdb.mi/mi-condbreak-call-thr-state.c: New file.
> * gdb.mi/mi-condbreak-call-thr-state.exp: New file.
> ---
> gdb/infrun.c | 44 ++++++--
> gdb/target.c | 3 +-
> .../gdb.mi/mi-condbreak-call-thr-state-mt.c | 61 +++++++++++
> .../gdb.mi/mi-condbreak-call-thr-state-st.c | 26 +++++
> gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c | 33 ++++++
> .../gdb.mi/mi-condbreak-call-thr-state.exp | 116 +++++++++++++++++++++
> 6 files changed, 271 insertions(+), 12 deletions(-)
> create mode 100644 gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-mt.c
> create mode 100644 gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-st.c
> create mode 100644 gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c
> create mode 100644 gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.exp
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 597a188..2e44fef 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1775,6 +1775,7 @@ resume (int step, enum gdb_signal sig)
> CORE_ADDR pc = regcache_read_pc (regcache);
> struct address_space *aspace = get_regcache_aspace (regcache);
> ptid_t resume_ptid;
> + int hw_step = step;
>
> QUIT;
>
> @@ -1794,7 +1795,7 @@ resume (int step, enum gdb_signal sig)
> if (debug_infrun)
> fprintf_unfiltered (gdb_stdlog,
> "infrun: resume : clear step\n");
> - step = 0;
> + hw_step = 0;
> }
>
> if (debug_infrun)
> @@ -1839,7 +1840,7 @@ a command like `return' or `jump' to continue execution."));
> step software breakpoint. */
> if (use_displaced_stepping (gdbarch)
> && (tp->control.trap_expected
> - || (step && gdbarch_software_single_step_p (gdbarch)))
> + || (hw_step && gdbarch_software_single_step_p (gdbarch)))
> && sig == GDB_SIGNAL_0
> && !current_inferior ()->waiting_for_vfork_done)
> {
> @@ -1849,11 +1850,14 @@ a command like `return' or `jump' to continue execution."));
> {
> /* Got placed in displaced stepping queue. Will be resumed
> later when all the currently queued displaced stepping
> - requests finish. The thread is not executing at this point,
> - and the call to set_executing will be made later. But we
> - need to call set_running here, since from frontend point of view,
> - the thread is running. */
> - set_running (inferior_ptid, 1);
> + requests finish. The thread is not executing at this
> + point, and the call to set_executing will be made later.
> + But we need to call set_running here, since from frontend
> + point of view, threads were set running. Unless we're
> + calling an inferior function, as in that case pretend we
> + inferior doesn't run at all. */
> + if (!tp->control.in_infcall)
> + set_running (user_visible_resume_ptid (step), 1);
> discard_cleanups (old_cleanups);
> return;
> }
> @@ -1863,8 +1867,8 @@ a command like `return' or `jump' to continue execution."));
> pc = regcache_read_pc (get_thread_regcache (inferior_ptid));
>
> displaced = get_displaced_stepping_state (ptid_get_pid (inferior_ptid));
> - step = gdbarch_displaced_step_hw_singlestep (gdbarch,
> - displaced->step_closure);
> + hw_step = gdbarch_displaced_step_hw_singlestep (gdbarch,
> + displaced->step_closure);
> }
>
> /* Do we need to do it the hard way, w/temp breakpoints? */
> @@ -1928,6 +1932,14 @@ a command like `return' or `jump' to continue execution."));
> by applying increasingly restricting conditions. */
> resume_ptid = user_visible_resume_ptid (step);
>
> + /* Even if RESUME_PTID is a wildcard, and we end up resuming less
> + (e.g., we might need to step over a breakpoint), from the
> + user/frontend's point of view, all threads in RESUME_PTID are now
> + running. Unless we're calling an inferior function, as in that
> + case pretend we inferior doesn't run at all. */
> + if (!tp->control.in_infcall)
> + set_running (resume_ptid, 1);
> +
> /* Maybe resume a single thread after all. */
> if ((step || singlestep_breakpoints_inserted_p)
> && tp->control.trap_expected)
> @@ -6172,8 +6184,18 @@ normal_stop (void)
> if (has_stack_frames () && !stop_stack_dummy)
> set_current_sal_from_frame (get_current_frame (), 1);
>
> - /* Let the user/frontend see the threads as stopped. */
> - do_cleanups (old_chain);
> + /* Let the user/frontend see the threads as stopped, but do nothing
> + if the thread was running an infcall. We may be e.g., evaluating
> + a breakpoint condition. In that case, the thread had state
> + THREAD_RUNNING before the infcall, and shall remain set to
> + running, all without informing the user/frontend about state
> + transition changes. If this is actually a call command, then the
> + thread was originally already stopped, so there's no state to
> + finish either. */
> + if (target_has_execution && inferior_thread ()->control.in_infcall)
> + discard_cleanups (old_chain);
> + else
> + do_cleanups (old_chain);
>
> /* Look up the hook_stop and run it (CLI internally handles problem
> of stop_command's pre-hook not existing). */
> diff --git a/gdb/target.c b/gdb/target.c
> index 1b48f79..c99b9c7 100644
> --- a/gdb/target.c
> +++ b/gdb/target.c
> @@ -2149,8 +2149,9 @@ target_resume (ptid_t ptid, int step, enum gdb_signal signal)
> gdb_signal_to_name (signal));
>
> registers_changed_ptid (ptid);
> + /* We only set the internal executing state here. The user/frontend
> + running state is set at a higher level. */
> set_executing (ptid, 1);
> - set_running (ptid, 1);
> clear_inline_frame_state (ptid);
> }
>
> diff --git a/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-mt.c b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-mt.c
> new file mode 100644
> index 0000000..112a5cb
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-mt.c
> @@ -0,0 +1,61 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + 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 <http://www.gnu.org/licenses/>. */
> +
> +/* This is the multi-threaded driver for the real test. */
> +
> +#include <stdlib.h>
> +#include <pthread.h>
> +
> +extern int test (void);
> +
> +#define NTHREADS 5
> +pthread_barrier_t barrier;
> +
> +void *
> +thread_func (void *arg)
> +{
> + pthread_barrier_wait (&barrier);
> +
> + while (1)
> + sleep (1);
> +}
> +
> +void
> +create_thread (void)
> +{
> + pthread_t tid;
> +
> + if (pthread_create (&tid, NULL, thread_func, NULL))
> + {
> + perror ("pthread_create");
> + exit (1);
> + }
> +}
> +
> +int
> +main (int argc, char *argv[])
> +{
> + int i;
> +
> + pthread_barrier_init (&barrier, NULL, NTHREADS + 1);
> +
> + for (i = 0; i < NTHREADS; i++)
> + create_thread ();
> + pthread_barrier_wait (&barrier);
> +
> + return test ();
> +}
> diff --git a/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-st.c b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-st.c
> new file mode 100644
> index 0000000..66335dd
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state-st.c
> @@ -0,0 +1,26 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + 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 <http://www.gnu.org/licenses/>. */
> +
> +/* This is single-threaded driver for the real test. */
> +
> +extern int test (void);
> +
> +int
> +main ()
> +{
> + return test ();
> +}
> diff --git a/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c
> new file mode 100644
> index 0000000..75d5601
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.c
> @@ -0,0 +1,33 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright (C) 2013-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 <http://www.gnu.org/licenses/>. */
> +
> +int
> +return_false (void)
> +{
> + return 0;
> +}
> +
> +int
> +test (void)
> +{
> + int a = 0;
> +
> + while (a < 10)
> + a++; /* set breakpoint here */
> +
> + return 0; /* set end breakpoint here */
> +}
> diff --git a/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.exp b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.exp
> new file mode 100644
> index 0000000..82ca6cb
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-condbreak-call-thr-state.exp
> @@ -0,0 +1,116 @@
> +# Copyright (C) 2013-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 <http://www.gnu.org/licenses/>.
> +
> +# Regression test for PR15693. A breakpoint with a condition that
> +# calls a function that evaluates false would result in a spurious
> +# *running event sent to the frontend each time the breakpoint is hit
> +# (and the target re-resumed). Like:
> +#
> +# -exec-continue
> +# ^running
> +# *running,thread-id="all"
> +# (gdb)
> +# *running,thread-id="1"
> +# *running,thread-id="1"
> +# *running,thread-id="1"
> +# *running,thread-id="1"
> +# *running,thread-id="1"
> +# ...
> +
> +load_lib mi-support.exp
> +set MIFLAGS "-i=mi"
> +
> +# Run either the multi-threaded or the single-threaded variant of the
> +# test, as determined by VARIANT.
> +proc test { variant } {
> + global gdb_test_file_name
> + global testfile srcdir subdir srcfile srcfile2 binfile
> + global mi_gdb_prompt async
> +
> + with_test_prefix "$variant" {
> + gdb_exit
> + if [mi_gdb_start] {
> + continue
> + }
> +
> + set options {debug}
> + if {$variant == "mt" } {
> + lappend options "pthreads"
> + }
> +
> + # Don't use standard_testfile as we need a different binary
> + # for each variant.
> + set testfile $gdb_test_file_name-$variant
> + set binfile [standard_output_file ${testfile}]
> + set srcfile $testfile.c
> + set srcfile2 $gdb_test_file_name.c
> +
> + if {[build_executable "failed to prepare" \
> + $testfile \
> + "${srcfile} ${srcfile2}" \
> + $options] == -1} {
> + return -1
> + }
> +
> + mi_delete_breakpoints
> + mi_gdb_reinitialize_dir $srcdir/$subdir
> + mi_gdb_reinitialize_dir $srcdir/$subdir
> + mi_gdb_load ${binfile}
> +
> + mi_runto test
> +
> + # Leave the breakpoint at test set, on purpose. The next
> + # resume shall emit a single '*running,thread-id="all"', even
> + # if GDB needs to step over a breakpoint (that is, even if GDB
> + # needs to run only one thread for a little bit).
> +
> + set bp_location [gdb_get_line_number "set breakpoint here" $srcfile2]
> + set bp_location_end [gdb_get_line_number "set end breakpoint here" $srcfile2]
> +
> + mi_gdb_test "-break-insert -c return_false() $srcfile2:$bp_location" ".*" \
> + "insert conditional breakpoint"
> +
> + mi_gdb_test "-break-insert $srcfile2:$bp_location_end" ".*" \
> + "insert end breakpoint"
> +
> + set msg "no spurious *running notifications"
> + send_gdb "-exec-continue\n"
> + gdb_expect {
> + -re "\\*running.*\\*running.*\\*stopped" {
> + fail $msg
> + }
> + -re "\\^running\r\n\\*running,thread-id=\"all\"\r\n${mi_gdb_prompt}.*\\*stopped" {
> + pass $msg
> + }
> + timeout {
> + fail "$msg (timeout)"
> + }
> + }
> +
> + # In sync mode, there's an extra prompt after *stopped. Consume it.
> + if {!$async} {
> + gdb_expect {
> + -re "$mi_gdb_prompt" {
> + }
> + }
> + }
> + }
> +}
> +
> +# Single-threaded.
> +test "st"
> +
> +# Multi-threaded.
> +test "mt"
> --
> 1.9.0
>
>