[3/5] gdbserver/linux: Read auxv from any thread of the process

Message ID 20230331034432.3037148-4-thiago.bauermann@linaro.org
State New
Headers
Series gdbserver: Follow-up on linux_get_auxv using PID parameter |

Commit Message

Thiago Jung Bauermann March 31, 2023, 3:44 a.m. UTC
  If the initial thread of the process exits, reading the process' auxiliary
vector via /proc/PID/auxv fails in one of two ways:

1. If gdbserver is root, then opening the file succeeds but reading from it
   returns 0 bytes.

2. If gdbserver isn't root, then opening the file fails with EACCES.

This race isn't easy to run into because one of the first things that GDB
does when connecting to gdbserver is to read the inferior's auxiliary
vector and store it in the auxv cache.  All further queries of the auxiliary
vector will be served from there, unless one of the cache-clearing
events ("inferior_exit", "inferior_appeared", "executable_changed") occurs.

To fix the race condition iterate through tasks in /proc/PID/task/ and
try to read their auxv file, returning the first one that succeeds.

Suggested-by: Pedro Alves <pedro@palves.net>
---
 gdb/nat/linux-procfs.c | 67 ++++++++++++++++++++++++++++++++++++++++++
 gdb/nat/linux-procfs.h |  7 +++++
 gdbserver/linux-low.cc | 21 +++----------
 3 files changed, 78 insertions(+), 17 deletions(-)
  

Patch

diff --git a/gdb/nat/linux-procfs.c b/gdb/nat/linux-procfs.c
index 9c2d1beb91fa..d53d3ea98bed 100644
--- a/gdb/nat/linux-procfs.c
+++ b/gdb/nat/linux-procfs.c
@@ -329,6 +329,73 @@  linux_proc_attach_tgid_threads (pid_t pid,
 
 /* See linux-procfs.h.  */
 
+bool
+linux_proc_read_auxv (pid_t pid, gdb_byte *readbuf, off_t offset, size_t len,
+		      ssize_t &xfered_len)
+{
+  if (linux_proc_get_tgid (pid) != pid)
+    return false;
+
+  std::string path_tasks = string_printf ("/proc/%ld/task", (long) pid);
+  gdb_dir_up dir (opendir (path_tasks.c_str ()));
+  if (dir == nullptr)
+    {
+      warning (_("Could not open /proc/%ld/task."), (long) pid);
+      return false;
+    }
+
+  /* In a multi-threaded process, any given thread (including the initial one)
+     can exit at any time.  Because of this, iterate through the threads trying
+     to read their auxv file and return the first one that succeeds.  */
+  struct dirent *dp;
+  while ((dp = readdir (dir.get ())) != nullptr)
+    {
+      /* Fetch one lwp.  */
+      unsigned long lwp = strtoul (dp->d_name, nullptr, 10);
+      if (lwp == 0)
+	continue;
+
+      std::string path_auxv = string_printf ("/proc/%lu/auxv", lwp);
+      scoped_fd fd = gdb_open_cloexec (path_auxv, O_RDONLY, 0);
+      if (fd.get () < 0)
+	{
+	  /* If the leader thread of the process exited and we aren't root, then
+	     trying to read its auxv file results in EACCES error.  */
+	  if (errno == EACCES)
+	    continue;
+
+	  /* Unexpected failure.  Give up.  */
+	  return false;
+	}
+
+      ssize_t l;
+      if (offset != 0 && lseek (fd.get (), offset, SEEK_SET) != offset)
+	l = -1;
+      else
+	l = read (fd.get (), readbuf, len);
+
+      /* Unexpected failure.  Give up.  */
+      if (l < 0)
+	return false;
+      /* The read call returns 0 if the leader thread of the process exited and
+	 we are root, or if any thread exited after we opened its auxv file but
+	 before we had a chance to read it.  This only applies during the first
+	 read into the file though (i.e., offset == 0).  */
+      else if (l != 0 || offset != 0)
+	{
+	  xfered_len = l;
+	  return true;
+	}
+    }
+
+  /* If we get here, either all open call failed with EACCES (meaning the
+     threads are gone), or all read calls returned 0 (meaning either EOF or that
+     the threads are gone).  */
+  return true;
+}
+
+/* See linux-procfs.h.  */
+
 int
 linux_proc_task_list_dir_exists (pid_t pid)
 {
diff --git a/gdb/nat/linux-procfs.h b/gdb/nat/linux-procfs.h
index 639d8efaf0bf..bcdcedcdc2a9 100644
--- a/gdb/nat/linux-procfs.h
+++ b/gdb/nat/linux-procfs.h
@@ -80,6 +80,13 @@  extern int linux_proc_task_list_dir_exists (pid_t pid);
 
 extern const char *linux_proc_pid_to_exec_file (int pid);
 
+/* Read the auxiliary vector for the PID process into READBUF which has LEN bytes,
+   starting at OFFSET.  Returns true if successful, with XFERED_LEN indicating how many
+   bytes were read, and false on error.  */
+
+extern bool linux_proc_read_auxv (pid_t pid, gdb_byte *readbuf, off_t offset,
+				  size_t len, ssize_t &xfered_len);
+
 /* Display possible problems on this system.  Display them only once
    per GDB execution.  */
 
diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index e6a39202a98a..d42eb3a49ad7 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -5486,24 +5486,11 @@  int
 linux_process_target::read_auxv (int pid, CORE_ADDR offset,
 				 unsigned char *myaddr, unsigned int len)
 {
-  char filename[PATH_MAX];
-  int fd, n;
-
-  xsnprintf (filename, sizeof filename, "/proc/%d/auxv", pid);
-
-  fd = open (filename, O_RDONLY);
-  if (fd < 0)
-    return -1;
-
-  if (offset != (CORE_ADDR) 0
-      && lseek (fd, (off_t) offset, SEEK_SET) != (off_t) offset)
-    n = -1;
-  else
-    n = read (fd, myaddr, len);
-
-  close (fd);
+  ssize_t xfered_len;
+  bool rc = linux_proc_read_auxv ((pid_t) pid, (gdb_byte *) myaddr,
+				  (off_t) offset, (size_t) len, xfered_len);
 
-  return n;
+  return rc ? (int) xfered_len : -1;
 }
 
 int