diff options
author | Daniel Jacobowitz <drow@false.org> | 2008-05-01 18:50:14 +0000 |
---|---|---|
committer | Daniel Jacobowitz <drow@false.org> | 2008-05-01 18:50:14 +0000 |
commit | a0ef42744d050b642351a3974faca030269f5504 (patch) | |
tree | c0e178b652d3d2b7d98a598b7e2bb4c35dcf8161 /gdb/linux-nat.c | |
parent | ca38c58efa3ca0ac1f632640c131db93164ac5f2 (diff) | |
download | gdb-a0ef42744d050b642351a3974faca030269f5504.zip gdb-a0ef42744d050b642351a3974faca030269f5504.tar.gz gdb-a0ef42744d050b642351a3974faca030269f5504.tar.bz2 |
2008-05-01 Daniel Jacobowitz <dan@codesourcery.com>
Pedro Alves <pedro@codesourcery.com>
Based on work by Jan Kratochvil <jan.kratochvil@redhat.com> and Jeff
Johnston <jjohnstn@redhat.com>.
* NEWS: Mention attach to stopped process fix.
* infcmd.c (detach_command, disconnect_command): Discard the thread
list.
* infrun.c (handle_inferior_event): Do not ignore non-SIGSTOP while
attaching. Use signal_stop_state.
(signal_stop_state): Check stop_soon.
* linux-nat.c (kill_lwp): Declare earlier.
(pid_is_stopped, linux_nat_post_attach_wait): New.
(lin_lwp_attach_lwp): Use linux_nat_post_attach_wait. Update
comments.
(linux_nat_attach): Use linux_nat_post_attach_wait.
(detach_callback, linux_nat_detach): Improve handling for signalled
processes.
(linux_nat_pid_to_str): Always print out the LWP ID if it differs
from the process ID.
* Makefile.in (infcmd.o): Update.
2008-05-01 Jan Kratochvil <jan.kratochvil@redhat.com>
Daniel Jacobowitz <dan@codesourcery.com>
* gdb.threads/attach-into-signal.c, gdb.threads/attach-into-signal.exp,
gdb.threads/attach-stopped.c, gdb.threads/attach-stopped.exp,
gdb.threads/attachstop-mt.c, gdb.threads/attachstop-mt.exp: New.
Diffstat (limited to 'gdb/linux-nat.c')
-rw-r--r-- | gdb/linux-nat.c | 282 |
1 files changed, 206 insertions, 76 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 9511815..b09cd49 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -50,6 +50,30 @@ #include "event-loop.h" #include "event-top.h" +/* Note on this file's use of signals: + + We stop threads by sending a SIGSTOP. The use of SIGSTOP instead + of another signal is not entirely significant; we just need for a + signal to be delivered, so that we can intercept it. SIGSTOP's + advantage is that it can not be blocked. A disadvantage is that it + is not a real-time signal, so it can only be queued once; we do not + keep track of other sources of SIGSTOP. + + Two other signals that can't be blocked are SIGCONT and SIGKILL. + But we can't use them, because they have special behavior when the + signal is generated - not when it is delivered. SIGCONT resumes + the entire thread group and SIGKILL kills the entire thread group. + + A delivered SIGSTOP would stop the entire thread group, not just the + thread we tkill'd. But we never let the SIGSTOP deliver; we always + intercept and cancel it (by PTRACE_CONT without passing SIGSTOP). + + We could use a real-time signal instead. This would solve those + problems; we could use PTRACE_GETSIGINFO to locate the specific + stop signals sent by GDB. But we would still have to have some + support for SIGSTOP, since PTRACE_ATTACH generates it, and there + are races with trying to find a signal that is not blocked. */ + #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif @@ -186,6 +210,7 @@ static void linux_nat_async (void (*callback) (enum inferior_event_type event_type, void *context), void *context); static int linux_nat_async_mask (int mask); +static int kill_lwp (int lwpid, int signo); /* Captures the result of a successful waitpid call, along with the options used in that call. */ @@ -1010,10 +1035,103 @@ exit_lwp (struct lwp_info *lp) delete_lwp (lp->ptid); } -/* Attach to the LWP specified by PID. If VERBOSE is non-zero, print - a message telling the user that a new LWP has been added to the - process. Return 0 if successful or -1 if the new LWP could not - be attached. */ +/* Detect `T (stopped)' in `/proc/PID/status'. + Other states including `T (tracing stop)' are reported as false. */ + +static int +pid_is_stopped (pid_t pid) +{ + FILE *status_file; + char buf[100]; + int retval = 0; + + snprintf (buf, sizeof (buf), "/proc/%d/status", (int) pid); + status_file = fopen (buf, "r"); + if (status_file != NULL) + { + int have_state = 0; + + while (fgets (buf, sizeof (buf), status_file)) + { + if (strncmp (buf, "State:", 6) == 0) + { + have_state = 1; + break; + } + } + if (have_state && strstr (buf, "T (stopped)") != NULL) + retval = 1; + fclose (status_file); + } + return retval; +} + +/* Wait for the LWP specified by LP, which we have just attached to. + Returns a wait status for that LWP, to cache. */ + +static int +linux_nat_post_attach_wait (ptid_t ptid, int first, int *cloned, + int *signalled) +{ + pid_t new_pid, pid = GET_LWP (ptid); + int status; + + if (pid_is_stopped (pid)) + { + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LNPAW: Attaching to a stopped process\n"); + + /* The process is definitely stopped. It is in a job control + stop, unless the kernel predates the TASK_STOPPED / + TASK_TRACED distinction, in which case it might be in a + ptrace stop. Make sure it is in a ptrace stop; from there we + can kill it, signal it, et cetera. + + First make sure there is a pending SIGSTOP. Since we are + already attached, the process can not transition from stopped + to running without a PTRACE_CONT; so we know this signal will + go into the queue. The SIGSTOP generated by PTRACE_ATTACH is + probably already in the queue (unless this kernel is old + enough to use TASK_STOPPED for ptrace stops); but since SIGSTOP + is not an RT signal, it can only be queued once. */ + kill_lwp (pid, SIGSTOP); + + /* Finally, resume the stopped process. This will deliver the SIGSTOP + (or a higher priority signal, just like normal PTRACE_ATTACH). */ + ptrace (PTRACE_CONT, pid, 0, 0); + } + + /* Make sure the initial process is stopped. The user-level threads + layer might want to poke around in the inferior, and that won't + work if things haven't stabilized yet. */ + new_pid = my_waitpid (pid, &status, 0); + if (new_pid == -1 && errno == ECHILD) + { + if (first) + warning (_("%s is a cloned process"), target_pid_to_str (ptid)); + + /* Try again with __WCLONE to check cloned processes. */ + new_pid = my_waitpid (pid, &status, __WCLONE); + *cloned = 1; + } + + gdb_assert (pid == new_pid && WIFSTOPPED (status)); + + if (WSTOPSIG (status) != SIGSTOP) + { + *signalled = 1; + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LNPAW: Received %s after attaching\n", + status_to_str (status)); + } + + return status; +} + +/* Attach to the LWP specified by PID. Return 0 if successful or -1 + if the new LWP could not be attached. */ int lin_lwp_attach_lwp (ptid_t ptid) @@ -1036,9 +1154,7 @@ lin_lwp_attach_lwp (ptid_t ptid) to happen. */ if (GET_LWP (ptid) != GET_PID (ptid) && lp == NULL) { - pid_t pid; - int status; - int cloned = 0; + int status, cloned = 0, signalled = 0; if (ptrace (PTRACE_ATTACH, GET_LWP (ptid), 0, 0) < 0) { @@ -1057,24 +1173,18 @@ lin_lwp_attach_lwp (ptid_t ptid) "LLAL: PTRACE_ATTACH %s, 0, 0 (OK)\n", target_pid_to_str (ptid)); - pid = my_waitpid (GET_LWP (ptid), &status, 0); - if (pid == -1 && errno == ECHILD) + status = linux_nat_post_attach_wait (ptid, 0, &cloned, &signalled); + lp = add_lwp (ptid); + lp->stopped = 1; + lp->cloned = cloned; + lp->signalled = signalled; + if (WSTOPSIG (status) != SIGSTOP) { - /* Try again with __WCLONE to check cloned processes. */ - pid = my_waitpid (GET_LWP (ptid), &status, __WCLONE); - cloned = 1; + lp->resumed = 1; + lp->status = status; } - gdb_assert (pid == GET_LWP (ptid) - && WIFSTOPPED (status) && WSTOPSIG (status)); - - if (lp == NULL) - lp = add_lwp (ptid); - lp->cloned = cloned; - - target_post_attach (pid); - - lp->stopped = 1; + target_post_attach (GET_LWP (lp->ptid)); if (debug_linux_nat) { @@ -1133,10 +1243,7 @@ static void linux_nat_attach (char *args, int from_tty) { struct lwp_info *lp; - pid_t pid; int status; - int cloned = 0; - int options = 0; /* FIXME: We should probably accept a list of process id's, and attach all of them. */ @@ -1151,54 +1258,69 @@ linux_nat_attach (char *args, int from_tty) sigdelset (&suspend_mask, SIGCHLD); } - /* Make sure the initial process is stopped. The user-level threads - layer might want to poke around in the inferior, and that won't - work if things haven't stabilized yet. */ - pid = my_waitpid (GET_PID (inferior_ptid), &status, options); - if (pid == -1 && errno == ECHILD) - { - warning (_("%s is a cloned process"), target_pid_to_str (inferior_ptid)); - - /* Try again with __WCLONE to check cloned processes. */ - options = __WCLONE; - pid = my_waitpid (GET_PID (inferior_ptid), &status, options); - cloned = 1; - } - - gdb_assert (pid == GET_PID (inferior_ptid) - && WIFSTOPPED (status) && WSTOPSIG (status) == SIGSTOP); - /* Add the initial process as the first LWP to the list. */ inferior_ptid = BUILD_LWP (GET_PID (inferior_ptid), GET_PID (inferior_ptid)); lp = add_lwp (inferior_ptid); - lp->cloned = cloned; + + status = linux_nat_post_attach_wait (lp->ptid, 1, &lp->cloned, + &lp->signalled); + lp->stopped = 1; /* If this process is not using thread_db, then we still don't detect any other threads, but add at least this one. */ add_thread_silent (lp->ptid); - lp->stopped = 1; + /* Save the wait status to report later. */ lp->resumed = 1; + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LNA: waitpid %ld, saving status %s\n", + (long) GET_PID (lp->ptid), status_to_str (status)); if (!target_can_async_p ()) - { - /* Fake the SIGSTOP that core GDB expects. */ - lp->status = W_STOPCODE (SIGSTOP); - if (debug_linux_nat) - fprintf_unfiltered (gdb_stdlog, - "LNA: waitpid %ld, faking SIGSTOP\n", (long) pid); - } + lp->status = status; else { /* We already waited for this LWP, so put the wait result on the pipe. The event loop will wake up and gets us to handling this event. */ - linux_nat_event_pipe_push (pid, status, options); + linux_nat_event_pipe_push (GET_PID (lp->ptid), status, + lp->cloned ? __WCLONE : 0); /* Register in the event loop. */ target_async (inferior_event_handler, 0); } } +/* Get pending status of LP. */ +static int +get_pending_status (struct lwp_info *lp, int *status) +{ + struct target_waitstatus last; + ptid_t last_ptid; + + get_last_target_status (&last_ptid, &last); + + /* If this lwp is the ptid that GDB is processing an event from, the + signal will be in stop_signal. Otherwise, in all-stop + sync + mode, we may cache pending events in lp->status while trying to + stop all threads (see stop_wait_callback). In async mode, the + events are always cached in waitpid_queue. */ + + *status = 0; + if (GET_LWP (lp->ptid) == GET_LWP (last_ptid)) + { + if (stop_signal != TARGET_SIGNAL_0 + && signal_pass_state (stop_signal)) + *status = W_STOPCODE (target_signal_to_host (stop_signal)); + } + else if (target_can_async_p ()) + queued_waitpid (GET_LWP (lp->ptid), status, __WALL); + else + *status = lp->status; + + return 0; +} + static int detach_callback (struct lwp_info *lp, void *data) { @@ -1209,40 +1331,30 @@ detach_callback (struct lwp_info *lp, void *data) strsignal (WSTOPSIG (lp->status)), target_pid_to_str (lp->ptid)); - while (lp->signalled && lp->stopped) + /* If there is a pending SIGSTOP, get rid of it. */ + if (lp->signalled) { - errno = 0; - if (ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, - WSTOPSIG (lp->status)) < 0) - error (_("Can't continue %s: %s"), target_pid_to_str (lp->ptid), - safe_strerror (errno)); - if (debug_linux_nat) fprintf_unfiltered (gdb_stdlog, - "DC: PTRACE_CONTINUE (%s, 0, %s) (OK)\n", - target_pid_to_str (lp->ptid), - status_to_str (lp->status)); + "DC: Sending SIGCONT to %s\n", + target_pid_to_str (lp->ptid)); - lp->stopped = 0; + kill_lwp (GET_LWP (lp->ptid), SIGCONT); lp->signalled = 0; - lp->status = 0; - /* FIXME drow/2003-08-26: There was a call to stop_wait_callback - here. But since lp->signalled was cleared above, - stop_wait_callback didn't do anything; the process was left - running. Shouldn't we be waiting for it to stop? - I've removed the call, since stop_wait_callback now does do - something when called with lp->signalled == 0. */ - - gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status)); } /* We don't actually detach from the LWP that has an id equal to the overall process id just yet. */ if (GET_LWP (lp->ptid) != GET_PID (lp->ptid)) { + int status = 0; + + /* Pass on any pending signal for this LWP. */ + get_pending_status (lp, &status); + errno = 0; if (ptrace (PTRACE_DETACH, GET_LWP (lp->ptid), 0, - WSTOPSIG (lp->status)) < 0) + WSTOPSIG (status)) < 0) error (_("Can't detach %s: %s"), target_pid_to_str (lp->ptid), safe_strerror (errno)); @@ -1252,7 +1364,6 @@ detach_callback (struct lwp_info *lp, void *data) target_pid_to_str (lp->ptid), strsignal (WSTOPSIG (lp->status))); - drain_queued_events (GET_LWP (lp->ptid)); delete_lwp (lp->ptid); } @@ -1263,6 +1374,9 @@ static void linux_nat_detach (char *args, int from_tty) { int pid; + int status; + enum target_signal sig; + if (target_can_async_p ()) linux_nat_async (NULL, 0); @@ -1271,6 +1385,21 @@ linux_nat_detach (char *args, int from_tty) /* Only the initial process should be left right now. */ gdb_assert (num_lwps == 1); + /* Pass on any pending signal for the last LWP. */ + if ((args == NULL || *args == '\0') + && get_pending_status (lwp_list, &status) != -1 + && WIFSTOPPED (status)) + { + /* Put the signal number in ARGS so that inf_ptrace_detach will + pass it along with PTRACE_DETACH. */ + args = alloca (8); + sprintf (args, "%d", (int) WSTOPSIG (status)); + fprintf_unfiltered (gdb_stdlog, + "LND: Sending signal %s to %s\n", + args, + target_pid_to_str (lwp_list->ptid)); + } + trap_ptid = null_ptid; /* Destroy LWP info; it's no longer valid. */ @@ -2848,7 +2977,9 @@ linux_nat_pid_to_str (ptid_t ptid) { static char buf[64]; - if (lwp_list && lwp_list->next && is_lwp (ptid)) + if (is_lwp (ptid) + && ((lwp_list && lwp_list->next) + || GET_PID (ptid) != GET_LWP (ptid))) { snprintf (buf, sizeof (buf), "LWP %ld", GET_LWP (ptid)); return buf; @@ -4205,4 +4336,3 @@ lin_thread_get_thread_signals (sigset_t *set) /* ... except during a sigsuspend. */ sigdelset (&suspend_mask, cancel); } - |