Compare commits

...

12 Commits

Author SHA1 Message Date
Markus Metzger
13403511a1 linux: handle split resume requests with target-async off
With

    target-async off
    scheduler-locking off
    schedule-multiple on

and

    record btrace

the record-btrace target splits resume and stop requests when there are
multiple inferiors and some are replaying while others are recording.

Since wait would be blocking in this configuration, we cannot afford to
split wait requests, as well, or risk a hang.

This leads to scenarios where the target beneath record-btrace received

    resume requests for inferiors other than the current inferior, since
    that would be handled by the record target above.

    stop requests, but has not gotten the chance to wait for the
    corresponding event before receiving another resume request.

Handle those cases for the linux native target.
2025-05-26 07:01:15 +00:00
Markus Metzger
57531a5fa4 gdb, btrace: per-inferior run-control
While recording is already per inferior, run-control isn't.  As soon as
any thread in any inferior is replaying, no other inferior can be resumed.

This is controlled by calls to record_is_replaying(minus_one_ptid).
Instead of minus_one_ptid, pass the ptid of the inferior to be checked,
and split requests for minus_one_ptid by inferior for resume and stop.

Since it is not safe to split a wait request for blocking targets, we
forward the minus_one_ptid request if there are no events to report for
replaying threads.
2025-05-26 07:01:15 +00:00
Markus Metzger
e20f632287 gdb, infrun: fix silent inferior switch in do_target_wait()
In do_target_wait(), we iterate over inferiors and call
do_target_wait_1(), which eventually calls target_wait() per inferior.

The surrounding code selects a random inferior and then iterates over all
inferiors starting from the selected inferior.  In each iteration, it
waits for one inferior.  We forgot to restrict the wait, however, so we
may end up with an event for a different inferior.

In some cases, e.g. gdb.threads/detach-step-over.exp, we ask to wait for
one inferior, and get an event from a different inferior back without
noticing the inferior switch.

Wait for a single inferior, instead.  Since we iterate over all inferiors,
we still cover everything.

This exposes another bug with STOP_QUIETLY_NO_SIGSTOP handling.

After attaching, we interrupt all threads in the new inferior, then call
do_target_wait() to receive the stopped events.  This randomly selects an
inferior to start waiting for and iterates over all inferiors starting
from there.

The initial stop event for the main thread is already queued up, so we
wouldn't actually wait() if we had started with the new inferior.  Or if
we had waited for minus_one_ptid, which would then have silently switched
inferiors.

Since we no longer allow that, we may actually wait() for the new inferior
and find other events to report, out of which we randomly select one.

If we selected an event for another thread, e.g. one that had been
interrupted as part of non-stop attach, STOP_QUIETLY_NO_SIGSTOP would be
applied to that thread (unnecessarily), leaving the main thread with a
SIGSTOP event but last_resume_kind = resume_continue.

When the main thread is later selected, SIGSTOP is reported to the user.

Normally, linux-nat's wait() turns the SIGSTOP it uses for interrupting
threads into GDB_SIGNAL_0.  This is based on last_resume_kind, which is
set to resume_stop when sending SIGSTOP to interrupt a thread.

We do this for all threads of the new inferior when interrupting them as
part of non-stop attach.  Except for the main thread, which we expect to
be reported before the first wait().

Set last_resume_kind to resume_stop for the main thread after attaching.
2025-05-26 07:01:15 +00:00
Markus Metzger
db9809ed6b btrace: stopped_by_*() consider the selected thread
In stopped_by_sw_breakpoint() and stopped_by_hw_breakpoint(), we check
whether any thread is replaying.  This is unnecessary as it only matters
if inferior_ptid is replaying.

Narrow the check to inferior_ptid.
2025-05-26 07:01:15 +00:00
Markus Metzger
d36829aa37 btrace: remove update_thread_list() and thread_alive()
The record btrace target does not create or destroy threads.  There is no
reason to override the update_thread_list() and thread_alive() target
methods.
2025-05-26 07:01:15 +00:00
Markus Metzger
9c29dc7d14 btrace, infrun: replay scheduler locking only depends on to-be-resumed thread
Similar to the parent commit, simplify schedlock_applies() by only
checking the argument thread.

