#if defined(DDBPROF) && (defined(__amd64__) || defined(__i386__))
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/exec_elf.h>
#include <ddb/db_elf.h>
#include <machine/db_machdep.h>
#include <ddb/db_extern.h>
#include <ddb/db_access.h>
#include <ddb/db_sym.h>
#include <ddb/db_interface.h>
#include <dev/dt/dtvar.h>
#define KPROBE_ENTRY 0x1
#define KPROBE_RETURN 0x2
extern db_symtab_t db_symtab;
extern char __kutext_end[];
extern int db_prof_on;
extern void db_prof_count(struct trapframe *);
extern vaddr_t db_get_probe_addr(struct trapframe *);
SLIST_HEAD(, dt_probe) *dtpf_entry;
SLIST_HEAD(, dt_probe) *dtpf_return;
int dt_prov_kprobe_alloc(struct dt_probe *, struct dt_softc *,
struct dt_pcb_list *, struct dtioc_req *);
int dt_prov_kprobe_hook(struct dt_provider *, ...);
int dt_prov_kprobe_dealloc(struct dt_probe *, struct dt_softc *,
struct dtioc_req *);
#define DTEVT_PROV_KPROBE (DTEVT_COMMON|DTEVT_FUNCARGS)
struct dt_provider dt_prov_kprobe = {
.dtpv_name = "kprobe",
.dtpv_alloc = dt_prov_kprobe_alloc,
.dtpv_enter = dt_prov_kprobe_hook,
.dtpv_leave = NULL,
.dtpv_dealloc = dt_prov_kprobe_dealloc,
};
uint32_t
ptr_hash(uint32_t a) {
a = (a + 0x7ed55d16) + (a<<12);
a = (a ^ 0xc761c23c) ^ (a>>19);
a = (a + 0x165667b1) + (a<<5);
a = (a + 0xd3a2646c) ^ (a<<9);
a = (a + 0xfd7046c5) + (a<<3);
a = (a ^ 0xb55a4f09) ^ (a>>16);
return a;
}
#define PPTSIZE (PAGE_SIZE * 30)
#define PPTMASK ((PPTSIZE / sizeof(struct dt_probe)) - 1)
#define INSTTOIDX(inst) (ptr_hash(inst) & PPTMASK)
#if defined(__amd64__)
#define IBT_SIZE 4
#define RTGD_MOV_SIZE 7
#define RTGD_XOR_SIZE 4
#define FRAME_SIZE (IBT_SIZE + RTGD_XOR_SIZE + RTGD_XOR_SIZE + 1 + 3)
#define RET_INST 0xc3
#define RET_SIZE 1
int
db_prologue_validate(Elf_Sym *symp)
{
uint8_t *inst = (uint8_t *)symp->st_value;
int off = symp->st_size - 1;
if (off < IBT_SIZE + RTGD_MOV_SIZE + RTGD_XOR_SIZE)
return -1;
if (inst[0] != 0xf3 || inst[1] != 0x0f ||
inst[2] != 0x1e || inst[3] != 0xfa)
return -1;
off = IBT_SIZE;
if (inst[off] != 0x4c || inst[off + 1] != 0x8b || inst[off + 2] != 0x1d)
return -1;
off += RTGD_MOV_SIZE;
if (inst[off] != 0x4c || inst[off + 1] != 0x33 || inst[off + 2] != 0x1c)
return -1;
off += RTGD_XOR_SIZE;
if (inst[off] != SSF_INST)
return -1;
if (inst[off + 1] != 0x48 || inst[off + 2] != 0x89 ||
inst[off + 3] != 0xe5)
return -1;
return off;
}
void
db_prologue_patch(vaddr_t addr, int restore)
{
uint8_t patch;
size_t size;
unsigned s;
CTASSERT(SSF_SIZE == BKPT_SIZE);
if (restore) {
patch = SSF_INST;
size = SSF_SIZE;
} else {
patch = BKPT_INST;
size = BKPT_SIZE;
}
s = intr_disable();
db_write_bytes(addr, size, &patch);
intr_restore(s);
}
int
db_epilogue_validate(Elf_Sym *symp)
{
uint8_t *inst = (uint8_t *)symp->st_value;
int off = symp->st_size - 1;
while (off > FRAME_SIZE + 2) {
if (inst[off - 1] == 0xcc && inst[off] == RET_INST)
return off;
off--;
}
return -1;
}
void
db_epilogue_patch(vaddr_t addr, int restore)
{
uint8_t patch;
size_t size;
unsigned s;
CTASSERT(RET_SIZE == BKPT_SIZE);
if (restore) {
patch = RET_INST;
size = RET_SIZE;
} else {
patch = BKPT_INST;
size = BKPT_SIZE;
}
s = intr_disable();
db_write_bytes(addr, size, &patch);
intr_restore(s);
}
#elif defined(__i386__)
#define POP_RBP_INST 0x5d
#define POP_RBP_SIZE 1
int
db_prologue_validate(Elf_Sym *symp)
{
uint8_t *inst = (uint8_t *)symp->st_value;
if (*inst != SSF_INST)
return -1;
return 0;
}
void
db_prologue_patch(vaddr_t addr, int restore)
{
uint8_t patch;
size_t size;
unsigned s;
CTASSERT(SSF_SIZE == BKPT_SIZE);
if (restore) {
patch = SSF_INST;
size = SSF_SIZE;
} else {
patch = BKPT_INST;
size = BKPT_SIZE;
}
s = intr_disable();
db_write_bytes(addr, size, &patch);
intr_restore(s);
}
int
db_epilogue_validate(Elf_Sym *symp)
{
vaddr_t limit = symp->st_value + symp->st_size;
#if 0
while(*((uint8_t *)inst) == 0xcc)
(*(uint8_t *)inst) -= 1;
#endif
if (*(uint8_t *)(limit - 2) != POP_RBP)
return -1;
return symp->st_size - 2;
}
void
db_epilogue_patch(vaddr_t addr, int restore)
{
uint8_t patch;
size_t size;
unsigned s;
CTASSERT(SSF_SIZE == BKPT_SIZE);
if (restore) {
patch = POP_RBP_INST;
size = POP_RBP_SIZE;
} else {
patch = BKPT_INST;
size = BKPT_SIZE;
}
s = intr_disable();
db_write_bytes(addr, size, &patch);
intr_restore(s);
}
#endif
int
dt_prov_kprobe_init(void)
{
struct dt_probe *dtp;
Elf_Sym *symp, *symtab_start, *symtab_end;
const char *strtab, *name;
vaddr_t inst;
int off, nb_sym, nb_probes = 0;
nb_sym = (db_symtab.end - db_symtab.start) / sizeof (Elf_Sym);
dtpf_entry = malloc(PPTSIZE, M_DT, M_NOWAIT|M_ZERO);
if (dtpf_entry == NULL)
return 0;
dtpf_return = malloc(PPTSIZE, M_DT, M_NOWAIT|M_ZERO);
if (dtpf_return == NULL) {
free(dtpf_entry, M_DT, PPTSIZE);
return 0;
}
db_symtab_t *stab = &db_symtab;
symtab_start = STAB_TO_SYMSTART(stab);
symtab_end = STAB_TO_SYMEND(stab);
strtab = db_elf_find_strtab(stab);
for (symp = symtab_start; symp < symtab_end; symp++) {
if (ELF_ST_TYPE(symp->st_info) != STT_FUNC)
continue;
inst = symp->st_value;
name = strtab + symp->st_name;
if (inst < KERNBASE || inst >= (vaddr_t)&__kutext_end)
continue;
if (strncmp(name, "dt_", 3) == 0 ||
strncmp(name, "trap", 4) == 0 ||
strncmp(name, "db_", 3) == 0)
continue;
off = db_prologue_validate(symp);
if (off < 0)
continue;
dtp = dt_dev_alloc_probe(name, "entry", &dt_prov_kprobe);
if (dtp == NULL)
break;
dtp->dtp_addr = inst + off;
dtp->dtp_type = KPROBE_ENTRY;
dtp->dtp_nargs = db_ctf_func_numargs(symp);
SLIST_INSERT_HEAD(&dtpf_entry[INSTTOIDX(dtp->dtp_addr)],
dtp, dtp_knext);
dt_dev_register_probe(dtp);
nb_probes++;
off = db_epilogue_validate(symp);
if (off < 0)
continue;
dtp = dt_dev_alloc_probe(name, "return", &dt_prov_kprobe);
if (dtp == NULL)
break;
dtp->dtp_addr = inst + off;
dtp->dtp_type = KPROBE_RETURN;
SLIST_INSERT_HEAD(&dtpf_return[INSTTOIDX(dtp->dtp_addr)],
dtp, dtp_knext);
dt_dev_register_probe(dtp);
nb_probes++;
}
return nb_probes;
}
int
dt_prov_kprobe_alloc(struct dt_probe *dtp, struct dt_softc *sc,
struct dt_pcb_list *plist, struct dtioc_req *dtrq)
{
struct dt_pcb *dp;
dp = dt_pcb_alloc(dtp, sc);
if (dp == NULL)
return ENOMEM;
dtp->dtp_ref++;
if (dtp->dtp_ref == 1) {
switch (dtp->dtp_type) {
case KPROBE_ENTRY:
db_prologue_patch(dtp->dtp_addr, 0);
break;
case KPROBE_RETURN:
db_epilogue_patch(dtp->dtp_addr, 0);
break;
default:
panic("unknown probe type %d", dtp->dtp_type);
}
}
dp->dp_evtflags = dtrq->dtrq_evtflags & DTEVT_PROV_KPROBE;
TAILQ_INSERT_HEAD(plist, dp, dp_snext);
return 0;
}
int
dt_prov_kprobe_dealloc(struct dt_probe *dtp, struct dt_softc *sc,
struct dtioc_req *dtrq)
{
dtp->dtp_ref--;
if (dtp->dtp_ref > 0)
return 0;
switch (dtp->dtp_type) {
case KPROBE_ENTRY:
db_prologue_patch(dtp->dtp_addr, 1);
break;
case KPROBE_RETURN:
db_epilogue_patch(dtp->dtp_addr, 1);
break;
default:
panic("unknown probe type %d", dtp->dtp_type);
}
return 0;
}
int
dt_prov_kprobe_hook(struct dt_provider *dtpv, ...)
{
struct dt_probe *dtp;
struct dt_pcb *dp;
struct trapframe *tf;
va_list ap;
int is_dt_bkpt = 0;
int error;
vaddr_t *args, addr;
size_t argsize;
register_t retval[2];
KASSERT(dtpv == &dt_prov_kprobe);
va_start(ap, dtpv);
tf = va_arg(ap, struct trapframe*);
va_end(ap);
addr = db_get_probe_addr(tf);
SLIST_FOREACH(dtp, &dtpf_entry[INSTTOIDX(addr)], dtp_knext) {
if (dtp->dtp_addr != addr)
continue;
is_dt_bkpt = 1;
if (db_prof_on)
db_prof_count(tf);
if (!dtp->dtp_recording)
continue;
smr_read_enter();
SMR_SLIST_FOREACH(dp, &dtp->dtp_pcbs, dp_pnext) {
struct dt_evt *dtev;
dtev = dt_pcb_ring_get(dp, 0);
if (dtev == NULL)
continue;
#if defined(__amd64__)
args = (vaddr_t *)tf->tf_rdi;
argsize = 6;
#elif defined(__i386__)
args = (vaddr_t *)(tf->tf_esp + 4);
argsize = 10;
#endif
if (ISSET(dp->dp_evtflags, DTEVT_FUNCARGS))
memcpy(dtev->dtev_args, args, argsize);
dt_pcb_ring_consume(dp, dtev);
}
smr_read_leave();
}
if (is_dt_bkpt)
return is_dt_bkpt;
SLIST_FOREACH(dtp, &dtpf_return[INSTTOIDX(addr)], dtp_knext) {
if (dtp->dtp_addr != addr)
continue;
is_dt_bkpt = 2;
if (!dtp->dtp_recording)
continue;
smr_read_enter();
SMR_SLIST_FOREACH(dp, &dtp->dtp_pcbs, dp_pnext) {
struct dt_evt *dtev;
dtev = dt_pcb_ring_get(dp, 0);
if (dtev == NULL)
continue;
#if defined(__amd64__)
retval[0] = tf->tf_rax;
retval[1] = 0;
error = 0;
#elif defined(__i386)
retval[0] = tf->tf_eax;
retval[1] = 0;
error = 0;
#endif
dtev->dtev_retval[0] = retval[0];
dtev->dtev_retval[1] = retval[1];
dtev->dtev_error = error;
dt_pcb_ring_consume(dp, dtev);
}
smr_read_leave();
}
return is_dt_bkpt;
}
void
dt_prov_kprobe_patch_all_entry(void)
{
struct dt_probe *dtp;
size_t i;
for (i = 0; i < PPTMASK; ++i) {
SLIST_FOREACH(dtp, &dtpf_entry[i], dtp_knext) {
dtp->dtp_ref++;
if (dtp->dtp_ref != 1)
continue;
db_prologue_patch(dtp->dtp_addr, 0);
}
}
}
void
dt_prov_kprobe_depatch_all_entry(void)
{
struct dt_probe *dtp;
size_t i;
for (i = 0; i < PPTMASK; ++i) {
SLIST_FOREACH(dtp, &dtpf_entry[i], dtp_knext) {
dtp->dtp_ref--;
if (dtp->dtp_ref != 0)
continue;
db_prologue_patch(dtp->dtp_addr, 1);
}
}
}
#endif