static void insert_longjmp_resume_breakpoint (struct gdbarch *, CORE_ADDR);
-static bool maybe_software_singlestep (struct gdbarch *gdbarch);
+static bool maybe_software_singlestep (const thread_info *tp,
+ gdbarch *gdbarch, CORE_ADDR pc);
static void resume (gdb_signal sig);
GDBARCH the current gdbarch. */
static bool
-maybe_software_singlestep (struct gdbarch *gdbarch)
+maybe_software_singlestep (const thread_info *tp, gdbarch *gdbarch,
+ CORE_ADDR pc)
{
bool hw_step = true;
- if (execution_direction == EXEC_FORWARD
+ if (tp->control.execution_direction == EXEC_FORWARD
&& gdbarch_software_single_step_p (gdbarch))
hw_step = !insert_single_step_breakpoints (gdbarch);
/* Install inferior's terminal modes. */
target_terminal::inferior ();
+ scoped_restore save_exec_dir
+ = make_scoped_restore (&execution_direction,
+ tp->control.execution_direction);
+
/* Avoid confusing the next resume, if the next stop/resume
happens to apply to another thread. */
tp->set_stop_signal (GDB_SIGNAL_0);
insert_breakpoints ();
resume_ptid = internal_resume_ptid (user_step);
+
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
tp->set_resumed (true);
return;
set_step_over_info (aspace, regcache_read_pc (regcache), 0,
tp->global_num);
- step = maybe_software_singlestep (gdbarch);
+ step = maybe_software_singlestep (tp, gdbarch, pc);
insert_breakpoints ();
}
/* Do we need to do it the hard way, w/temp breakpoints? */
else if (step)
- step = maybe_software_singlestep (gdbarch);
+ step = maybe_software_singlestep (tp, gdbarch, pc);
/* Currently, our software single-step implementation leads to different
results than hardware single-stepping in one situation: when stepping
else
resume_ptid = internal_resume_ptid (user_step);
- if (execution_direction != EXEC_REVERSE
+ if (tp->control.execution_direction != EXEC_REVERSE
&& step && breakpoint_inserted_here_p (aspace, pc))
{
/* There are two cases where we currently need to step a
bpstat_clear (&tp->control.stop_bpstat);
tp->control.is_replaying = target_record_is_replaying (tp->ptid);
+ tp->control.execution_direction = ::execution_direction;
}
/* Notify the current interpreter and observers that the target is about to
&& tp->control.stepping_command)
|| (scheduler_mode == schedlock_replay
&& target_record_will_replay (minus_one_ptid,
- execution_direction)));
+ tp->control.execution_direction)));
}
/* When FORCE_P is false, set process_stratum_target::COMMIT_RESUMED_STATE
if (cur_thr->stop_pc_p ()
&& pc == cur_thr->stop_pc ()
&& breakpoint_here_p (aspace, pc) == ordinary_breakpoint_here
- && execution_direction != EXEC_REVERSE)
+ && cur_thr->control.execution_direction != EXEC_REVERSE)
/* There is a breakpoint at the address we will resume at,
step one instruction before inserting breakpoints so that
we do not stop right away (and report a second hit at this
breakpoint at PC - 1. We'd then report a hit on B1, although
INSN1 hadn't been de-executed yet. Doing nothing is the correct
behaviour. */
- if (execution_direction == EXEC_REVERSE)
+ if (thread->control.execution_direction == EXEC_REVERSE)
return;
/* If the target can tell whether the thread hit a SW breakpoint,
delete_step_resume_breakpoint (ecs->event_thread);
if (ecs->event_thread->control.proceed_to_finish
- && execution_direction == EXEC_REVERSE)
+ && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
{
struct thread_info *tp = ecs->event_thread;
}
fill_in_stop_func (gdbarch, ecs);
if (ecs->event_thread->stop_pc () == ecs->stop_func_start
- && execution_direction == EXEC_REVERSE)
+ && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
{
/* We are stepping over a function call in reverse, and just
hit the step-resume breakpoint at the start address of
if (pc_in_thread_step_range (ecs->event_thread->stop_pc (),
ecs->event_thread)
- && (execution_direction != EXEC_REVERSE
+ && (ecs->event_thread->control.execution_direction != EXEC_REVERSE
|| *curr_frame_id == original_frame_id))
{
infrun_debug_printf
CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
if (stop_pc == ecs->event_thread->control.step_range_start
&& stop_pc != ecs->stop_func_start
- && execution_direction == EXEC_REVERSE)
+ && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
end_stepping_range (ecs);
else
keep_going (ecs);
backward through the trampoline code, and that's handled further
down, so there is nothing for us to do here. */
- if (execution_direction != EXEC_REVERSE
+ if (ecs->event_thread->control.execution_direction != EXEC_REVERSE
&& ecs->event_thread->control.step_over_calls == STEP_OVER_UNDEBUGGABLE
&& in_solib_dynsym_resolve_code (ecs->event_thread->stop_pc ())
&& (ecs->event_thread->control.step_start_function == nullptr
/* Reverse stepping through solib trampolines. */
- if (execution_direction == EXEC_REVERSE
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
&& ecs->event_thread->control.step_over_calls != STEP_OVER_NONE
&& (gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc)
|| (ecs->stop_func_start == 0
stepped into (backwards), and continue to there. When we
get there, we'll need to single-step back to the caller. */
- if (execution_direction == EXEC_REVERSE)
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
{
/* If we're already at the start of the function, we've either
just stepped backward into a single instruction function,
tmp_sal)
&& !inline_frame_is_marked_for_skip (true, ecs->event_thread))
{
- if (execution_direction == EXEC_REVERSE)
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
handle_step_into_function_backward (gdbarch, ecs);
else
handle_step_into_function (gdbarch, ecs);
return;
}
- if (execution_direction == EXEC_REVERSE)
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
{
/* If we're already at the start of the function, we've either just
stepped backward into a single instruction function without line
/* Reverse stepping through solib trampolines. */
- if (execution_direction == EXEC_REVERSE
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
&& ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
{
CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
}
}
- if (execution_direction == EXEC_REVERSE
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
&& ecs->event_thread->control.proceed_to_finish
&& ecs->event_thread->stop_pc () >= ecs->stop_func_alt_start
&& ecs->event_thread->stop_pc () < ecs->stop_func_start)
if (stop_pc_sal.is_stmt)
{
- if (execution_direction == EXEC_REVERSE)
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
{
/* We are stepping backwards make sure we have reached the
beginning of the line. */
}
}
- if (execution_direction == EXEC_REVERSE
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
&& *curr_frame_id != original_frame_id
&& original_frame_id.code_addr_p && curr_frame_id->code_addr_p
&& original_frame_id.code_addr == curr_frame_id->code_addr)
infrun_debug_printf ("keep going");
- if (execution_direction == EXEC_REVERSE)
+ if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
{
CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
tp->set_resumed (true);
resume_ptid = internal_resume_ptid (tp->control.stepping_command);
+
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
}
else
--- /dev/null
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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 that we stop replaying other threads when stepping a thread at the
+# end of its execution history.
+#
+# This is similar to the last test in multi-thread-step.exp, except that
+# we reverse-step instead of record goto begin to start replaying and we
+# step instead of continuing.
+#
+# This triggered a bug where GDB confused the execution direction and kept
+# stepping both threads backwards instead of forwards.
+
+require allow_btrace_tests
+
+standard_testfile multi-thread-step.c
+if [prepare_for_testing "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+# Set up breakpoints.
+set bp_1 [gdb_get_line_number "bp.1" $srcfile]
+set bp_2 [gdb_get_line_number "bp.2" $srcfile]
+
+# Trace the code between the two breakpoints.
+gdb_breakpoint $srcfile:$bp_1
+gdb_continue_to_breakpoint "continue to bp.1" ".*$srcfile:$bp_1\r\n.*"
+
+# Make sure GDB knows about the new thread.
+gdb_test "info threads"
+gdb_test_no_output "record btrace"
+
+# We have two threads at or close to bp.1 but handled only one stop event.
+# Remove the breakpoint so we do not need to deal with the 2nd event.
+delete_breakpoints
+gdb_breakpoint $srcfile:$bp_2
+gdb_continue_to_breakpoint "continue to bp.2" ".*$srcfile:$bp_2\r\n.*"
+
+# Determine the thread that reported the breakpoint.
+set thread [get_integer_valueof "\$_thread" bad]
+
+# Determine the other thread.
+set other "bad"
+if { $thread == 1 } {
+ set other 2
+} elseif { $thread == 2 } {
+ set other 1
+}
+
+# This test only works for scheduler-locking 'replay'.
+gdb_test_no_output "set scheduler-locking replay"
+
+# Remove breakpoints or we might run into it right away.
+delete_breakpoints
+
+# Start replaying the other thread.
+gdb_test "thread apply $other reverse-stepi"
+gdb_test "thread apply $other info record" "Replay in progress.*" \
+ "other thread is replaying"
+
+# Step the thread that reported the breakpoint, which is not replaying.
+#
+# There is a chance that the other thread exits while we step. We could
+# slow it down to make this less likely, but we can also handle this case.
+set other_exited 0
+gdb_test_multiple "next" {} {
+ -re "Thread.*exited" {
+ set other_exited 1
+ exp_continue
+ }
+ -re -wrap "return arg;.*" {
+ pass $gdb_test_name
+ }
+}
+
+# The other thread stopped replaying. If it still exists.
+if {$other_exited == 1} {
+ pass "other thread stopped replaying"
+} else {
+ gdb_test "thread apply $other info record" "Recorded \[^\\\r\\\n\]*" \
+ "other thread stopped replaying"
+}