| /* 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 <system.h> |
| |
| #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 (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) |
| { |
| /* 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 = 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 (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) |
| { |
| /* 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 __ */ |
| |