Thread options & clone events (Linux GDBserver)

This patch teaches the Linux GDBserver backend to report clone events
to GDB, when GDB has requested them with the GDB_THREAD_OPTION_CLONE
thread option, via the new QThreadOptions packet.

This shuffles code in linux_process_target::handle_extended_wait
around to a more logical order when we now have to handle and
potentially report all of fork/vfork/clone.

Raname lwp_info::fork_relative -> lwp_info::relative as the field is
no longer only about (v)fork.

With this, gdb.threads/stepi-over-clone.exp now cleanly passes against
GDBserver, so remove the native-target-only requirement from that
testcase.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
Reviewed-By: Andrew Burgess <aburgess@redhat.com>
Change-Id: I3a19bc98801ec31e5c6fdbe1ebe17df855142bb2
This commit is contained in:
Pedro Alves
2021-11-23 20:35:12 +00:00
parent 25b16bc9e7
commit 393a6b5947
3 changed files with 159 additions and 145 deletions

View File

@@ -19,12 +19,6 @@
# disassembly output. For now this is only implemented for x86-64. # disassembly output. For now this is only implemented for x86-64.
require {istarget x86_64-*-*} require {istarget x86_64-*-*}
# Test only on native targets, for now.
proc is_native_target {} {
return [expr {[target_info gdb_protocol] == ""}]
}
require is_native_target
standard_testfile standard_testfile
if { [prepare_for_testing "failed to prepare" $testfile $srcfile \ if { [prepare_for_testing "failed to prepare" $testfile $srcfile \

View File

@@ -491,7 +491,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
struct lwp_info *event_lwp = *orig_event_lwp; struct lwp_info *event_lwp = *orig_event_lwp;
int event = linux_ptrace_get_extended_event (wstat); int event = linux_ptrace_get_extended_event (wstat);
struct thread_info *event_thr = get_lwp_thread (event_lwp); struct thread_info *event_thr = get_lwp_thread (event_lwp);
struct lwp_info *new_lwp;
gdb_assert (event_lwp->waitstatus.kind () == TARGET_WAITKIND_IGNORE); gdb_assert (event_lwp->waitstatus.kind () == TARGET_WAITKIND_IGNORE);
@@ -503,7 +502,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK) if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
|| (event == PTRACE_EVENT_CLONE)) || (event == PTRACE_EVENT_CLONE))
{ {
ptid_t ptid;
unsigned long new_pid; unsigned long new_pid;
int ret, status; int ret, status;
@@ -527,35 +525,29 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
warning ("wait returned unexpected status 0x%x", status); warning ("wait returned unexpected status 0x%x", status);
} }
if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK) if (debug_threads)
{ {
struct process_info *parent_proc; debug_printf ("HEW: Got %s event from LWP %ld, new child is %ld\n",
struct process_info *child_proc; (event == PTRACE_EVENT_FORK ? "fork"
struct lwp_info *child_lwp; : event == PTRACE_EVENT_VFORK ? "vfork"
struct thread_info *child_thr; : event == PTRACE_EVENT_CLONE ? "clone"
: "???"),
ptid = ptid_t (new_pid, new_pid);
threads_debug_printf ("Got fork event from LWP %ld, "
"new child is %d",
ptid_of (event_thr).lwp (), ptid_of (event_thr).lwp (),
ptid.pid ()); new_pid);
}
/* Add the new process to the tables and clone the breakpoint ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
lists of the parent. We need to do this even if the new process ? ptid_t (new_pid, new_pid)
will be detached, since we will need the process object and the : ptid_t (ptid_of (event_thr).pid (), new_pid));
breakpoints to remove any breakpoints from memory when we
detach, and the client side will access registers. */ lwp_info *child_lwp = add_lwp (child_ptid);
child_proc = add_linux_process (new_pid, 0);
gdb_assert (child_proc != NULL);
child_lwp = add_lwp (ptid);
gdb_assert (child_lwp != NULL); gdb_assert (child_lwp != NULL);
child_lwp->stopped = 1; child_lwp->stopped = 1;
if (event != PTRACE_EVENT_CLONE)
child_lwp->must_set_ptrace_flags = 1; child_lwp->must_set_ptrace_flags = 1;
child_lwp->status_pending_p = 0; child_lwp->status_pending_p = 0;
child_thr = get_lwp_thread (child_lwp);
child_thr->last_resume_kind = resume_stop; thread_info *child_thr = get_lwp_thread (child_lwp);
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
/* If we're suspending all threads, leave this one suspended /* If we're suspending all threads, leave this one suspended
too. If the fork/clone parent is stepping over a breakpoint, too. If the fork/clone parent is stepping over a breakpoint,
@@ -568,9 +560,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
child_lwp->suspended = 1; child_lwp->suspended = 1;
} }
parent_proc = get_thread_process (event_thr);
child_proc->attached = parent_proc->attached;
if (event_lwp->bp_reinsert != 0 if (event_lwp->bp_reinsert != 0
&& supports_software_single_step () && supports_software_single_step ()
&& event == PTRACE_EVENT_VFORK) && event == PTRACE_EVENT_VFORK)
@@ -582,6 +571,19 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
uninsert_single_step_breakpoints (event_thr); uninsert_single_step_breakpoints (event_thr);
} }
if (event != PTRACE_EVENT_CLONE)
{
/* 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
will be detached, since we will need the process object and the
breakpoints to remove any breakpoints from memory when we
detach, and the client side will access registers. */
process_info *child_proc = add_linux_process (new_pid, 0);
gdb_assert (child_proc != NULL);
process_info *parent_proc = get_thread_process (event_thr);
child_proc->attached = parent_proc->attached;
clone_all_breakpoints (child_thr, event_thr); clone_all_breakpoints (child_thr, event_thr);
target_desc_up tdesc = allocate_target_description (); target_desc_up tdesc = allocate_target_description ();
@@ -590,23 +592,31 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
/* Clone arch-specific process data. */ /* Clone arch-specific process data. */
low_new_fork (parent_proc, child_proc); low_new_fork (parent_proc, child_proc);
}
/* Save fork info in the parent thread. */ /* Save fork/clone info in the parent thread. */
if (event == PTRACE_EVENT_FORK) if (event == PTRACE_EVENT_FORK)
event_lwp->waitstatus.set_forked (ptid); event_lwp->waitstatus.set_forked (child_ptid);
else if (event == PTRACE_EVENT_VFORK) else if (event == PTRACE_EVENT_VFORK)
event_lwp->waitstatus.set_vforked (ptid); event_lwp->waitstatus.set_vforked (child_ptid);
else if (event == PTRACE_EVENT_CLONE
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) != 0)
event_lwp->waitstatus.set_thread_cloned (child_ptid);
if (event != PTRACE_EVENT_CLONE
|| (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) != 0)
{
/* The status_pending field contains bits denoting the /* The status_pending field contains bits denoting the
extended event, so when the pending event is handled, extended event, so when the pending event is handled, the
the handler will look at lwp->waitstatus. */ handler will look at lwp->waitstatus. */
event_lwp->status_pending_p = 1; event_lwp->status_pending_p = 1;
event_lwp->status_pending = wstat; event_lwp->status_pending = wstat;
/* Link the threads until the parent event is passed on to /* Link the threads until the parent's event is passed on to
higher layers. */ GDB. */
event_lwp->fork_relative = child_lwp; event_lwp->relative = child_lwp;
child_lwp->fork_relative = event_lwp; child_lwp->relative = event_lwp;
}
/* If the parent thread is doing step-over with single-step /* If the parent thread is doing step-over with single-step
breakpoints, the list of single-step breakpoints are cloned breakpoints, the list of single-step breakpoints are cloned
@@ -625,54 +635,55 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
gdb_assert (!has_single_step_breakpoints (child_thr)); gdb_assert (!has_single_step_breakpoints (child_thr));
} }
/* Report the event. */
return 0;
}
threads_debug_printf
("Got clone event from LWP %ld, new child is LWP %ld",
lwpid_of (event_thr), new_pid);
ptid = ptid_t (pid_of (event_thr), new_pid);
new_lwp = add_lwp (ptid);
/* Either we're going to immediately resume the new thread
or leave it stopped. resume_one_lwp is a nop if it
thinks the thread is currently running, so set this first
before calling resume_one_lwp. */
new_lwp->stopped = 1;
/* If we're suspending all threads, leave this one suspended
too. If the fork/clone parent is stepping over a breakpoint,
all other threads have been suspended already. Leave the
child suspended too. */
if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
|| event_lwp->bp_reinsert != 0)
new_lwp->suspended = 1;
/* Normally we will get the pending SIGSTOP. But in some cases /* Normally we will get the pending SIGSTOP. But in some cases
we might get another signal delivered to the group first. we might get another signal delivered to the group first.
If we do get another signal, be sure not to lose it. */ If we do get another signal, be sure not to lose it. */
if (WSTOPSIG (status) != SIGSTOP) if (WSTOPSIG (status) != SIGSTOP)
{ {
new_lwp->stop_expected = 1; child_lwp->stop_expected = 1;
new_lwp->status_pending_p = 1; child_lwp->status_pending_p = 1;
new_lwp->status_pending = status; child_lwp->status_pending = status;
} }
else if (cs.report_thread_events) else if (event == PTRACE_EVENT_CLONE && cs.report_thread_events)
{ {
new_lwp->waitstatus.set_thread_created (); child_lwp->waitstatus.set_thread_created ();
new_lwp->status_pending_p = 1; child_lwp->status_pending_p = 1;
new_lwp->status_pending = status; child_lwp->status_pending = status;
} }
if (event == PTRACE_EVENT_CLONE)
{
#ifdef USE_THREAD_DB #ifdef USE_THREAD_DB
thread_db_notice_clone (event_thr, ptid); thread_db_notice_clone (event_thr, child_ptid);
#endif #endif
}
/* Don't report the event. */ if (event == PTRACE_EVENT_CLONE
&& (event_thr->thread_options & GDB_THREAD_OPTION_CLONE) == 0)
{
threads_debug_printf
("not reporting clone event from LWP %ld, new child is %ld\n",
ptid_of (event_thr).lwp (),
new_pid);
return 1; return 1;
} }
/* Leave the child stopped until GDB processes the parent
event. */
child_thr->last_resume_kind = resume_stop;
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
/* Report the event. */
threads_debug_printf
("reporting %s event from LWP %ld, new child is %ld\n",
(event == PTRACE_EVENT_FORK ? "fork"
: event == PTRACE_EVENT_VFORK ? "vfork"
: event == PTRACE_EVENT_CLONE ? "clone"
: "???"),
ptid_of (event_thr).lwp (),
new_pid);
return 0;
}
else if (event == PTRACE_EVENT_VFORK_DONE) else if (event == PTRACE_EVENT_VFORK_DONE)
{ {
event_lwp->waitstatus.set_vfork_done (); event_lwp->waitstatus.set_vfork_done ();
@@ -3531,15 +3542,14 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
if (event_child->waitstatus.kind () != TARGET_WAITKIND_IGNORE) if (event_child->waitstatus.kind () != TARGET_WAITKIND_IGNORE)
{ {
/* If the reported event is an exit, fork, vfork or exec, let /* If the reported event is an exit, fork, vfork, clone or exec,
GDB know. */ let GDB know. */
/* Break the unreported fork relationship chain. */ /* Break the unreported fork/vfork/clone relationship chain. */
if (event_child->waitstatus.kind () == TARGET_WAITKIND_FORKED if (is_new_child_status (event_child->waitstatus.kind ()))
|| event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED)
{ {
event_child->fork_relative->fork_relative = NULL; event_child->relative->relative = NULL;
event_child->fork_relative = NULL; event_child->relative = NULL;
} }
*ourstatus = event_child->waitstatus; *ourstatus = event_child->waitstatus;
@@ -4272,15 +4282,14 @@ linux_set_resume_request (thread_info *thread, thread_resume *resume, size_t n)
continue; continue;
} }
/* Don't let wildcard resumes resume fork children that GDB /* Don't let wildcard resumes resume fork/vfork/clone
does not yet know are new fork children. */ children that GDB does not yet know are new children. */
if (lwp->fork_relative != NULL) if (lwp->relative != NULL)
{ {
struct lwp_info *rel = lwp->fork_relative; struct lwp_info *rel = lwp->relative;
if (rel->status_pending_p if (rel->status_pending_p
&& (rel->waitstatus.kind () == TARGET_WAITKIND_FORKED && is_new_child_status (rel->waitstatus.kind ()))
|| rel->waitstatus.kind () == TARGET_WAITKIND_VFORKED))
{ {
threads_debug_printf threads_debug_printf
("not resuming LWP %ld: has queued stop reply", ("not resuming LWP %ld: has queued stop reply",
@@ -5907,6 +5916,14 @@ linux_process_target::supports_vfork_events ()
return true; return true;
} }
/* Return the set of supported thread options. */
gdb_thread_options
linux_process_target::supported_thread_options ()
{
return GDB_THREAD_OPTION_CLONE;
}
/* Check if exec events are supported. */ /* Check if exec events are supported. */
bool bool

View File

@@ -234,6 +234,8 @@ public:
bool supports_vfork_events () override; bool supports_vfork_events () override;
gdb_thread_options supported_thread_options () override;
bool supports_exec_events () override; bool supports_exec_events () override;
void handle_new_gdb_connection () override; void handle_new_gdb_connection () override;
@@ -732,48 +734,47 @@ struct pending_signal
struct lwp_info struct lwp_info
{ {
/* If this LWP is a fork child that wasn't reported to GDB yet, return /* If this LWP is a fork/vfork/clone child that wasn't reported to
its parent, else nullptr. */ GDB yet, return its parent, else nullptr. */
lwp_info *pending_parent () const lwp_info *pending_parent () const
{ {
if (this->fork_relative == nullptr) if (this->relative == nullptr)
return nullptr; return nullptr;
gdb_assert (this->fork_relative->fork_relative == this); gdb_assert (this->relative->relative == this);
/* In a fork parent/child relationship, the parent has a status pending and /* In a parent/child relationship, the parent has a status pending and
the child does not, and a thread can only be in one such relationship the child does not, and a thread can only be in one such relationship
at most. So we can recognize who is the parent based on which one has at most. So we can recognize who is the parent based on which one has
a pending status. */ a pending status. */
gdb_assert (!!this->status_pending_p gdb_assert (!!this->status_pending_p
!= !!this->fork_relative->status_pending_p); != !!this->relative->status_pending_p);
if (!this->fork_relative->status_pending_p) if (!this->relative->status_pending_p)
return nullptr; return nullptr;
const target_waitstatus &ws const target_waitstatus &ws
= this->fork_relative->waitstatus; = this->relative->waitstatus;
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|| ws.kind () == TARGET_WAITKIND_VFORKED); || ws.kind () == TARGET_WAITKIND_VFORKED);
return this->fork_relative; return this->relative; }
}
/* If this LWP is the parent of a fork child we haven't reported to GDB yet, /* If this LWP is the parent of a fork/vfork/clone child we haven't
return that child, else nullptr. */ reported to GDB yet, return that child, else nullptr. */
lwp_info *pending_child () const lwp_info *pending_child () const
{ {
if (this->fork_relative == nullptr) if (this->relative == nullptr)
return nullptr; return nullptr;
gdb_assert (this->fork_relative->fork_relative == this); gdb_assert (this->relative->relative == this);
/* In a fork parent/child relationship, the parent has a status pending and /* In a parent/child relationship, the parent has a status pending and
the child does not, and a thread can only be in one such relationship the child does not, and a thread can only be in one such relationship
at most. So we can recognize who is the parent based on which one has at most. So we can recognize who is the parent based on which one has
a pending status. */ a pending status. */
gdb_assert (!!this->status_pending_p gdb_assert (!!this->status_pending_p
!= !!this->fork_relative->status_pending_p); != !!this->relative->status_pending_p);
if (!this->status_pending_p) if (!this->status_pending_p)
return nullptr; return nullptr;
@@ -782,7 +783,7 @@ struct lwp_info
gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
|| ws.kind () == TARGET_WAITKIND_VFORKED); || ws.kind () == TARGET_WAITKIND_VFORKED);
return this->fork_relative; return this->relative;
} }
/* Backlink to the parent object. */ /* Backlink to the parent object. */
@@ -820,11 +821,13 @@ struct lwp_info
information or exit status until it can be reported to GDB. */ information or exit status until it can be reported to GDB. */
struct target_waitstatus waitstatus; struct target_waitstatus waitstatus;
/* A pointer to the fork child/parent relative. Valid only while /* A pointer to the fork/vfork/clone child/parent relative (like
the parent fork event is not reported to higher layers. Used to people, LWPs have relatives). Valid only while the parent
avoid wildcard vCont actions resuming a fork child before GDB is fork/vfork/clone event is not reported to higher layers. Used to
notified about the parent's fork event. */ avoid wildcard vCont actions resuming a fork/vfork/clone child
struct lwp_info *fork_relative = nullptr; before GDB is notified about the parent's fork/vfork/clone
event. */
struct lwp_info *relative = nullptr;
/* When stopped is set, this is where the lwp last stopped, with /* When stopped is set, this is where the lwp last stopped, with
decr_pc_after_break already accounted for. If the LWP is decr_pc_after_break already accounted for. If the LWP is