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(-)
@@ -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)
{
@@ -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. */
@@ -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