commit fb601dca422c0e8807d1b607b40ef6c64aeab58d Author: Kamil Rytarowski Date: Fri May 1 02:38:27 2020 +0200 Implement follow-fork and follow-vfork on NetBSD diff --git a/gdb/nbsd-nat.c b/gdb/nbsd-nat.c index 254a768eb37..cd342bf1652 100644 --- a/gdb/nbsd-nat.c +++ b/gdb/nbsd-nat.c @@ -222,7 +222,25 @@ nbsd_add_threads (nbsd_nat_target *target, pid_t pid) nbsd_thread_lister (pid, fn); } -/* Enable additional event reporting on new processes. */ +/* Enable additional event reporting on new processes. + + To catch fork and vfork events, PTRACE_FORK and PTRACE_VFORK are + set on every traced process to enable stops on returns from fork + or vfork. Note that both the parent and child will always stop, + even if system call stops are not enabled. + + To catch unblocking of the vfork parent (after child's exit or + exec), PTRACE_VFORK_DONE is set on every traced process. Note that + the parent will always stop, even if the system call stops are not + enabled. The child does not receive the PTRACE_VFORK_DONE event. + + To catch LWP (threading) events, PTRACE_LWP_CREATE and PTRACE_LWP_EXIT + are set on every process. This enables stops on the birth for new LWPs + (excluding the main LWP) and the death of LWPs (excluding the last LWP + in a process). Note that unlike fork and vfork events, the LWP events + are reported only once by the newborn or terminating thread. The thread + that creates a new LWP does not report an event. +*/ static void nbsd_enable_proc_events (pid_t pid) @@ -232,6 +250,12 @@ nbsd_enable_proc_events (pid_t pid) if (ptrace (PT_GET_EVENT_MASK, pid, &events, sizeof (events)) == -1) perror_with_name (("ptrace")); + /* Child process events. */ + events |= PTRACE_FORK; + events |= PTRACE_VFORK; + events |= PTRACE_VFORK_DONE; + + /* LWP thread events. */ events |= PTRACE_LWP_CREATE; events |= PTRACE_LWP_EXIT; @@ -261,6 +285,9 @@ nbsd_nat_target::post_attach (int pid) void nbsd_nat_target::update_thread_list () { + /* With support for thread events, threads are added/deleted from the + list as events are reported, so just try deleting exited threads. */ + delete_exited_threads (); } @@ -647,27 +674,24 @@ nbsd_nat_target::resume (ptid_t ptid, int step, enum gdb_signal signal) /* Implement a safe wrapper around waitpid(). */ static pid_t -nbsd_wait (ptid_t ptid, struct target_waitstatus *ourstatus, int options) +nbsd_waitpid (pid_t pid, int *status, int options) { - pid_t pid; - int status; + pid_t wpid; set_sigint_trap (); do { - /* The common code passes WNOHANG that leads to crashes, overwrite it. */ - pid = waitpid (ptid.pid (), &status, 0); + wpid = waitpid (pid, status, options); } - while (pid == -1 && errno == EINTR); + while (wpid == -1 && errno == EINTR); clear_sigint_trap (); - if (pid == -1) + if (wpid == -1) perror_with_name (_("Child process unexpectedly missing")); - store_waitstatus (ourstatus, status); - return pid; + return wpid; } /* Wait for the child specified by PTID to do something. Return the @@ -678,9 +702,15 @@ ptid_t nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus, int target_options) { - pid_t pid = nbsd_wait (ptid, ourstatus, target_options); + /* The common code passes WNOHANG that leads to crashes, overwrite it. */ + target_options = 0; + + int status; + pid_t pid = nbsd_waitpid (ptid.pid (), &status, target_options); ptid_t wptid = ptid_t (pid); + store_waitstatus (ourstatus, status); + /* If the child stopped, keep investigating its status. */ if (ourstatus->kind != TARGET_WAITKIND_STOPPED) return wptid; @@ -712,7 +742,7 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus, /* Process state for threading events */ ptrace_state_t pst = {}; - if (code == TRAP_LWP) + if (code == TRAP_LWP || code == TRAP_CHLD) { if (ptrace (PT_GET_PROCESS_STATE, pid, &pst, sizeof (pst)) == -1) perror_with_name (("ptrace")); @@ -746,6 +776,100 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus, return wptid; } + if (code == TRAP_CHLD && (pst.pe_report_event == PTRACE_FORK + || pst.pe_report_event == PTRACE_VFORK)) + { + /* Note that both the parent and child will always stop (as we + set PTRACE_FORK and PTRACE_VFORK) and report an event, even + if system call stops are not enabled. + + The order of reporting is not deterministic, thus discover + whether this is an event for a new process or parent. */ + bool newborn = true; + for (inferior *inf : all_non_exited_inferiors (this)) + { + if (inf->pid == pst.pe_other_pid) + { + newborn = false; + break; + } + } + + /* Construct event of the other process (regardless of whether it is child or process. */ + int other_status; + pid_t other_pid = pst.pe_other_pid; + pid_t other_wpid = nbsd_waitpid (other_pid, &other_status, 0); + + if (other_wpid != other_pid) + { + /* If the parent disappeared, we are in real trouble. */ + gdb_assert (!newborn); + + /* Child disappeared? */ + ourstatus->kind = TARGET_WAITKIND_SPURIOUS; + return wptid; + } + + if (!WIFSTOPPED(other_status) || WSTOPSIG(other_status) != SIGTRAP) + { + /* If the parent reported a different status, we are in real + trouble. */ + gdb_assert (!newborn); + + /* Child killed? */ + ourstatus->kind = TARGET_WAITKIND_SPURIOUS; + return wptid; + } + + /* Extract the event and thread that received a signal. */ + ptrace_siginfo_t other_psi; + if (ptrace (PT_GET_SIGINFO, other_pid, &other_psi, sizeof (other_psi)) + == -1) + perror_with_name (("ptrace")); + + /* Pick other pid's siginfo_t. */ + siginfo_t *other_si = &other_psi.psi_siginfo; + int other_lwp = other_psi.psi_lwpid; + + int other_signo = other_si->si_signo; + int other_code = other_si->si_code; + + if (other_signo != SIGTRAP || other_code != TRAP_CHLD) + { + /* If the parent reported a different status, we are in real + trouble. */ + gdb_assert (!newborn); + + /* Abnormal situation? */ + ourstatus->kind = TARGET_WAITKIND_SPURIOUS; + return wptid; + } + + /* Construct child's PTID. */ + pid_t child_pid = newborn ? pid : other_pid; + int child_lwp = newborn ? lwp : other_lwp; + ptid_t child_ptid = ptid_t (child_pid, child_lwp, 0); + + /* Construct parent's PTID. */ + pid_t parent_pid = !newborn ? pid : other_pid; + int parent_lwp = !newborn ? lwp : other_lwp; + ptid_t parent_ptid = ptid_t (parent_pid, parent_lwp, 0); + + /* Enable additional events on the child process. */ + nbsd_enable_proc_events (child_pid); + + if (in_thread_list (this, ptid_t (pid))) + thread_change_ptid (this, ptid_t (pid), parent_ptid); + + if (pst.pe_report_event == PTRACE_FORK) + ourstatus->kind = TARGET_WAITKIND_FORKED; + else + ourstatus->kind = TARGET_WAITKIND_VFORKED; + + ourstatus->value.related_pid = child_ptid; + return parent_ptid; + } + if (in_thread_list (this, ptid_t (pid))) thread_change_ptid (this, ptid_t (pid), wptid); @@ -765,6 +889,12 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus, return wptid; } + if (code == TRAP_CHLD && pst.pe_report_event == PTRACE_VFORK_DONE) + { + ourstatus->kind = TARGET_WAITKIND_VFORK_DONE; + return wptid; + } + if (code == TRAP_EXEC) { ourstatus->kind = TARGET_WAITKIND_EXECD; @@ -837,3 +967,56 @@ nbsd_nat_target::set_syscall_catchpoint (int pid, bool needed, are filtered by GDB rather than the kernel. */ return 0; } + +/* Target hook for follow_fork. On entry and at return inferior_ptid is + the ptid of the followed inferior. */ + +bool +nbsd_nat_target::follow_fork (bool follow_child, bool detach_fork) +{ + if (!follow_child && detach_fork) + { + struct thread_info *tp = inferior_thread (); + pid_t child_pid = tp->pending_follow.value.related_pid.pid (); + + /* Breakpoints have already been detached from the child by + infrun.c. */ + + if (ptrace (PT_DETACH, child_pid, (void *)1, 0) == -1) + perror_with_name (("ptrace")); + } + + return 0; +} + +/* Implement the "insert_fork_catchpoint" target_ops method. */ + +int +nbsd_nat_target::insert_fork_catchpoint (int pid) +{ + return 0; +} + +/* Implement the "remove_fork_catchpoint" target_ops method. */ + +int +nbsd_nat_target::remove_fork_catchpoint (int pid) +{ + return 0; +} + +/* Implement the "insert_vfork_catchpoint" target_ops method. */ + +int +nbsd_nat_target::insert_vfork_catchpoint (int pid) +{ + return 0; +} + +/* Implement the "remove_vfork_catchpoint" target_ops method. */ + +int +nbsd_nat_target::remove_vfork_catchpoint (int pid) +{ + return 0; +} diff --git a/gdb/nbsd-nat.h b/gdb/nbsd-nat.h index 4a8b96026e6..917fbf47448 100644 --- a/gdb/nbsd-nat.h +++ b/gdb/nbsd-nat.h @@ -47,7 +47,11 @@ struct nbsd_nat_target : public inf_ptrace_target int set_syscall_catchpoint (int pid, bool needed, int any_count, gdb::array_view syscall_counts) override; - + bool follow_fork (bool follow_child, bool detach_fork) override; + int insert_fork_catchpoint (int) override; + int remove_fork_catchpoint (int) override; + int insert_vfork_catchpoint (int) override; + int remove_vfork_catchpoint (int) override; }; #endif /* nbsd-nat.h */