mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-12-06 07:33:08 +00:00
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:
@@ -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 \
|
||||||
|
|||||||
@@ -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,61 +525,65 @@ 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_of (event_thr).lwp (),
|
||||||
|
new_pid);
|
||||||
|
}
|
||||||
|
|
||||||
ptid = ptid_t (new_pid, new_pid);
|
ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
|
||||||
|
? ptid_t (new_pid, new_pid)
|
||||||
|
: ptid_t (ptid_of (event_thr).pid (), new_pid));
|
||||||
|
|
||||||
threads_debug_printf ("Got fork event from LWP %ld, "
|
lwp_info *child_lwp = add_lwp (child_ptid);
|
||||||
"new child is %d",
|
gdb_assert (child_lwp != NULL);
|
||||||
ptid_of (event_thr).lwp (),
|
child_lwp->stopped = 1;
|
||||||
ptid.pid ());
|
if (event != PTRACE_EVENT_CLONE)
|
||||||
|
child_lwp->must_set_ptrace_flags = 1;
|
||||||
|
child_lwp->status_pending_p = 0;
|
||||||
|
|
||||||
|
thread_info *child_thr = get_lwp_thread (child_lwp);
|
||||||
|
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
threads_debug_printf ("leaving child suspended");
|
||||||
|
child_lwp->suspended = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event_lwp->bp_reinsert != 0
|
||||||
|
&& supports_software_single_step ()
|
||||||
|
&& event == PTRACE_EVENT_VFORK)
|
||||||
|
{
|
||||||
|
/* If we leave single-step breakpoints there, child will
|
||||||
|
hit it, so uninsert single-step breakpoints from parent
|
||||||
|
(and child). Once vfork child is done, reinsert
|
||||||
|
them back to parent. */
|
||||||
|
uninsert_single_step_breakpoints (event_thr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event != PTRACE_EVENT_CLONE)
|
||||||
|
{
|
||||||
/* Add the new process to the tables and clone the breakpoint
|
/* 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
|
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
|
will be detached, since we will need the process object and the
|
||||||
breakpoints to remove any breakpoints from memory when we
|
breakpoints to remove any breakpoints from memory when we
|
||||||
detach, and the client side will access registers. */
|
detach, and the client side will access registers. */
|
||||||
child_proc = add_linux_process (new_pid, 0);
|
process_info *child_proc = add_linux_process (new_pid, 0);
|
||||||
gdb_assert (child_proc != NULL);
|
gdb_assert (child_proc != NULL);
|
||||||
child_lwp = add_lwp (ptid);
|
|
||||||
gdb_assert (child_lwp != NULL);
|
|
||||||
child_lwp->stopped = 1;
|
|
||||||
child_lwp->must_set_ptrace_flags = 1;
|
|
||||||
child_lwp->status_pending_p = 0;
|
|
||||||
child_thr = get_lwp_thread (child_lwp);
|
|
||||||
child_thr->last_resume_kind = resume_stop;
|
|
||||||
child_thr->last_status.set_stopped (GDB_SIGNAL_0);
|
|
||||||
|
|
||||||
/* If we're suspending all threads, leave this one suspended
|
process_info *parent_proc = get_thread_process (event_thr);
|
||||||
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)
|
|
||||||
{
|
|
||||||
threads_debug_printf ("leaving child suspended");
|
|
||||||
child_lwp->suspended = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent_proc = get_thread_process (event_thr);
|
|
||||||
child_proc->attached = parent_proc->attached;
|
child_proc->attached = parent_proc->attached;
|
||||||
|
|
||||||
if (event_lwp->bp_reinsert != 0
|
|
||||||
&& supports_software_single_step ()
|
|
||||||
&& event == PTRACE_EVENT_VFORK)
|
|
||||||
{
|
|
||||||
/* If we leave single-step breakpoints there, child will
|
|
||||||
hit it, so uninsert single-step breakpoints from parent
|
|
||||||
(and child). Once vfork child is done, reinsert
|
|
||||||
them back to parent. */
|
|
||||||
uninsert_single_step_breakpoints (event_thr);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,88 +592,97 @@ 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
|
|
||||||
breakpoints, the list of single-step breakpoints are cloned
|
|
||||||
from the parent's. Remove them from the child process.
|
|
||||||
In case of vfork, we'll reinsert them back once vforked
|
|
||||||
child is done. */
|
|
||||||
if (event_lwp->bp_reinsert != 0
|
|
||||||
&& supports_software_single_step ())
|
|
||||||
{
|
|
||||||
/* The child process is forked and stopped, so it is safe
|
|
||||||
to access its memory without stopping all other threads
|
|
||||||
from other processes. */
|
|
||||||
delete_single_step_breakpoints (child_thr);
|
|
||||||
|
|
||||||
gdb_assert (has_single_step_breakpoints (event_thr));
|
|
||||||
gdb_assert (!has_single_step_breakpoints (child_thr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Report the event. */
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
threads_debug_printf
|
/* If the parent thread is doing step-over with single-step
|
||||||
("Got clone event from LWP %ld, new child is LWP %ld",
|
breakpoints, the list of single-step breakpoints are cloned
|
||||||
lwpid_of (event_thr), new_pid);
|
from the parent's. Remove them from the child process.
|
||||||
|
In case of vfork, we'll reinsert them back once vforked
|
||||||
|
child is done. */
|
||||||
|
if (event_lwp->bp_reinsert != 0
|
||||||
|
&& supports_software_single_step ())
|
||||||
|
{
|
||||||
|
/* The child process is forked and stopped, so it is safe
|
||||||
|
to access its memory without stopping all other threads
|
||||||
|
from other processes. */
|
||||||
|
delete_single_step_breakpoints (child_thr);
|
||||||
|
|
||||||
ptid = ptid_t (pid_of (event_thr), new_pid);
|
gdb_assert (has_single_step_breakpoints (event_thr));
|
||||||
new_lwp = add_lwp (ptid);
|
gdb_assert (!has_single_step_breakpoints (child_thr));
|
||||||
|
}
|
||||||
/* 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
|
||||||
return 1;
|
&& (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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)
|
||||||
{
|
{
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user