aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/infrun.c158
-rw-r--r--gdb/linux-nat.c248
-rw-r--r--gdb/linux-nat.h2
-rw-r--r--gdb/target-delegates.c24
-rw-r--r--gdb/target.c7
-rw-r--r--gdb/target.h2
-rw-r--r--gdb/target/waitstatus.c1
-rw-r--r--gdb/target/waitstatus.h31
8 files changed, 281 insertions, 192 deletions
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 0590310..f778667 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1583,6 +1583,7 @@ step_over_info_valid_p (void)
/* Return true if THREAD is doing a displaced step. */
static bool
+ATTRIBUTE_UNUSED
displaced_step_in_progress_thread (thread_info *thread)
{
gdb_assert (thread != nullptr);
@@ -1897,6 +1898,31 @@ static displaced_step_finish_status
displaced_step_finish (thread_info *event_thread,
const target_waitstatus &event_status)
{
+ /* Check whether the parent is displaced stepping. */
+ struct regcache *regcache = get_thread_regcache (event_thread);
+ struct gdbarch *gdbarch = regcache->arch ();
+ inferior *parent_inf = event_thread->inf;
+
+ /* If this was a fork/vfork/clone, this event indicates that the
+ displaced stepping of the syscall instruction has been done, so
+ we perform cleanup for parent here. Also note that this
+ operation also cleans up the child for vfork, because their pages
+ are shared. */
+
+ /* If this is a fork (child gets its own address space copy) and
+ some displaced step buffers were in use at the time of the fork,
+ restore the displaced step buffer bytes in the child process.
+
+ Architectures which support displaced stepping and fork events
+ must supply an implementation of
+ gdbarch_displaced_step_restore_all_in_ptid. This is not enforced
+ during gdbarch validation to support architectures which support
+ displaced stepping but not forks. */
+ if (event_status.kind () == TARGET_WAITKIND_FORKED
+ && gdbarch_supports_displaced_stepping (gdbarch))
+ gdbarch_displaced_step_restore_all_in_ptid
+ (gdbarch, parent_inf, event_status.child_ptid ());
+
displaced_step_thread_state *displaced = &event_thread->displaced_step_state;
/* Was this thread performing a displaced step? */
@@ -1916,8 +1942,39 @@ displaced_step_finish (thread_info *event_thread,
/* Do the fixup, and release the resources acquired to do the displaced
step. */
- return gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
- event_thread, event_status);
+ displaced_step_finish_status status
+ = gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
+ event_thread, event_status);
+
+ if (event_status.kind () == TARGET_WAITKIND_FORKED
+ || event_status.kind () == TARGET_WAITKIND_VFORKED
+ || event_status.kind () == TARGET_WAITKIND_THREAD_CLONED)
+ {
+ /* Since the vfork/fork/clone syscall instruction was executed
+ in the scratchpad, the child's PC is also within the
+ scratchpad. Set the child's PC to the parent's PC value,
+ which has already been fixed up. Note: we use the parent's
+ aspace here, although we're touching the child, because the
+ child hasn't been added to the inferior list yet at this
+ point. */
+
+ struct regcache *child_regcache
+ = get_thread_arch_aspace_regcache (parent_inf->process_target (),
+ event_status.child_ptid (),
+ gdbarch,
+ parent_inf->aspace);
+ /* Read PC value of parent. */
+ CORE_ADDR parent_pc = regcache_read_pc (regcache);
+
+ displaced_debug_printf ("write child pc from %s to %s",
+ paddress (gdbarch,
+ regcache_read_pc (child_regcache)),
+ paddress (gdbarch, parent_pc));
+
+ regcache_write_pc (child_regcache, parent_pc);
+ }
+
+ return status;
}
/* Data to be passed around while handling an event. This data is
@@ -5663,67 +5720,13 @@ handle_inferior_event (struct execution_control_state *ecs)
case TARGET_WAITKIND_FORKED:
case TARGET_WAITKIND_VFORKED:
- /* Check whether the inferior is displaced stepping. */
- {
- struct regcache *regcache = get_thread_regcache (ecs->event_thread);
- struct gdbarch *gdbarch = regcache->arch ();
- inferior *parent_inf = find_inferior_ptid (ecs->target, ecs->ptid);
-
- /* If this is a fork (child gets its own address space copy)
- and some displaced step buffers were in use at the time of
- the fork, restore the displaced step buffer bytes in the
- child process.
-
- Architectures which support displaced stepping and fork
- events must supply an implementation of
- gdbarch_displaced_step_restore_all_in_ptid. This is not
- enforced during gdbarch validation to support architectures
- which support displaced stepping but not forks. */
- if (ecs->ws.kind () == TARGET_WAITKIND_FORKED
- && gdbarch_supports_displaced_stepping (gdbarch))
- gdbarch_displaced_step_restore_all_in_ptid
- (gdbarch, parent_inf, ecs->ws.child_ptid ());
-
- /* If displaced stepping is supported, and thread ecs->ptid is
- displaced stepping. */
- if (displaced_step_in_progress_thread (ecs->event_thread))
- {
- struct regcache *child_regcache;
- CORE_ADDR parent_pc;
-
- /* GDB has got TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED,
- indicating that the displaced stepping of syscall instruction
- has been done. Perform cleanup for parent process here. Note
- that this operation also cleans up the child process for vfork,
- because their pages are shared. */
- displaced_step_finish (ecs->event_thread, ecs->ws);
- /* Start a new step-over in another thread if there's one
- that needs it. */
- start_step_over ();
-
- /* Since the vfork/fork syscall instruction was executed in the scratchpad,
- the child's PC is also within the scratchpad. Set the child's PC
- to the parent's PC value, which has already been fixed up.
- FIXME: we use the parent's aspace here, although we're touching
- the child, because the child hasn't been added to the inferior
- list yet at this point. */
-
- child_regcache
- = get_thread_arch_aspace_regcache (parent_inf->process_target (),
- ecs->ws.child_ptid (),
- gdbarch,
- parent_inf->aspace);
- /* Read PC value of parent process. */
- parent_pc = regcache_read_pc (regcache);
-
- displaced_debug_printf ("write child pc from %s to %s",
- paddress (gdbarch,
- regcache_read_pc (child_regcache)),
- paddress (gdbarch, parent_pc));
-
- regcache_write_pc (child_regcache, parent_pc);
- }
- }
+ case TARGET_WAITKIND_THREAD_CLONED:
+
+ displaced_step_finish (ecs->event_thread, ecs->ws);
+
+ /* Start a new step-over in another thread if there's one that
+ needs it. */
+ start_step_over ();
context_switch (ecs);
@@ -5739,7 +5742,7 @@ handle_inferior_event (struct execution_control_state *ecs)
need to unpatch at follow/detach time instead to be certain
that new breakpoints added between catchpoint hit time and
vfork follow are detached. */
- if (ecs->ws.kind () != TARGET_WAITKIND_VFORKED)
+ if (ecs->ws.kind () == TARGET_WAITKIND_FORKED)
{
/* This won't actually modify the breakpoint list, but will
physically remove the breakpoints from the child. */
@@ -5771,14 +5774,24 @@ handle_inferior_event (struct execution_control_state *ecs)
if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat))
{
bool follow_child
- = (follow_fork_mode_string == follow_fork_mode_child);
+ = (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
+ && follow_fork_mode_string == follow_fork_mode_child);
ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
process_stratum_target *targ
= ecs->event_thread->inf->process_target ();
- bool should_resume = follow_fork ();
+ bool should_resume;
+ if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED)
+ should_resume = follow_fork ();
+ else
+ {
+ should_resume = true;
+ inferior *inf = ecs->event_thread->inf;
+ inf->top_target ()->follow_clone (ecs->ws.child_ptid ());
+ ecs->event_thread->pending_follow.set_spurious ();
+ }
/* Note that one of these may be an invalid pointer,
depending on detach_fork. */
@@ -5789,16 +5802,21 @@ handle_inferior_event (struct execution_control_state *ecs)
child is marked stopped. */
/* If not resuming the parent, mark it stopped. */
- if (follow_child && !detach_fork && !non_stop && !sched_multi)
+ if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
+ && follow_child && !detach_fork && !non_stop && !sched_multi)
parent->set_running (false);
/* If resuming the child, mark it running. */
- if (follow_child || (!detach_fork && (non_stop || sched_multi)))
+ if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
+ || (follow_child || (!detach_fork && (non_stop || sched_multi))))
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 ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
+ && target_is_non_stop_p ())
+ || (!detach_fork && (non_stop
+ || (sched_multi
+ && target_is_non_stop_p ()))))
{
if (follow_child)
switch_to_thread (parent);
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 5ee3227..f3d02b7 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1286,64 +1286,79 @@ get_detach_signal (struct lwp_info *lp)
return 0;
}
-/* Detach from LP. If SIGNO_P is non-NULL, then it points to the
- signal number that should be passed to the LWP when detaching.
- Otherwise pass any pending signal the LWP may have, if any. */
+/* If LP has a pending fork/vfork/clone status, return it. */
-static void
-detach_one_lwp (struct lwp_info *lp, int *signo_p)
+static gdb::optional<target_waitstatus>
+get_pending_child_status (lwp_info *lp)
{
- int lwpid = lp->ptid.lwp ();
- int signo;
-
- gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
-
- /* If the lwp/thread we are about to detach has a pending fork event,
- there is a process GDB is attached to that the core of GDB doesn't know
- about. Detach from it. */
-
/* Check in lwp_info::status. */
if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
{
int event = linux_ptrace_get_extended_event (lp->status);
- if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+ if (event == PTRACE_EVENT_FORK
+ || event == PTRACE_EVENT_VFORK
+ || event == PTRACE_EVENT_CLONE)
{
unsigned long child_pid;
int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
if (ret == 0)
- detach_one_pid (child_pid, 0);
+ {
+ target_waitstatus ws;
+
+ if (event == PTRACE_EVENT_FORK)
+ ws.set_forked (ptid_t (child_pid, child_pid));
+ else if (event == PTRACE_EVENT_VFORK)
+ ws.set_vforked (ptid_t (child_pid, child_pid));
+ else if (event == PTRACE_EVENT_CLONE)
+ ws.set_thread_cloned (ptid_t (lp->ptid.pid (), child_pid));
+ else
+ gdb_assert_not_reached ("unhandled");
+
+ return ws;
+ }
else
- perror_warning_with_name (_("Failed to detach fork child"));
+ {
+ perror_warning_with_name (_("Failed to retrieve event msg"));
+ return {};
+ }
}
}
/* Check in lwp_info::waitstatus. */
- if (lp->waitstatus.kind () == TARGET_WAITKIND_VFORKED
- || lp->waitstatus.kind () == TARGET_WAITKIND_FORKED)
- detach_one_pid (lp->waitstatus.child_ptid ().pid (), 0);
-
+ if (is_new_child_status (lp->waitstatus.kind ()))
+ return lp->waitstatus;
- /* Check in thread_info::pending_waitstatus. */
thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
- if (tp->has_pending_waitstatus ())
- {
- const target_waitstatus &ws = tp->pending_waitstatus ();
- if (ws.kind () == TARGET_WAITKIND_VFORKED
- || ws.kind () == TARGET_WAITKIND_FORKED)
- detach_one_pid (ws.child_ptid ().pid (), 0);
- }
+ /* Check in thread_info::pending_waitstatus. */
+ if (tp->has_pending_waitstatus ()
+ && is_new_child_status (tp->pending_waitstatus ().kind ()))
+ return tp->pending_waitstatus ();
/* Check in thread_info::pending_follow. */
- if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
- || tp->pending_follow.kind () == TARGET_WAITKIND_FORKED)
- detach_one_pid (tp->pending_follow.child_ptid ().pid (), 0);
+ if (is_new_child_status (tp->pending_follow.kind ()))
+ return tp->pending_follow;
- if (lp->status != 0)
- linux_nat_debug_printf ("Pending %s for %s on detach.",
- strsignal (WSTOPSIG (lp->status)),
- lp->ptid.to_string ().c_str ());
+ return {};
+}
+
+/* Detach from LP. If SIGNO_P is non-NULL, then it points to the
+ signal number that should be passed to the LWP when detaching.
+ Otherwise pass any pending signal the LWP may have, if any. */
+
+static void
+detach_one_lwp (struct lwp_info *lp, int *signo_p)
+{
+ int lwpid = lp->ptid.lwp ();
+ int signo;
+
+ /* If the lwp/thread we are about to detach has a pending fork/clone
+ event, there is a process/thread GDB is attached to that the core
+ of GDB doesn't know about. Detach from it. */
+
+ if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
+ detach_one_pid (ws->child_ptid ().lwp (), 0);
/* If there is a pending SIGSTOP, get rid of it. */
if (lp->signalled)
@@ -1821,6 +1836,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
return 1;
}
+void
+linux_nat_target::follow_clone (ptid_t child_ptid)
+{
+ lwp_info *new_lp = add_lwp (child_ptid);
+ new_lp->stopped = 1;
+
+ /* If the thread_db layer is active, let it record the user
+ level thread id and status, and add the thread to GDB's
+ list. */
+ if (!thread_db_notice_clone (inferior_ptid, new_lp->ptid))
+ {
+ /* The process is not using thread_db. Add the LWP to
+ GDB's list. */
+ add_thread (linux_target, new_lp->ptid);
+ }
+
+ /* We just created NEW_LP so it cannot yet contain STATUS. */
+ gdb_assert (new_lp->status == 0);
+
+ if (!pull_pid_from_list (&stopped_pids, child_ptid.lwp (), &new_lp->status))
+ internal_error (_("no saved status for clone lwp"));
+
+ if (WSTOPSIG (new_lp->status) != SIGSTOP)
+ {
+ /* This can happen if someone starts sending signals to
+ the new thread before it gets a chance to run, which
+ have a lower number than SIGSTOP (e.g. SIGUSR1).
+ This is an unlikely case, and harder to handle for
+ fork / vfork than for clone, so we do not try - but
+ we handle it for clone events here. */
+
+ new_lp->signalled = 1;
+
+ /* Save the wait status to report later. */
+ linux_nat_debug_printf
+ ("waitpid of new LWP %ld, saving status %s",
+ (long) new_lp->ptid.lwp (), status_to_str (new_lp->status).c_str ());
+ }
+ else
+ {
+ new_lp->status = 0;
+
+ if (report_thread_events)
+ new_lp->waitstatus.set_thread_created ();
+ }
+}
+
/* Handle a GNU/Linux extended wait response. If we see a clone
event, we need to add the new LWP to our list (and not report the
trap to higher layers). This function returns non-zero if the
@@ -1861,11 +1923,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
internal_error (_("wait returned unexpected status 0x%x"), status);
}
- ptid_t child_ptid (new_pid, new_pid);
-
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
{
- open_proc_mem_file (child_ptid);
+ open_proc_mem_file (ptid_t (new_pid, new_pid));
/* The arch-specific native code may need to know about new
forks even if those end up never mapped to an
@@ -1902,66 +1962,18 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
}
if (event == PTRACE_EVENT_FORK)
- ourstatus->set_forked (child_ptid);
+ ourstatus->set_forked (ptid_t (new_pid, new_pid));
else if (event == PTRACE_EVENT_VFORK)
- ourstatus->set_vforked (child_ptid);
+ ourstatus->set_vforked (ptid_t (new_pid, new_pid));
else if (event == PTRACE_EVENT_CLONE)
{
- struct lwp_info *new_lp;
-
- ourstatus->set_ignore ();
-
linux_nat_debug_printf
("Got clone event from LWP %d, new child is LWP %ld", pid, new_pid);
- new_lp = add_lwp (ptid_t (lp->ptid.pid (), new_pid));
- new_lp->stopped = 1;
- new_lp->resumed = 1;
+ /* Save the status again, we'll use it in follow_clone. */
+ add_to_pid_list (&stopped_pids, new_pid, status);
- /* If the thread_db layer is active, let it record the user
- level thread id and status, and add the thread to GDB's
- list. */
- if (!thread_db_notice_clone (lp->ptid, new_lp->ptid))
- {
- /* The process is not using thread_db. Add the LWP to
- GDB's list. */
- add_thread (linux_target, new_lp->ptid);
- }
-
- /* Even if we're stopping the thread for some reason
- internal to this module, from the perspective of infrun
- and the user/frontend, this new thread is running until
- it next reports a stop. */
- set_running (linux_target, new_lp->ptid, true);
- set_executing (linux_target, new_lp->ptid, true);
-
- if (WSTOPSIG (status) != SIGSTOP)
- {
- /* This can happen if someone starts sending signals to
- the new thread before it gets a chance to run, which
- have a lower number than SIGSTOP (e.g. SIGUSR1).
- This is an unlikely case, and harder to handle for
- fork / vfork than for clone, so we do not try - but
- we handle it for clone events here. */
-
- new_lp->signalled = 1;
-
- /* We created NEW_LP so it cannot yet contain STATUS. */
- gdb_assert (new_lp->status == 0);
-
- /* Save the wait status to report later. */
- linux_nat_debug_printf
- ("waitpid of new LWP %ld, saving status %s",
- (long) new_lp->ptid.lwp (), status_to_str (status).c_str ());
- new_lp->status = status;
- }
- else if (report_thread_events)
- {
- new_lp->waitstatus.set_thread_created ();
- new_lp->status = status;
- }
-
- return 1;
+ ourstatus->set_thread_cloned (ptid_t (lp->ptid.pid (), new_pid));
}
return 0;
@@ -3536,59 +3548,55 @@ kill_wait_callback (struct lwp_info *lp)
return 0;
}
-/* Kill the fork children of any threads of inferior INF that are
- stopped at a fork event. */
+/* Kill the fork/clone child of LP if it has an unfollowed child. */
-static void
-kill_unfollowed_fork_children (struct inferior *inf)
+static int
+kill_unfollowed_child_callback (lwp_info *lp)
{
- for (thread_info *thread : inf->non_exited_threads ())
+ if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
{
- struct target_waitstatus *ws = &thread->pending_follow;
-
- if (ws->kind () == TARGET_WAITKIND_FORKED
- || ws->kind () == TARGET_WAITKIND_VFORKED)
- {
- ptid_t child_ptid = ws->child_ptid ();
- int child_pid = child_ptid.pid ();
- int child_lwp = child_ptid.lwp ();
+ ptid_t child_ptid = ws->child_ptid ();
+ int child_pid = child_ptid.pid ();
+ int child_lwp = child_ptid.lwp ();
- kill_one_lwp (child_lwp);
- kill_wait_one_lwp (child_lwp);
+ kill_one_lwp (child_lwp);
+ kill_wait_one_lwp (child_lwp);
- /* Let the arch-specific native code know this process is
- gone. */
- linux_target->low_forget_process (child_pid);
- }
+ /* Let the arch-specific native code know this process is
+ gone. */
+ if (ws->kind () != TARGET_WAITKIND_THREAD_CLONED)
+ linux_target->low_forget_process (child_pid);
}
+
+ return 0;
}
void
linux_nat_target::kill ()
{
- /* If we're stopped while forking and we haven't followed yet,
- kill the other task. We need to do this first because the
+ ptid_t pid_ptid (inferior_ptid.pid ());
+
+ /* If we're stopped while forking/cloning and we haven't followed
+ yet, kill the child task. We need to do this first because the
parent will be sleeping if this is a vfork. */
- kill_unfollowed_fork_children (current_inferior ());
+ iterate_over_lwps (pid_ptid, kill_unfollowed_child_callback);
if (forks_exist_p ())
linux_fork_killall ();
else
{
- ptid_t ptid = ptid_t (inferior_ptid.pid ());
-
/* Stop all threads before killing them, since ptrace requires
that the thread is stopped to successfully PTRACE_KILL. */
- iterate_over_lwps (ptid, stop_callback);
+ iterate_over_lwps (pid_ptid, stop_callback);
/* ... and wait until all of them have reported back that
they're no longer running. */
- iterate_over_lwps (ptid, stop_wait_callback);
+ iterate_over_lwps (pid_ptid, stop_wait_callback);
/* Kill all LWP's ... */
- iterate_over_lwps (ptid, kill_callback);
+ iterate_over_lwps (pid_ptid, kill_callback);
/* ... and wait until we've flushed all events. */
- iterate_over_lwps (ptid, kill_wait_callback);
+ iterate_over_lwps (pid_ptid, kill_wait_callback);
}
target_mourn_inferior (inferior_ptid);
diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h
index a9b91a5..3ed25cc 100644
--- a/gdb/linux-nat.h
+++ b/gdb/linux-nat.h
@@ -129,6 +129,8 @@ public:
void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
+ void follow_clone (ptid_t) override;
+
std::vector<static_tracepoint_marker>
static_tracepoint_markers_by_strid (const char *id) override;
diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c
index daf4682..bee4660 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -76,6 +76,7 @@ struct dummy_target : public target_ops
int insert_vfork_catchpoint (int arg0) override;
int remove_vfork_catchpoint (int arg0) override;
void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
+ void follow_clone (ptid_t arg0) override;
int insert_exec_catchpoint (int arg0) override;
int remove_exec_catchpoint (int arg0) override;
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
@@ -250,6 +251,7 @@ struct debug_target : public target_ops
int insert_vfork_catchpoint (int arg0) override;
int remove_vfork_catchpoint (int arg0) override;
void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
+ void follow_clone (ptid_t arg0) override;
int insert_exec_catchpoint (int arg0) override;
int remove_exec_catchpoint (int arg0) override;
void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
@@ -1545,6 +1547,28 @@ debug_target::follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bo
gdb_puts (")\n", gdb_stdlog);
}
+void
+target_ops::follow_clone (ptid_t arg0)
+{
+ this->beneath ()->follow_clone (arg0);
+}
+
+void
+dummy_target::follow_clone (ptid_t arg0)
+{
+ default_follow_clone (this, arg0);
+}
+
+void
+debug_target::follow_clone (ptid_t arg0)
+{
+ gdb_printf (gdb_stdlog, "-> %s->follow_clone (...)\n", this->beneath ()->shortname ());
+ this->beneath ()->follow_clone (arg0);
+ gdb_printf (gdb_stdlog, "<- %s->follow_clone (", this->beneath ()->shortname ());
+ target_debug_print_ptid_t (arg0);
+ gdb_puts (")\n", gdb_stdlog);
+}
+
int
target_ops::insert_exec_catchpoint (int arg0)
{
diff --git a/gdb/target.c b/gdb/target.c
index f781f7e..2fb09c2 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2732,6 +2732,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
internal_error (_("could not find a target to follow fork"));
}
+static void
+default_follow_clone (struct target_ops *self, ptid_t child_ptid)
+{
+ /* Some target returned a clone event, but did not know how to follow it. */
+ internal_error (_("could not find a target to follow clone"));
+}
+
/* See target.h. */
void
diff --git a/gdb/target.h b/gdb/target.h
index 28aa927..aab390a 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -637,6 +637,8 @@ struct target_ops
TARGET_DEFAULT_RETURN (1);
virtual void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool)
TARGET_DEFAULT_FUNC (default_follow_fork);
+ virtual void follow_clone (ptid_t)
+ TARGET_DEFAULT_FUNC (default_follow_clone);
virtual int insert_exec_catchpoint (int)
TARGET_DEFAULT_RETURN (1);
virtual int remove_exec_catchpoint (int)
diff --git a/gdb/target/waitstatus.c b/gdb/target/waitstatus.c
index ef432bb..3e45e4f 100644
--- a/gdb/target/waitstatus.c
+++ b/gdb/target/waitstatus.c
@@ -45,6 +45,7 @@ DIAGNOSTIC_ERROR_SWITCH
case TARGET_WAITKIND_FORKED:
case TARGET_WAITKIND_VFORKED:
+ case TARGET_WAITKIND_THREAD_CLONED:
return string_appendf (str, ", child_ptid = %s",
this->child_ptid ().to_string ().c_str ());
diff --git a/gdb/target/waitstatus.h b/gdb/target/waitstatus.h
index 63bbd73..2072eb0 100644
--- a/gdb/target/waitstatus.h
+++ b/gdb/target/waitstatus.h
@@ -95,6 +95,13 @@ enum target_waitkind
/* There are no resumed children left in the program. */
TARGET_WAITKIND_NO_RESUMED,
+ /* The thread was cloned. The event's ptid corresponds to the
+ cloned parent. The cloned child is held stopped at its entry
+ point, and its ptid is in the event's m_child_ptid. The target
+ must not add the cloned child to GDB's thread list until
+ target_ops::follow_clone() is called. */
+ TARGET_WAITKIND_THREAD_CLONED,
+
/* The thread was created. */
TARGET_WAITKIND_THREAD_CREATED,
@@ -102,6 +109,17 @@ enum target_waitkind
TARGET_WAITKIND_THREAD_EXITED,
};
+/* Determine if KIND represents an event with a new child - a fork,
+ vfork, or clone. */
+
+static inline bool
+is_new_child_status (target_waitkind kind)
+{
+ return (kind == TARGET_WAITKIND_FORKED
+ || kind == TARGET_WAITKIND_VFORKED
+ || kind == TARGET_WAITKIND_THREAD_CLONED);
+}
+
/* Return KIND as a string. */
static inline const char *
@@ -125,6 +143,8 @@ DIAGNOSTIC_ERROR_SWITCH
return "FORKED";
case TARGET_WAITKIND_VFORKED:
return "VFORKED";
+ case TARGET_WAITKIND_THREAD_CLONED:
+ return "THREAD_CLONED";
case TARGET_WAITKIND_EXECD:
return "EXECD";
case TARGET_WAITKIND_VFORK_DONE:
@@ -325,6 +345,14 @@ struct target_waitstatus
return *this;
}
+ target_waitstatus &set_thread_cloned (ptid_t child_ptid)
+ {
+ this->reset ();
+ m_kind = TARGET_WAITKIND_THREAD_CLONED;
+ m_value.child_ptid = child_ptid;
+ return *this;
+ }
+
target_waitstatus &set_thread_created ()
{
this->reset ();
@@ -369,8 +397,7 @@ struct target_waitstatus
ptid_t child_ptid () const
{
- gdb_assert (m_kind == TARGET_WAITKIND_FORKED
- || m_kind == TARGET_WAITKIND_VFORKED);
+ gdb_assert (is_new_child_status (m_kind));
return m_value.child_ptid;
}