binutils-gdb/gdb/nat/linux-procfs.c
Andrew Burgess e58beedf2c gdb: attach to a process when the executable has been deleted
Bug PR gdb/28313 describes attaching to a process when the executable
has been deleted.  The bug is for S390 and describes how a user sees a
message 'PC not saved'.

On x86-64 (GNU/Linux) I don't see a 'PC not saved' message, but
instead I see this:

  (gdb) attach 901877
  Attaching to process 901877
  No executable file now.
  warning: Could not load vsyscall page because no executable was specified
  0x00007fa9d9c121e7 in ?? ()
  (gdb) bt
  #0  0x00007fa9d9c121e7 in ?? ()
  #1  0x00007fa9d9c1211e in ?? ()
  #2  0x0000000000000007 in ?? ()
  #3  0x000000002dc8b18d in ?? ()
  #4  0x0000000000000000 in ?? ()
  (gdb)

Notice that the addresses in the backtrace don't seem right, quickly
heading to 0x7 and finally ending at 0x0.

What's going on, in both the s390 case and the x86-64 case is that the
architecture's prologue scanner is going wrong and causing the stack
unwinding to fail.

The prologue scanner goes wrong because GDB has no unwind information.

And GDB has no unwind information because, of course, the executable
has been deleted.

Notice in the example session above we get this line in the output:

  No executable file now.

which indicates that GDB failed to find an executable to debug.

For GNU/Linux when GDB tries to find an executable for a given pid we
end up calling linux_proc_pid_to_exec_file in gdb/nat/linux-procfs.c.
Within this function we call `readlink` on /proc/PID/exe to find the
path of the actual executable.

If the `readlink` call fails then we already fallback on using
/proc/PID/exe as the path to the executable to debug.

However, when the executable has been deleted the `readlink` call
doesn't fail, but the path that is returned points to a non-existent
file.

I propose that we add an `access` call to linux_proc_pid_to_exec_file
to check that the target file exists and can be read.  If the target
can't be read then we should fall back to /proc/PID/exe (assuming that
/proc/PID/exe can be read).

Now on x86-64 the output looks like this:

  (gdb) attach 901877
  Attaching to process 901877
  Reading symbols from /proc/901877/exe...
  Reading symbols from /lib64/libc.so.6...
  (No debugging symbols found in /lib64/libc.so.6)
  Reading symbols from /lib64/ld-linux-x86-64.so.2...
  (No debugging symbols found in /lib64/ld-linux-x86-64.so.2)
  0x00007fa9d9c121e7 in nanosleep () from /lib64/libc.so.6
  (gdb) bt
  #0  0x00007fa9d9c121e7 in nanosleep () from /lib64/libc.so.6
  #1  0x00007fa9d9c1211e in sleep () from /lib64/libc.so.6
  #2  0x000000000040117e in spin_forever () at attach-test.c:17
  #3  0x0000000000401198 in main () at attach-test.c:24
  (gdb)

which is much better.

I've also tagged the bug PR gdb/29782 which concerns the test
gdb.server/connect-with-no-symbol-file.exp.  After making this change,
when running gdb.server/connect-with-no-symbol-file.exp GDB would now
pick up the /proc/PID/exe file as the executable in some cases.

As GDB is not restarted for the multiple iterations of this test
GDB (or rather BFD) would given a warning/error like:

  (gdb) PASS: gdb.server/connect-with-no-symbol-file.exp: sysroot=target:: action=permission: setup: disconnect
  set sysroot target:
  BFD: reopening /proc/3283001/exe: No such file or directory
  (gdb) FAIL: gdb.server/connect-with-no-symbol-file.exp: sysroot=target:: action=permission: setup: adjust sysroot

What's happening is that an executable found for an earlier iteration
of the test is still registered for the inferior when we are setting
up for a second iteration of the test.  When the sysroot changes, if
there's an executable registered GDB tries to reopen it, but in this
case the file has disappeared (the previous inferior has exited by
this point).

I did think about maybe, when the executable is /proc/PID/exe, we
should auto-delete the file from the inferior.  But in the end I
thought this was a bad idea.  Not only would this require a lot of
special code in GDB just to support this edge case: we'd need to track
if the exe file name came from /proc and should be auto-deleted, or
we'd need target specific code to check if a path should be
auto-deleted.....

