aboutsummaryrefslogtreecommitdiff
path: root/gdb/nat/linux-procfs.c
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2014-12-16 16:12:24 +0000
committerPedro Alves <palves@redhat.com>2015-01-09 11:39:49 +0000
commit8784d56326e72e2e6863e8443b1f97e45a46ba36 (patch)
tree624a552b91801b257a556eb16f92228600cb3075 /gdb/nat/linux-procfs.c
parent883ed13e4af121e28de1c0df70a8d66d94a8bc7b (diff)
downloadgdb-8784d56326e72e2e6863e8443b1f97e45a46ba36.zip
gdb-8784d56326e72e2e6863e8443b1f97e45a46ba36.tar.gz
gdb-8784d56326e72e2e6863e8443b1f97e45a46ba36.tar.bz2
Linux: on attach, attach to lwps listed under /proc/$pid/task/
... instead of relying on libthread_db. I wrote a test that attaches to a program that constantly spawns short-lived threads, which exposed several issues. This is one of them. On Linux, we need to attach to all threads of a process (thread group) individually. We currently rely on libthread_db to list the threads, but that is problematic, because libthread_db relies on reading data structures out of the inferior (which may well be corrupted). If threads are being created or exiting just while we try to attach, we may trip on inconsistencies in the inferior's thread list. To work around that, when we see a seemingly corrupt list, we currently retry a few times: static void thread_db_find_new_threads_2 (ptid_t ptid, int until_no_new) { ... if (until_no_new) { /* Require 4 successive iterations which do not find any new threads. The 4 is a heuristic: there is an inherent race here, and I have seen that 2 iterations in a row are not always sufficient to "capture" all threads. */ ... That heuristic may well fail, and when it does, we end up with threads in the program that aren't under GDB's control. That's obviously bad and results in quite mistifying failures, like e.g., the process dying for seeminly no reason when a thread that wasn't attached trips on a breakpoint. There's really no reason to rely on libthread_db for this nowadays when we have /proc mounted. In that case, which is the usual case, we can list the LWPs from /proc/PID/task/. In fact, GDBserver is already doing this. The patch factors out that code that knows to walk the task/ directory out of GDBserver, and makes GDB use it too. Like GDBserver, the patch makes GDB attach to LWPs and _not_ wait for them to stop immediately. Instead, we just tag the LWP as having an expected stop. Because we can only set the ptrace options when the thread stops, we need a new flag in the lwp structure to keep track of whether we've already set the ptrace options, just like in GDBserver. Note that nothing issues any ptrace command to the threads between the PTRACE_ATTACH and the stop, so this is safe (unlike one scenario described in gdbserver's linux-low.c). When we attach to a program that has threads exiting while we attach, it's easy to race with a thread just exiting as we try to attach to it, like: #1 - get current list of threads #2 - attach to each listed thread #3 - ooops, attach failed, thread is already gone As this is pretty normal, we shouldn't be issuing a scary warning in step #3. When #3 happens, PTRACE_ATTACH usually fails with ESRCH, but sometimes we'll see EPERM as well. That happens when the kernel still has the thread in its task list, but the thread is marked as dead. Unfortunately, EPERM is ambiguous and we'll get it also on other scenarios where the thread isn't dead, and in those cases, it's useful to get a warning. To distiguish the cases, when we get an EPERM failure, we open /proc/PID/status, and check the thread's state -- if the /proc file no longer exists, or the state is "Z (Zombie)" or "X (Dead)", we ignore the EPERM error silently; otherwise, we'll warn. Unfortunately, there seems to be a kernel race here. Sometimes I get EPERM, and then the /proc state still indicates "R (Running)"... If we wait a bit and retry, we do end up seeing X or Z state, or get an ESRCH. I thought of making GDB retry the attach a few times, but even with a 500ms wait and 4 retries, I still see the warning sometimes. I haven't been able to identify the kernel path that causes this yet, but in any case, it looks like a kernel bug to me. As this just results failure to suppress a warning that we've been printing since about forever anyway, I'm just making the test cope with it, and issue an XFAIL. gdb/gdbserver/ 2015-01-09 Pedro Alves <palves@redhat.com> * linux-low.c (linux_attach_fail_reason_string): Move to nat/linux-ptrace.c, and rename. (linux_attach_lwp): Update comment. (attach_proc_task_lwp_callback): New function. (linux_attach): Adjust to rename and use linux_proc_attach_tgid_threads. (linux_attach_fail_reason_string): Delete declaration. gdb/ 2015-01-09 Pedro Alves <palves@redhat.com> * linux-nat.c (attach_proc_task_lwp_callback): New function. (linux_nat_attach): Use linux_proc_attach_tgid_threads. (wait_lwp, linux_nat_filter_event): If not set yet, set the lwp's ptrace option flags. * linux-nat.h (struct lwp_info) <must_set_ptrace_flags>: New field. * nat/linux-procfs.c: Include <dirent.h>. (linux_proc_get_int): New parameter "warn". Handle it. (linux_proc_get_tgid): Adjust. (linux_proc_get_tracerpid): Rename to ... (linux_proc_get_tracerpid_nowarn): ... this. (linux_proc_pid_get_state): New function, factored out from (linux_proc_pid_has_state): ... this. Add new parameter "warn" and handle it. (linux_proc_pid_is_gone): New function. (linux_proc_pid_is_stopped): Adjust. (linux_proc_pid_is_zombie_maybe_warn) (linux_proc_pid_is_zombie_nowarn): New functions. (linux_proc_pid_is_zombie): Use linux_proc_pid_is_zombie_maybe_warn. (linux_proc_attach_tgid_threads): New function. * nat/linux-procfs.h (linux_proc_get_tgid): Update comment. (linux_proc_get_tracerpid): Rename to ... (linux_proc_get_tracerpid_nowarn): ... this, and update comment. (linux_proc_pid_is_gone): New declaration. (linux_proc_pid_is_zombie): Update comment. (linux_proc_pid_is_zombie_nowarn): New declaration. (linux_proc_attach_lwp_func): New typedef. (linux_proc_attach_tgid_threads): New declaration. * nat/linux-ptrace.c (linux_ptrace_attach_fail_reason): Adjust to use nowarn functions. (linux_ptrace_attach_fail_reason_string): Move here from gdbserver/linux-low.c and rename. (ptrace_supports_feature): If the current ptrace options are not known yet, check them now, instead of asserting. * nat/linux-ptrace.h (linux_ptrace_attach_fail_reason_string): Declare.
Diffstat (limited to 'gdb/nat/linux-procfs.c')
-rw-r--r--gdb/nat/linux-procfs.c153
1 files changed, 136 insertions, 17 deletions
diff --git a/gdb/nat/linux-procfs.c b/gdb/nat/linux-procfs.c
index f020d27..00b6a70 100644
--- a/gdb/nat/linux-procfs.c
+++ b/gdb/nat/linux-procfs.c
@@ -19,12 +19,13 @@
#include "common-defs.h"
#include "linux-procfs.h"
#include "filestuff.h"
+#include <dirent.h>
/* Return the TGID of LWPID from /proc/pid/status. Returns -1 if not
found. */
static int
-linux_proc_get_int (pid_t lwpid, const char *field)
+linux_proc_get_int (pid_t lwpid, const char *field, int warn)
{
size_t field_len = strlen (field);
FILE *status_file;
@@ -35,7 +36,8 @@ linux_proc_get_int (pid_t lwpid, const char *field)
status_file = gdb_fopen_cloexec (buf, "r");
if (status_file == NULL)
{
- warning (_("unable to open /proc file '%s'"), buf);
+ if (warn)
+ warning (_("unable to open /proc file '%s'"), buf);
return -1;
}
@@ -56,45 +58,87 @@ linux_proc_get_int (pid_t lwpid, const char *field)
int
linux_proc_get_tgid (pid_t lwpid)
{
- return linux_proc_get_int (lwpid, "Tgid");
+ return linux_proc_get_int (lwpid, "Tgid", 1);
}
/* See linux-procfs.h. */
pid_t
-linux_proc_get_tracerpid (pid_t lwpid)
+linux_proc_get_tracerpid_nowarn (pid_t lwpid)
{
- return linux_proc_get_int (lwpid, "TracerPid");
+ return linux_proc_get_int (lwpid, "TracerPid", 0);
}
-/* Return non-zero if 'State' of /proc/PID/status contains STATE. */
+/* Fill in BUFFER, a buffer with BUFFER_SIZE bytes with the 'State'
+ line of /proc/PID/status. Returns -1 on failure to open the /proc
+ file, 1 if the line is found, and 0 if not found. If WARN, warn on
+ failure to open the /proc file. */
static int
-linux_proc_pid_has_state (pid_t pid, const char *state)
+linux_proc_pid_get_state (pid_t pid, char *buffer, size_t buffer_size,
+ int warn)
{
- char buffer[100];
FILE *procfile;
- int retval;
int have_state;
- xsnprintf (buffer, sizeof (buffer), "/proc/%d/status", (int) pid);
+ xsnprintf (buffer, buffer_size, "/proc/%d/status", (int) pid);
procfile = gdb_fopen_cloexec (buffer, "r");
if (procfile == NULL)
{
- warning (_("unable to open /proc file '%s'"), buffer);
- return 0;
+ if (warn)
+ warning (_("unable to open /proc file '%s'"), buffer);
+ return -1;
}
have_state = 0;
- while (fgets (buffer, sizeof (buffer), procfile) != NULL)
+ while (fgets (buffer, buffer_size, procfile) != NULL)
if (strncmp (buffer, "State:", 6) == 0)
{
have_state = 1;
break;
}
- retval = (have_state && strstr (buffer, state) != NULL);
fclose (procfile);
- return retval;
+ return have_state;
+}
+
+/* See linux-procfs.h declaration. */
+
+int
+linux_proc_pid_is_gone (pid_t pid)
+{
+ char buffer[100];
+ int have_state;
+
+ have_state = linux_proc_pid_get_state (pid, buffer, sizeof buffer, 0);
+ if (have_state < 0)
+ {
+ /* If we can't open the status file, assume the thread has
+ disappeared. */
+ return 1;
+ }
+ else if (have_state == 0)
+ {
+ /* No "State:" line, assume thread is alive. */
+ return 0;
+ }
+ else
+ {
+ return (strstr (buffer, "Z (") != NULL
+ || strstr (buffer, "X (") != NULL);
+ }
+}
+
+/* Return non-zero if 'State' of /proc/PID/status contains STATE. If
+ WARN, warn on failure to open the /proc file. */
+
+static int
+linux_proc_pid_has_state (pid_t pid, const char *state, int warn)
+{
+ char buffer[100];
+ int have_state;
+
+ have_state = linux_proc_pid_get_state (pid, buffer, sizeof buffer, warn);
+ return (have_state > 0 && strstr (buffer, state) != NULL);
}
/* Detect `T (stopped)' in `/proc/PID/status'.
@@ -103,7 +147,24 @@ linux_proc_pid_has_state (pid_t pid, const char *state)
int
linux_proc_pid_is_stopped (pid_t pid)
{
- return linux_proc_pid_has_state (pid, "T (stopped)");
+ return linux_proc_pid_has_state (pid, "T (stopped)", 1);
+}
+
+/* Return non-zero if PID is a zombie. If WARN, warn on failure to
+ open the /proc file. */
+
+static int
+linux_proc_pid_is_zombie_maybe_warn (pid_t pid, int warn)
+{
+ return linux_proc_pid_has_state (pid, "Z (zombie)", warn);
+}
+
+/* See linux-procfs.h declaration. */
+
+int
+linux_proc_pid_is_zombie_nowarn (pid_t pid)
+{
+ return linux_proc_pid_is_zombie_maybe_warn (pid, 0);
}
/* See linux-procfs.h declaration. */
@@ -111,7 +172,7 @@ linux_proc_pid_is_stopped (pid_t pid)
int
linux_proc_pid_is_zombie (pid_t pid)
{
- return linux_proc_pid_has_state (pid, "Z (zombie)");
+ return linux_proc_pid_is_zombie_maybe_warn (pid, 1);
}
/* See linux-procfs.h declaration. */
@@ -132,3 +193,61 @@ linux_proc_pid_get_ns (pid_t pid, const char *ns)
return NULL;
}
+
+/* See linux-procfs.h. */
+
+void
+linux_proc_attach_tgid_threads (pid_t pid,
+ linux_proc_attach_lwp_func attach_lwp)
+{
+ DIR *dir;
+ char pathname[128];
+ int new_threads_found;
+ int iterations;
+
+ if (linux_proc_get_tgid (pid) != pid)
+ return;
+
+ xsnprintf (pathname, sizeof (pathname), "/proc/%ld/task", (long) pid);
+ dir = opendir (pathname);
+ if (dir == NULL)
+ {
+ warning (_("Could not open /proc/%ld/task.\n"), (long) pid);
+ return;
+ }
+
+ /* Scan the task list for existing threads. While we go through the
+ threads, new threads may be spawned. Cycle through the list of
+ threads until we have done two iterations without finding new
+ threads. */
+ for (iterations = 0; iterations < 2; iterations++)
+ {
+ struct dirent *dp;
+
+ new_threads_found = 0;
+ while ((dp = readdir (dir)) != NULL)
+ {
+ unsigned long lwp;
+
+ /* Fetch one lwp. */
+ lwp = strtoul (dp->d_name, NULL, 10);
+ if (lwp != 0)
+ {
+ ptid_t ptid = ptid_build (pid, lwp, 0);
+
+ if (attach_lwp (ptid))
+ new_threads_found = 1;
+ }
+ }
+
+ if (new_threads_found)
+ {
+ /* Start over. */
+ iterations = -1;
+ }
+
+ rewinddir (dir);
+ }
+
+ closedir (dir);
+}