mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
3df7843699
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 <natalia.saiapova@intel.com> Co-authored-by: Tankut Baris Aktemur <tankut.baris.aktemur@intel.com> Reviewed-By: Tankut Baris Aktemur <tankut.baris.aktemur@intel.com> Tested-By: Luis Machado <luis.machado@arm.com> Tested-By: Keith Seitz <keiths@redhat.com>
175 lines
6.4 KiB
Plaintext
175 lines
6.4 KiB
Plaintext
# Copyright 2022-2024 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/>.
|
|
|
|
# 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 GDB will stop at the 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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|