When resuming that thread, GDB will automatically stop replaying its
inferior.  The replay state of other inferiors is not considered by
user_visible_resume_ptid(), so let's not consider them in
schedlock_applies(), either.
2025-05-26 07:01:15 +00:00
Markus Metzger
252498d882 btrace, infrun: simplify scheduler-locking replay
When scheduler-locking is set to replay and we're resuming a thread at the
end of its execution history, we check whether anything is replaying in
user_visible_resume_ptid() only to check again in clear_proceed_status()
before we stop replaying the current process.

What really matters is whether the selected thread is replaying or will
start replaying.

Simplify this by removing redundant checks.

Also avoid a redundant pass over all threads to check whether anything is
replaying before stopping replaying.  Make record_stop_replaying() handle
the case when we're not replaying gracefully.
2025-05-26 07:01:15 +00:00
Markus Metzger
60113a4b3b gdb, remote: adjust debug printing
remote::wait () may get called rather frequently, polluting the logging
output with tons of

    [remote] wait: enter
    [remote] wait: exit

messages.

Similarly, remote_target::remote_notif_remove_queued_reply () will print
the debug message even if nothing was actually removed.  Change that to
only print a debug message if a stop reply was removed.
2025-05-26 07:01:14 +00:00
Markus Metzger
c925bff825 gdb, btrace: set wait status to ignore if nothing is moving
When record_btrace::wait() is called and no threads are moving, we set the
wait status to no_resumed.  Change that to ignore.

This helps with enabling per-inferior run-control for the record btrace
target as it avoids breaking out of do_target_wait() too early with
no_resumed when there would have been an event on another thread.
2025-05-26 07:01:14 +00:00
Markus Metzger
5f5d5a5165 gdb, btrace: simplify gdb.btrace/multi-inferior.exp
We don't really need three inferiors to test multi-inferior recording.
We don't really need to start recording on the second inferior first.
We don't really need to check info record before starting recording.
If we were recording, there would be output, causing a fail.

This just complicates the test when there is something to debug.
2025-05-26 07:01:14 +00:00
Markus Metzger
18c82d81fe gdb, record: notify_normal_stop on 'record stop' when selected thread moves
As a side effect of the 'record stop' command, the selected thread may
move to the end of the execution history if it had been replaying.

Call notify_normal_stop () in this case to tell users about the change in
location.
2025-05-26 07:01:14 +00:00
Markus Metzger
b384d69217 gdb, btrace: fix pr19340
GDB fails with an assertion when stopping recording on a replaying thread
and then resuming that thread.  Stopping recording left the thread
replaying but the record target is gone.

Stop replaying all threads in the selected inferior before stopping recording.

I had to change the stepping test slightly to account for different
compilers generating slightly different debug information, so when
stepping the 'return 0' after 'record stop' I would end up in a different
location depending on which compiler I used.  The test still covers all
stepping commands.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19340
2025-05-26 07:01:14 +00:00
11 changed files with 311 additions and 142 deletions

View File

