#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include "_libproc.h"
#if defined(__aarch64__)
#define AARCH64_BRK 0xd4200000
#define AARCH64_BRK_IMM16_SHIFT 5
#define AARCH64_BRK_IMM16_VAL (0xd << AARCH64_BRK_IMM16_SHIFT)
#define BREAKPOINT_INSTR (AARCH64_BRK | AARCH64_BRK_IMM16_VAL)
#define BREAKPOINT_INSTR_SZ 4
#elif defined(__amd64__) || defined(__i386__)
#define BREAKPOINT_INSTR 0xcc
#define BREAKPOINT_INSTR_SZ 1
#define BREAKPOINT_ADJUST_SZ BREAKPOINT_INSTR_SZ
#elif defined(__arm__)
#define BREAKPOINT_INSTR 0xe7ffffff
#define BREAKPOINT_INSTR_SZ 4
#elif defined(__powerpc__)
#define BREAKPOINT_INSTR 0x7fe00008
#define BREAKPOINT_INSTR_SZ 4
#elif defined(__riscv)
#define BREAKPOINT_INSTR 0x00100073
#define BREAKPOINT_INSTR_SZ 4
#else
#error "Add support for your architecture"
#endif
typedef uint32_t instr_t;
static int
proc_stop(struct proc_handle *phdl)
{
int status;
if (kill(proc_getpid(phdl), SIGSTOP) == -1) {
DPRINTF("kill %d", proc_getpid(phdl));
return (-1);
} else if (waitpid(proc_getpid(phdl), &status, WSTOPPED) == -1) {
DPRINTF("waitpid %d", proc_getpid(phdl));
return (-1);
} else if (!WIFSTOPPED(status)) {
DPRINTFX("waitpid: unexpected status 0x%x", status);
return (-1);
}
return (0);
}
int
proc_bkptset(struct proc_handle *phdl, uintptr_t address,
unsigned long *saved)
{
struct ptrace_io_desc piod;
int ret = 0, stopped;
instr_t instr;
*saved = 0;
if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
phdl->status == PS_IDLE) {
errno = ENOENT;
return (-1);
}
DPRINTFX("adding breakpoint at 0x%lx", (unsigned long)address);
stopped = 0;
if (phdl->status != PS_STOP) {
if (proc_stop(phdl) != 0)
return (-1);
stopped = 1;
}
instr = 0;
piod.piod_op = PIOD_READ_I;
piod.piod_offs = (void *)address;
piod.piod_addr = &instr;
piod.piod_len = BREAKPOINT_INSTR_SZ;
if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
DPRINTF("ERROR: couldn't read instruction at address 0x%jx",
(uintmax_t)address);
ret = -1;
goto done;
}
*saved = instr;
instr = BREAKPOINT_INSTR;
piod.piod_op = PIOD_WRITE_I;
piod.piod_offs = (void *)address;
piod.piod_addr = &instr;
piod.piod_len = BREAKPOINT_INSTR_SZ;
if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
DPRINTF("ERROR: couldn't write instruction at address 0x%jx",
(uintmax_t)address);
ret = -1;
goto done;
}
done:
if (stopped)
proc_continue(phdl);
return (ret);
}
int
proc_bkptdel(struct proc_handle *phdl, uintptr_t address,
unsigned long saved)
{
struct ptrace_io_desc piod;
int ret = 0, stopped;
instr_t instr;
if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
phdl->status == PS_IDLE) {
errno = ENOENT;
return (-1);
}
DPRINTFX("removing breakpoint at 0x%lx", (unsigned long)address);
stopped = 0;
if (phdl->status != PS_STOP) {
if (proc_stop(phdl) != 0)
return (-1);
stopped = 1;
}
instr = saved;
piod.piod_op = PIOD_WRITE_I;
piod.piod_offs = (void *)address;
piod.piod_addr = &instr;
piod.piod_len = BREAKPOINT_INSTR_SZ;
if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
DPRINTF("ERROR: couldn't write instruction at address 0x%jx",
(uintmax_t)address);
ret = -1;
}
if (stopped)
proc_continue(phdl);
return (ret);
}
void
proc_bkptregadj(unsigned long *pc)
{
(void)pc;
#ifdef BREAKPOINT_ADJUST_SZ
*pc = *pc - BREAKPOINT_ADJUST_SZ;
#endif
}
int
proc_bkptexec(struct proc_handle *phdl, unsigned long saved)
{
unsigned long pc;
unsigned long samesaved;
int status;
if (proc_regget(phdl, REG_PC, &pc) < 0) {
DPRINTFX("ERROR: couldn't get PC register");
return (-1);
}
proc_bkptregadj(&pc);
if (proc_bkptdel(phdl, pc, saved) < 0) {
DPRINTFX("ERROR: couldn't delete breakpoint");
return (-1);
}
proc_regset(phdl, REG_PC, pc);
if (ptrace(PT_STEP, proc_getpid(phdl), (caddr_t)1, 0) < 0) {
DPRINTFX("ERROR: ptrace step failed");
return (-1);
}
proc_wstatus(phdl);
status = proc_getwstat(phdl);
if (!WIFSTOPPED(status)) {
DPRINTFX("ERROR: don't know why process stopped");
return (-1);
}
if (proc_bkptset(phdl, pc, &samesaved) < 0) {
DPRINTFX("ERROR: couldn't restore breakpoint");
return (-1);
}
assert(samesaved == saved);
return (0);
}