aboutsummaryrefslogtreecommitdiff
path: root/gdb/linux-nat.c
diff options
context:
space:
mode:
authorDaniel Jacobowitz <drow@false.org>2008-05-01 18:50:14 +0000
committerDaniel Jacobowitz <drow@false.org>2008-05-01 18:50:14 +0000
commita0ef42744d050b642351a3974faca030269f5504 (patch)
treec0e178b652d3d2b7d98a598b7e2bb4c35dcf8161 /gdb/linux-nat.c
parentca38c58efa3ca0ac1f632640c131db93164ac5f2 (diff)
downloadgdb-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.c282
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);
}
-