rizin/libr/debug/p/native/linux/linux_debug.c
yossizap 2e5f4b41b4 Fix multithreaded breakpoint behavior in linux ##debug
The plugin wouldn't properly trace breakpoint hits on different threads
since they weren't switched to and the events weren't always handled.
Also, since the breakpoints are removed after they are found in one of the
threads, it's best to stop all threads for now even if dbg.threads is false.
2020-01-10 00:30:18 +01:00

1244 lines
33 KiB
C

/* radare - LGPL - Copyright 2009-2019 - pancake */
#include <r_userconf.h>
#if DEBUGGER
#include <r_debug.h>
#include <r_asm.h>
#include <r_reg.h>
#include <r_lib.h>
#include <r_anal.h>
#include <signal.h>
#include <sys/uio.h>
#include <errno.h>
#include "linux_debug.h"
#include "../procfs.h"
#include <sys/syscall.h>
#include <unistd.h>
#include <elf.h>
char *linux_reg_profile (RDebug *dbg) {
#if __arm__
# include "reg/linux-arm.h"
#elif __riscv
# include "reg/linux-riscv64.h"
#elif __arm64__ || __aarch64__
# include "reg/linux-arm64.h"
#elif __mips__
if ((dbg->bits & R_SYS_BITS_32) && (dbg->bp->endian == 1)) {
# include "reg/linux-mips.h"
} else {
# include "reg/linux-mips64.h"
}
#elif (__i386__ || __x86_64__)
if (dbg->bits & R_SYS_BITS_32) {
#if __x86_64__
# include "reg/linux-x64-32.h"
#else
# include "reg/linux-x86.h"
#endif
} else {
# include "reg/linux-x64.h"
# include <bits/sigcontext.h>
}
#elif __powerpc__
if (dbg->bits & R_SYS_BITS_32) {
# include "reg/linux-ppc.h"
} else {
# include "reg/linux-ppc64.h"
}
#elif __s390x__
# include "reg/linux-s390x.h"
#else
# error "Unsupported Linux CPU"
return NULL;
#endif
}
static void linux_detach_all (RDebug *dbg);
static char *read_link (int pid, const char *file);
static bool linux_attach_single_pid (RDebug *dbg, int ptid);
static void linux_attach_all (RDebug *dbg);
static void linux_remove_thread (RDebug *dbg, int pid);
static void linux_add_and_attach_new_thread (RDebug *dbg, int tid);
static bool linux_stop_thread (int tid);
static bool linux_kill_thread (int tid, int signo);
static void linux_dbg_wait_break (RDebug *dbg);
int linux_handle_signals (RDebug *dbg, int tid) {
siginfo_t siginfo = { 0 };
int ret = r_debug_ptrace (dbg, PTRACE_GETSIGINFO, tid, 0, (r_ptrace_data_t)(size_t)&siginfo);
if (ret == -1) {
/* ESRCH means the process already went away :-/ */
if (errno == ESRCH) {
dbg->reason.type = R_DEBUG_REASON_DEAD;
return true;
}
r_sys_perror ("ptrace GETSIGINFO");
return false;
}
if (siginfo.si_signo > 0) {
//siginfo_t newsiginfo = {0};
//ptrace (PTRACE_SETSIGINFO, dbg->pid, 0, &siginfo);
dbg->reason.type = R_DEBUG_REASON_SIGNAL;
dbg->reason.signum = siginfo.si_signo;
dbg->stopaddr = (ut64)siginfo.si_addr;
//dbg->errno = siginfo.si_errno;
// siginfo.si_code -> HWBKPT, USER, KERNEL or WHAT
// TODO: DO MORE RDEBUGREASON HERE
switch (dbg->reason.signum) {
case SIGTRAP:
{
if (dbg->glob_libs || dbg->glob_unlibs) {
ut64 pc_addr = r_debug_reg_get (dbg, "PC");
RBreakpointItem *b = r_bp_get_at (dbg->bp, pc_addr - dbg->bpsize);
if (b && b->internal) {
char *p = strstr (b->data, "dbg.");
if (p) {
if (r_str_startswith (p, "dbg.libs")) {
const char *name;
if (strstr (b->data, "sym.imp.dlopen")) {
name = r_reg_get_name (dbg->reg, R_REG_NAME_A0);
} else {
name = r_reg_get_name (dbg->reg, R_REG_NAME_A1);
}
b->data = r_str_appendf (b->data, ";ps@r:%s", name);
dbg->reason.type = R_DEBUG_REASON_NEW_LIB;
} else if (r_str_startswith (p, "dbg.unlibs")) {
dbg->reason.type = R_DEBUG_REASON_EXIT_LIB;
}
}
}
}
if (dbg->reason.type != R_DEBUG_REASON_NEW_LIB &&
dbg->reason.type != R_DEBUG_REASON_EXIT_LIB) {
dbg->reason.bp_addr = (ut64)(size_t)siginfo.si_addr;
dbg->reason.type = R_DEBUG_REASON_BREAKPOINT;
// Switch to the thread that hit the breakpoint
r_debug_select (dbg, dbg->pid, tid);
dbg->tid = tid;
}
}
break;
case SIGABRT: // 6 / SIGIOT // SIGABRT
dbg->reason.type = R_DEBUG_REASON_ABORT;
break;
case SIGSEGV:
dbg->reason.type = R_DEBUG_REASON_SEGFAULT;
break;
case SIGCHLD:
dbg->reason.type = R_DEBUG_REASON_SIGNAL;
break;
default:
break;
}
if (dbg->reason.signum != SIGTRAP) {
eprintf ("[+] SIGNAL %d errno=%d addr=0x%08"PFMT64x
" code=%d ret=%d\n",
siginfo.si_signo, siginfo.si_errno,
(ut64)(size_t)siginfo.si_addr, siginfo.si_code, ret);
}
return true;
}
return false;
}
#if __ANDROID__
#undef PT_GETEVENTMSG
#define PT_GETEVENTMSG
#endif
// Used to remove breakpoints before detaching from a fork, without it the child
// will die upon hitting a breakpoint while not being traced
static void linux_remove_fork_bps(RDebug *dbg) {
RListIter *iter;
RBreakpointItem *b;
int prev_pid = dbg->pid;
int prev_tid = dbg->tid;
// Set dbg tid to the new child temporarily
dbg->pid = dbg->forked_pid;
dbg->tid = dbg->forked_pid;
r_debug_select (dbg, dbg->forked_pid, dbg->forked_pid);
// Unset all hw breakpoints in the child process
r_debug_reg_sync (dbg, R_REG_TYPE_DRX, false);
r_list_foreach (dbg->bp->bps, iter, b) {
r_debug_drx_unset (dbg, r_bp_get_index_at (dbg->bp, b->addr));
}
r_debug_reg_sync (dbg, R_REG_TYPE_DRX, true);
// Unset software breakpoints in the child process
r_debug_bp_update (dbg);
r_bp_restore (dbg->bp, false);
// Return to the parent
dbg->pid = prev_pid;
dbg->tid = prev_tid;
r_debug_select (dbg, dbg->pid, dbg->pid);
// Restore sw breakpoints in the parent
r_bp_restore (dbg->bp, true);
}
#ifdef PT_GETEVENTMSG
/*
* Handle PTRACE_EVENT_*
*
* Returns R_DEBUG_REASON_*
*
* If it's not something we handled, we return ..._UNKNOWN to
* tell the caller to keep trying to figure out what to do.
*
* If something went horribly wrong, we return ..._ERROR;
*
* NOTE: This API was added in Linux 2.5.46
*/
RDebugReasonType linux_ptrace_event (RDebug *dbg, int pid, int status) {
ut32 pt_evt;
#if __powerpc64__ || __arm64__ || __aarch64__ || __x86_64__
ut64 data;
#else
ut32 data;
#endif
/* we only handle stops with SIGTRAP here */
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) {
return R_DEBUG_REASON_UNKNOWN;
}
pt_evt = status >> 16;
switch (pt_evt) {
case 0:
/* NOTE: this case is handled by linux_handle_signals */
break;
case PTRACE_EVENT_CLONE:
if (r_debug_ptrace (dbg, PTRACE_GETEVENTMSG, pid, 0, (r_ptrace_data_t)(size_t)&data) == -1) {
r_sys_perror ("ptrace GETEVENTMSG");
return R_DEBUG_REASON_ERROR;
}
linux_add_and_attach_new_thread (dbg, (int)data);
if (dbg->trace_clone) {
r_debug_select (dbg, dbg->pid, (int)data);
}
eprintf ("(%d) Created thread %d\n", dbg->pid, (int)data);
return R_DEBUG_REASON_NEW_TID;
case PTRACE_EVENT_VFORK:
case PTRACE_EVENT_FORK:
if (r_debug_ptrace (dbg, PTRACE_GETEVENTMSG, pid, 0, (r_ptrace_data_t)(size_t)&data) == -1) {
r_sys_perror ("ptrace GETEVENTMSG");
return R_DEBUG_REASON_ERROR;
}
dbg->forked_pid = data;
if (!dbg->trace_forks) {
// The new child has a pending SIGSTOP. We can't affect it until it
// hits the SIGSTOP, but we're already attached. */
if (waitpid (dbg->forked_pid, &status, __WALL) == -1) {
perror ("waitpid");
}
// We need to do this even if the new process will be detached since the
// breakpoints are inherited from the parent
linux_remove_fork_bps (dbg);
if (r_debug_ptrace (dbg, PTRACE_DETACH, dbg->forked_pid, NULL, (r_ptrace_data_t)(size_t)NULL) == -1) {
perror ("PTRACE_DETACH");
}
}
return R_DEBUG_REASON_NEW_PID;
case PTRACE_EVENT_EXIT:
if (r_debug_ptrace (dbg, PTRACE_GETEVENTMSG, pid, 0, (r_ptrace_data_t)(size_t)&data) == -1) {
r_sys_perror ("ptrace GETEVENTMSG");
return R_DEBUG_REASON_ERROR;
}
//eprintf ("PTRACE_EVENT_EXIT pid=%d, status=0x%"PFMT64x"\n", pid, (ut64)data);
return pid != dbg->pid ? R_DEBUG_REASON_EXIT_TID : R_DEBUG_REASON_EXIT_PID;
default:
eprintf ("Unknown PTRACE_EVENT encountered: %d\n", pt_evt);
break;
}
return R_DEBUG_REASON_UNKNOWN;
}
#endif
int linux_step(RDebug *dbg) {
int ret = false;
int pid = dbg->tid;
ut64 addr = r_debug_reg_get (dbg, "PC");
ret = r_debug_ptrace (dbg, PTRACE_SINGLESTEP, pid, (void*)(size_t)addr, 0);
//XXX(jjd): why?? //linux_handle_signals (dbg);
if (ret == -1) {
perror ("native-singlestep");
ret = false;
} else {
ret = true;
}
return ret;
}
bool linux_set_options(RDebug *dbg, int pid) {
int traceflags = 0;
traceflags |= PTRACE_O_TRACEFORK;
traceflags |= PTRACE_O_TRACEVFORK;
traceflags |= PTRACE_O_TRACECLONE;
if (dbg->trace_forks) {
traceflags |= PTRACE_O_TRACEVFORKDONE;
}
if (dbg->trace_execs) {
traceflags |= PTRACE_O_TRACEEXEC;
}
if (dbg->trace_aftersyscall) {
traceflags |= PTRACE_O_TRACEEXIT;
}
/* SIGTRAP | 0x80 on signal handler .. not supported on all archs */
traceflags |= PTRACE_O_TRACESYSGOOD;
if (r_debug_ptrace (dbg, PTRACE_SETOPTIONS, pid, 0, (r_ptrace_data_t)(size_t)traceflags) == -1) {
perror ("ptrace (PT_SETOPTIONS)");
return false;
}
return true;
}
static void linux_detach_all(RDebug *dbg) {
RList *th_list = dbg->threads;
if (th_list) {
RDebugPid *th;
RListIter *it;
r_list_foreach (th_list, it, th) {
if (th->pid != dbg->main_pid) {
if (r_debug_ptrace (dbg, PTRACE_DETACH, th->pid, NULL, (r_ptrace_data_t)(size_t)NULL) == -1) {
perror ("PTRACE_DETACH");
}
}
}
}
// Detaching from main proc
if (r_debug_ptrace (dbg, PTRACE_DETACH, dbg->main_pid, NULL, (r_ptrace_data_t)(size_t)NULL) == -1) {
perror ("PTRACE_DETACH");
}
}
static void linux_remove_thread (RDebug *dbg, int pid) {
RList *th_list = dbg->threads;
if (th_list) {
RDebugPid *th;
RListIter *it;
r_list_foreach (th_list, it, th) {
if (th->pid == pid) {
r_list_delete_data (th_list, th);
dbg->n_threads--;
}
}
}
}
bool linux_select(RDebug *dbg, int pid, int tid) {
if (dbg->pid != -1 && dbg->pid != pid) {
return linux_attach_new_process (dbg, pid);
}
return linux_attach (dbg, tid);
}
bool linux_attach_new_process(RDebug *dbg, int pid) {
linux_detach_all (dbg);
if (dbg->threads) {
r_list_free (dbg->threads);
dbg->threads = NULL;
}
if (!linux_attach (dbg, pid)) {
return false;
}
// Call select to syncrhonize the thread's data.
dbg->pid = pid;
dbg->tid = pid;
r_debug_select (dbg, pid, pid);
return true;
}
static void linux_dbg_wait_break(RDebug *dbg) {
// Stop the currently debugged thread
if (!linux_kill_thread (dbg->tid, SIGSTOP)) {
eprintf ("Could not stop pid (%d)\n", dbg->pid);
return;
}
}
RDebugReasonType linux_dbg_wait(RDebug *dbg, int my_pid) {
RDebugReasonType reason = R_DEBUG_REASON_UNKNOWN;
int pid = dbg->tid;
int status, flags = __WALL;
int ret = -1;
if (pid == -1) {
flags |= WNOHANG;
}
// Ignore keyboard interrupt while waiting to avoid signaling the child process twice on
// break(SIGSTOP is sent by wait_break) which forced the user to continue an extra time
// to handle SIGINT
r_sys_signal (SIGINT, SIG_IGN);
r_cons_break_push ((RConsBreak)linux_dbg_wait_break, dbg);
repeat:
for (;;) {
if (r_cons_is_breaked ()) {
reason = R_DEBUG_REASON_USERSUSP;
break;
}
void *bed = r_cons_sleep_begin ();
ret = waitpid (-1, &status, flags);
r_cons_sleep_end (bed);
if (ret < 0) {
perror ("waitpid");
break;
} else if (!ret) {
flags &= ~WNOHANG;
} else {
pid = ret;
reason = linux_ptrace_event (dbg, pid, status);
if (reason == R_DEBUG_REASON_NEW_PID) {
break;
}
if (reason == R_DEBUG_REASON_EXIT_TID) {
r_debug_ptrace (dbg, PTRACE_CONT, pid, NULL, 0);
goto repeat;
}
if (reason != R_DEBUG_REASON_UNKNOWN) {
break;
}
if (WIFEXITED (status)) {
eprintf ("child exited with status %d\n", WEXITSTATUS (status));
if (pid == dbg->main_pid) {
reason = R_DEBUG_REASON_DEAD;
} else {
reason = R_DEBUG_REASON_EXIT_TID;
linux_remove_thread (dbg, pid);
}
} else if (WIFSIGNALED (status)) {
eprintf ("child received signal %d\n", WTERMSIG (status));
reason = R_DEBUG_REASON_SIGNAL;
} else if (WIFSTOPPED (status)) {
if (WSTOPSIG (status) != SIGTRAP &&
WSTOPSIG (status) != SIGSTOP) {
if (WSTOPSIG (status) == SIGINT) {
reason = R_DEBUG_REASON_USERSUSP;
break;
}
eprintf ("child stopped with signal %d\n", WSTOPSIG (status));
reason = R_DEBUG_REASON_DEAD;
}
if (!linux_handle_signals (dbg, pid)) {
eprintf ("can't handle signals\n");
return R_DEBUG_REASON_ERROR;
}
reason = dbg->reason.type;
#ifdef WIFCONTINUED
} else if (WIFCONTINUED (status)) {
eprintf ("child continued...\n");
reason = R_DEBUG_REASON_NONE;
#endif
} else if (status == 1) {
eprintf ("EEK DEAD DEBUGEE!\n");
reason = R_DEBUG_REASON_DEAD;
} else if (status == 0) {
eprintf ("STATUS=0?!?!?!?\n");
reason = R_DEBUG_REASON_DEAD;
} else {
if (ret != pid) {
reason = R_DEBUG_REASON_NEW_PID;
} else {
eprintf ("CRAP. returning from wait without knowing why...\n");
}
}
if (reason != R_DEBUG_REASON_UNKNOWN) {
break;
}
}
}
r_cons_break_pop ();
r_sys_signal (SIGINT, SIG_DFL);
dbg->reason.tid = pid;
return reason;
}
int match_pid(const void *pid_o, const void *th_o) {
int pid = *(int *)pid_o;
RDebug *th = (RDebug *)th_o;
return pid == th->pid;
}
static void linux_add_and_attach_new_thread(RDebug *dbg, int tid) {
int uid = getuid(); // XXX
char info[1024] = {0};
RDebugPid *tid_info;
if (!procfs_pid_slurp (tid, "status", info, sizeof (info))) {
tid_info = fill_pid_info (info, NULL, tid);
} else {
tid_info = r_debug_pid_new ("new_path", tid, uid, 's', 0);
}
(void) linux_attach_single_pid (dbg, tid);
r_list_append (dbg->threads, tid_info);
dbg->n_threads++;
}
static bool linux_kill_thread(int tid, int signo) {
int ret = syscall (__NR_tkill, tid, signo);
if (ret == -1) {
perror ("tkill");
return false;
}
return true;
}
static bool linux_stop_thread(int tid) {
int status;
int ret = -1;
if (linux_kill_thread (tid, SIGSTOP)) {
if ((ret = waitpid (tid, &status, WUNTRACED)) == -1) {
perror("waitpid");
}
}
return ret == tid;
}
bool linux_stop_threads(RDebug *dbg, int except) {
// Stop all currently debugged threads
RList *list = dbg->threads;
bool ret = true;
if (list) {
RDebugPid *th;
RListIter *it;
r_list_foreach (list, it, th) {
if (th->pid && th->pid != except) {
if (!linux_stop_thread (th->pid)) {
eprintf ("Could not stop pid (%d)\n", th->pid);
ret = false;
}
}
}
}
return ret;
}
static bool linux_attach_single_pid(RDebug *dbg, int ptid) {
siginfo_t sig = { 0 };
if (ptid < 0) {
return false;
}
// Safely check if the PID has already been attached to avoid printing errors.
// Attaching to a process that has already been started with PTRACE_TRACEME.
// sets errno to "Operation not permitted" which may be misleading.
// GETSIGINFO can be called multiple times and would fail without attachment.
if (r_debug_ptrace (dbg, PTRACE_GETSIGINFO, ptid, NULL,
(r_ptrace_data_t)&sig) == -1) {
if (r_debug_ptrace (dbg, PTRACE_ATTACH, ptid, NULL,
(r_ptrace_data_t)NULL) == -1) {
perror ("ptrace (PT_ATTACH)");
return false;
}
// Make sure SIGSTOP is delivered and wait for it since we can't affect the pid
// until it hits SIGSTOP.
if (!linux_stop_thread (ptid)) {
eprintf ("Could not stop pid (%d)\n", ptid);
}
}
if (!linux_set_options (dbg, ptid)) {
eprintf("failed set_options on %d\n", ptid);
return false;
}
return true;
}
static RList *get_pid_thread_list(RDebug *dbg, int main_pid) {
RList *list = r_list_new ();
if (list) {
list = linux_thread_list (dbg, main_pid, list);
dbg->main_pid = main_pid;
}
return list;
}
static void linux_attach_all(RDebug *dbg) {
linux_attach_single_pid (dbg, dbg->main_pid);
RList *list = dbg->threads;
if (list) {
RDebugPid *th;
RListIter *it;
r_list_foreach (list, it, th) {
if (th->pid && th->pid != dbg->main_pid) {
if (!linux_attach_single_pid (dbg, th->pid)) {
eprintf ("PID %d\n", th->pid);
}
}
}
}
}
int linux_attach(RDebug *dbg, int pid) {
// First time we run: We try to attach to all "possible" threads and to the main pid
if (!dbg->threads) {
dbg->threads = get_pid_thread_list (dbg, pid);
linux_attach_all (dbg);
} else {
// This means we did a first run, so we probably attached to all possible threads already.
// So check if the requested thread is being traced already. If yes: skip
if (dbg->threads && !r_list_find (dbg->threads, &pid, &match_pid)) {
goto out;
}
linux_attach_single_pid (dbg, pid);
}
out:
return pid;
}
static char *read_link(int pid, const char *file) {
char path[1024] = {0};
char buf[1024] = {0};
snprintf (path, sizeof (path), "/proc/%d/%s", pid, file);
int ret = readlink (path, buf, sizeof (buf));
if (ret > 0) {
buf[sizeof (buf) - 1] = '\0';
return strdup (buf);
}
return NULL;
}
RDebugInfo *linux_info(RDebug *dbg, const char *arg) {
char proc_buff[1024];
RDebugInfo *rdi = R_NEW0 (RDebugInfo);
if (!rdi) {
return NULL;
}
RList *th_list;
bool list_alloc = false;
if (dbg->threads) {
th_list = dbg->threads;
} else {
th_list = r_list_new ();
list_alloc = true;
if (th_list) {
th_list = linux_thread_list (dbg, dbg->pid, th_list);
}
}
RDebugPid *th;
RListIter *it;
bool found = false;
r_list_foreach (th_list, it, th) {
if (th->pid == dbg->pid) {
found = true;
break;
}
}
rdi->pid = dbg->pid;
rdi->tid = dbg->tid;
rdi->uid = found ? th->uid : -1;
rdi->gid = found ? th->gid : -1;
rdi->cwd = read_link (rdi->pid, "cwd");
rdi->exe = read_link (rdi->pid, "exe");
snprintf (proc_buff, sizeof (proc_buff), "/proc/%d/cmdline", rdi->pid);
rdi->cmdline = r_file_slurp (proc_buff, NULL);
snprintf (proc_buff, sizeof (proc_buff), "/proc/%d/stack", rdi->pid);
rdi->kernel_stack = r_file_slurp (proc_buff, NULL);
rdi->status = found ? th->status : R_DBG_PROC_STOP;
if (list_alloc) {
r_list_free (th_list);
}
return rdi;
}
RDebugPid *fill_pid_info(const char *info, const char *path, int tid) {
RDebugPid *pid_info = R_NEW0 (RDebugPid);
if (!pid_info) {
return NULL;
}
char *ptr = strstr (info, "State:");
if (ptr) {
switch (*(ptr + 7)) {
case 'R':
pid_info->status = R_DBG_PROC_RUN;
break;
case 'S':
pid_info->status = R_DBG_PROC_SLEEP;
break;
case 'T':
case 't':
pid_info->status = R_DBG_PROC_STOP;
break;
case 'Z':
pid_info->status = R_DBG_PROC_ZOMBIE;
break;
case 'X':
pid_info->status = R_DBG_PROC_DEAD;
break;
default:
pid_info->status = R_DBG_PROC_SLEEP;
break;
}
}
ptr = strstr (info, "PPid:");
if (ptr) {
pid_info->ppid = atoi (ptr + 5);
}
ptr = strstr (info, "Uid:");
if (ptr) {
pid_info->uid = atoi (ptr + 5);
}
ptr = strstr (info, "Gid:");
if (ptr) {
pid_info->gid = atoi (ptr + 5);
}
pid_info->pid = tid;
pid_info->path = path ? strdup (path) : NULL;
pid_info->runnable = true;
pid_info->pc = 0;
return pid_info;
}
RList *linux_pid_list(int pid, RList *list) {
list->free = (RListFree)&r_debug_pid_free;
DIR *dh = NULL;
struct dirent *de = NULL;
char path[PATH_MAX], info[PATH_MAX];
int i = -1;
RDebugPid *pid_info = NULL;
dh = opendir ("/proc");
if (!dh) {
r_sys_perror ("opendir /proc");
r_list_free (list);
return NULL;
}
while ((de = readdir (dh))) {
path[0] = 0;
info[0] = 0;
// For each existing pid file
if ((i = atoi (de->d_name)) <= 0) {
continue;
}
procfs_pid_slurp (i, "cmdline", path, sizeof (path));
if (!procfs_pid_slurp (i, "status", info, sizeof (info))) {
// Get information about pid (status, pc, etc.)
pid_info = fill_pid_info (info, path, i);
} else {
pid_info = r_debug_pid_new (path, i, 0, R_DBG_PROC_STOP, 0);
}
// Unless pid 0 is requested, only add the requested pid and it's child processes
if (0 == pid || i == pid || pid_info->ppid == pid) {
r_list_append (list, pid_info);
}
}
closedir (dh);
return list;
}
RList *linux_thread_list(RDebug *dbg, int pid, RList *list) {
int i = 0, thid = 0;
char *ptr, buf[PATH_MAX];
RDebugPid *pid_info = NULL;
ut64 pc = 0;
int prev_tid = dbg->tid;
if (!pid) {
r_list_free (list);
return NULL;
}
list->free = (RListFree)&r_debug_pid_free;
/* if this process has a task directory, use that */
snprintf (buf, sizeof (buf), "/proc/%d/task", pid);
if (r_file_is_directory (buf)) {
struct dirent *de;
DIR *dh = opendir (buf);
// Update the process' memory maps to set correct paths
dbg->corebind.syncDebugMaps (dbg->corebind.core);
while ((de = readdir (dh))) {
if (!strcmp (de->d_name, ".") || !strcmp (de->d_name, "..")) {
continue;
}
int tid = atoi (de->d_name);
char info[PATH_MAX];
int uid = 0;
if (!procfs_pid_slurp (tid, "status", info, sizeof (info))) {
ptr = strstr (info, "Uid:");
if (ptr) {
uid = atoi (ptr + 4);
}
ptr = strstr (info, "Tgid:");
if (ptr) {
int tgid = atoi (ptr + 5);
if (tgid != pid) {
/* Ignore threads that aren't in the pid's thread group */
continue;
}
}
}
// Switch to the currently inspected thread to get it's program counter
if (dbg->tid != tid) {
linux_attach_single_pid (dbg, tid);
dbg->tid = tid;
}
r_debug_reg_sync (dbg, R_REG_TYPE_GPR, false);
pc = r_debug_reg_get (dbg, "PC");
if (!procfs_pid_slurp (tid, "status", info, sizeof (info))) {
// Get information about pid (status, pc, etc.)
pid_info = fill_pid_info (info, NULL, tid);
pid_info->pc = pc;
} else {
pid_info = r_debug_pid_new (NULL, tid, uid, 's', pc);
}
r_list_append (list, pid_info);
}
closedir (dh);
// Return to the original thread
linux_attach_single_pid (dbg, prev_tid);
dbg->tid = prev_tid;
r_debug_reg_sync (dbg, R_REG_TYPE_GPR, false);
} else {
/* Some linux configurations might hide threads from /proc, use this workaround instead */
#undef MAXPID
#define MAXPID 99999
/* otherwise, brute force the pids */
for (i = pid; i < MAXPID; i++) { // XXX
if (procfs_pid_slurp (i, "status", buf, sizeof(buf)) == -1) {
continue;
}
int uid = 0;
/* look for a thread group id */
ptr = strstr (buf, "Uid:");
if (ptr) {
uid = atoi (ptr + 4);
}
ptr = strstr (buf, "Tgid:");
if (ptr) {
int tgid = atoi (ptr + 5);
/* if it is not in our thread group, we don't want it */
if (tgid != pid) {
continue;
}
if (procfs_pid_slurp (i, "comm", buf, sizeof(buf)) == -1) {
/* fall back to auto-id */
snprintf (buf, sizeof(buf), "thread_%d", thid++);
}
r_list_append (list, r_debug_pid_new (buf, i, uid, 's', 0));
}
}
}
return list;
}
#define PRINT_FPU(fpregs) \
r_cons_printf ("cwd = 0x%04x ; control ", (fpregs).cwd);\
r_cons_printf ("swd = 0x%04x ; status\n", (fpregs).swd);\
r_cons_printf ("ftw = 0x%04x ", (fpregs).ftw);\
r_cons_printf ("fop = 0x%04x\n", (fpregs).fop);\
r_cons_printf ("rip = 0x%016"PFMT64x" ", (ut64)(fpregs).rip);\
r_cons_printf ("rdp = 0x%016"PFMT64x"\n", (ut64)(fpregs).rdp);\
r_cons_printf ("mxcsr = 0x%08x ", (fpregs).mxcsr);\
r_cons_printf ("mxcr_mask = 0x%08x\n", (fpregs).mxcr_mask)\
#define PRINT_FPU_NOXMM(fpregs) \
r_cons_printf ("cwd = 0x%04lx ; control ", (fpregs).cwd);\
r_cons_printf ("swd = 0x%04lx ; status\n", (fpregs).swd);\
r_cons_printf ("twd = 0x%04lx ", (fpregs).twd);\
r_cons_printf ("fip = 0x%04lx \n", (fpregs).fip);\
r_cons_printf ("fcs = 0x%04lx ", (fpregs).fcs);\
r_cons_printf ("foo = 0x%04lx \n", (fpregs).foo);\
r_cons_printf ("fos = 0x%04lx ", (fpregs).fos)
static void print_fpu(void *f){
#if __x86_64__
int i,j;
struct user_fpregs_struct fpregs = *(struct user_fpregs_struct *)f;
#if __ANDROID__
PRINT_FPU (fpregs);
for (i = 0;i < 8; i++) {
ut64 *b = (ut64 *)&fpregs.st_space[i*4];
ut32 *c = (ut32*)&fpregs.st_space;
float *f = (float *)&fpregs.st_space;
c = c + (i * 4);
f = f + (i * 4);
r_cons_printf ("st%d =%0.3lg (0x%016"PFMT64x") | %0.3f (%08x) | "\
"%0.3f (%08x) \n", i,
(double)*((double*)&fpregs.st_space[i*4]), *b, (float) f[0],
c[0], (float) f[1], c[1]);
}
#else
r_cons_printf ("---- x86-64 ----\n");
PRINT_FPU (fpregs);
r_cons_printf ("size = 0x%08x\n", (ut32)sizeof (fpregs));
for (i = 0; i < 16; i++) {
ut32 *a = (ut32 *)&fpregs.xmm_space;
a = a + (i * 4);
r_cons_printf ("xmm%d = %08x %08x %08x %08x ", i, (int)a[0], (int)a[1],
(int)a[2], (int)a[3] );
if (i < 8) {
ut64 *st_u64 = (ut64*)&fpregs.st_space[i * 4];
ut8 *st_u8 = (ut8 *)&fpregs.st_space[i * 4];
long double *st_ld = (long double *)&fpregs.st_space[i * 4];
r_cons_printf ("mm%d = 0x%016" PFMT64x " | st%d = ", i, *st_u64, i);
// print as hex TBYTE - always little endian
for (j = 9; j >= 0; j--) {
r_cons_printf ("%02x", st_u8[j]);
}
// Using %Lf and %Le even though we do not show the extra precision to avoid another cast
// %f with (double)*st_ld would also work
r_cons_printf (" %Le %Lf\n", *st_ld, *st_ld);
} else {
r_cons_printf ("\n");
}
}
#endif // __ANDROID__
#elif __i386__
int i,j;
#if __ANDROID__
struct user_fpxregs_struct fpxregs = *(struct user_fpxregs_struct*)f;
r_cons_printf ("---- x86-32 ----\n");
r_cons_printf ("cwd = 0x%04x ; control ", fpxregs.cwd);
r_cons_printf ("swd = 0x%04x ; status\n", fpxregs.swd);
r_cons_printf ("twd = 0x%04x ", fpxregs.twd);
r_cons_printf ("fop = 0x%04x\n", fpxregs.fop);
r_cons_printf ("fip = 0x%08x\n", (ut32)fpxregs.fip);
r_cons_printf ("fcs = 0x%08x\n", (ut32)fpxregs.fcs);
r_cons_printf ("foo = 0x%08x\n", (ut32)fpxregs.foo);
r_cons_printf ("fos = 0x%08x\n", (ut32)fpxregs.fos);
r_cons_printf ("mxcsr = 0x%08x\n", (ut32)fpxregs.mxcsr);
for(i = 0; i < 8; i++) {
ut32 *a = (ut32*)(&fpxregs.xmm_space);
ut64 *b = (ut64 *)(&fpxregs.st_space[i * 4]);
ut32 *c = (ut32*)&fpxregs.st_space;
float *f = (float *)&fpxregs.st_space;
a = a + (i * 4);
c = c + (i * 4);
f = f + (i * 4);
r_cons_printf ("xmm%d = %08x %08x %08x %08x ", i, (int)a[0],
(int)a[1], (int)a[2], (int)a[3] );
r_cons_printf ("st%d = %0.3lg (0x%016"PFMT64x") | %0.3f (0x%08x) | "\
"%0.3f (0x%08x)\n", i,
(double)*((double*)(&fpxregs.st_space[i*4])), b[0],
f[0], c[0], f[1], c[1]);
}
#else
struct user_fpregs_struct fpregs = *(struct user_fpregs_struct *)f;
r_cons_printf ("---- x86-32-noxmm ----\n");
PRINT_FPU_NOXMM (fpregs);
for(i = 0; i < 8; i++) {
ut64 *b = (ut64 *)(&fpregs.st_space[i*4]);
double *d = (double*)b;
ut32 *c = (ut32*)&fpregs.st_space;
float *f = (float *)&fpregs.st_space;
c = c + (i * 4);
f = f + (i * 4);
r_cons_printf ("st%d = %0.3lg (0x%016"PFMT64x") | %0.3f (0x%08x) | "\
"%0.3f (0x%08x)\n", i, d[0], b[0], f[0], c[0], f[1], c[1]);
}
#endif
#else
#warning print_fpu not implemented for this platform
#endif
}
int linux_reg_read(RDebug *dbg, int type, ut8 *buf, int size) {
bool showfpu = false;
int pid = dbg->tid;
int ret;
if (type < -1) {
showfpu = true;
type = -type;
}
switch (type) {
case R_REG_TYPE_DRX:
#if __POWERPC__
// no drx for powerpc
return false;
#elif __i386__ || __x86_64__
#if !__ANDROID__
{
int i;
for (i = 0; i < 8; i++) { //DR0-DR7
if (i == 4 || i == 5) {
continue;
}
long ret = r_debug_ptrace (dbg, PTRACE_PEEKUSER, pid,
(void *)r_offsetof (struct user, u_debugreg[i]), 0);
if ((i+1) * sizeof (ret) > size) {
eprintf ("linux_reg_get: Buffer too small %d\n", size);
break;
}
memcpy (buf + (i * sizeof (ret)), &ret, sizeof (ret));
}
struct user a;
return sizeof (a.u_debugreg);
}
#else
#warning Android X86 does not support DRX
#endif
#endif
return true;
break;
case R_REG_TYPE_FPU:
case R_REG_TYPE_MMX:
case R_REG_TYPE_XMM:
#if __POWERPC__
return false;
#elif __x86_64__ || __i386__
{
int ret1 = 0;
struct user_fpregs_struct fpregs;
if (type == R_REG_TYPE_FPU) {
#if __x86_64__
ret1 = r_debug_ptrace (dbg, PTRACE_GETFPREGS, pid, NULL, &fpregs);
if (showfpu) {
print_fpu ((void *)&fpregs);
}
if (ret1 != 0) {
return false;
}
if (sizeof (fpregs) < size) {
size = sizeof (fpregs);
}
memcpy (buf, &fpregs, size);
return sizeof(fpregs);
#elif __i386__
#if !__ANDROID__
struct user_fpxregs_struct fpxregs;
ret1 = r_debug_ptrace (dbg, PTRACE_GETFPXREGS, pid, NULL, &fpxregs);
if (ret1 == 0) {
if (showfpu) print_fpu ((void *)&fpxregs);
if (sizeof(fpxregs) < size) size = sizeof(fpxregs);
memcpy (buf, &fpxregs, size);
return sizeof(fpxregs);
} else {
ret1 = r_debug_ptrace (dbg, PTRACE_GETFPREGS, pid, NULL, &fpregs);
if (showfpu) print_fpu ((void *)&fpregs);
if (ret1 != 0) return false;
if (sizeof(fpregs) < size) size = sizeof(fpregs);
memcpy (buf, &fpregs, size);
return sizeof(fpregs);
}
#else
ret1 = r_debug_ptrace (dbg, PTRACE_GETFPREGS, pid, NULL, &fpregs);
if (showfpu) print_fpu ((void *)&fpregs);
if (ret1 != 0) return false;
if (sizeof (fpregs) < size) size = sizeof(fpregs);
memcpy (buf, &fpregs, size);
return sizeof(fpregs);
#endif // !__ANDROID__
#endif // __i386__
}
}
#else
#warning getfpregs not implemented for this platform
#endif
break;
case R_REG_TYPE_SEG:
case R_REG_TYPE_FLG:
case R_REG_TYPE_GPR:
{
R_DEBUG_REG_T regs;
memset (&regs, 0, sizeof (regs));
memset (buf, 0, size);
#if __arm64__ || __aarch64__ || __s390x__
struct iovec io = {
.iov_base = &regs,
.iov_len = sizeof (regs)
};
ret = r_debug_ptrace (dbg, PTRACE_GETREGSET, pid, 1, &io);
// ret = ptrace (PTRACE_GETREGSET, pid, (void*)(size_t)(NT_PRSTATUS), NULL); // &io);
if (ret != 0) {
perror("ptrace");
}
#elif __BSD__ && (__POWERPC__ || __sparc__)
ret = r_debug_ptrace (dbg, PTRACE_GETREGS, pid, &regs, NULL);
#else
/* linux -{arm/mips/riscv/x86/x86_64} */
ret = r_debug_ptrace (dbg, PTRACE_GETREGS, pid, NULL, &regs);
#endif
/*
* if perror here says 'no such process' and the
* process exists still.. is because there's a missing call
* to 'wait'. and the process is not yet available to accept
* more ptrace queries.
*/
if (ret != 0) {
return false;
}
memcpy (buf, &regs, R_MIN (sizeof (regs), size));
return sizeof (regs);
}
break;
case R_REG_TYPE_YMM:
{
#if __x86_64__
ut32 ymm_space[128]; // full ymm registers
struct _xstate xstate;
struct iovec iov;
iov.iov_base = &xstate;
iov.iov_len = sizeof(struct _xstate);
ret = r_debug_ptrace (dbg, PTRACE_GETREGSET, pid, (void*)NT_X86_XSTATE, &iov);
if (ret != 0) {
return false;
}
// stitch together xstate.fpstate._xmm and xstate.ymmh assuming LE
int ri,rj;
for (ri = 0; ri < 16; ri++) {
for (rj=0; rj < 4; rj++) {
ymm_space[ri*8+rj] = xstate.fpstate._xmm[ri].element[rj];
}
for (rj=0; rj < 4; rj++) {
ymm_space[ri*8+(rj+4)] = xstate.ymmh.ymmh_space[ri*4+rj];
}
}
memcpy (buf, &ymm_space, sizeof (ymm_space));
return sizeof (ymm_space);
#endif
return false;
}
break;
}
return false;
}
int linux_reg_write (RDebug *dbg, int type, const ut8 *buf, int size) {
int pid = dbg->tid;
if (type == R_REG_TYPE_DRX) {
#if !__ANDROID__ && (__i386__ || __x86_64__)
int i;
long *val = (long*)buf;
for (i = 0; i < 8; i++) { // DR0-DR7
if (i == 4 || i == 5) {
continue;
}
if (r_debug_ptrace (dbg, PTRACE_POKEUSER, pid,
(void *)r_offsetof (struct user, u_debugreg[i]), (r_ptrace_data_t)val[i])) {
eprintf ("ptrace error for dr %d\n", i);
r_sys_perror ("ptrace POKEUSER");
}
}
return sizeof (R_DEBUG_REG_T);
#else
return false;
#endif
}
if (type == R_REG_TYPE_GPR) {
#if __arm64__ || __aarch64__ || __s390x__
struct iovec io = {
.iov_base = (void*)buf,
.iov_len = sizeof (R_DEBUG_REG_T)
};
int ret = r_debug_ptrace (dbg, PTRACE_SETREGSET, pid, (void*)(size_t)NT_PRSTATUS, (r_ptrace_data_t)(size_t)&io);
#elif __POWERPC__ || __sparc__
int ret = r_debug_ptrace (dbg, PTRACE_SETREGS, pid, buf, NULL);
#else
int ret = r_debug_ptrace (dbg, PTRACE_SETREGS, pid, 0, (void*)buf);
#endif
#if DEAD_CODE
if (size > sizeof (R_DEBUG_REG_T)) {
size = sizeof (R_DEBUG_REG_T);
}
#endif
return (ret != 0) ? false : true;
}
if (type == R_REG_TYPE_FPU) {
#if __i386__ || __x86_64__
int ret = r_debug_ptrace (dbg, PTRACE_SETFPREGS, pid, 0, (void*)buf);
return (ret != 0) ? false : true;
#endif
}
return false;
}
RList *linux_desc_list (int pid) {
RList *ret = NULL;
char path[512], file[512], buf[512];
struct dirent *de;
RDebugDesc *desc;
int type, perm;
int len, len2;
struct stat st;
DIR *dd = NULL;
snprintf (path, sizeof (path), "/proc/%i/fd/", pid);
if (!(dd = opendir (path))) {
r_sys_perror ("opendir /proc/x/fd");
return NULL;
}
ret = r_list_new ();
if (!ret) {
closedir (dd);
return NULL;
}
ret->free = (RListFree)r_debug_desc_free;
while ((de = (struct dirent *)readdir(dd))) {
if (de->d_name[0] == '.') {
continue;
}
len = strlen (path);
len2 = strlen (de->d_name);
if (len + len2 + 1 >= sizeof(file)) {
r_list_free (ret);
closedir (dd);
eprintf ("Filename is too long");
return NULL;
}
memcpy (file, path, len);
memcpy (file + len, de->d_name, len2 + 1);
memset (buf, 0, sizeof(buf));
(void)readlink (file, buf, sizeof (buf) - 1);
buf[sizeof (buf)-1] = 0;
type = perm = 0;
if (stat (file, &st) != -1) {
type = st.st_mode & S_IFIFO ? 'P':
#ifdef S_IFSOCK
st.st_mode & S_IFSOCK ? 'S':
#endif
st.st_mode & S_IFCHR ? 'C':'-';
}
if (lstat(path, &st) != -1) {
if (st.st_mode & S_IRUSR) {
perm |= R_PERM_R;
}
if (st.st_mode & S_IWUSR) {
perm |= R_PERM_W;
}
}
//TODO: Offset
desc = r_debug_desc_new (atoi (de->d_name), buf, perm, type, 0);
if (!desc) {
break;
}
r_list_append (ret, desc);
}
closedir (dd);
return ret;
}
#endif