void
debug_vprintf (const char *fmt, va_list ap)
{
- vfprintf_unfiltered (gdb_stdlog, fmt, ap);
+ static FILE *out = nullptr;
+ if (!out)
+ out = fopen("/tmp/gdb.txt", "w");
+ vfprintf (out, fmt, ap);
+ fflush (out);
}
{
struct thread_info *lowest = inferior_thread ();
- stop_all_threads ();
+ stop_all_threads ("attaching");
/* It's not defined which thread will report the attach
stop. For consistency, always select the thread with
exits or execs. */
bool pending_detach = false;
- /* True if this inferior is a vfork parent waiting for a vfork child
- not under our control to be done with the shared memory region,
- either by exiting or execing. */
- bool waiting_for_vfork_done = false;
+ /* FIX COMMENT */
+ thread_info *thread_waiting_for_vfork_done = nullptr;
/* True if we're in the process of detaching from this inferior. */
bool detaching = false;
static void wait_for_inferior (inferior *inf);
+static void restart_threads (struct thread_info *event_thread, inferior *inf);
+
+static bool start_step_over (void);
+
+static bool step_over_info_valid_p (void);
+
/* Asynchronous signal handler registered as event loop source for
when we have pending events ready to be passed to the core. */
static struct async_event_handler *infrun_async_inferior_event_token;
insert breakpoints, so that we can debug it. A
subsequent child exec or exit is enough to know when does
the child stops using the parent's address space. */
- parent_inf->waiting_for_vfork_done = detach_fork;
+ gdb_assert (parent_inf->thread_waiting_for_vfork_done == nullptr);
+ parent_inf->thread_waiting_for_vfork_done
+ = detach_fork ? inferior_thread () : nullptr;
parent_inf->pspace->breakpoints_not_allowed = detach_fork;
}
}
child_inf->pending_detach = 0;
parent_inf->vfork_child = child_inf;
parent_inf->pending_detach = detach_fork;
- parent_inf->waiting_for_vfork_done = 0;
+ parent_inf->thread_waiting_for_vfork_done = nullptr;
}
else if (detach_fork)
{
parent = inferior_ptid;
child = tp->pending_follow.value.related_pid;
+ if (tp->pending_follow.kind == TARGET_WAITKIND_VFORKED
+ && target_is_non_stop_p ())
+ stop_all_threads ("handling vfork", tp->inf);
+
process_stratum_target *parent_targ = tp->inf->process_target ();
/* Set up inferior(s) as specified by the caller, and tell the
target to do whatever is necessary to follow either parent
}
}
+static void
+handle_vfork_done (thread_info *event_thread)
+{
+ if (event_thread->inf->thread_waiting_for_vfork_done == nullptr)
+ return;
+
+ gdb_assert (event_thread->inf->thread_waiting_for_vfork_done == event_thread);
+ event_thread->inf->thread_waiting_for_vfork_done = nullptr;
+ current_inferior ()->pspace->breakpoints_not_allowed = 0;
+
+ INFRUN_SCOPED_DEBUG_ENTER_EXIT;
+
+ if (target_is_non_stop_p ())
+ {
+ scoped_restore_current_thread restore_thread;
+
+ insert_breakpoints ();
+ restart_threads (event_thread, event_thread->inf);
+ start_step_over ();
+ }
+}
+
/* Enum strings for "set|show follow-exec-mode". */
static const char follow_exec_mode_new[] = "new";
|| stepping_past_nonsteppable_watchpoint ());
}
-\f
/* Displaced stepping. */
/* In non-stop debugging mode, we must take special care to manage
continue;
}
+ if (tp->inf->thread_waiting_for_vfork_done)
+ {
+ /* FIXME COMMENT */
+ continue;
+ }
+
/* Remove thread from the THREADS_TO_STEP chain. If anything goes wrong
while we try to prepare the displaced step, we don't add it back to
the global step over chain. This is to avoid a thread staying in the
return a wildcard ptid. */
if (target_is_non_stop_p ())
return inferior_ptid;
- else
- return user_visible_resume_ptid (user_step);
+
+ /* The rest of the function assumes non-stop==off and
+ target-non-stop==off. */
+
+ /* If a thread in the resumption set is waiting for a vfork-done event (the
+ vfork child is not under GDB's control), resume just that thread.
+
+ If the target_resume interface was more flexible, we could be smarter
+ here when schedule-multiple is on . For example, imagine 3 inferiors with
+ 2 threads each (1.1, 1.2, 2.1, 2.2, 3.1 and 3.2). Threads 2.1 and 3.2 are
+ both waiting for a vfork-done event. Then we could ask the target(s) to
+ resume:
+
+ - All threads of inferior 1
+ - Thread 2.1
+ - Thread 3.2
+
+ Since we don't have that flexibility, just resume the first thread waiting
+ for a vfork-done event we find (e.g. thread 2.1). */
+ if (sched_multi)
+ {
+ for (inferior *inf : all_non_exited_inferiors ())
+ if (inf->thread_waiting_for_vfork_done != nullptr)
+ return inf->thread_waiting_for_vfork_done->ptid;
+ }
+ else if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
+ return current_inferior ()->thread_waiting_for_vfork_done->ptid;
+
+ /* If an inferior (so, under GDB's control) is a vfork child of another
+ continue just that inferior. */
+ if (sched_multi)
+ {
+ for (inferior *inf : all_non_exited_inferiors ())
+ if (inf->vfork_parent != nullptr)
+ return ptid_t (inf->pid);
+ }
+ else if (current_inferior ()->vfork_parent != nullptr)
+ return ptid_t (current_inferior ()->pid);
+
+ return user_visible_resume_ptid (user_step);
}
/* Wrapper for target_resume, that handles infrun-specific
else
target_pass_signals (signal_pass);
+ infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
+ resume_ptid.to_string ().c_str (),
+ step, gdb_signal_to_symbol_string (sig));
target_resume (resume_ptid, step, sig);
if (target_can_async_p ())
struct gdbarch *gdbarch = regcache->arch ();
struct thread_info *tp = inferior_thread ();
const address_space *aspace = regcache->aspace ();
- ptid_t resume_ptid;
/* This represents the user's step vs continue request. When
deciding whether "set scheduler-locking step" applies, it's the
user's intention that counts. */
gdb_assert (!tp->stop_requested);
gdb_assert (!thread_is_in_step_over_chain (tp));
+ gdb_assert (tp->inf->thread_waiting_for_vfork_done == nullptr
+ || tp->inf->thread_waiting_for_vfork_done == tp);
if (tp->suspend.waitstatus_pending_p)
{
/* Depends on stepped_breakpoint. */
step = currently_stepping (tp);
- if (current_inferior ()->waiting_for_vfork_done)
+ if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
{
/* Don't try to single-step a vfork parent that is waiting for
the child to get out of the shared memory region (by exec'ing
insert_single_step_breakpoint (gdbarch, aspace, pc);
insert_breakpoints ();
- resume_ptid = internal_resume_ptid (user_step);
+ ptid_t resume_ptid = internal_resume_ptid (user_step);
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
tp->resumed = true;
return;
&& use_displaced_stepping (tp)
&& !step_over_info_valid_p ()
&& sig == GDB_SIGNAL_0
- && !current_inferior ()->waiting_for_vfork_done)
+ && current_inferior ()->thread_waiting_for_vfork_done == nullptr)
{
displaced_step_prepare_status prepare_status
= displaced_step_prepare (tp);
/* Fallback to stepping over the breakpoint in-line. */
if (target_is_non_stop_p ())
- stop_all_threads ();
+ stop_all_threads ("displaced stepping falling back on inline stepping");
set_step_over_info (regcache->aspace (),
regcache_read_pc (regcache), 0, tp->global_num);
gdb_assert (!(thread_has_single_step_breakpoints_set (tp) && step));
/* Decide the set of threads to ask the target to resume. */
+ ptid_t resume_ptid;
if (tp->control.trap_expected)
{
/* We're allowing a thread to run past a breakpoint it has
CORE_ADDR pc;
struct execution_control_state ecss;
struct execution_control_state *ecs = &ecss;
- bool started;
/* If we're stopped at a fork/vfork, follow the branch set by the
"set follow-fork-mode" command; otherwise, we'll just proceed
{
scoped_disable_commit_resumed disable_commit_resumed ("proceeding");
- started = start_step_over ();
+ bool displaced_step_started = start_step_over ();
if (step_over_info_valid_p ())
{
other thread was already doing one. In either case, don't
resume anything else until the step-over is finished. */
}
- else if (started && !target_is_non_stop_p ())
+ else if (displaced_step_started && !target_is_non_stop_p ())
{
/* A new displaced stepping sequence was started. In all-stop,
we can't talk to the target anymore until it next stops. */
continue;
}
+ if (tp->inf->thread_waiting_for_vfork_done != nullptr
+ && tp != tp->inf->thread_waiting_for_vfork_done)
+ {
+ infrun_debug_printf ("[%s] a thread of this inferior is waiting for vfork-done",
+ tp->ptid.to_string ().c_str ());
+ continue;
+ }
+
+ //if (tp->inf->pending_detach)
+ // {
+ //infrun_debug_printf ("[%s] inferior pending detach",
+ // tp->ptid.to_string ().c_str ());
+ //continue;
+ // }
+
infrun_debug_printf ("resuming %s",
target_pid_to_str (tp->ptid).c_str ());
error (_("Command aborted."));
}
}
- else if (!cur_thr->resumed && !thread_is_in_step_over_chain (cur_thr))
+ else if (!cur_thr->resumed
+ && !thread_is_in_step_over_chain (cur_thr)
+ && !(non_stop && cur_thr->inf->thread_waiting_for_vfork_done))
{
/* The thread wasn't started, and isn't queued, run it now. */
reset_ecs (ecs, cur_thr);
};
static bool handle_one (const wait_one_event &event);
-static void restart_threads (struct thread_info *event_thread);
/* Prepare and stabilize the inferior for detaching it. E.g.,
detaching while a thread is displaced stepping is a recipe for
previously-stepping thread, since that one is still
running). */
if (!step_over_info_valid_p ())
- restart_threads (thr);
+ restart_threads (thr, nullptr);
}
}
}
/* See infrun.h. */
void
-stop_all_threads (void)
+stop_all_threads (const char *reason, inferior *inf)
{
/* We may need multiple passes to discover all threads. */
int pass;
gdb_assert (exists_non_stop_target ());
- infrun_debug_printf ("starting");
+ INFRUN_SCOPED_DEBUG_START_END ("reason=%s, inf=%d", reason, inf != nullptr ? inf->num : -1);
scoped_restore_current_thread restore_thread;
/* Enable thread events of all targets. */
for (auto *target : all_non_exited_process_targets ())
{
+ if (inf != nullptr && inf->process_target () != target)
+ continue;
+
switch_to_target_no_thread (target);
target_thread_events (true);
}
/* Disable thread events of all targets. */
for (auto *target : all_non_exited_process_targets ())
{
+ if (inf != nullptr && inf->process_target () != target)
+ continue;
+
switch_to_target_no_thread (target);
target_thread_events (false);
}
for (auto *target : all_non_exited_process_targets ())
{
+ if (inf != nullptr && inf->process_target () != target)
+ continue;
+
switch_to_target_no_thread (target);
update_thread_list ();
}
to tell the target to stop. */
for (thread_info *t : all_non_exited_threads ())
{
+ if (inf != nullptr && t->inf != inf)
+ continue;
+
/* For a single-target setting with an all-stop target,
we would not even arrive here. For a multi-target
setting, until GDB is able to handle a mixture of
child->set_running (true);
/* In non-stop mode, also resume the other branch. */
- if (!detach_fork && (non_stop
- || (sched_multi && target_is_non_stop_p ())))
+ if (!detach_fork
+ && (non_stop || (sched_multi && target_is_non_stop_p ())))
{
if (follow_child)
switch_to_thread (parent);
ecs->event_thread = inferior_thread ();
ecs->ptid = inferior_ptid;
- keep_going (ecs);
+ if (current_inferior ()->vfork_child == nullptr)
+ keep_going (ecs);
}
if (follow_child)
ecs->ptid = inferior_ptid;
if (should_resume)
- keep_going (ecs);
+ {
+ if (ecs->ws.kind == TARGET_WAITKIND_VFORKED)
+ {
+ if (current_inferior ()->vfork_child != nullptr
+ && target_is_non_stop_p ())
+ prepare_to_wait (ecs);
+ else
+ keep_going (ecs);
+ }
+ else
+ if (!switch_back_to_stepped_thread (ecs))
+ keep_going (ecs);
+ }
else
stop_waiting (ecs);
return;
context_switch (ecs);
- current_inferior ()->waiting_for_vfork_done = 0;
- current_inferior ()->pspace->breakpoints_not_allowed = 0;
+ handle_vfork_done (ecs->event_thread);
+
+ gdb_assert (inferior_thread () == ecs->event_thread);
if (handle_stop_requested (ecs))
return;
/* This also takes care of reinserting breakpoints in the
previously locked inferior. */
- keep_going (ecs);
+ if (!switch_back_to_stepped_thread (ecs))
+ {
+ gdb_assert (inferior_thread () == ecs->event_thread);
+ keep_going (ecs);
+ }
return;
case TARGET_WAITKIND_EXECD:
ignored. */
static void
-restart_threads (struct thread_info *event_thread)
+restart_threads (struct thread_info *event_thread, inferior *inf)
{
+ INFRUN_SCOPED_DEBUG_ENTER_EXIT;
+
+ gdb_assert (target_is_non_stop_p ());
+
+ scoped_restore_current_thread restore_thread;
+
/* In case the instruction just stepped spawned a new thread. */
update_thread_list ();
for (thread_info *tp : all_non_exited_threads ())
{
+ if (inf != nullptr && tp->inf != inf)
+ continue;
+
if (tp->inf->detaching)
{
infrun_debug_printf ("restart threads: [%s] inferior detaching",
context_switch (ecs);
insert_breakpoints ();
- restart_threads (ecs->event_thread);
+ restart_threads (ecs->event_thread, nullptr);
/* If we have events pending, go through handle_inferior_event
again, picking up a pending event at random. This avoids
/* If all-stop, but there exists a non-stop target, stop all
threads now that we're presenting the stop to the user. */
if (!non_stop && exists_non_stop_target ())
- stop_all_threads ();
+ stop_all_threads ("presenting stop to user in all-stop");
}
/* Like keep_going, but passes the signal to the inferior, even if the
we're about to step over, otherwise other threads could miss
it. */
if (step_over_info_valid_p () && target_is_non_stop_p ())
- stop_all_threads ();
+ stop_all_threads ("starting in-line step-over");
/* Stop stepping if inserting breakpoints fails. */
try
/* Print "infrun" start/end debug statements. */
-#define INFRUN_SCOPED_DEBUG_START_END(msg) \
- scoped_debug_start_end (debug_infrun, "infrun", msg)
+#define INFRUN_SCOPED_DEBUG_START_END(msg, ...) \
+ scoped_debug_start_end (debug_infrun, "infrun", msg, ##__VA_ARGS__)
/* Print "infrun" enter/exit debug statements. */
extern void nullify_last_target_wait_ptid ();
/* Stop all threads. Only returns after everything is halted. */
-extern void stop_all_threads (void);
+extern void stop_all_threads (const char *reason, struct inferior *inf = nullptr);
extern void prepare_for_detach (void);
static int
resume_set_callback (struct lwp_info *lp)
{
+ inferior *inf = find_inferior_ptid (linux_target, lp->ptid);
+ gdb_assert (inf != nullptr);
+
+ ///* When we are waiting for a detached vfork child to exec or exit,
+ // breakpoints are removed, so only let the */
+
+ //if (inf->thread_waiting_for_vfork_done != nullptr
+ // && inf->thread_waiting_for_vfork_done->ptid != lp->ptid)
+ // return 0;
+
+ //if (inf->pending_detach)
+ // return 0;
+
lp->resumed = 1;
lp->last_resume_kind = resume_continue;
+
return 0;
}
if (event == PTRACE_EVENT_VFORK_DONE)
{
- if (current_inferior ()->waiting_for_vfork_done)
- {
- linux_nat_debug_printf
- ("Got expected PTRACE_EVENT_VFORK_DONE from LWP %ld: stopping",
- lp->ptid.lwp ());
-
- ourstatus->kind = TARGET_WAITKIND_VFORK_DONE;
- return 0;
- }
-
linux_nat_debug_printf
- ("Got PTRACE_EVENT_VFORK_DONE from LWP %ld: ignoring", lp->ptid.lwp ());
-
- return 1;
+ ("Got PTRACE_EVENT_VFORK_DONE from LWP %ld: stopping", lp->ptid.lwp ());
+ ourstatus->kind = TARGET_WAITKIND_VFORK_DONE;
+ return 0;
}
internal_error (__FILE__, __LINE__,
void packet_command (const char *args, int from_tty);
+ void commit_resumed (ptid_t filter);
+
private: /* data fields */
/* The remote state. Don't reference this directly. Use the
}
if (inf == nullptr)
{
+ gdb_assert(0);
/* Since all inferiors were already bound to a process, add
a new inferior. */
inf = add_inferior_with_spaces ();
+
}
switch_to_inferior_no_thread (inf);
inf->push_target (this);
the inferiors. */
if (!non_stop)
{
- stop_all_threads ();
+ stop_all_threads ("remote connect in all-stop");
/* If all threads of an inferior were already stopped, we
haven't setup the inferior yet. */
void
remote_target::commit_resumed ()
+{
+ this->commit_resumed (minus_one_ptid);
+}
+
+void
+remote_target::commit_resumed (ptid_t filter)
{
/* If connected in all-stop mode, we'd send the remote resume
request directly from remote_resume. Likewise if
(vCont;c). We can still send process-wide wildcards though. */
/* Start by assuming a global wildcard (vCont;c) is possible. */
- bool may_global_wildcard_vcont = true;
+ bool may_global_wildcard_vcont = filter == minus_one_ptid;
/* And assume every process is individually wildcard-able too. */
for (inferior *inf : all_non_exited_inferiors (this))
{
remote_inferior *priv = get_remote_inferior (inf);
- priv->may_wildcard_vcont = true;
+ priv->may_wildcard_vcont = filter == minus_one_ptid || (filter.is_pid () && inf->pid == filter.pid ());
}
/* Check for any pending events (not reported or processed yet) and
bool any_pending_vcont_resume = false;
- for (thread_info *tp : all_non_exited_threads (this))
+ for (thread_info *tp : all_non_exited_threads (this, filter))
{
remote_thread_info *priv = get_remote_thread_info (tp);
struct vcont_builder vcont_builder (this);
/* Threads first. */
- for (thread_info *tp : all_non_exited_threads (this))
+ for (thread_info *tp : all_non_exited_threads (this, filter))
{
remote_thread_info *remote_thr = get_remote_thread_info (tp);
}
if (needs_commit)
- commit_resumed ();
+ commit_resumed (ptid);
else
for (thread_info *tp : all_non_exited_threads (this, ptid))
{
/* We have tried hard enough, and just can't receive the
packet/notification. Give up. */
printf_unfiltered (_("Ignoring packet error, continuing...\n"));
+ gdb_assert(0);
+
+
/* Skip the ack char if we're in no-ack mode. */
if (!rs->noack_mode)
discard_pending_stop_replies (current_inferior ());
/* In 'target remote' mode with one inferior, we close the connection. */
- if (!rs->extended && number_of_live_inferiors (this) <= 1)
+ if (!rs->extended)
{
- remote_unpush_target (this);
- return;
+ // needed to make the test not hang at exit with native-gdbserver, should be investigated
+ if (number_of_live_inferiors (this) <= 1)
+ {
+ remote_unpush_target (this);
+ return;
+ }
+ else
+ pop_all_targets_at_and_above (process_stratum);
}
/* In case we got here due to an error, but we're going to stay
char *p = rs->buf.data ();
int left = get_remote_packet_size () - 1;
+ REMOTE_SCOPED_DEBUG_ENTER_EXIT;
+ remote_debug_printf ("inf=%d, filename=%s", inf != nullptr ? inf->num : -1, filename);
+
if (warn_if_slow)
{
static int warning_issued = 0;
--- /dev/null
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <limits.h>
+
+#define N_FORKERS 4
+
+static void *
+forker (void *arg)
+{
+ for (;;)
+ {
+ pid_t pid = FORK_FUNC ();
+
+ if (pid == 0)
+ _exit(0);
+
+ int ret;
+
+ do {
+ ret = waitpid (pid, NULL, 0);
+ } while (ret == EINTR);
+
+ assert (ret == pid);
+
+ usleep (1000 * 40);
+ }
+
+ return NULL;
+}
+
+static void
+sleep_a_bit (void)
+{
+ usleep (1000 * 50);
+}
+
+int
+main (void)
+{
+ int dummy;
+
+ alarm (60);
+
+ pthread_t thread[N_FORKERS];
+ for (int i = 0; i < N_FORKERS; ++i)
+ {
+ int ret = pthread_create (&thread[i], NULL, forker, NULL);
+ assert (ret == 0);
+ }
+
+ for (int i = 0; i < INT_MAX; ++i) /* for loop */
+ {
+ sleep_a_bit (); /* break here */
+ sleep_a_bit (); /* other line */
+ }
+
+ for (int i = 0; i < N_FORKERS; ++i)
+ pthread_join (thread[i], NULL);
+
+ return 0;
+}
--- /dev/null
+# Copyright 2020-2021 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/>.
+
+standard_testfile
+
+set break_here_line [gdb_get_line_number "break here"]
+
+set timeout 99999
+
+foreach_with_prefix fork_func {fork vfork} {
+ set opts [list debug pthreads additional_flags=-DFORK_FUNC=${fork_func}]
+ if { [build_executable "failed to prepare" \
+ ${testfile}-${fork_func} ${srcfile} $opts] } {
+ return
+ }
+}
+
+proc do_test { fork_func target-non-stop non-stop displaced-stepping } {
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\" -ex \"set non-stop ${non-stop}\""
+ clean_restart ${::binfile}-${fork_func}
+ }
+ #gdb_test "set debug infrun"
+ #gdb_test "set debug remote-packet-max-chars 4096"
+ #gdb_test "set debug remote 1"
+ #gdb_test "set remotetimeout 300"
+
+ gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
+
+ if { ![runto_main] } {
+ return
+ }
+
+ # The "Detached after (v)fork" messages get in the way in non-stop, disable them.
+ gdb_test_no_output "set print inferior-events off"
+
+ gdb_test "break $::break_here_line" "Breakpoint $::decimal at $::hex.*"
+ gdb_test "continue" "hit Breakpoint $::decimal, main.*"
+
+ for { set i 0 } { $i < 20 } { incr i } {
+ with_test_prefix "i=$i" {
+ if { [gdb_test "next" "other line.*" "next to other line"] != 0 } {
+ return
+ }
+
+ if { [gdb_test "next" "for loop.*" "next to for loop"] != 0 } {
+ return
+ }
+
+ if { [gdb_test "next" "break here.*" "next to break here"] != 0} {
+ return
+ }
+ }
+ }
+}
+
+foreach_with_prefix fork_func {fork vfork} {
+ foreach_with_prefix target-non-stop {auto on off} {
+ foreach_with_prefix non-stop {off on} {
+ foreach_with_prefix displaced-stepping {auto on off} {
+ do_test ${fork_func} ${target-non-stop} ${non-stop} ${displaced-stepping}
+ }
+ }
+ }
+}
--- /dev/null
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+// Start with:
+//
+// ./gdb -nx -q --data-directory=data-directory repro -ex "tb break_here_first" -ex r -ex "b should_break_here"
+//
+// Then do "continue".
+//
+// The main thread will likely cross should_break_here while we are handling
+// the vfork and breakpoints are removed, therefore missing the breakpoint.
+
+static volatile int release_vfork = 0;
+static volatile int release_main = 0;
+
+static void *vforker(void *arg)
+{
+ while (!release_vfork);
+
+ if (!vfork()) {
+ /* A vfork child is not supposed to mess with the state of the program,
+ but it is helpful for the purpose of this test. */
+ release_main = 1;
+ _exit(0);
+ }
+
+ return NULL;
+}
+
+static void should_break_here(void) {}
+
+int main()
+{
+
+ pthread_t thread;
+ int ret = pthread_create(&thread, NULL, vforker, NULL);
+ assert(ret == 0);
+
+ /* We break here first, while the thread is stuck on `!release_fork`. */
+ release_vfork = 1;
+
+ /* We set a breakpoint on should_break_here.
+
+ We then set "release_fork" from the debugger and continue. The main
+ thread hangs on `!release_main` while the non-main thread vforks. During
+ the window of time where the two processes have a shared address space
+ (after vfork, before _exit), GDB removes the breakpoints from the address
+ space. During that window, only the vfork-ing thread (the non-main
+ thread) is frozen by the kernel. The main thread is free to execute. The
+ child process sets `release_main`, releasing the main thread. A buggy GDB
+ would let the main thread execute during that window, leading to the
+ breakpoint on should_break_here being missed. A fixed GDB does not resume
+ the threads of the vforking process other than the vforking thread. When
+ the vfork child exits, the fixed GDB resumes the main thread, after
+ breakpoints are reinserted, so the breakpoint is not missed. */
+
+ while (!release_main);
+
+ should_break_here();
+
+ pthread_join (thread, NULL);
+
+ return 0;
+}
--- /dev/null
+# Copyright 2020-2021 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 a multi-threaded program doing a vfork doesn't miss breakpoints.
+#
+# When a program vforks, its address space is shared with the parent. When we
+# detach a vfork child, we must therefore keep breakpoints out of that address
+# space until the child either exits or execs. For this reason, threads from
+# the parent must be held stopped, otherwise they could miss breakpoints.
+#
+# The thread that did the vfork is suspended by the kernel, so it's not a
+# concern, the other threads need to be manually stopped by GDB, and restarted
+# once the vfork critical region is done.
+#
+# This test spawns one thread that calls vfork. Meanwhile, the main thread
+# crosses a breakpoint. A buggy GDB would let the main thread run while
+# breakpoints are removed, so the main thread would miss the breakpoint and run
+# until exit.
+
+standard_testfile
+
+if { [build_executable "failed to prepare" ${testfile} ${srcfile} {debug pthreads}] } {
+ return
+}
+
+set any "\[^\r\n\]*"
+
+# A bunch of util procedures to continue an inferior to an expected point.
+
+proc continue_to_parent_breakpoint {} {
+ gdb_test "continue" \
+ "hit Breakpoint .* should_break_here .*" \
+ "continue parent to breakpoint"
+}
+
+proc continue_to_parent_end {} {
+ gdb_test "continue" "Inferior 1.*exited normally.*" \
+ "continue parent to end"
+}
+
+proc continue_to_child_end {} {
+ gdb_test "continue" "Inferior 2.*exited normally.*" \
+ "continue child to end"
+}
+
+proc continue_to_child_end_ns {} {
+ # Since we're in non-stop, the vfork child is always resumed and executes
+ # until exit, while the parent is blocked. When the child exits, the
+ # prompt is shown, then the parent resumes and hits its breakpoint, which
+ # prints additional text to the console. Consume up to the prompt.
+ gdb_test_multiple "continue" "continue to end of inferior 2" {
+ -re "\\\[Inferior 2${::any}exited normally\\\]\r\n${::gdb_prompt} " {
+ pass $gdb_test_name
+ }
+ }
+}
+
+proc consume_parent_breakpoint_hit_ns {} {
+ # After the child exits, the parent gets resumed and hits its breakpoint.
+ # Consume part of this output.
+ gdb_test_multiple "" "stop at should_break_here" {
+ -re "Thread 1.1 ${::any} hit Breakpoint ${::decimal}, should_break_here " {
+ pass $gdb_test_name
+ }
+ }
+}
+
+# Switch to the parent inferior.
+
+proc switch_to_parent { {msg "switch to parent"} } {
+ gdb_test "inferior 1" "Switching to inferior 1 .*" \
+ $msg
+}
+
+# Test with detach-on-fork on/off.
+#
+# Other axes values can be checked using global variables defined by the
+# iteration below (e.g. ${::non-stop}).
+
+proc test_dof_on {} {
+ if { ${::follow-fork-mode} == "parent" } {
+ continue_to_parent_breakpoint
+ continue_to_parent_end
+ } else {
+ continue_to_child_end
+ }
+}
+
+proc test_dof_off {} {
+ if { ${::non-stop} == "on" } {
+ set seen_inferior_1_stop 0
+ set seen_inferior_2_exit 0
+
+ gdb_test_multiple "continue" "" {
+ -re -wrap "Inferior 2${::any}exited normally.*" {
+ incr seen_inferior_2_exit
+ }
+
+ -re -wrap "Thread 1.1${::any}hit Breakpoint.*" {
+ incr seen_inferior_1_stop
+ }
+ }
+
+ gdb_test_multiple "" "consume second event" {
+ -re "Inferior 2${::any}exited normally" {
+ incr seen_inferior_2_exit
+ }
+
+ -re "Thread 1.1${::any}hit Breakpoint" {
+ incr seen_inferior_1_stop
+ }
+ }
+
+ gdb_assert { $seen_inferior_1_stop == 1 }
+ gdb_assert { $seen_inferior_2_exit == 1 }
+
+ switch_to_parent
+ continue_to_parent_end
+ } elseif { ${::follow-fork-mode} == "parent" && ${::schedule-multiple} == "off" } {
+ gdb_test "continue" \
+ "Can not resume the parent process over vfork .*" \
+ "continue 1"
+ gdb_test "continue" \
+ "Can not resume the parent process over vfork .*" \
+ "continue 2"
+
+ gdb_test_no_output "set detach-on-fork on"
+ test_dof_on
+ } else {
+ continue_to_child_end
+ switch_to_parent
+ continue_to_parent_breakpoint
+ continue_to_parent_end
+# set seen_inferior_1_stop 0
+# set seen_inferior_2_exit 0
+#
+# gdb_test_multiple "continue" "continue 1" {
+# -re -wrap "Inferior 2${::any}exited normally.*" {
+# incr seen_inferior_2_exit
+# }
+#
+# -re -wrap "Thread 1.1${::any}hit Breakpoint.*" {
+# incr seen_inferior_1_stop
+# }
+# }
+#
+# switch_to_parent "switch to parent 1"
+#
+# gdb_test_multiple "continue" "continue 2" {
+# -re -wrap "Inferior 2${::any}exited normally.*" {
+# incr seen_inferior_2_exit
+# }
+#
+# -re -wrap "Thread 1.1${::any}hit Breakpoint.*" {
+# incr seen_inferior_1_stop
+# }
+# }
+#
+# gdb_assert { $seen_inferior_1_stop == 1 }
+# gdb_assert { $seen_inferior_2_exit == 1 }
+#
+# switch_to_parent "switch to parent 2"
+# continue_to_parent_end
+ }
+}
+
+proc do_test { target-non-stop non-stop follow-fork-mode detach-on-fork schedule-multiple } {
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\" -ex \"set non-stop ${non-stop}\""
+ clean_restart ${::binfile}
+ }
+
+ gdb_test_no_output "set follow-fork-mode ${follow-fork-mode}"
+ gdb_test_no_output "set detach-on-fork ${detach-on-fork}"
+ gdb_test_no_output "set schedule-multiple ${schedule-multiple}"
+
+ if { ![runto_main] } {
+ return
+ }
+
+ # The main thread is expected to hit this breakpoint.
+ gdb_test "break should_break_here" "Breakpoint $::decimal at .*"
+
+ test_dof_${detach-on-fork}
+}
+
+foreach_with_prefix target-non-stop {auto on off} {
+ foreach_with_prefix non-stop {off on} {
+ foreach_with_prefix follow-fork-mode {parent child} {
+ foreach_with_prefix detach-on-fork {on off} {
+ foreach_with_prefix schedule-multiple {off on} {
+ do_test ${target-non-stop} ${non-stop} ${follow-fork-mode} ${detach-on-fork} ${schedule-multiple}
+ }
+ }
+ }
+ }
+}
}
}
+ drain_gdbserver_output
+
set code $early_processed_code
append code {
-re ".*A problem internal to GDB has been detected" {
# Always load compatibility stuff.
load_lib future.exp
+
+proc drain_gdbserver_output { } {
+ if { [info exists ::server_spawn_id] } {
+ #puts "gonna expect"
+ gdb_expect {
+ -i "$::server_spawn_id"
+ -timeout 0
+
+ -re ".+" {
+ exp_continue
+ #puts "consumed: $expect_out(buffer)"
+ }
+
+
+ }
+ #puts "expected"
+ }
+}
set serialport_re [string_to_regexp $serialport]
for {set i 1} {$i <= 3} {incr i} {
send_gdb "target $targetname $serialport\n"
+ drain_gdbserver_output
gdb_expect 60 {
-re "A program is being debugged already.*ill it.*y or n. $" {
send_gdb "y\n"
}
-re "Remote debugging using .*$serialport_re.*$additional_text.*$gdb_prompt $" {
verbose "Set target to $targetname"
+ drain_gdbserver_output
return 0
}
-re "Remote debugging using stdio.*$additional_text.*$gdb_prompt $" {
# Returns the target protocol and socket to connect to.
proc gdbserver_start { options arguments } {
+ #return [list "remote" localhost:1234]
global portnum
global GDB_TEST_SOCKETHOST
append gdbserver_command " --once"
}
+ #append gdbserver_command " --event-loop-debug"
+ #append gdbserver_command " --remote-debug"
+ #append gdbserver_command " --debug"
+
# Enable debug if set.
if [gdbserver_debug_enabled] {
global gdbserverdebug
break
}
+ drain_gdbserver_output
+
return [list $protocol [$get_remote_address $debughost $portnum]]
}
global gdbserver_protocol
global gdbserver_gdbport
+ drain_gdbserver_output
+
# Kill anything running before we try to start gdbserver, in case
# we are sharing a serial connection.
global gdb_prompt
void
debug_vprintf (const char *format, va_list ap)
{
+ static FILE *foo = nullptr;
+ if (!foo)
+ foo = fopen("/tmp/gdbserver.txt", "w");
+
#if !defined (IN_PROCESS_AGENT)
/* N.B. Not thread safe, and can't be used, as is, with IPA. */
static int new_line = 1;
}
#endif
- vfprintf (debug_file, format, ap);
+
+
+ vfprintf (foo, format, ap);
+ fflush(foo);
#if !defined (IN_PROCESS_AGENT)
if (*format)
ptid = ptid_t (new_pid, new_pid, 0);
- if (debug_threads)
- {
- debug_printf ("HEW: Got fork event from LWP %ld, "
+ debug_printf("HEW: Got fork event from LWP %ld, "
"new child is %d\n",
ptid_of (event_thr).lwp (),
ptid.pid ());
- }
+
/* Add the new process to the tables and clone the breakpoint
lists of the parent. We need to do this even if the new process
if (event_child->waitstatus.kind == TARGET_WAITKIND_FORKED
|| event_child->waitstatus.kind == TARGET_WAITKIND_VFORKED)
{
+ debug_printf("wait returning vfork %s -> %s\n", event_child->thread->id.to_string().c_str(),
+ event_child->thread->fork_child->id.to_string().c_str());
gdb_assert (event_child->thread->fork_child != nullptr);
gdb_assert (event_child->thread->fork_child->fork_parent != nullptr);
event_child->thread->fork_child->fork_parent = nullptr;
event_child->thread->fork_child = nullptr;
}
+
*ourstatus = event_child->waitstatus;
/* Clear the event lwp's waitstatus since we handled it already. */
event_child->waitstatus.kind = TARGET_WAITKIND_IGNORE;
SIGSTOP is an implementation detail. */
ourstatus->value.sig = GDB_SIGNAL_0;
}
- else if (current_thread->last_resume_kind == resume_stop
- && WSTOPSIG (w) != SIGSTOP)
- {
- /* A thread that has been requested to stop by GDB with vCont;t,
- but, it stopped for other reasons. */
- ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
- }
else if (ourstatus->kind == TARGET_WAITKIND_STOPPED)
- {
- ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
- }
+ ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
gdb_assert (step_over_bkpt == null_ptid);
&& event_ptid != null_ptid)
async_file_mark ();
+ debug_printf(":;wait return %s, %s\n", event_ptid.to_string().c_str(), target_waitstatus_to_string(ourstatus).c_str());
+
return event_ptid;
}
know about this process, and must not know about it until it gets the
corresponding (v)fork event. Exclude this thread from the list. */
if (thread->fork_parent != nullptr)
+ {
+ debug_printf("Hiding thread %s\n", thread->id.to_string().c_str());
return;
+ }
write_ptid (ptid_s, ptid);