@@ -2392,7 +2392,7 @@ user_visible_resume_ptid (int step)
resume_ptid = inferior_ptid;
}
else if ((scheduler_mode == schedlock_replay)
&& target_record_will_replay (minus_one_ptid, execution_direction))
&& target_record_will_replay (inferior_ptid, execution_direction))
{
/* User-settable 'scheduler' mode requires solo thread resume in replay
mode. */
@@ -3104,16 +3104,14 @@ notify_about_to_proceed ()
void
clear_proceed_status (int step)
{
/* With scheduler-locking replay, stop replaying other threads if we're
not replaying the user-visible resume ptid.
/* With scheduler-locking replay, stop replaying other threads in the
same process if we're not replaying the selected thread.
This is a convenience feature to not require the user to explicitly
stop replaying the other threads. We're assuming that the user's
intent is to resume tracing the recorded process. */
if (!non_stop && scheduler_mode == schedlock_replay
&& target_record_is_replaying (minus_one_ptid)
&& !target_record_will_replay (user_visible_resume_ptid (step),
execution_direction))
&& !target_record_will_replay (inferior_ptid, execution_direction))
target_record_stop_replaying ();
if (!non_stop && inferior_ptid != null_ptid)
@@ -3197,8 +3195,7 @@ schedlock_applies (struct thread_info *tp)
|| (scheduler_mode == schedlock_step
&& tp->control.stepping_command)
|| (scheduler_mode == schedlock_replay
&& target_record_will_replay (minus_one_ptid,
execution_direction)));
&& target_record_will_replay (tp->ptid, execution_direction)));
}
/* When FORCE_P is false, set process_stratum_target::COMMIT_RESUMED_STATE
@@ -4206,7 +4203,24 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs,
auto do_wait = [&] (inferior *inf)
{
ecs->ptid = do_target_wait_1 (inf, wait_ptid, &ecs->ws, options);
ptid_t ptid { inf->pid };
/* Make sure we're not widening WAIT_PTID. */
if (!ptid.matches (wait_ptid)
/* Targets that cannot async will be asked for a blocking wait.
Blocking wait does not work inferior-by-inferior if the target
provides more than one inferior. Fall back to waiting for
WAIT_PTID in that case. */
|| !target_can_async_p () || ((options & TARGET_WNOHANG) == 0)
/* We cannot wait for inferiors without a pid.
One such inferior is created by initialize_inferiors () to
ensure that there always is an inferior. */
|| !ptid.is_pid ())
ptid = wait_ptid;
ecs->ptid = do_target_wait_1 (inf, ptid, &ecs->ws, options);
ecs->target = inf->process_target ();
return (ecs->ws.kind () != TARGET_WAITKIND_IGNORE);
};
@@ -4216,6 +4230,18 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs,
reported the stop to the user, polling for events. */
scoped_restore_current_thread restore_thread;
/* The first TARGET_WAITKIND_NO_RESUMED execution state.
We do not want to return TARGET_WAITKIND_NO_RESUMED right away since
another inferior may have a more interesting event to report. If
there is no other event to report, after all, we want to report the
first such event.
This variable holds that first event, which will be copied on the
first TARGET_WAITKIND_NO_RESUMED below. */
execution_control_state no_resumed {};
no_resumed.ptid = null_ptid;
intrusive_list_iterator<inferior> start
= inferior_list.iterator_to (*selected);
@@ -4226,7 +4252,13 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs,
inferior *inf = &*it;
if (inferior_matches (inf) && do_wait (inf))
return true;
{
if (ecs->ws.kind () != TARGET_WAITKIND_NO_RESUMED)
return true;
if (no_resumed.ptid == null_ptid)
no_resumed = *ecs;
}
}
for (intrusive_list_iterator<inferior> it = inferior_list.begin ();
@@ -4236,7 +4268,19 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs,
inferior *inf = &*it;
if (inferior_matches (inf) && do_wait (inf))
return true;
{
if (ecs->ws.kind () != TARGET_WAITKIND_NO_RESUMED)
return true;
if (no_resumed.ptid == null_ptid)
no_resumed = *ecs;
}
}
if (no_resumed.ptid != null_ptid)
{
*ecs = no_resumed;
return true;
}
ecs->ws.set_ignore ();

View File