... in addition, we'd still want to warn the user when we auto-deleted
the file from the inferior, otherwise they might be surprised to find
their inferior suddenly has no executable attached, so we wouldn't
actually reduce the number of warnings the user sees.

So in the end I figured that the best solution is to just update the
test to avoid the warning.  This is easily done by manually removing
the executable from the inferior once each iteration of the test has
completed.

Now, in bug PR gdb/29782 GDB is clearly managing to pick up an
executable from the NFS cache somehow.  I guess what's happening is
that when the original file is deleted /proc/PID/exe is actually
pointing to a file in the NFS cache which is only deleted at some
later point, and so when GDB starts up we do manage to associate a
file with the inferior, this results in the same message being emitted
from BFD as I was seeing.  The fix included in this commit should also
fix that bug.

One final note:  On x86-64 GNU/Linux, the
gdb.server/connect-with-no-symbol-file.exp test will produce 2 core
files.  This is due to a bug in gdbserver that is nothing to do with
this test.  These core files are created before and after this
commit.  I am working on a fix for the gdbserver issue, but will post
that separately.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28313
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29782

Approved-By: Tom Tromey <tom@tromey.com>
2024-02-02 15:37:05 +00:00

378 lines
8.6 KiB
C

/* Linux-specific PROCFS manipulation routines.
Copyright (C) 2009-2024 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "gdbsupport/common-defs.h"
#include "linux-procfs.h"
#include "gdbsupport/filestuff.h"
#include <dirent.h>
#include <sys/stat.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, int warn)
{
size_t field_len = strlen (field);
char buf[100];
int retval = -1;
snprintf (buf, sizeof (buf), "/proc/%d/status", (int) lwpid);
gdb_file_up status_file = gdb_fopen_cloexec (buf, "r");
if (status_file == NULL)
{
if (warn)
warning (_("unable to open /proc file '%s'"), buf);
return -1;
}
while (fgets (buf, sizeof (buf), status_file.get ()))
if (strncmp (buf, field, field_len) == 0 && buf[field_len] == ':')
{
retval = strtol (&buf[field_len + 1], NULL, 10);
break;
}
return retval;
}
/* Return the TGID of LWPID from /proc/pid/status. Returns -1 if not
found. */
int
linux_proc_get_tgid (pid_t lwpid)
{
return linux_proc_get_int (lwpid, "Tgid", 1);
}
/* See linux-procfs.h. */
pid_t
linux_proc_get_tracerpid_nowarn (pid_t lwpid)
{
return linux_proc_get_int (lwpid, "TracerPid", 0);
}
/* Process states as discovered in the 'State' line of
/proc/PID/status. Not all possible states are represented here,
only those that we care about. */
enum proc_state
{
/* Some state we don't handle. */
PROC_STATE_UNKNOWN,
/* Stopped on a signal. */
PROC_STATE_STOPPED,
/* Tracing stop. */
PROC_STATE_TRACING_STOP,
/* Dead. */
PROC_STATE_DEAD,
/* Zombie. */
PROC_STATE_ZOMBIE,
};
/* Parse a PROC_STATE out of STATE, a buffer with the state found in
the 'State:' line of /proc/PID/status. */
static enum proc_state
parse_proc_status_state (const char *state)
{
state = skip_spaces (state);
switch (state[0])
{
case 't':
return PROC_STATE_TRACING_STOP;
case 'T':
/* Before Linux 2.6.33, tracing stop used uppercase T. */
if (strcmp (state, "T (stopped)\n") == 0)
return PROC_STATE_STOPPED;
else /* "T (tracing stop)\n" */
return PROC_STATE_TRACING_STOP;
case 'X':
return PROC_STATE_DEAD;
case 'Z':
return PROC_STATE_ZOMBIE;
}
return PROC_STATE_UNKNOWN;
}
/* Fill in STATE, 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_get_state (pid_t pid, int warn, enum proc_state *state)
{
int have_state;
char buffer[100];
xsnprintf (buffer, sizeof (buffer), "/proc/%d/status", (int) pid);
gdb_file_up procfile = gdb_fopen_cloexec (buffer, "r");
if (procfile == NULL)
{
if (warn)
warning (_("unable to open /proc file '%s'"), buffer);
return -1;
}
have_state = 0;
while (fgets (buffer, sizeof (buffer), procfile.get ()) != NULL)
if (startswith (buffer, "State:"))
{
have_state = 1;
*state = parse_proc_status_state (buffer + sizeof ("State:") - 1);
break;
}
return have_state;
}
/* See linux-procfs.h declaration. */
int
linux_proc_pid_is_gone (pid_t pid)
{
int have_state;
enum proc_state state;
have_state = linux_proc_pid_get_state (pid, 0, &state);
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 (state == PROC_STATE_ZOMBIE || state == PROC_STATE_DEAD);
}
/* 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, enum proc_state state, int warn)
{
int have_state;
enum proc_state cur_state;
have_state = linux_proc_pid_get_state (pid, warn, &cur_state);
return (have_state > 0 && cur_state == state);
}
/* Detect `T (stopped)' in `/proc/PID/status'.
Other states including `T (tracing stop)' are reported as false. */
int
linux_proc_pid_is_stopped (pid_t pid)
{
return linux_proc_pid_has_state (pid, PROC_STATE_STOPPED, 1);
}
/* Detect `t (tracing stop)' in `/proc/PID/status'.
Other states including `T (stopped)' are reported as false. */
int
linux_proc_pid_is_trace_stopped_nowarn (pid_t pid)
{
return linux_proc_pid_has_state (pid, PROC_STATE_TRACING_STOP, 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, PROC_STATE_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. */
int
linux_proc_pid_is_zombie (pid_t pid)
{
return linux_proc_pid_is_zombie_maybe_warn (pid, 1);
}
/* See linux-procfs.h. */
const char *
linux_proc_tid_get_name (ptid_t ptid)
{
#define TASK_COMM_LEN 16 /* As defined in the kernel's sched.h. */
static char comm_buf[TASK_COMM_LEN];
char comm_path[100];
const char *comm_val;
pid_t pid = ptid.pid ();
pid_t tid = ptid.lwp_p () ? ptid.lwp () : ptid.pid ();
xsnprintf (comm_path, sizeof (comm_path),
"/proc/%ld/task/%ld/comm", (long) pid, (long) tid);
gdb_file_up comm_file = gdb_fopen_cloexec (comm_path, "r");
if (comm_file == NULL)
return NULL;
comm_val = fgets (comm_buf, sizeof (comm_buf), comm_file.get ());
if (comm_val != NULL)
{
int i;
/* Make sure there is no newline at the end. */
for (i = 0; i < sizeof (comm_buf); i++)
{
if (comm_buf[i] == '\n')
{
comm_buf[i] = '\0';
break;
}
}
}
return comm_val;
}
/* See linux-procfs.h. */
void
linux_proc_attach_tgid_threads (pid_t pid,
linux_proc_attach_lwp_func attach_lwp)
{
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);
gdb_dir_up dir (opendir (pathname));
if (dir == NULL)
{
warning (_("Could not open %s."), pathname);
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.get ())) != NULL)
{
unsigned long lwp;
/* Fetch one lwp. */
lwp = strtoul (dp->d_name, NULL, 10);
if (lwp != 0)
{
ptid_t ptid = ptid_t (pid, lwp);
if (attach_lwp (ptid))
new_threads_found = 1;
}
}
if (new_threads_found)
{
/* Start over. */
iterations = -1;
}
rewinddir (dir.get ());
}
}
/* See linux-procfs.h. */
int
linux_proc_task_list_dir_exists (pid_t pid)
{
char pathname[128];
struct stat buf;
xsnprintf (pathname, sizeof (pathname), "/proc/%ld/task", (long) pid);
return (stat (pathname, &buf) == 0);
}
/* See linux-procfs.h. */
const char *
linux_proc_pid_to_exec_file (int pid)
{
static char buf[PATH_MAX];
char name[PATH_MAX];
ssize_t len;
xsnprintf (name, PATH_MAX, "/proc/%d/exe", pid);
len = readlink (name, buf, PATH_MAX - 1);
if (len <= 0)
strcpy (buf, name);
else
buf[len] = '\0';
/* Use /proc/PID/exe if the actual file can't be read, but /proc/PID/exe
can be. */
if (access (buf, R_OK) != 0 && access (name, R_OK) == 0)
strcpy (buf, name);
return buf;
}
/* See linux-procfs.h. */
void
linux_proc_init_warnings ()
{
static bool warned = false;
if (warned)
return;
warned = true;
struct stat st;
if (stat ("/proc/self", &st) != 0)
warning (_("/proc is not accessible."));
}