#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <system.h>
#include "libelfP.h"
#include "libdwflP.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.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 (startswith (buffer, "State:"))
{
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)
{
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
static bool
read_cached_memory (struct __libdwfl_pid_arg *pid_arg,
Dwarf_Addr addr, Dwarf_Word *result)
{
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 = 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
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;
}
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
return false;
#endif
}
#if SIZEOF_LONG == 8
bool lowered = (addr & 4) != 0;
if (lowered)
addr -= 4;
#endif
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
*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;
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;
}
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;
}
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)
{
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;
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 (startswith (line, "Tgid:"))
{
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)
{
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
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