@@ -1162,6 +1162,7 @@ linux_nat_target::attach (const char *args, int from_tty)
/* Add the initial process as the first LWP to the list. */
lp = add_initial_lwp (ptid);
lp->last_resume_kind = resume_stop;
status = linux_nat_post_attach_wait (lp->ptid, &lp->signalled);
if (!WIFSTOPPED (status))
@@ -1769,9 +1770,45 @@ linux_nat_target::resume (ptid_t scope_ptid, int step, enum gdb_signal signo)
last_resume_kind to resume_continue. */
iterate_over_lwps (scope_ptid, resume_set_callback);
/* Let's see if we're supposed to resume INFERIOR_PTID at all. */
if (!inferior_ptid.matches (scope_ptid))
{
linux_nat_debug_printf ("inferior_ptid %s not in scope %s",
inferior_ptid.to_string ().c_str (),
scope_ptid.to_string ().c_str ());
iterate_over_lwps (scope_ptid, [=] (struct lwp_info *info)
{
return linux_nat_resume_callback (info, nullptr);
});
if (target_can_async_p ())
{
target_async (true);
/* Tell the event loop we have something to process. */
async_file_mark ();
}
return;
}
lp = find_lwp_pid (inferior_ptid);
gdb_assert (lp != NULL);
if (!lp->stopped)
{
linux_nat_debug_printf ("inferior_ptid %s not stopped",
inferior_ptid.to_string ().c_str ());
if (target_can_async_p ())
{
target_async (true);
/* Tell the event loop we have something to process. */
async_file_mark ();
}
return;
}
/* Remember if we're stepping. */
lp->last_resume_kind = step ? resume_step : resume_continue;
@@ -3324,12 +3361,18 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
moment at which we know its PID. */
if (ptid.is_pid () && find_lwp_pid (ptid) == nullptr)
{
ptid_t lwp_ptid (ptid.pid (), ptid.pid ());
/* Unless we already did and this is simply a request to wait for a
particular inferior. */
inferior *inf = find_inferior_ptid (linux_target, ptid);
if ((inf != nullptr) && (inf->find_thread (ptid) != nullptr))
{
ptid_t lwp_ptid (ptid.pid (), ptid.pid ());
/* Upgrade the main thread's ptid. */
thread_change_ptid (linux_target, ptid, lwp_ptid);
lp = add_initial_lwp (lwp_ptid);
lp->resumed = 1;
/* Upgrade the main thread's ptid. */
thread_change_ptid (linux_target, ptid, lwp_ptid);
lp = add_initial_lwp (lwp_ptid);
lp->resumed = 1;
}
}
/* Make sure SIGCHLD is blocked until the sigsuspend below. */

View File

