rizin/librz/debug/p/native/linux/linux_debug.c
Florian Märkl 3b0b966b29 Remove dbg.bpsize and instead calculate when needed
In practice, dbg.bpsize was always automatically set to
rz_bp_size(core->dbg->bp, core->analysis->bits) whenever the global bits
changed. But this breaks as soon as different bits are needed for where
the breakpoint is supposed to be set vs. the current value of asm.bits
(for example for thumb/arm32).
So this removes the global bpsize and always calculates it for the
specific breakpoint address.
As a sidenote, dbg.bpsize was not even used for manual db (for dcu it
was though).
2022-01-26 17:12:26 +01:00

1366 lines
36 KiB
C

// SPDX-FileCopyrightText: 2009-2019 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_userconf.h>
#if DEBUGGER
#include <rz_debug.h>
#include <rz_reg.h>
#include <rz_lib.h>
#include <rz_analysis.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>
#include "linux_ptrace.h"
#ifdef __GLIBC__
#define HAVE_YMM 1
#else
#define HAVE_YMM 0
#endif
char *linux_reg_profile(RzDebug *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 & RZ_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 & RZ_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"
}
#elif __powerpc__
if (dbg->bits & RZ_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(RzDebug *dbg);
static char *read_link(int pid, const char *file);
static bool linux_attach_single_pid(RzDebug *dbg, int ptid);
static void linux_remove_thread(RzDebug *dbg, int pid);
static void linux_add_new_thread(RzDebug *dbg, int tid);
static bool linux_stop_thread(RzDebug *dbg, int tid);
static bool linux_kill_thread(int tid, int signo);
static void linux_dbg_wait_break_main(RzDebug *dbg);
static void linux_dbg_wait_break(RzDebug *dbg);
static RzDebugReasonType linux_handle_new_task(RzDebug *dbg, int tid);
int linux_handle_signals(RzDebug *dbg, int tid) {
siginfo_t siginfo = { 0 };
int ret = rz_debug_ptrace(dbg, PTRACE_GETSIGINFO, tid, 0, (rz_ptrace_data_t)(size_t)&siginfo);
if (ret == -1) {
/* ESRCH means the process already went away :-/ */
if (errno == ESRCH) {
dbg->reason.type = RZ_DEBUG_REASON_DEAD;
return true;
}
rz_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 = RZ_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 = rz_debug_reg_get(dbg, "PC");
RzBreakpointItem *b = rz_bp_get_ending_at(dbg->bp, pc_addr);
if (b && b->internal) {
char *p = strstr(b->data, "dbg.");
if (p) {
if (rz_str_startswith(p, "dbg.libs")) {
const char *name;
if (strstr(b->data, "sym.imp.dlopen")) {
name = rz_reg_get_name(dbg->reg, RZ_REG_NAME_A0);
} else {
name = rz_reg_get_name(dbg->reg, RZ_REG_NAME_A1);
}
b->data = rz_str_appendf(b->data, ";ps@r:%s", name);
dbg->reason.type = RZ_DEBUG_REASON_NEW_LIB;
break;
} else if (rz_str_startswith(p, "dbg.unlibs")) {
dbg->reason.type = RZ_DEBUG_REASON_EXIT_LIB;
break;
}
}
}
}
if (dbg->reason.type != RZ_DEBUG_REASON_NEW_LIB &&
dbg->reason.type != RZ_DEBUG_REASON_EXIT_LIB) {
if (siginfo.si_code == TRAP_TRACE) {
dbg->reason.type = RZ_DEBUG_REASON_STEP;
} else {
dbg->reason.bp_addr = (ut64)(size_t)siginfo.si_addr;
dbg->reason.type = RZ_DEBUG_REASON_BREAKPOINT;
// Switch to the thread that hit the breakpoint
rz_debug_select(dbg, dbg->pid, tid);
dbg->tid = tid;
}
}
} break;
case SIGINT:
dbg->reason.type = RZ_DEBUG_REASON_USERSUSP;
break;
case SIGABRT: // 6 / SIGIOT // SIGABRT
dbg->reason.type = RZ_DEBUG_REASON_ABORT;
break;
case SIGSEGV:
dbg->reason.type = RZ_DEBUG_REASON_SEGFAULT;
break;
case SIGCHLD:
dbg->reason.type = RZ_DEBUG_REASON_SIGNAL;
break;
default:
break;
}
if (dbg->reason.signum != SIGTRAP &&
(dbg->reason.signum != SIGINT || !rz_cons_is_breaked())) {
eprintf("[+] SIGNAL %d errno=%d addr=0x%08" PFMT64x
" code=%d si_pid=%d ret=%d\n",
siginfo.si_signo, siginfo.si_errno,
(ut64)(size_t)siginfo.si_addr, siginfo.si_code, siginfo.si_pid, 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(RzDebug *dbg) {
RzListIter *iter;
RzBreakpointItem *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;
rz_debug_select(dbg, dbg->forked_pid, dbg->forked_pid);
// Unset all hw breakpoints in the child process
rz_debug_reg_sync(dbg, RZ_REG_TYPE_DRX, false);
rz_list_foreach (dbg->bp->bps, iter, b) {
rz_debug_drx_unset(dbg, rz_bp_get_index_at(dbg->bp, b->addr));
}
rz_debug_reg_sync(dbg, RZ_REG_TYPE_DRX, true);
// Unset software breakpoints in the child process
rz_debug_bp_update(dbg);
rz_bp_restore(dbg->bp, false);
// Return to the parent
dbg->pid = prev_pid;
dbg->tid = prev_tid;
rz_debug_select(dbg, dbg->pid, dbg->pid);
// Restore sw breakpoints in the parent
rz_bp_restore(dbg->bp, true);
}
#ifdef PT_GETEVENTMSG
/*
* @brief Handle PTRACE_EVENT_* when receiving SIGTRAP
*
* @param dowait Do waitpid to consume any signal in the newly created task
* @return RzDebugReasonType,
* - RZ_DEBUG_REASON_UNKNOWN if the ptrace_event cannot be handled,
* - RZ_DEBUG_REASON_ERROR if a ptrace command failed.
*
* NOTE: This API was added in Linux 2.5.46
*/
RzDebugReasonType linux_ptrace_event(RzDebug *dbg, int ptid, int status, bool dowait) {
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 RZ_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:
// Get the tid of the new thread
if (rz_debug_ptrace(dbg, PTRACE_GETEVENTMSG, ptid, 0, (rz_ptrace_data_t)(size_t)&data) == -1) {
rz_sys_perror("ptrace GETEVENTMSG");
return RZ_DEBUG_REASON_ERROR;
}
if (dowait) {
// The new child has a pending SIGSTOP. We can't affect it until it
// hits the SIGSTOP, but we're already attached. */
if (waitpid((int)data, &status, 0) == -1) {
perror("waitpid");
}
}
linux_add_new_thread(dbg, (int)data);
if (dbg->trace_clone) {
rz_debug_select(dbg, dbg->pid, (int)data);
}
eprintf("(%d) Created thread %d\n", ptid, (int)data);
return RZ_DEBUG_REASON_NEW_TID;
case PTRACE_EVENT_VFORK:
case PTRACE_EVENT_FORK:
// Get the pid of the new process
if (rz_debug_ptrace(dbg, PTRACE_GETEVENTMSG, ptid, 0, (rz_ptrace_data_t)(size_t)&data) == -1) {
rz_sys_perror("ptrace GETEVENTMSG");
return RZ_DEBUG_REASON_ERROR;
}
dbg->forked_pid = data;
if (dowait) {
// 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, 0) == -1) {
perror("waitpid");
}
}
eprintf("(%d) Created process %d\n", ptid, (int)data);
if (!dbg->trace_forks) {
// 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 (rz_debug_ptrace(dbg, PTRACE_DETACH, dbg->forked_pid, NULL, (rz_ptrace_data_t)(size_t)NULL) == -1) {
perror("PTRACE_DETACH");
}
}
return RZ_DEBUG_REASON_NEW_PID;
case PTRACE_EVENT_EXIT:
// Get the exit status of the exiting task
if (rz_debug_ptrace(dbg, PTRACE_GETEVENTMSG, ptid, 0, (rz_ptrace_data_t)(size_t)&data) == -1) {
rz_sys_perror("ptrace GETEVENTMSG");
return RZ_DEBUG_REASON_ERROR;
}
// TODO: Check other processes exit if dbg->trace_forks is on
if (ptid != dbg->pid) {
eprintf("(%d) Thread exited with status=0x%" PFMT64x "\n", ptid, (ut64)data);
return RZ_DEBUG_REASON_EXIT_TID;
} else {
eprintf("(%d) Process exited with status=0x%" PFMT64x "\n", ptid, (ut64)data);
return RZ_DEBUG_REASON_EXIT_PID;
}
default:
eprintf("Unknown PTRACE_EVENT encountered: %d\n", pt_evt);
break;
}
return RZ_DEBUG_REASON_UNKNOWN;
}
#endif
/*
* @brief Search for the parent of the newly created task and
* handle the pending SIGTRAP with PTRACE_EVENT_*
*
* @param tid, TID of the new task
* @return RzDebugReasonType, Debug reason
*/
static RzDebugReasonType linux_handle_new_task(RzDebug *dbg, int tid) {
int ret, status;
if (dbg->threads) {
RzDebugPid *th;
RzListIter *it;
// Search for SIGTRAP with PTRACE_EVENT_* in other threads.
rz_list_foreach (dbg->threads, it, th) {
if (th->pid == tid) {
continue;
}
// Retrieve the signal without consuming it with PTRACE_GETSIGINFO
siginfo_t siginfo = { 0 };
ret = rz_debug_ptrace(dbg, PTRACE_GETSIGINFO, th->pid, 0, (rz_ptrace_data_t)(size_t)&siginfo);
// Skip if PTRACE_GETSIGINFO fails when the thread is running.
if (ret == -1) {
continue;
}
#ifdef PT_GETEVENTMSG
// NOTE: This API was added in Linux 2.5.46
if (siginfo.si_signo == SIGTRAP) {
// si_code = (SIGTRAP | PTRACE_EVENT_* << 8)
int pt_evt = siginfo.si_code >> 8;
// Handle PTRACE_EVENT_* that creates a new task (fork/clone)
switch (pt_evt) {
case PTRACE_EVENT_CLONE:
case PTRACE_EVENT_VFORK:
case PTRACE_EVENT_FORK:
ret = waitpid(th->pid, &status, 0);
return linux_ptrace_event(dbg, ret, status, false);
default:
break;
}
}
#endif
}
}
return RZ_DEBUG_REASON_UNKNOWN;
}
int linux_step(RzDebug *dbg) {
int ret = false;
int pid = dbg->tid;
ret = rz_debug_ptrace(dbg, PTRACE_SINGLESTEP, pid, 0, 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(RzDebug *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;
// PTRACE_SETOPTIONS can fail because of the asynchronous nature of ptrace
// If the target is traced, the loop will always end with success
while (rz_debug_ptrace(dbg, PTRACE_SETOPTIONS, pid, 0, (rz_ptrace_data_t)(size_t)traceflags) == -1) {
void *bed = rz_cons_sleep_begin();
usleep(1000);
rz_cons_sleep_end(bed);
}
return true;
}
static void linux_detach_all(RzDebug *dbg) {
RzList *th_list = dbg->threads;
if (th_list) {
RzDebugPid *th;
RzListIter *it;
rz_list_foreach (th_list, it, th) {
if (th->pid != dbg->main_pid) {
if (rz_debug_ptrace(dbg, PTRACE_DETACH, th->pid, NULL, (rz_ptrace_data_t)(size_t)NULL) == -1) {
perror("PTRACE_DETACH");
}
}
}
}
// Detaching from main proc
if (rz_debug_ptrace(dbg, PTRACE_DETACH, dbg->main_pid, NULL, (rz_ptrace_data_t)(size_t)NULL) == -1) {
perror("PTRACE_DETACH");
}
}
static void linux_remove_thread(RzDebug *dbg, int tid) {
if (dbg->threads) {
RzDebugPid *th;
RzListIter *it;
rz_list_foreach (dbg->threads, it, th) {
if (th->pid == tid) {
rz_list_delete(dbg->threads, it);
dbg->n_threads--;
break;
}
}
}
}
bool linux_select(RzDebug *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(RzDebug *dbg, int pid) {
linux_detach_all(dbg);
if (dbg->threads) {
rz_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;
rz_debug_select(dbg, pid, pid);
return true;
}
static void linux_dbg_wait_break_main(RzDebug *dbg) {
// Get the debugger and debuggee process group ID
pid_t dpgid = getpgid(0);
if (dpgid == -1) {
rz_sys_perror("getpgid");
return;
}
pid_t tpgid = getpgid(dbg->pid);
if (tpgid == -1) {
rz_sys_perror("getpgid");
return;
}
// If the debuggee process is created by the debugger, do nothing because
// SIGINT is already sent to both the debugger and debuggee in the same
// process group.
// If the debuggee is attached by the debugger, send SIGINT to the debuggee
// in another process group.
if (dpgid != tpgid) {
if (!linux_kill_thread(dbg->pid, SIGINT)) {
eprintf("Could not interrupt pid (%d)\n", dbg->pid);
}
}
}
static void linux_dbg_wait_break(RzDebug *dbg) {
if (!linux_kill_thread(dbg->pid, SIGINT)) {
eprintf("Could not interrupt pid (%d)\n", dbg->pid);
}
}
RzDebugReasonType linux_dbg_wait(RzDebug *dbg, int pid) {
RzDebugReasonType reason = RZ_DEBUG_REASON_UNKNOWN;
int tid = 0;
int status, flags = __WALL;
int ret = -1;
if (pid == -1) {
flags |= WNOHANG;
}
for (;;) {
// In the main context, SIGINT is propagated to the debuggee if it is
// in the same process group. Otherwise, the task is running in
// background and SIGINT will not be propagated to the debuggee.
if (rz_cons_context_is_main()) {
rz_cons_break_push((RzConsBreak)linux_dbg_wait_break_main, dbg);
} else {
rz_cons_break_push((RzConsBreak)linux_dbg_wait_break, dbg);
}
void *bed = rz_cons_sleep_begin();
if (dbg->continue_all_threads) {
ret = waitpid(-1, &status, flags);
} else {
ret = waitpid(pid, &status, flags);
}
rz_cons_sleep_end(bed);
rz_cons_break_pop();
if (ret < 0) {
// Continue when interrupted by user;
if (errno == EINTR) {
continue;
}
perror("waitpid");
break;
} else if (ret == 0) {
// Unset WNOHANG to call next waitpid in blocking mode.
flags &= ~WNOHANG;
} else {
tid = ret;
// Handle SIGTRAP with PTRACE_EVENT_*
reason = linux_ptrace_event(dbg, tid, status, true);
if (reason != RZ_DEBUG_REASON_UNKNOWN) {
break;
}
if (WIFEXITED(status)) {
if (tid == dbg->main_pid) {
rz_list_free(dbg->threads);
dbg->threads = NULL;
reason = RZ_DEBUG_REASON_DEAD;
eprintf("(%d) Process terminated with status %d\n", tid, WEXITSTATUS(status));
break;
} else {
eprintf("(%d) Child terminated with status %d\n", tid, WEXITSTATUS(status));
linux_remove_thread(dbg, tid);
continue;
}
} else if (WIFSIGNALED(status)) {
eprintf("child received signal %d\n", WTERMSIG(status));
reason = RZ_DEBUG_REASON_SIGNAL;
} else if (WIFSTOPPED(status)) {
// If tid is not in the thread list and stopped by SIGSTOP,
// handle it as a new task.
if (!rz_list_find(dbg->threads, &tid, &match_pid) &&
WSTOPSIG(status) == SIGSTOP) {
reason = linux_handle_new_task(dbg, tid);
if (reason != RZ_DEBUG_REASON_UNKNOWN) {
break;
}
}
if (linux_handle_signals(dbg, tid)) {
reason = dbg->reason.type;
} else {
eprintf("can't handle signals\n");
return RZ_DEBUG_REASON_ERROR;
}
#ifdef WIFCONTINUED
} else if (WIFCONTINUED(status)) {
eprintf("child continued...\n");
reason = RZ_DEBUG_REASON_NONE;
#endif
} else if (status == 1) {
eprintf("debugger is dead with status 1\n");
reason = RZ_DEBUG_REASON_DEAD;
} else if (status == 0) {
eprintf("debugger is dead with status 0\n");
reason = RZ_DEBUG_REASON_DEAD;
} else {
if (ret != tid) {
reason = RZ_DEBUG_REASON_NEW_PID;
} else {
eprintf("returning from wait without knowing why...\n");
}
}
if (reason != RZ_DEBUG_REASON_UNKNOWN) {
break;
}
}
}
dbg->reason.tid = tid;
return reason;
}
int match_pid(const void *pid_o, const void *th_o) {
int pid = *(int *)pid_o;
RzDebugPid *th = (RzDebugPid *)th_o;
return (pid == th->pid) ? 0 : 1;
}
static void linux_add_new_thread(RzDebug *dbg, int tid) {
int uid = getuid(); // XXX
char info[1024] = { 0 };
RzDebugPid *tid_info;
if (!procfs_pid_slurp(tid, "status", info, sizeof(info))) {
tid_info = fill_pid_info(info, NULL, tid);
} else {
tid_info = rz_debug_pid_new("new_path", tid, uid, 's', 0);
}
linux_set_options(dbg, tid);
rz_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(RzDebug *dbg, int tid) {
int status, ret;
siginfo_t siginfo = { 0 };
// Return if the thread is already stopped
ret = rz_debug_ptrace(dbg, PTRACE_GETSIGINFO, tid, 0,
(rz_ptrace_data_t)(intptr_t)&siginfo);
if (ret == 0) {
return true;
}
if (linux_kill_thread(tid, SIGSTOP)) {
if ((ret = waitpid(tid, &status, 0)) == -1) {
perror("waitpid");
}
return ret == tid;
}
return false;
}
bool linux_stop_threads(RzDebug *dbg, int except) {
bool ret = true;
if (dbg->threads) {
RzDebugPid *th;
RzListIter *it;
rz_list_foreach (dbg->threads, it, th) {
if (th->pid && th->pid != except) {
if (!linux_stop_thread(dbg, th->pid)) {
ret = false;
}
}
}
}
return ret;
}
static bool linux_attach_single_pid(RzDebug *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 (rz_debug_ptrace(dbg, PTRACE_GETSIGINFO, ptid, NULL,
(rz_ptrace_data_t)&sig) == -1) {
if (rz_debug_ptrace(dbg, PTRACE_ATTACH, ptid, NULL, 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(dbg, ptid)) {
eprintf("Could not stop pid (%d)\n", ptid);
return false;
}
}
if (!linux_set_options(dbg, ptid)) {
eprintf("failed set_options on %d\n", ptid);
return false;
}
return true;
}
static RzList *get_pid_thread_list(RzDebug *dbg, int main_pid) {
RzList *list = rz_list_new();
if (list) {
list = linux_thread_list(dbg, main_pid, list);
dbg->main_pid = main_pid;
}
return list;
}
int linux_attach(RzDebug *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);
} 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 not, attach it
if (!rz_list_find(dbg->threads, &pid, &match_pid)) {
linux_attach_single_pid(dbg, pid);
}
}
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;
}
RzDebugInfo *linux_info(RzDebug *dbg, const char *arg) {
char proc_buff[1024];
RzDebugInfo *rdi = RZ_NEW0(RzDebugInfo);
if (!rdi) {
return NULL;
}
RzList *th_list;
bool list_alloc = false;
if (dbg->threads) {
th_list = dbg->threads;
} else {
th_list = rz_list_new();
list_alloc = true;
if (th_list) {
th_list = linux_thread_list(dbg, dbg->pid, th_list);
}
}
RzDebugPid *th;
RzListIter *it;
bool found = false;
rz_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 = rz_file_slurp(proc_buff, NULL);
snprintf(proc_buff, sizeof(proc_buff), "/proc/%d/stack", rdi->pid);
rdi->kernel_stack = rz_file_slurp(proc_buff, NULL);
rdi->status = found ? th->status : RZ_DBG_PROC_STOP;
if (list_alloc) {
rz_list_free(th_list);
}
return rdi;
}
RzDebugPid *fill_pid_info(const char *info, const char *path, int tid) {
RzDebugPid *pid_info = RZ_NEW0(RzDebugPid);
if (!pid_info) {
return NULL;
}
char *ptr = strstr(info, "State:");
if (ptr) {
switch (*(ptr + 7)) {
case 'R':
pid_info->status = RZ_DBG_PROC_RUN;
break;
case 'S':
pid_info->status = RZ_DBG_PROC_SLEEP;
break;
case 'T':
case 't':
pid_info->status = RZ_DBG_PROC_STOP;
break;
case 'Z':
pid_info->status = RZ_DBG_PROC_ZOMBIE;
break;
case 'X':
pid_info->status = RZ_DBG_PROC_DEAD;
break;
default:
pid_info->status = RZ_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;
}
RzList *linux_pid_list(int pid, RzList *list) {
list->free = (RzListFree)&rz_debug_pid_free;
DIR *dh = NULL;
struct dirent *de = NULL;
char path[PATH_MAX], info[PATH_MAX];
int i = -1;
RzDebugPid *pid_info = NULL;
dh = opendir("/proc");
if (!dh) {
rz_sys_perror("opendir /proc");
rz_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 = rz_debug_pid_new(path, i, 0, RZ_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) {
rz_list_append(list, pid_info);
}
}
closedir(dh);
return list;
}
RzList *linux_thread_list(RzDebug *dbg, int pid, RzList *list) {
int i = 0, thid = 0;
char *ptr, buf[PATH_MAX];
RzDebugPid *pid_info = NULL;
ut64 pc = 0;
int prev_tid = dbg->tid;
if (!pid) {
rz_list_free(list);
return NULL;
}
list->free = (RzListFree)&rz_debug_pid_free;
/* if this process has a task directory, use that */
snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
if (rz_file_is_directory(buf)) {
struct dirent *de;
DIR *dh = opendir(buf);
// Update the process' memory maps to set correct paths
rz_debug_map_sync(dbg);
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;
}
rz_debug_reg_sync(dbg, RZ_REG_TYPE_GPR, false);
pc = rz_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 = rz_debug_pid_new(NULL, tid, uid, 's', pc);
}
rz_list_append(list, pid_info);
dbg->n_threads++;
}
closedir(dh);
// Return to the original thread
linux_attach_single_pid(dbg, prev_tid);
dbg->tid = prev_tid;
rz_debug_reg_sync(dbg, RZ_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++);
}
rz_list_append(list, rz_debug_pid_new(buf, i, uid, 's', 0));
}
}
}
return list;
}
#define PRINT_FPU(fpregs) \
rz_cons_printf("cwd = 0x%04x ; control ", (fpregs).cwd); \
rz_cons_printf("swd = 0x%04x ; status\n", (fpregs).swd); \
rz_cons_printf("ftw = 0x%04x ", (fpregs).ftw); \
rz_cons_printf("fop = 0x%04x\n", (fpregs).fop); \
rz_cons_printf("rip = 0x%016" PFMT64x " ", (ut64)(fpregs).rip); \
rz_cons_printf("rdp = 0x%016" PFMT64x "\n", (ut64)(fpregs).rdp); \
rz_cons_printf("mxcsr = 0x%08x ", (fpregs).mxcsr); \
rz_cons_printf("mxcr_mask = 0x%08x\n", (fpregs).mxcr_mask)
#define PRINT_FPU_NOXMM(fpregs) \
rz_cons_printf("cwd = 0x%04lx ; control ", (fpregs).cwd); \
rz_cons_printf("swd = 0x%04lx ; status\n", (fpregs).swd); \
rz_cons_printf("twd = 0x%04lx ", (fpregs).twd); \
rz_cons_printf("fip = 0x%04lx \n", (fpregs).fip); \
rz_cons_printf("fcs = 0x%04lx ", (fpregs).fcs); \
rz_cons_printf("foo = 0x%04lx \n", (fpregs).foo); \
rz_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);
rz_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
rz_cons_printf("---- x86-64 ----\n");
PRINT_FPU(fpregs);
rz_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);
rz_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];
rz_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--) {
rz_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
rz_cons_printf(" %Le %Lf\n", *st_ld, *st_ld);
} else {
rz_cons_printf("\n");
}
}
#endif // __ANDROID__
#elif __i386__
int i, j;
#if __ANDROID__
struct user_fpxregs_struct fpxregs = *(struct user_fpxregs_struct *)f;
rz_cons_printf("---- x86-32 ----\n");
rz_cons_printf("cwd = 0x%04x ; control ", fpxregs.cwd);
rz_cons_printf("swd = 0x%04x ; status\n", fpxregs.swd);
rz_cons_printf("twd = 0x%04x ", fpxregs.twd);
rz_cons_printf("fop = 0x%04x\n", fpxregs.fop);
rz_cons_printf("fip = 0x%08x\n", (ut32)fpxregs.fip);
rz_cons_printf("fcs = 0x%08x\n", (ut32)fpxregs.fcs);
rz_cons_printf("foo = 0x%08x\n", (ut32)fpxregs.foo);
rz_cons_printf("fos = 0x%08x\n", (ut32)fpxregs.fos);
rz_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);
rz_cons_printf("xmm%d = %08x %08x %08x %08x ", i, (int)a[0],
(int)a[1], (int)a[2], (int)a[3]);
rz_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;
rz_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);
rz_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(RzDebug *dbg, int type, ut8 *buf, int size) {
bool showfpu = false;
int pid = dbg->tid;
int ret = 0;
if (type < -1) {
showfpu = true;
type = -type;
}
switch (type) {
case RZ_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 = rz_debug_ptrace(dbg, PTRACE_PEEKUSER, pid,
(void *)rz_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 RZ_REG_TYPE_FPU:
case RZ_REG_TYPE_MMX:
case RZ_REG_TYPE_XMM:
#if __POWERPC__
return false;
#elif __x86_64__ || __i386__
{
struct user_fpregs_struct fpregs;
if (type == RZ_REG_TYPE_FPU) {
#if __x86_64__
ret = rz_debug_ptrace(dbg, PTRACE_GETFPREGS, pid, NULL, &fpregs);
if (ret != 0) {
rz_sys_perror("PTRACE_GETFPREGS");
return false;
}
if (showfpu) {
print_fpu((void *)&fpregs);
}
size = RZ_MIN(sizeof(fpregs), size);
memcpy(buf, &fpregs, size);
return size;
#elif __i386__
#if !__ANDROID__
struct user_fpxregs_struct fpxregs;
ret = rz_debug_ptrace(dbg, PTRACE_GETFPXREGS, pid, NULL, &fpxregs);
if (ret == 0) {
if (showfpu) {
print_fpu((void *)&fpxregs);
}
size = RZ_MIN(sizeof(fpxregs), size);
memcpy(buf, &fpxregs, size);
return size;
} else {
ret = rz_debug_ptrace(dbg, PTRACE_GETFPREGS, pid, NULL, &fpregs);
if (showfpu) {
print_fpu((void *)&fpregs);
}
if (ret != 0) {
rz_sys_perror("PTRACE_GETFPREGS");
return false;
}
size = RZ_MIN(sizeof(fpregs), size);
memcpy(buf, &fpregs, size);
return size;
}
#else
ret = rz_debug_ptrace(dbg, PTRACE_GETFPREGS, pid, NULL, &fpregs);
if (showfpu) {
print_fpu((void *)&fpregs);
}
if (ret != 0) {
rz_sys_perror("PTRACE_GETFPREGS");
return false;
}
size = RZ_MIN(sizeof(fpregs), size);
memcpy(buf, &fpregs, size);
return size;
#endif // !__ANDROID__
#endif // __i386__
}
}
#else
#warning getfpregs not implemented for this platform
#endif
break;
case RZ_REG_TYPE_SEG:
case RZ_REG_TYPE_FLG:
case RZ_REG_TYPE_GPR: {
RZ_DEBUG_REG_T regs;
memset(&regs, 0, sizeof(regs));
memset(buf, 0, size);
#if (__arm64__ || __aarch64__ || __s390x__) && defined(PTRACE_GETREGSET)
struct iovec io = {
.iov_base = &regs,
.iov_len = sizeof(regs)
};
ret = rz_debug_ptrace(dbg, PTRACE_GETREGSET, pid, 1, &io);
// ret = ptrace (PTRACE_GETREGSET, pid, (void*)(size_t)(NT_PRSTATUS), NULL); // &io);
if (ret != 0) {
rz_sys_perror("PTRACE_GETREGSET");
return false;
}
#elif __BSD__ && (__POWERPC__ || __sparc__)
ret = rz_debug_ptrace(dbg, PTRACE_GETREGS, pid, &regs, NULL);
#else
/* linux -{arm/mips/riscv/x86/x86_64} */
ret = rz_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) {
rz_sys_perror("PTRACE_GETREGS");
return false;
}
size = RZ_MIN(sizeof(regs), size);
memcpy(buf, &regs, size);
return size;
} break;
case RZ_REG_TYPE_YMM: {
#if HAVE_YMM && __x86_64__ && defined(PTRACE_GETREGSET)
ut32 ymm_space[128]; // full ymm registers
struct _xstate xstate;
struct iovec iov;
iov.iov_base = &xstate;
iov.iov_len = sizeof(struct _xstate);
ret = rz_debug_ptrace(dbg, PTRACE_GETREGSET, pid, (void *)NT_X86_XSTATE, &iov);
if (ret != 0) {
rz_sys_perror("PTRACE_GETREGSET");
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++) {
#ifdef __ANDROID__
ymm_space[ri * 8 + rj] = ((struct _libc_fpstate *)&xstate.fpstate)->_xmm[ri].element[rj];
#else
ymm_space[ri * 8 + rj] = xstate.fpstate._xmm[ri].element[rj];
#endif
}
for (rj = 0; rj < 4; rj++) {
ymm_space[ri * 8 + (rj + 4)] = xstate.ymmh.ymmh_space[ri * 4 + rj];
}
}
size = RZ_MIN(sizeof(ymm_space), size);
memcpy(buf, &ymm_space, size);
return size;
#endif
return false;
} break;
}
return false;
}
int linux_reg_write(RzDebug *dbg, int type, const ut8 *buf, int size) {
int pid = dbg->tid;
if (type == RZ_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 (rz_debug_ptrace(dbg, PTRACE_POKEUSER, pid,
(void *)rz_offsetof(struct user, u_debugreg[i]), (rz_ptrace_data_t)val[i])) {
eprintf("ptrace error for dr %d\n", i);
rz_sys_perror("ptrace POKEUSER");
}
}
return sizeof(RZ_DEBUG_REG_T);
#else
return false;
#endif
}
if (type == RZ_REG_TYPE_GPR) {
#if __arm64__ || __aarch64__ || __s390x__
struct iovec io = {
.iov_base = (void *)buf,
.iov_len = sizeof(RZ_DEBUG_REG_T)
};
int ret = rz_debug_ptrace(dbg, PTRACE_SETREGSET, pid, (void *)(size_t)NT_PRSTATUS, (rz_ptrace_data_t)(size_t)&io);
#elif __POWERPC__ || __sparc__
int ret = rz_debug_ptrace(dbg, PTRACE_SETREGS, pid, buf, NULL);
#else
int ret = rz_debug_ptrace(dbg, PTRACE_SETREGS, pid, 0, (void *)buf);
#endif
#if DEAD_CODE
if (size > sizeof(RZ_DEBUG_REG_T)) {
size = sizeof(RZ_DEBUG_REG_T);
}
#endif
if (ret == -1) {
rz_sys_perror("reg_write");
return false;
}
return true;
}
if (type == RZ_REG_TYPE_FPU) {
#if __i386__ || __x86_64__
int ret = rz_debug_ptrace(dbg, PTRACE_SETFPREGS, pid, 0, (void *)buf);
return (ret != 0) ? false : true;
#endif
}
return false;
}
RzList *linux_desc_list(int pid) {
RzList *ret = NULL;
char path[512], file[512], buf[512];
struct dirent *de;
RzDebugDesc *desc;
int type, perm;
int len, len2;
struct stat st;
DIR *dd = NULL;
rz_strf(path, "/proc/%i/fd/", pid);
if (!(dd = opendir(path))) {
rz_sys_perror("opendir /proc/x/fd");
return NULL;
}
ret = rz_list_newf((RzListFree)rz_debug_desc_free);
if (!ret) {
closedir(dd);
return NULL;
}
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)) {
RZ_LOG_ERROR("Filename is too long.\n");
goto fail;
}
memcpy(file, path, len);
memcpy(file + len, de->d_name, len2 + 1);
buf[0] = 0;
if (readlink(file, buf, sizeof(buf) - 1) == -1) {
RZ_LOG_ERROR("readlink %s failed.\n", file);
goto fail;
}
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 |= RZ_PERM_R;
}
if (st.st_mode & S_IWUSR) {
perm |= RZ_PERM_W;
}
}
// TODO: Offset
desc = rz_debug_desc_new(atoi(de->d_name), buf, perm, type, 0);
if (!desc) {
break;
}
rz_list_append(ret, desc);
}
closedir(dd);
return ret;
fail:
rz_list_free(ret);
closedir(dd);
return NULL;
}
#endif