/* $OpenBSD: dt_prov_kprobe.c,v 1.10 2025/08/14 13:04:48 mpi Exp $ */ /* * Copyright (c) 2024 Martin Pieuchot * Copyright (c) 2020 Tom Rollet * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #if defined(DDBPROF) && (defined(__amd64__) || defined(__i386__)) #include #include #include #include #include #include #include #include #include #include #include #include #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 *); /* Lists of probes per ELF symbol. */ 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, }; /* Bob Jenkin's public domain 32-bit integer hashing function. * Original at https://burtleburtle.net/bob/hash/integer.html. */ 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) /* XXX */ #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 /* * Validate that this prologue respect a well-known layout and return * the offset where the symbol can be patched. */ // XXX use db_get_value(); 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; /* Check for IBT */ if (inst[0] != 0xf3 || inst[1] != 0x0f || inst[2] != 0x1e || inst[3] != 0xfa) return -1; /* Check for retguard */ off = IBT_SIZE; if (inst[off] != 0x4c || inst[off + 1] != 0x8b || inst[off + 2] != 0x1d) return -1; /* Check for `xorq off(%rsp), %reg' */ off += RTGD_MOV_SIZE; if (inst[off] != 0x4c || inst[off + 1] != 0x33 || inst[off + 2] != 0x1c) return -1; /* Check for `pushq %rbp' */ off += RTGD_XOR_SIZE; if (inst[off] != SSF_INST) return -1; /* Check for `movq %rsp, %rbp' */ if (inst[off + 1] != 0x48 || inst[off + 2] != 0x89 || inst[off + 3] != 0xe5) return -1; return off; } /* * Insert a breakpoint or restore `pushq %rbp'. */ 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; /* No retguard or IBT on i386 */ if (*inst != SSF_INST) return -1; return 0; } /* * Insert a breakpoint or restore */ 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 /* * Little temporary hack to find some return probe * => always int3 after 'pop %rpb; ret' */ 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 /* defined(__i386__) */ /* Initialize all entry and return probes and store them in global arrays */ 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; /* Filter function that are not mapped in memory */ if (inst < KERNBASE || inst >= (vaddr_t)&__kutext_end) continue; /* Remove some function to avoid recursive tracing */ 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); } /* Deallocation of PCB is done by dt_pcb_purge when closing the dev */ 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; /* Return values for return probes*/ 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; /* XXX: use CTF to get the number of arguments. */ argsize = 6; #elif defined(__i386__) /* All args on stack */ 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; } /* Called by ddb to patch all functions without allocating 1 pcb per probe */ 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); } } } /* Called by ddb to patch all functions without allocating 1 pcb per probe */ 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 /* __amd64__ || __i386__ */