static int inf_ptrace_follow_fork (struct target_ops *ops, int follow_child) { pid_t pid, fpid; ptrace_state_t pe; /* FIXME: kettenis/20050720: This stuff should really be passed as an argument by our caller. */ { ptid_t ptid; struct target_waitstatus status; get_last_target_status (&ptid, &status); gdb_assert (status.kind == TARGET_WAITKIND_FORKED); pid = ptid_get_pid (ptid); } if (ptrace (PT_GET_PROCESS_STATE, pid, (PTRACE_TYPE_ARG3)&pe, sizeof pe) == -1) perror_with_name (("ptrace")); gdb_assert (pe.pe_report_event == PTRACE_FORK); fpid = pe.pe_other_pid; if (follow_child) { inferior_ptid = pid_to_ptid (fpid); detach_breakpoints (pid); /* Reset breakpoints in the child as appropriate. */ follow_inferior_reset_breakpoints (); if (ptrace (PT_DETACH, pid, (PTRACE_TYPE_ARG3)1, 0) == -1) perror_with_name (("ptrace")); } else { inferior_ptid = pid_to_ptid (pid); detach_breakpoints (fpid); if (ptrace (PT_DETACH, fpid, (PTRACE_TYPE_ARG3)1, 0) == -1) perror_with_name (("ptrace")); } return 0; }
int child_follow_fork (int follow_child) { ptid_t last_ptid; struct target_waitstatus last_status; int has_vforked; int parent_pid, child_pid; get_last_target_status (&last_ptid, &last_status); has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED); parent_pid = ptid_get_pid (last_ptid); child_pid = last_status.value.related_pid; /* At this point, if we are vforking, breakpoints were already detached from the child in child_wait; and the child has already called execve(). If we are forking, both the parent and child have breakpoints inserted. */ if (! follow_child) { if (! has_vforked) { detach_breakpoints (child_pid); #ifdef SOLIB_REMOVE_INFERIOR_HOOK SOLIB_REMOVE_INFERIOR_HOOK (child_pid); #endif } /* Detach from the child. */ printf_unfiltered ("Detaching after fork from %s\n", target_pid_to_str (pid_to_ptid (child_pid))); hppa_require_detach (child_pid, 0); /* The parent and child of a vfork share the same address space. Also, on some targets the order in which vfork and exec events are received for parent in child requires some delicate handling of the events. For instance, on ptrace-based HPUX we receive the child's vfork event first, at which time the parent has been suspended by the OS and is essentially untouchable until the child's exit or second exec event arrives. At that time, the parent's vfork event is delivered to us, and that's when we see and decide how to follow the vfork. But to get to that point, we must continue the child until it execs or exits. To do that smoothly, all breakpoints must be removed from the child, in case there are any set between the vfork() and exec() calls. But removing them from the child also removes them from the parent, due to the shared-address-space nature of a vfork'd parent and child. On HPUX, therefore, we must take care to restore the bp's to the parent before we continue it. Else, it's likely that we may not stop in the expected place. (The worst scenario is when the user tries to step over a vfork() call; the step-resume bp must be restored for the step to properly stop in the parent after the call completes!) Sequence of events, as reported to gdb from HPUX: Parent Child Action for gdb to take ------------------------------------------------------- 1 VFORK Continue child 2 EXEC 3 EXEC or EXIT 4 VFORK Now that the child has safely exec'd or exited, we must restore the parent's breakpoints before we continue it. Else, we may cause it run past expected stopping points. */ if (has_vforked) reattach_breakpoints (parent_pid); } else { /* Needed to keep the breakpoint lists in sync. */ if (! has_vforked) detach_breakpoints (child_pid); /* Before detaching from the parent, remove all breakpoints from it. */ remove_breakpoints (); /* Also reset the solib inferior hook from the parent. */ #ifdef SOLIB_REMOVE_INFERIOR_HOOK SOLIB_REMOVE_INFERIOR_HOOK (PIDGET (inferior_ptid)); #endif /* Detach from the parent. */ target_detach (NULL, 1); /* Attach to the child. */ printf_unfiltered ("Attaching after fork to %s\n", target_pid_to_str (pid_to_ptid (child_pid))); hppa_require_attach (child_pid); inferior_ptid = pid_to_ptid (child_pid); /* If we vforked, then we've also execed by now. The exec will be reported momentarily. follow_exec () will handle breakpoints, so we don't have to.. */ if (!has_vforked) follow_inferior_reset_breakpoints (); } if (has_vforked) { /* If we followed the parent, don't try to follow the child's exec. */ if (saved_vfork_state != STATE_GOT_PARENT && saved_vfork_state != STATE_FAKE_EXEC) fprintf_unfiltered (gdb_stdout, "hppa: post follow vfork: confused state\n"); if (! follow_child || saved_vfork_state == STATE_GOT_PARENT) saved_vfork_state = STATE_NONE; else return 1; } return 0; }
ptid_t child_wait (ptid_t ptid, struct target_waitstatus *ourstatus) { int save_errno; int status; char *execd_pathname = NULL; int exit_status; int related_pid; int syscall_id; enum target_waitkind kind; int pid; if (saved_vfork_state == STATE_FAKE_EXEC) { saved_vfork_state = STATE_NONE; ourstatus->kind = TARGET_WAITKIND_EXECD; ourstatus->value.execd_pathname = saved_child_execd_pathname; return inferior_ptid; } do { set_sigint_trap (); /* Causes SIGINT to be passed on to the attached process. */ set_sigio_trap (); pid = ptrace_wait (inferior_ptid, &status); save_errno = errno; clear_sigio_trap (); clear_sigint_trap (); if (pid == -1) { if (save_errno == EINTR) continue; fprintf_unfiltered (gdb_stderr, "Child process unexpectedly missing: %s.\n", safe_strerror (save_errno)); /* Claim it exited with unknown signal. */ ourstatus->kind = TARGET_WAITKIND_SIGNALLED; ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN; return pid_to_ptid (-1); } /* Did it exit? */ if (target_has_exited (pid, status, &exit_status)) { /* ??rehrauer: For now, ignore this. */ continue; } if (!target_thread_alive (pid_to_ptid (pid))) { ourstatus->kind = TARGET_WAITKIND_SPURIOUS; return pid_to_ptid (pid); } if (hpux_has_forked (pid, &related_pid)) { /* Ignore the parent's fork event. */ if (pid == PIDGET (inferior_ptid)) { ourstatus->kind = TARGET_WAITKIND_IGNORE; return inferior_ptid; } /* If this is the child's fork event, report that the process has forked. */ if (related_pid == PIDGET (inferior_ptid)) { ourstatus->kind = TARGET_WAITKIND_FORKED; ourstatus->value.related_pid = pid; return inferior_ptid; } } if (hpux_has_vforked (pid, &related_pid)) { if (pid == PIDGET (inferior_ptid)) { if (saved_vfork_state == STATE_GOT_CHILD) saved_vfork_state = STATE_GOT_PARENT; else if (saved_vfork_state == STATE_GOT_EXEC) saved_vfork_state = STATE_FAKE_EXEC; else fprintf_unfiltered (gdb_stdout, "hppah: parent vfork: confused\n"); } else if (related_pid == PIDGET (inferior_ptid)) { if (saved_vfork_state == STATE_NONE) saved_vfork_state = STATE_GOT_CHILD; else fprintf_unfiltered (gdb_stdout, "hppah: child vfork: confused\n"); } else fprintf_unfiltered (gdb_stdout, "hppah: unknown vfork: confused\n"); if (saved_vfork_state == STATE_GOT_CHILD) { child_post_startup_inferior (pid_to_ptid (pid)); detach_breakpoints (pid); #ifdef SOLIB_REMOVE_INFERIOR_HOOK SOLIB_REMOVE_INFERIOR_HOOK (pid); #endif child_resume (pid_to_ptid (pid), 0, TARGET_SIGNAL_0); ourstatus->kind = TARGET_WAITKIND_IGNORE; return pid_to_ptid (related_pid); } else if (saved_vfork_state == STATE_FAKE_EXEC) { ourstatus->kind = TARGET_WAITKIND_VFORKED; ourstatus->value.related_pid = related_pid; return pid_to_ptid (pid); } else { /* We saw the parent's vfork, but we haven't seen the exec yet. Wait for it, for simplicity's sake. It should be pending. */ saved_vfork_pid = related_pid; ourstatus->kind = TARGET_WAITKIND_IGNORE; return pid_to_ptid (pid); } } if (hpux_has_execd (pid, &execd_pathname)) { /* On HP-UX, events associated with a vforking inferior come in threes: a vfork event for the child (always first), followed a vfork event for the parent and an exec event for the child. The latter two can come in either order. Make sure we get both. */ if (saved_vfork_state != STATE_NONE) { if (saved_vfork_state == STATE_GOT_CHILD) { saved_vfork_state = STATE_GOT_EXEC; /* On HP/UX with ptrace, the child must be resumed before the parent vfork event is delivered. A single-step suffices. */ if (RESUME_EXECD_VFORKING_CHILD_TO_GET_PARENT_VFORK ()) target_resume (pid_to_ptid (pid), 1, TARGET_SIGNAL_0); ourstatus->kind = TARGET_WAITKIND_IGNORE; } else if (saved_vfork_state == STATE_GOT_PARENT) { saved_vfork_state = STATE_FAKE_EXEC; ourstatus->kind = TARGET_WAITKIND_VFORKED; ourstatus->value.related_pid = saved_vfork_pid; } else fprintf_unfiltered (gdb_stdout, "hppa: exec: unexpected state\n"); saved_child_execd_pathname = execd_pathname; return inferior_ptid; } /* Are we ignoring initial exec events? (This is likely because we're in the process of starting up the inferior, and another (older) mechanism handles those.) If so, we'll report this as a regular stop, not an exec. */ if (inferior_ignoring_startup_exec_events) { inferior_ignoring_startup_exec_events--; } else { ourstatus->kind = TARGET_WAITKIND_EXECD; ourstatus->value.execd_pathname = execd_pathname; return pid_to_ptid (pid); } } /* All we must do with these is communicate their occurrence to wait_for_inferior... */ if (hpux_has_syscall_event (pid, &kind, &syscall_id)) { ourstatus->kind = kind; ourstatus->value.syscall_id = syscall_id; return pid_to_ptid (pid); } /*## } while (pid != PIDGET (inferior_ptid)); ## *//* Some other child died or stopped */ /* hack for thread testing */ } while ((pid != PIDGET (inferior_ptid)) && not_same_real_pid); /*## */ store_waitstatus (ourstatus, status); return pid_to_ptid (pid); }
/* An inferior Unix process CHILD_PID has been created by a call to fork() (or variants like vfork). It is presently stopped, and waiting to be resumed. clone_and_follow_inferior will fork the debugger, and that clone will "follow" (attach to) CHILD_PID. The original copy of the debugger will not touch CHILD_PID again. Also, the original debugger will set FOLLOWED_CHILD FALSE, while the clone will set it TRUE. */ void clone_and_follow_inferior (int child_pid, int *followed_child) { int debugger_pid; int status; char pid_spelling[100]; /* Arbitrary but sufficient length. */ /* This semaphore is used to coordinate the two debuggers' handoff of CHILD_PID. The original debugger will detach from CHILD_PID, and then the clone debugger will attach to it. (It must be done this way because on some targets, only one process at a time can trace another. Thus, the original debugger must relinquish its tracing rights before the clone can pick them up.) */ #define SEM_TALK (1) #define SEM_LISTEN (0) int handoff_semaphore[2]; /* Original "talks" to [1], clone "listens" to [0] */ int talk_value = 99; int listen_value; /* Set debug_fork then attach to the child while it sleeps, to debug. */ static int debug_fork = 0; /* It is generally good practice to flush any possible pending stdio output prior to doing a fork, to avoid the possibility of both the parent and child flushing the same data after the fork. */ gdb_flush (gdb_stdout); gdb_flush (gdb_stderr); /* Open the semaphore pipes. */ status = pipe (handoff_semaphore); if (status < 0) error ("error getting pipe for handoff semaphore"); /* Clone the debugger. Note that the apparent call to vfork() below *might* actually be a call to fork() due to the fact that autoconf will ``#define vfork fork'' on certain platforms. */ if (debug_fork) debugger_pid = fork (); else debugger_pid = vfork (); if (debugger_pid < 0) perror_with_name ("fork"); /* Are we the original debugger? If so, we must relinquish all claims to CHILD_PID. */ if (debugger_pid != 0) { char signal_spelling[100]; /* Arbitrary but sufficient length */ /* Detach from CHILD_PID. Deliver a "stop" signal when we do, though, so that it remains stopped until the clone debugger can attach to it. */ detach_breakpoints (child_pid); sprintf (signal_spelling, "%d", target_signal_to_host (TARGET_SIGNAL_STOP)); target_require_detach (child_pid, signal_spelling, 1); /* Notify the clone debugger that it should attach to CHILD_PID. */ write (handoff_semaphore[SEM_TALK], &talk_value, sizeof (talk_value)); *followed_child = 0; } /* We're the child. */ else { if (debug_fork) sleep (debug_fork); /* The child (i.e., the cloned debugger) must now attach to CHILD_PID. inferior_ptid is presently set to the parent process of the fork, while CHILD_PID should be the child process of the fork. Wait until the original debugger relinquishes control of CHILD_PID, though. */ read (handoff_semaphore[SEM_LISTEN], &listen_value, sizeof (listen_value)); /* Note that we DON'T want to actually detach from inferior_ptid, because that would allow it to run free. The original debugger wants to retain control of the process. So, we just reset inferior_ptid to CHILD_PID, and then ensure that all breakpoints are really set in CHILD_PID. */ target_mourn_inferior (); /* Ask the tty subsystem to switch to the one we specified earlier (or to share the current terminal, if none was specified). */ new_tty (); dont_repeat (); sprintf (pid_spelling, "%d", child_pid); target_require_attach (pid_spelling, 1); /* Perform any necessary cleanup, after attachment. (This form of attaching can behave differently on some targets than the standard method, where a process formerly not under debugger control was suddenly attached to..) */ target_post_follow_inferior_by_clone (); *followed_child = 1; } /* Discard the handoff sempahore. */ (void) close (handoff_semaphore[SEM_LISTEN]); (void) close (handoff_semaphore[SEM_TALK]); }