@@ -80,7 +80,7 @@ public:
enum record_method record_method (ptid_t ptid) override;
void stop_recording () override;
bool stop_recording () override;
void info_record () override;
void insn_history (int size, gdb_disassembly_flags flags) override;
@@ -123,8 +123,6 @@ public:
ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
void stop (ptid_t) override;
void update_thread_list () override;
bool thread_alive (ptid_t ptid) override;
void goto_record_begin () override;
void goto_record_end () override;
void goto_record (ULONGEST insn) override;
@@ -408,16 +406,20 @@ record_btrace_target_open (const char *args, int from_tty)
/* The stop_recording method of target record-btrace. */
void
bool
record_btrace_target::stop_recording ()
{
DEBUG ("stop recording");
bool is_replaying = record_is_replaying (inferior_ptid);
record_stop_replaying ();
record_btrace_auto_disable ();
for (thread_info *tp : current_inferior ()->non_exited_threads ())
if (tp->btrace.target != NULL)
btrace_disable (tp);
return is_replaying;
}
/* The disconnect method of target record-btrace. */
@@ -2189,11 +2191,14 @@ record_btrace_target::resume (ptid_t ptid, int step, enum gdb_signal signal)
For non-stop targets this means that no thread is replaying. In order to
make progress, we may need to explicitly move replaying threads to the end
of their execution history. */
if ((::execution_direction != EXEC_REVERSE)
&& !record_is_replaying (minus_one_ptid))
if (::execution_direction != EXEC_REVERSE)
{
this->beneath ()->resume (ptid, step, signal);
return;
ptid_t check { ptid == minus_one_ptid ? ptid : ptid_t (ptid.pid ()) };
if (!record_is_replaying (check))
{
this->beneath ()->resume (ptid, step, signal);
return;
}
}
/* Compute the btrace thread flag for the requested move. */
@@ -2213,25 +2218,45 @@ record_btrace_target::resume (ptid_t ptid, int step, enum gdb_signal signal)
For all-stop targets, we only step INFERIOR_PTID and continue others. */
process_stratum_target *proc_target = current_inferior ()->process_target ();
process_stratum_target *proc_target
= current_inferior ()->process_target ();
if (!target_is_non_stop_p ())
/* Split a minus_one_ptid request into per-inferior requests, so we can
forward them for inferiors that are not replaying. */
if ((::execution_direction != EXEC_REVERSE) && (ptid == minus_one_ptid))
{
for (inferior *inf : all_non_exited_inferiors (proc_target))
{
ptid_t inf_ptid { inf->pid };
if (!record_is_replaying (inf_ptid))
{
this->beneath ()->resume (inf_ptid, step, signal);
continue;
}
for (thread_info *tp : inf->non_exited_threads ())
{
if (target_is_non_stop_p ()
|| tp->ptid.matches (inferior_ptid))
record_btrace_resume_thread (tp, flag);
else
record_btrace_resume_thread (tp, cflag);
}
}
}
else
{
gdb_assert (inferior_ptid.matches (ptid));
for (thread_info *tp : all_non_exited_threads (proc_target, ptid))
{
if (tp->ptid.matches (inferior_ptid))
if (target_is_non_stop_p ()
|| tp->ptid.matches (inferior_ptid))
record_btrace_resume_thread (tp, flag);
else
record_btrace_resume_thread (tp, cflag);
}
}
else
{
for (thread_info *tp : all_non_exited_threads (proc_target, ptid))
record_btrace_resume_thread (tp, flag);
}
/* Async support. */
if (target_can_async_p ())
@@ -2310,14 +2335,14 @@ btrace_step_spurious (void)
return status;
}
/* Return a target_waitstatus indicating that the thread was not resumed. */
/* Return a target_waitstatus indicating that nothing is moving. */
static struct target_waitstatus
btrace_step_no_resumed (void)
btrace_step_no_moving_threads (void)
{
struct target_waitstatus status;
status.set_no_resumed ();
status.set_ignore ();
return status;
}
@@ -2608,10 +2633,11 @@ record_btrace_target::wait (ptid_t ptid, struct target_waitstatus *status,
(unsigned) options);
/* As long as we're not replaying, just forward the request. */
if ((::execution_direction != EXEC_REVERSE)
&& !record_is_replaying (minus_one_ptid))
if (::execution_direction != EXEC_REVERSE)
{
return this->beneath ()->wait (ptid, status, options);
ptid_t check { ptid == minus_one_ptid ? ptid : ptid_t (ptid.pid ()) };
if (!record_is_replaying (check))
return this->beneath ()->wait (ptid, status, options);
}
/* Keep a work list of moving threads. */
@@ -2622,7 +2648,20 @@ record_btrace_target::wait (ptid_t ptid, struct target_waitstatus *status,
if (moving.empty ())
{
*status = btrace_step_no_resumed ();
/* Splitting a minus_one_ptid wait request per inferior is not safe
for blocking targets. If one of the inferiors has an event to
report, but we happen to forward the wait request on another
inferior first that has nothing to report, we'd hang, whereas a
minus_one_ptid request would succeed.
A replaying inferior would be completely stopped for the target
beneath, so waiting for it should not result in any events. It
should be safe to forward the minus_one_ptid request. */
if ((::execution_direction != EXEC_REVERSE)
&& (ptid == minus_one_ptid))
return this->beneath ()->wait (ptid, status, options);
*status = btrace_step_no_moving_threads ();
DEBUG ("wait ended by %s: %s", null_ptid.to_string ().c_str (),
status->to_string ().c_str ());
@@ -2692,17 +2731,28 @@ record_btrace_target::wait (ptid_t ptid, struct target_waitstatus *status,
gdb_assert (eventing != NULL);
/* We kept threads replaying at the end of their execution history. Stop
replaying EVENTING now that we are going to report its stop. */
record_btrace_stop_replaying_at_end (eventing);
/* Stop all other threads. */
if (!target_is_non_stop_p ())
{
for (thread_info *tp : current_inferior ()->non_exited_threads ())
record_btrace_cancel_resume (tp);
if (tp != eventing)
record_btrace_cancel_resume (tp);
if ((::execution_direction != EXEC_REVERSE) && (ptid == minus_one_ptid))
{
for (inferior *inf : all_non_exited_inferiors (proc_target))
{
ptid_t inf_ptid { inf->pid };
if (!record_is_replaying (inf_ptid))
this->beneath ()->stop (inf_ptid);
}
}
}
/* We kept threads replaying at the end of their execution history. Stop
replaying EVENTING now that we are going to report its stop. */
record_btrace_stop_replaying_at_end (eventing);
/* In async mode, we need to announce further events. */
if (target_is_async_p ())
record_btrace_maybe_mark_async_event (moving, no_history);
@@ -2729,21 +2779,46 @@ record_btrace_target::stop (ptid_t ptid)
DEBUG ("stop %s", ptid.to_string ().c_str ());
/* As long as we're not replaying, just forward the request. */
if ((::execution_direction != EXEC_REVERSE)
&& !record_is_replaying (minus_one_ptid))
if (::execution_direction != EXEC_REVERSE)
{
this->beneath ()->stop (ptid);
ptid_t check { ptid == minus_one_ptid ? ptid : ptid_t (ptid.pid ()) };
if (!record_is_replaying (check))
{
this->beneath ()->stop (ptid);
return;
}
}
process_stratum_target *proc_target
= current_inferior ()->process_target ();
/* Split a minus_one_ptid request into per-inferior requests, so we can
forward them for inferiors that are not replaying. */
if ((::execution_direction != EXEC_REVERSE) && (ptid == minus_one_ptid))
{
for (inferior *inf : all_non_exited_inferiors (proc_target))
{
ptid_t inf_ptid { inf->pid };
if (!record_is_replaying (inf_ptid))
{
this->beneath ()->stop (inf_ptid);
continue;
}
for (thread_info *tp : inf->non_exited_threads ())
{
tp->btrace.flags &= ~BTHR_MOVE;
tp->btrace.flags |= BTHR_STOP;
}
}
}
else
{
process_stratum_target *proc_target
= current_inferior ()->process_target ();
for (thread_info *tp : all_non_exited_threads (proc_target, ptid))
{
tp->btrace.flags &= ~BTHR_MOVE;
tp->btrace.flags |= BTHR_STOP;
}
{
tp->btrace.flags &= ~BTHR_MOVE;
tp->btrace.flags |= BTHR_STOP;
}
}
}
@@ -2760,7 +2835,7 @@ record_btrace_target::can_execute_reverse ()
bool
record_btrace_target::stopped_by_sw_breakpoint ()
{
if (record_is_replaying (minus_one_ptid))
if (record_is_replaying (inferior_ptid))
{
struct thread_info *tp = inferior_thread ();
@@ -2775,7 +2850,7 @@ record_btrace_target::stopped_by_sw_breakpoint ()
bool
record_btrace_target::stopped_by_hw_breakpoint ()
{
if (record_is_replaying (minus_one_ptid))
if (record_is_replaying (inferior_ptid))
{
struct thread_info *tp = inferior_thread ();
@@ -2785,32 +2860,6 @@ record_btrace_target::stopped_by_hw_breakpoint ()
return this->beneath ()->stopped_by_hw_breakpoint ();
}
/* The update_thread_list method of target record-btrace. */
void
record_btrace_target::update_thread_list ()
{
/* We don't add or remove threads during replay. */
if (record_is_replaying (minus_one_ptid))
return;
/* Forward the request. */
this->beneath ()->update_thread_list ();
}
/* The thread_alive method of target record-btrace. */
bool
record_btrace_target::thread_alive (ptid_t ptid)
{
/* We don't add or remove threads during replay. */
if (record_is_replaying (minus_one_ptid))
return true;
/* Forward the request. */
return this->beneath ()->thread_alive (ptid);
}
/* Set the replay branch trace instruction iterator. If IT is NULL, replay
is stopped. */

View File

@@ -2072,7 +2072,8 @@ record_full_base_target::goto_record (ULONGEST target_insn)
void
record_full_base_target::record_stop_replaying ()
{
goto_record_end ();
if (RECORD_FULL_IS_REPLAY)
goto_record_end ();
}
/* "resume" method for prec over corefile. */

View File

@@ -152,12 +152,12 @@ record_read_memory (struct gdbarch *gdbarch,
/* Stop recording. */
static void
static bool
record_stop (struct target_ops *t)
{
DEBUG ("stop %s", t->shortname ());
t->stop_recording ();
return t->stop_recording ();
}
/* Unpush the record target. */
@@ -179,7 +179,7 @@ record_disconnect (struct target_ops *t, const char *args, int from_tty)
DEBUG ("disconnect %s", t->shortname ());
record_stop (t);
(void) record_stop (t);
record_unpush (t);
target_disconnect (args, from_tty);
@@ -194,7 +194,7 @@ record_detach (struct target_ops *t, inferior *inf, int from_tty)
DEBUG ("detach %s", t->shortname ());
record_stop (t);
(void) record_stop (t);
record_unpush (t);
target_detach (inf, from_tty);
@@ -306,16 +306,17 @@ cmd_record_delete (const char *args, int from_tty)
static void
cmd_record_stop (const char *args, int from_tty)
{
struct target_ops *t;
t = require_record_target ();
record_stop (t);
struct target_ops *t = require_record_target ();
bool notify_stop = record_stop (t);
record_unpush (t);
gdb_printf (_("Process record is stopped and all execution "
"logs are deleted.\n"));
/* INFERIOR_PTID may have moved when we stopped recording. */
if (notify_stop)
notify_normal_stop (nullptr, true);
interps_notify_record_changed (current_inferior (), 0, NULL, NULL);
}

View File

@@ -7919,23 +7919,35 @@ remote_target::remote_notif_remove_queued_reply (ptid_t ptid)
{
remote_state *rs = get_remote_state ();
auto pred = [=] (const stop_reply_up &event)
{
/* A null ptid should only happen if we have a single process. It
wouldn't match the process ptid, though, so let's check this case
separately. */
if ((event->ptid == null_ptid) && ptid.is_pid ())
return true;
/* A minus one ptid should only happen for events that match
everything. It wouldn't match a process or thread ptid, though, so
let's check this case separately. */
if (event->ptid == minus_one_ptid)
return true;
return event->ptid.matches (ptid);
};
auto iter = std::find_if (rs->stop_reply_queue.begin (),
rs->stop_reply_queue.end (),
[=] (const stop_reply_up &event)
{
return event->ptid.matches (ptid);
});
rs->stop_reply_queue.end (), pred);
stop_reply_up result;
if (iter != rs->stop_reply_queue.end ())
{
result = std::move (*iter);
rs->stop_reply_queue.erase (iter);
}
if (notif_debug)
gdb_printf (gdb_stdlog,
"notif: discard queued event: 'Stop' in %s\n",
ptid.to_string ().c_str ());
if (notif_debug)
gdb_printf (gdb_stdlog,
"notif: discard queued event: 'Stop' in %s\n",
ptid.to_string ().c_str ());
}
return result;
}
@@ -8841,8 +8853,6 @@ ptid_t
remote_target::wait (ptid_t ptid, struct target_waitstatus *status,
target_wait_flags options)
{
REMOTE_SCOPED_DEBUG_ENTER_EXIT;
remote_state *rs = get_remote_state ();
/* Start by clearing the flag that asks for our wait method to be called,

View File

@@ -172,7 +172,7 @@ struct dummy_target : public target_ops
enum btrace_error read_btrace (struct btrace_data *arg0, struct btrace_target_info *arg1, enum btrace_read_type arg2) override;
const struct btrace_config *btrace_conf (const struct btrace_target_info *arg0) override;
enum record_method record_method (ptid_t arg0) override;
void stop_recording () override;
bool stop_recording () override;
void info_record () override;
void save_record (const char *arg0) override;
bool supports_delete_record () override;
@@ -353,7 +353,7 @@ struct debug_target : public target_ops
enum btrace_error read_btrace (struct btrace_data *arg0, struct btrace_target_info *arg1, enum btrace_read_type arg2) override;
const struct btrace_config *btrace_conf (const struct btrace_target_info *arg0) override;
enum record_method record_method (ptid_t arg0) override;
void stop_recording () override;
bool stop_recording () override;
void info_record () override;
void save_record (const char *arg0) override;
bool supports_delete_record () override;
@@ -3794,24 +3794,28 @@ debug_target::record_method (ptid_t arg0)
return result;
}
void
bool
target_ops::stop_recording ()
{
this->beneath ()->stop_recording ();
return this->beneath ()->stop_recording ();
}
void
bool
dummy_target::stop_recording ()
{
return false;
}
void
bool
debug_target::stop_recording ()
{
target_debug_printf_nofunc ("-> %s->stop_recording (...)", this->beneath ()->shortname ());
this->beneath ()->stop_recording ();
target_debug_printf_nofunc ("<- %s->stop_recording ()",
this->beneath ()->shortname ());
bool result
= this->beneath ()->stop_recording ();
target_debug_printf_nofunc ("<- %s->stop_recording () = %s",
this->beneath ()->shortname (),
target_debug_print_bool (result).c_str ());
return result;
}
void

View File

@@ -1236,9 +1236,9 @@ struct target_ops
virtual enum record_method record_method (ptid_t ptid)
TARGET_DEFAULT_RETURN (RECORD_METHOD_NONE);
/* Stop trace recording. */
virtual void stop_recording ()
TARGET_DEFAULT_IGNORE ();
/* Stop trace recording. Return whether the selected thread moved. */
virtual bool stop_recording ()
TARGET_DEFAULT_RETURN (false);
/* Print information about the recording. */
virtual void info_record ()

View File

@@ -15,8 +15,16 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
static int
fun (void)
{
int x = fun (); /* fun.1 */
return x; /* fun.2 */
}
int
main (void)
{
return 0;
int x = fun (); /* main.1 */
return x; /* main.2 */
}

View File

@@ -37,6 +37,10 @@ with_test_prefix "inferior 1" {
if ![runto_main] {
return -1
}
gdb_test_no_output "record btrace"
gdb_test "step 4" "fun\.1.*"
gdb_test "reverse-step" "fun\.1.*"
}
with_test_prefix "inferior 2" {
@@ -48,25 +52,22 @@ with_test_prefix "inferior 2" {
return -1
}
gdb_test_no_output "record btrace" "record btrace"
gdb_test_no_output "record btrace"
gdb_test "step 4" "fun\.1.*"
gdb_test "reverse-step" "fun\.1.*"
gdb_test "info record" "Replay in progress.*"
gdb_test "record stop" "Process record is stopped.*"
gdb_test "step" "fun\.1.*"
}
with_test_prefix "inferior 1" {
gdb_test "inferior 1" "Switching to inferior 1.*"
gdb_test "info record" "No recording is currently active\\."
gdb_test_no_output "record btrace" "record btrace"
}
with_test_prefix "inferior 3" {
gdb_test "add-inferior -exec ${host_binfile}" "Added inferior 3.*" \
"add third inferior"
gdb_test "inferior 3" "Switching to inferior 3.*"
if ![runto_main] {
return -1
}
gdb_test "info record" "No recording is currently active\\."
gdb_test_no_output "record btrace" "record btrace"
gdb_test "info record" "Replay in progress.*"
gdb_test "reverse-finish" "fun\.1.*"
gdb_test "record goto end" "fun\.1.*"
gdb_test "step 2" "fun\.1.*"
gdb_test "reverse-step 3"
}

View File

@@ -28,21 +28,29 @@ if ![runto_main] {
return -1
}
# trace the call to the test function
# Trace the call to the test function.
with_test_prefix "record" {
gdb_test_no_output "record btrace"
gdb_test "next"
gdb_test "step"
gdb_test "next 2"
}
# let's step around a bit
# Let's step around a bit.
with_test_prefix "replay" {
gdb_test "reverse-next" ".*main\.2.*" "reverse-next.1"
gdb_test "reverse-finish" ".*main\.2.*" "reverse-finish.1"
gdb_test "step" ".*fun4\.2.*" "step.1"
gdb_test "next" ".*fun4\.3.*" "next.1"
gdb_test "step" ".*fun2\.2.*" "step.2"
gdb_test "finish" ".*fun4\.4.*" "finish.1"
gdb_test "reverse-step" ".*fun2\.3.*" "reverse-step.1"
gdb_test "reverse-finish" ".*fun4\.3.*" "reverse-finish.1"
gdb_test "reverse-next" ".*fun4\.2.*" "reverse-next.2"
gdb_test "reverse-finish" ".*main\.2.*" "reverse-finish.2"
gdb_test "reverse-finish" ".*fun4\.3.*" "reverse-finish.2"
gdb_test "reverse-step" ".*fun1\.2.*" "reverse-step.1"
gdb_test "finish" ".*fun4\.3.*" "finish.1"
gdb_test "reverse-next" ".*fun4\.2.*" "reverse-next.1"
gdb_test "reverse-finish" ".*main\.2.*" "reverse-finish.3"
}
# Stop recording and try to step live (pr19340).
with_test_prefix "live" {
gdb_test "record stop" "Process record is stopped.*fun4\.4.*"
gdb_test "reverse-next" "Target .* does not support this command.*"
gdb_test "step" ".*fun3\.2.*"
}