539 lines
14 KiB
C
539 lines
14 KiB
C
/* Get Dwarf Frame state for target live PID process.
|
|
Copyright (C) 2013, 2014, 2015, 2018 Red Hat, Inc.
|
|
This file is part of elfutils.
|
|
|
|
This file is free software; you can redistribute it and/or modify
|
|
it under the terms of either
|
|
|
|
* the GNU Lesser General Public License as published by the Free
|
|
Software Foundation; either version 3 of the License, or (at
|
|
your option) any later version
|
|
|
|
or
|
|
|
|
* the GNU General Public License as published by the Free
|
|
Software Foundation; either version 2 of the License, or (at
|
|
your option) any later version
|
|
|
|
or both in parallel, as here.
|
|
|
|
elfutils 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 copies of the GNU General Public License and
|
|
the GNU Lesser General Public License along with this program. If
|
|
not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include "libelfP.h"
|
|
#include "libdwflP.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef __linux__
|
|
|
|
#include <sys/uio.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/wait.h>
|
|
|
|
static bool
|
|
linux_proc_pid_is_stopped (pid_t pid)
|
|
{
|
|
char buffer[64];
|
|
FILE *procfile;
|
|
bool retval, have_state;
|
|
|
|
snprintf (buffer, sizeof (buffer), "/proc/%ld/status", (long) pid);
|
|
procfile = fopen (buffer, "r");
|
|
if (procfile == NULL)
|
|
return false;
|
|
|
|
have_state = false;
|
|
while (fgets (buffer, sizeof (buffer), procfile) != NULL)
|
|
if (strncmp (buffer, "State:", 6) == 0)
|
|
{
|
|
have_state = true;
|
|
break;
|
|
}
|
|
retval = (have_state && strstr (buffer, "T (stopped)") != NULL);
|
|
fclose (procfile);
|
|
return retval;
|
|
}
|
|
|
|
bool
|
|
internal_function
|
|
__libdwfl_ptrace_attach (pid_t tid, bool *tid_was_stoppedp)
|
|
{
|
|
if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0)
|
|
{
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return false;
|
|
}
|
|
*tid_was_stoppedp = linux_proc_pid_is_stopped (tid);
|
|
if (*tid_was_stoppedp)
|
|
{
|
|
/* Make sure there is a SIGSTOP signal pending even when the process is
|
|
already State: T (stopped). Older kernels might fail to generate
|
|
a SIGSTOP notification in that case in response to our PTRACE_ATTACH
|
|
above. Which would make the waitpid below wait forever. So emulate
|
|
it. Since there can only be one SIGSTOP notification pending this is
|
|
safe. See also gdb/linux-nat.c linux_nat_post_attach_wait. */
|
|
syscall (__NR_tkill, tid, SIGSTOP);
|
|
ptrace (PTRACE_CONT, tid, NULL, NULL);
|
|
}
|
|
for (;;)
|
|
{
|
|
int status;
|
|
if (waitpid (tid, &status, __WALL) != tid || !WIFSTOPPED (status))
|
|
{
|
|
int saved_errno = errno;
|
|
ptrace (PTRACE_DETACH, tid, NULL, NULL);
|
|
errno = saved_errno;
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return false;
|
|
}
|
|
if (WSTOPSIG (status) == SIGSTOP)
|
|
break;
|
|
if (ptrace (PTRACE_CONT, tid, NULL,
|
|
(void *) (uintptr_t) WSTOPSIG (status)) != 0)
|
|
{
|
|
int saved_errno = errno;
|
|
ptrace (PTRACE_DETACH, tid, NULL, NULL);
|
|
errno = saved_errno;
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifdef HAVE_PROCESS_VM_READV
|
|
/* Note that the result word size depends on the architecture word size.
|
|
That is sizeof long. */
|
|
static bool
|
|
read_cached_memory (struct __libdwfl_pid_arg *pid_arg,
|
|
Dwarf_Addr addr, Dwarf_Word *result)
|
|
{
|
|
/* Let the ptrace fallback deal with the corner case of the address
|
|
possibly crossing a page boundary. */
|
|
if ((addr & ((Dwarf_Addr)__LIBDWFL_REMOTE_MEM_CACHE_SIZE - 1))
|
|
> (Dwarf_Addr)__LIBDWFL_REMOTE_MEM_CACHE_SIZE - sizeof (unsigned long))
|
|
return false;
|
|
|
|
struct __libdwfl_remote_mem_cache *mem_cache = pid_arg->mem_cache;
|
|
if (mem_cache == NULL)
|
|
{
|
|
size_t mem_cache_size = sizeof (struct __libdwfl_remote_mem_cache);
|
|
mem_cache = (struct __libdwfl_remote_mem_cache *) malloc (mem_cache_size);
|
|
if (mem_cache == NULL)
|
|
return false;
|
|
|
|
mem_cache->addr = 0;
|
|
mem_cache->len = 0;
|
|
pid_arg->mem_cache = mem_cache;
|
|
}
|
|
|
|
unsigned char *d;
|
|
if (addr >= mem_cache->addr && addr - mem_cache->addr < mem_cache->len)
|
|
{
|
|
d = &mem_cache->buf[addr - mem_cache->addr];
|
|
if ((((uintptr_t) d) & (sizeof (unsigned long) - 1)) == 0)
|
|
*result = *(unsigned long *) d;
|
|
else
|
|
memcpy (result, d, sizeof (unsigned long));
|
|
return true;
|
|
}
|
|
|
|
struct iovec local, remote;
|
|
mem_cache->addr = addr & ~((Dwarf_Addr)__LIBDWFL_REMOTE_MEM_CACHE_SIZE - 1);
|
|
local.iov_base = mem_cache->buf;
|
|
local.iov_len = __LIBDWFL_REMOTE_MEM_CACHE_SIZE;
|
|
remote.iov_base = (void *) (uintptr_t) mem_cache->addr;
|
|
remote.iov_len = __LIBDWFL_REMOTE_MEM_CACHE_SIZE;
|
|
|
|
ssize_t res = process_vm_readv (pid_arg->tid_attached,
|
|
&local, 1, &remote, 1, 0);
|
|
if (res != __LIBDWFL_REMOTE_MEM_CACHE_SIZE)
|
|
{
|
|
mem_cache->len = 0;
|
|
return false;
|
|
}
|
|
|
|
mem_cache->len = res;
|
|
d = &mem_cache->buf[addr - mem_cache->addr];
|
|
if ((((uintptr_t) d) & (sizeof (unsigned long) - 1)) == 0)
|
|
*result = *(unsigned long *) d;
|
|
else
|
|
memcpy (result, d, sizeof (unsigned long));
|
|
return true;
|
|
}
|
|
#endif /* HAVE_PROCESS_VM_READV */
|
|
|
|
static void
|
|
clear_cached_memory (struct __libdwfl_pid_arg *pid_arg)
|
|
{
|
|
struct __libdwfl_remote_mem_cache *mem_cache = pid_arg->mem_cache;
|
|
if (mem_cache != NULL)
|
|
mem_cache->len = 0;
|
|
}
|
|
|
|
/* Note that the result word size depends on the architecture word size.
|
|
That is sizeof long. */
|
|
static bool
|
|
pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg)
|
|
{
|
|
struct __libdwfl_pid_arg *pid_arg = arg;
|
|
pid_t tid = pid_arg->tid_attached;
|
|
Dwfl_Process *process = dwfl->process;
|
|
assert (tid > 0);
|
|
|
|
#ifdef HAVE_PROCESS_VM_READV
|
|
if (read_cached_memory (pid_arg, addr, result))
|
|
{
|
|
#if SIZEOF_LONG == 8
|
|
# if BYTE_ORDER == BIG_ENDIAN
|
|
if (ebl_get_elfclass (process->ebl) == ELFCLASS32)
|
|
*result >>= 32;
|
|
# endif
|
|
#endif
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
if (ebl_get_elfclass (process->ebl) == ELFCLASS64)
|
|
{
|
|
#if SIZEOF_LONG == 8
|
|
errno = 0;
|
|
*result = ptrace (PTRACE_PEEKDATA, tid, (void *) (uintptr_t) addr, NULL);
|
|
return errno == 0;
|
|
#else /* SIZEOF_LONG != 8 */
|
|
/* This should not happen. */
|
|
return false;
|
|
#endif /* SIZEOF_LONG != 8 */
|
|
}
|
|
#if SIZEOF_LONG == 8
|
|
/* We do not care about reads unaliged to 4 bytes boundary.
|
|
But 0x...ffc read of 8 bytes could overrun a page. */
|
|
bool lowered = (addr & 4) != 0;
|
|
if (lowered)
|
|
addr -= 4;
|
|
#endif /* SIZEOF_LONG == 8 */
|
|
errno = 0;
|
|
*result = ptrace (PTRACE_PEEKDATA, tid, (void *) (uintptr_t) addr, NULL);
|
|
if (errno != 0)
|
|
return false;
|
|
#if SIZEOF_LONG == 8
|
|
# if BYTE_ORDER == BIG_ENDIAN
|
|
if (! lowered)
|
|
*result >>= 32;
|
|
# else
|
|
if (lowered)
|
|
*result >>= 32;
|
|
# endif
|
|
#endif /* SIZEOF_LONG == 8 */
|
|
*result &= 0xffffffff;
|
|
return true;
|
|
}
|
|
|
|
static pid_t
|
|
pid_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg,
|
|
void **thread_argp)
|
|
{
|
|
struct __libdwfl_pid_arg *pid_arg = dwfl_arg;
|
|
struct dirent *dirent;
|
|
/* Start fresh on first traversal. */
|
|
if (*thread_argp == NULL)
|
|
rewinddir (pid_arg->dir);
|
|
do
|
|
{
|
|
errno = 0;
|
|
dirent = readdir (pid_arg->dir);
|
|
if (dirent == NULL)
|
|
{
|
|
if (errno != 0)
|
|
{
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
while (strcmp (dirent->d_name, ".") == 0
|
|
|| strcmp (dirent->d_name, "..") == 0);
|
|
char *end;
|
|
errno = 0;
|
|
long tidl = strtol (dirent->d_name, &end, 10);
|
|
if (errno != 0)
|
|
{
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return -1;
|
|
}
|
|
pid_t tid = tidl;
|
|
if (tidl <= 0 || (end && *end) || tid != tidl)
|
|
{
|
|
__libdwfl_seterrno (DWFL_E_PARSE_PROC);
|
|
return -1;
|
|
}
|
|
*thread_argp = dwfl_arg;
|
|
return tid;
|
|
}
|
|
|
|
/* Just checks that the thread id exists. */
|
|
static bool
|
|
pid_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t tid,
|
|
void *dwfl_arg, void **thread_argp)
|
|
{
|
|
*thread_argp = dwfl_arg;
|
|
if (kill (tid, 0) < 0)
|
|
{
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Implement the ebl_set_initial_registers_tid setfunc callback. */
|
|
|
|
static bool
|
|
pid_thread_state_registers_cb (int firstreg, unsigned nregs,
|
|
const Dwarf_Word *regs, void *arg)
|
|
{
|
|
Dwfl_Thread *thread = (Dwfl_Thread *) arg;
|
|
if (firstreg < 0)
|
|
{
|
|
assert (firstreg == -1);
|
|
assert (nregs == 1);
|
|
INTUSE(dwfl_thread_state_register_pc) (thread, *regs);
|
|
return true;
|
|
}
|
|
assert (nregs > 0);
|
|
return INTUSE(dwfl_thread_state_registers) (thread, firstreg, nregs, regs);
|
|
}
|
|
|
|
static bool
|
|
pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg)
|
|
{
|
|
struct __libdwfl_pid_arg *pid_arg = thread_arg;
|
|
assert (pid_arg->tid_attached == 0);
|
|
pid_t tid = INTUSE(dwfl_thread_tid) (thread);
|
|
if (! pid_arg->assume_ptrace_stopped
|
|
&& ! __libdwfl_ptrace_attach (tid, &pid_arg->tid_was_stopped))
|
|
return false;
|
|
pid_arg->tid_attached = tid;
|
|
Dwfl_Process *process = thread->process;
|
|
Ebl *ebl = process->ebl;
|
|
return ebl_set_initial_registers_tid (ebl, tid,
|
|
pid_thread_state_registers_cb, thread);
|
|
}
|
|
|
|
static void
|
|
pid_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg)
|
|
{
|
|
struct __libdwfl_pid_arg *pid_arg = dwfl_arg;
|
|
elf_end (pid_arg->elf);
|
|
free (pid_arg->mem_cache);
|
|
close (pid_arg->elf_fd);
|
|
closedir (pid_arg->dir);
|
|
free (pid_arg);
|
|
}
|
|
|
|
void
|
|
internal_function
|
|
__libdwfl_ptrace_detach (pid_t tid, bool tid_was_stopped)
|
|
{
|
|
/* This handling is needed only on older Linux kernels such as
|
|
2.6.32-358.23.2.el6.ppc64. Later kernels such as
|
|
3.11.7-200.fc19.x86_64 remember the T (stopped) state
|
|
themselves and no longer need to pass SIGSTOP during
|
|
PTRACE_DETACH. */
|
|
ptrace (PTRACE_DETACH, tid, NULL,
|
|
(void *) (intptr_t) (tid_was_stopped ? SIGSTOP : 0));
|
|
}
|
|
|
|
static void
|
|
pid_thread_detach (Dwfl_Thread *thread, void *thread_arg)
|
|
{
|
|
struct __libdwfl_pid_arg *pid_arg = thread_arg;
|
|
pid_t tid = INTUSE(dwfl_thread_tid) (thread);
|
|
assert (pid_arg->tid_attached == tid);
|
|
pid_arg->tid_attached = 0;
|
|
clear_cached_memory (pid_arg);
|
|
if (! pid_arg->assume_ptrace_stopped)
|
|
__libdwfl_ptrace_detach (tid, pid_arg->tid_was_stopped);
|
|
}
|
|
|
|
static const Dwfl_Thread_Callbacks pid_thread_callbacks =
|
|
{
|
|
pid_next_thread,
|
|
pid_getthread,
|
|
pid_memory_read,
|
|
pid_set_initial_registers,
|
|
pid_detach,
|
|
pid_thread_detach,
|
|
};
|
|
|
|
int
|
|
dwfl_linux_proc_attach (Dwfl *dwfl, pid_t pid, bool assume_ptrace_stopped)
|
|
{
|
|
char buffer[36];
|
|
FILE *procfile;
|
|
int err = 0; /* The errno to return and set for dwfl->attcherr. */
|
|
|
|
/* Make sure to report the actual PID (thread group leader) to
|
|
dwfl_attach_state. */
|
|
snprintf (buffer, sizeof (buffer), "/proc/%ld/status", (long) pid);
|
|
procfile = fopen (buffer, "r");
|
|
if (procfile == NULL)
|
|
{
|
|
err = errno;
|
|
fail:
|
|
if (dwfl->process == NULL && dwfl->attacherr == DWFL_E_NOERROR)
|
|
{
|
|
errno = err;
|
|
dwfl->attacherr = __libdwfl_canon_error (DWFL_E_ERRNO);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
char *line = NULL;
|
|
size_t linelen = 0;
|
|
while (getline (&line, &linelen, procfile) >= 0)
|
|
if (strncmp (line, "Tgid:", 5) == 0)
|
|
{
|
|
errno = 0;
|
|
char *endptr;
|
|
long val = strtol (&line[5], &endptr, 10);
|
|
if ((errno == ERANGE && val == LONG_MAX)
|
|
|| *endptr != '\n' || val < 0 || val != (pid_t) val)
|
|
pid = 0;
|
|
else
|
|
pid = (pid_t) val;
|
|
break;
|
|
}
|
|
free (line);
|
|
fclose (procfile);
|
|
|
|
if (pid == 0)
|
|
{
|
|
err = ESRCH;
|
|
goto fail;
|
|
}
|
|
|
|
char name[64];
|
|
int i = snprintf (name, sizeof (name), "/proc/%ld/task", (long) pid);
|
|
if (i <= 0 || i >= (ssize_t) sizeof (name) - 1)
|
|
{
|
|
errno = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
DIR *dir = opendir (name);
|
|
if (dir == NULL)
|
|
{
|
|
err = errno;
|
|
goto fail;
|
|
}
|
|
|
|
Elf *elf;
|
|
i = snprintf (name, sizeof (name), "/proc/%ld/exe", (long) pid);
|
|
assert (i > 0 && i < (ssize_t) sizeof (name) - 1);
|
|
int elf_fd = open (name, O_RDONLY);
|
|
if (elf_fd >= 0)
|
|
{
|
|
elf = elf_begin (elf_fd, ELF_C_READ_MMAP, NULL);
|
|
if (elf == NULL)
|
|
{
|
|
/* Just ignore, dwfl_attach_state will fall back to trying
|
|
to associate the Dwfl with one of the existing DWfl_Module
|
|
ELF images (to know the machine/class backend to use). */
|
|
close (elf_fd);
|
|
elf_fd = -1;
|
|
}
|
|
}
|
|
else
|
|
elf = NULL;
|
|
struct __libdwfl_pid_arg *pid_arg = malloc (sizeof *pid_arg);
|
|
if (pid_arg == NULL)
|
|
{
|
|
elf_end (elf);
|
|
close (elf_fd);
|
|
closedir (dir);
|
|
err = ENOMEM;
|
|
goto fail;
|
|
}
|
|
pid_arg->dir = dir;
|
|
pid_arg->elf = elf;
|
|
pid_arg->elf_fd = elf_fd;
|
|
pid_arg->mem_cache = NULL;
|
|
pid_arg->tid_attached = 0;
|
|
pid_arg->assume_ptrace_stopped = assume_ptrace_stopped;
|
|
if (! INTUSE(dwfl_attach_state) (dwfl, elf, pid, &pid_thread_callbacks,
|
|
pid_arg))
|
|
{
|
|
elf_end (elf);
|
|
close (elf_fd);
|
|
closedir (dir);
|
|
free (pid_arg);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
INTDEF (dwfl_linux_proc_attach)
|
|
|
|
struct __libdwfl_pid_arg *
|
|
internal_function
|
|
__libdwfl_get_pid_arg (Dwfl *dwfl)
|
|
{
|
|
if (dwfl != NULL && dwfl->process != NULL
|
|
&& dwfl->process->callbacks == &pid_thread_callbacks)
|
|
return (struct __libdwfl_pid_arg *) dwfl->process->callbacks_arg;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#else /* __linux__ */
|
|
|
|
bool
|
|
internal_function
|
|
__libdwfl_ptrace_attach (pid_t tid __attribute__ ((unused)),
|
|
bool *tid_was_stoppedp __attribute__ ((unused)))
|
|
{
|
|
errno = ENOSYS;
|
|
__libdwfl_seterrno (DWFL_E_ERRNO);
|
|
return false;
|
|
}
|
|
|
|
void
|
|
internal_function
|
|
__libdwfl_ptrace_detach (pid_t tid __attribute__ ((unused)),
|
|
bool tid_was_stopped __attribute__ ((unused)))
|
|
{
|
|
}
|
|
|
|
int
|
|
dwfl_linux_proc_attach (Dwfl *dwfl __attribute__ ((unused)),
|
|
pid_t pid __attribute__ ((unused)),
|
|
bool assume_ptrace_stopped __attribute__ ((unused)))
|
|
{
|
|
return ENOSYS;
|
|
}
|
|
INTDEF (dwfl_linux_proc_attach)
|
|
|
|
struct __libdwfl_pid_arg *
|
|
internal_function
|
|
__libdwfl_get_pid_arg (Dwfl *dwfl __attribute__ ((unused)))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* ! __linux __ */
|
|
|