Changeset View
Standalone View
sys/cddl/dev/kinst/kinst.c
- This file was added.
/* | |||||||||||
* SPDX-License-Identifier: CDDL 1.0 | |||||||||||
*/ | |||||||||||
#include <sys/param.h> | |||||||||||
#include <sys/systm.h> | |||||||||||
#include <sys/conf.h> | |||||||||||
#include <sys/kernel.h> | |||||||||||
#include <sys/linker.h> | |||||||||||
#include <sys/module.h> | |||||||||||
#include <sys/dtrace.h> | |||||||||||
#include <cddl/dev/dtrace/dtrace_cddl.h> | |||||||||||
#include <dis_tables.h> | |||||||||||
#include <machine/cpufunc.h> | |||||||||||
#include <machine/md_var.h> | |||||||||||
#include <machine/stdarg.h> | |||||||||||
#include "kinst.h" | |||||||||||
#include "trampoline.h" | |||||||||||
#define KINST_PUSHL_EBP 0x55 | |||||||||||
#define KINST_CALL 0xe8 | |||||||||||
#define KINST_JMP 0xe9 | |||||||||||
#define KINST_JMP_LEN 5 | |||||||||||
#define KINST_NEARJMP_PREFIX 0x0f | |||||||||||
#define KINST_NEARJMP_FIRST 0x80 | |||||||||||
#define KINST_NEARJMP_LAST 0x8f | |||||||||||
#define KINST_NEARJMP_LEN 6 | |||||||||||
#define KINST_UNCOND_SHORTJMP 0xeb | |||||||||||
#define KINST_SHORTJMP_FIRST 0x70 | |||||||||||
#define KINST_SHORTJMP_LAST 0x7f | |||||||||||
#define KINST_SHORTJMP_LEN 2 | |||||||||||
#define KINST_MODRM_RIPREL 0x05 | |||||||||||
#define KINST_MOD(b) (((b) & 0xc0) >> 6) | |||||||||||
#define KINST_RM(b) ((b) & 0x07) | |||||||||||
MALLOC_DEFINE(M_KINST, "kinst", "Kernel Instruction Tracing"); | |||||||||||
static d_open_t kinst_open; | |||||||||||
static d_close_t kinst_close; | |||||||||||
static d_ioctl_t kinst_ioctl; | |||||||||||
static int kinst_linker_file_cb(linker_file_t, void *); | |||||||||||
static int kinst_dis_get_byte(void *); | |||||||||||
static int32_t kinst_displ(uint8_t *, uint8_t *, int); | |||||||||||
static int kinst_is_call_or_uncond_jmp(uint8_t *); | |||||||||||
static int kinst_is_short_jmp(uint8_t *); | |||||||||||
static int kinst_is_near_jmp(uint8_t *); | |||||||||||
static int kinst_is_jmp(uint8_t *); | |||||||||||
static void kinst_provide_module(void *, modctl_t *); | |||||||||||
static void kinst_getargdesc(void *, dtrace_id_t, void *, | |||||||||||
dtrace_argdesc_t *); | |||||||||||
static void kinst_destroy(void *, dtrace_id_t, void *); | |||||||||||
static void kinst_enable(void *, dtrace_id_t, void *); | |||||||||||
static void kinst_disable(void *, dtrace_id_t, void *); | |||||||||||
static void kinst_load(void *); | |||||||||||
static int kinst_unload(void); | |||||||||||
static int kinst_modevent(module_t, int, void *); | |||||||||||
static dtrace_pattr_t kinst_attr = { | |||||||||||
{ DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, | |||||||||||
{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN }, | |||||||||||
{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_ISA }, | |||||||||||
{ DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, | |||||||||||
{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_ISA }, | |||||||||||
}; | |||||||||||
static dtrace_pops_t kinst_pops = { | |||||||||||
.dtps_provide = NULL, | |||||||||||
.dtps_provide_module = kinst_provide_module, | |||||||||||
.dtps_enable = kinst_enable, | |||||||||||
.dtps_disable = kinst_disable, | |||||||||||
.dtps_suspend = NULL, | |||||||||||
.dtps_resume = NULL, | |||||||||||
.dtps_getargdesc = kinst_getargdesc, | |||||||||||
.dtps_getargval = NULL, | |||||||||||
.dtps_usermode = NULL, | |||||||||||
.dtps_destroy = kinst_destroy | |||||||||||
}; | |||||||||||
static struct cdevsw kinst_cdevsw = { | |||||||||||
.d_name = "kinst", | |||||||||||
.d_version = D_VERSION, | |||||||||||
.d_flags = D_TRACKCLOSE, | |||||||||||
.d_open = kinst_open, | |||||||||||
.d_close = kinst_close, | |||||||||||
.d_ioctl = kinst_ioctl, | |||||||||||
}; | |||||||||||
static struct cdev *kinst_cdev; | |||||||||||
static dtrace_provider_id_t kinst_id; | |||||||||||
/* TODO: convert to hashtable */ | |||||||||||
TAILQ_HEAD(, kinst_probe) kinst_probes; | |||||||||||
markj: I explained further in an email, but we need to synchronize access to this list. One CPU can be… | |||||||||||
markjUnsubmitted Done Inline Actions
markj: | |||||||||||
int | |||||||||||
kinst_invop(uintptr_t addr, struct trapframe *frame, uintptr_t rval) | |||||||||||
{ | |||||||||||
solaris_cpu_t *cpu; | |||||||||||
uintptr_t *stack; | |||||||||||
struct kinst_probe *kp; | |||||||||||
#ifdef __amd64__ | |||||||||||
stack = (uintptr_t *)frame->tf_rsp; | |||||||||||
#else | |||||||||||
/* Skip hardware-saved registers. */ | |||||||||||
stack = (uintptr_t *)frame->tf_isp + 3; | |||||||||||
#endif | |||||||||||
markjUnsubmitted Done Inline ActionsNo need for the ifdef, this code is amd64-only. (IMO it is simply not worth implementing i386 support, or at least, it's very very low priority.) markj: No need for the ifdef, this code is amd64-only. (IMO it is simply not worth implementing i386… | |||||||||||
cpu = &solaris_cpu[curcpu]; | |||||||||||
/* FIXME: not thread-safe */ | |||||||||||
TAILQ_FOREACH(kp, &kinst_probes, kp_next) { | |||||||||||
if ((uintptr_t)kp->kp_patchpoint != addr) | |||||||||||
continue; | |||||||||||
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT); | |||||||||||
cpu->cpu_dtrace_caller = stack[0]; | |||||||||||
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT | CPU_DTRACE_BADADDR); | |||||||||||
dtrace_probe(kp->kp_id, 0, 0, 0, 0, 0); | |||||||||||
cpu->cpu_dtrace_caller = 0; | |||||||||||
/* Redirect execution to the trampoline after iret. */ | |||||||||||
frame->tf_rip = (register_t)kp->kp_trampoline; | |||||||||||
return (DTRACE_INVOP_NOP); | |||||||||||
} | |||||||||||
return (0); | |||||||||||
} | |||||||||||
void | |||||||||||
kinst_patch_tracepoint(struct kinst_probe *kp, kinst_patchval_t val) | |||||||||||
{ | |||||||||||
register_t reg; | |||||||||||
int oldwp; | |||||||||||
reg = intr_disable(); | |||||||||||
oldwp = disable_wp(); | |||||||||||
*kp->kp_patchpoint = val; | |||||||||||
restore_wp(oldwp); | |||||||||||
intr_restore(reg); | |||||||||||
} | |||||||||||
int | |||||||||||
kinst_make_probe(linker_file_t lf, int symindx, linker_symval_t *symval, | |||||||||||
markjUnsubmitted Done Inline ActionsI think this and the functions above can be defined as static? markj: I think this and the functions above can be defined as `static`? | |||||||||||
christosAuthorUnsubmitted Done Inline ActionsThese functions are supposed to be architecture dependent, meaning they'll eventually christos: These functions are supposed to be architecture dependent, meaning they'll eventually
go to… | |||||||||||
void *opaque) | |||||||||||
{ | |||||||||||
struct kinst_probe *kp; | |||||||||||
dis86_t d86; | |||||||||||
dtrace_kinst_probedesc_t *pd; | |||||||||||
int n, off, mode, opclen, trlen; | |||||||||||
int32_t displ, origdispl; | |||||||||||
uint8_t *instr, *limit, *bytes; | |||||||||||
pd = opaque; | |||||||||||
if (strcmp(symval->name, pd->func) != 0 || | |||||||||||
strcmp(symval->name, "trap_check") == 0) | |||||||||||
return (0); | |||||||||||
instr = (uint8_t *)symval->value; | |||||||||||
limit = (uint8_t *)symval->value + symval->size; | |||||||||||
mode = (DATAMODEL_LP64 == DATAMODEL_NATIVE) ? SIZE64 : SIZE32; | |||||||||||
if (instr >= limit) | |||||||||||
return (0); | |||||||||||
if (instr[0] != KINST_PUSHL_EBP) | |||||||||||
return (0); | |||||||||||
n = 0; | |||||||||||
/* TODO: explain */ | |||||||||||
while (instr < limit) { | |||||||||||
off = (int)(instr - (uint8_t *)symval->value); | |||||||||||
/* | |||||||||||
* If pd->off is -1 we want to create probes for all | |||||||||||
* instructions at once to reduce overhead. | |||||||||||
*/ | |||||||||||
if (pd->off != off && pd->off != -1) { | |||||||||||
instr += dtrace_instr_size(instr); | |||||||||||
continue; | |||||||||||
} | |||||||||||
if (++n > KINST_PROBE_MAX) { | |||||||||||
KINST_LOG("probe list full: %d entries", n); | |||||||||||
return (ENOMEM); | |||||||||||
} | |||||||||||
kp = malloc(sizeof(struct kinst_probe), M_KINST, M_WAITOK | M_ZERO); | |||||||||||
snprintf(kp->kp_name, sizeof(kp->kp_name), "%d", off); | |||||||||||
/* | |||||||||||
* Save the first byte of the instruction so that we can | |||||||||||
* recover it when the probe is disabled. | |||||||||||
*/ | |||||||||||
kp->kp_savedval = *instr; | |||||||||||
kp->kp_patchval = KINST_PATCHVAL; | |||||||||||
kp->kp_patchpoint = instr; | |||||||||||
if ((kp->kp_trampoline = kinst_trampoline_alloc()) == NULL) { | |||||||||||
KINST_LOG("cannot allocate trampoline for: %p", instr); | |||||||||||
return (ENOMEM); | |||||||||||
} | |||||||||||
d86.d86_data = (void **)&instr; | |||||||||||
d86.d86_get_byte = kinst_dis_get_byte; | |||||||||||
d86.d86_check_func = NULL; | |||||||||||
if (dtrace_disx86(&d86, mode) != 0) { | |||||||||||
markjUnsubmitted Done Inline ActionsIt occurs to me that we must never instrument sti. Similarly, popf must be disallowed (because it can potentially set PSL_I, which is what sti does). Otherwise, we can't use dtrace_sync() to create a barrier. Those instructions are rare, so I don't expect it to be impactful. markj: It occurs to me that we must never instrument `sti`. Similarly, `popf` must be disallowed… | |||||||||||
KINST_LOG("failed to disassemble instruction at: %p", instr); | |||||||||||
return (EINVAL); | |||||||||||
} | |||||||||||
bytes = d86.d86_bytes; | |||||||||||
/* | |||||||||||
* Copy current instruction to the trampoline to be executed | |||||||||||
* when the probe fires. In case the instruction takes %rip as | |||||||||||
* an implicit operand, we have to modify it first in order for | |||||||||||
* the offset encodings to be correct. | |||||||||||
*/ | |||||||||||
if (kinst_is_jmp(bytes)) { | |||||||||||
markjUnsubmitted Done Inline ActionsHere's a wrinkle that we missed: a call instruction pushes the address of the next instruction onto the stack, then jumps to the call target. Later, a ret will pop the stack and place the value in %rip. When a call is executed from a trampoline, the return address will be the next instruction in the trampoline, i.e., the jmp back to the original code. Suppose that a probe of a call instruction is disabled after the probe fires in a thread, but before the thread executes the corresponding ret. Then, when it does execute ret, it'll jump to the trampoline, which has been filled with breakpoints. Oops. I see two options for handling call:
I think the first option is much easier to implement. Basically, it means that call instructions don't require a trampoline at all. BTW, the following script helped me narrow down the bug: # cat /tmp/kinst-test.sh #!/bin/sh -x #set -e for i in $(seq 0 2316); do echo $i dtrace -n "kinst::amd64_syscall:$i {} tick-25ms {exit(0);}" -x switchrate=50hz done It just tries to enable each probe in amd64_syscall() individually. Eventually I found that the kernel crashes after enabling kinst::amd64_syscall:300, which for me is call *(%rcx). markj: Here's a wrinkle that we missed: a `call` instruction pushes the address of the next… | |||||||||||
markjUnsubmitted Done Inline ActionsWe've since fixed this, though it requires a fair bit of work in the instruction dissector and in kinst_invop(). So far I haven't found any other instructions where this problem exists. markj: We've since fixed this, though it requires a fair bit of work in the instruction dissector and… | |||||||||||
opclen = kinst_is_near_jmp(bytes) ? 2 : 1; | |||||||||||
memcpy(&origdispl, &bytes[opclen], sizeof(origdispl)); | |||||||||||
if (kinst_is_short_jmp(bytes)) { | |||||||||||
if (*bytes == KINST_UNCOND_SHORTJMP) { | |||||||||||
/* | |||||||||||
* Convert unconditional short JMP to a | |||||||||||
* regular JMP. | |||||||||||
*/ | |||||||||||
kp->kp_trampoline[0] = KINST_JMP; | |||||||||||
trlen = KINST_JMP_LEN; | |||||||||||
} else { | |||||||||||
/* | |||||||||||
* "Recalculate" the opcode length | |||||||||||
* since we are converting from a short | |||||||||||
* to near jump. That's a hack. | |||||||||||
*/ | |||||||||||
opclen = 0; | |||||||||||
kp->kp_trampoline[opclen++] = | |||||||||||
KINST_NEARJMP_PREFIX; | |||||||||||
/* | |||||||||||
* Convert short-jump to its near-jmp | |||||||||||
* equivalent. | |||||||||||
*/ | |||||||||||
kp->kp_trampoline[opclen++] = | |||||||||||
*bytes + 0x10; | |||||||||||
trlen = KINST_NEARJMP_LEN; | |||||||||||
} | |||||||||||
displ = kinst_displ(instr - d86.d86_len + | |||||||||||
(origdispl & 0xff) + KINST_SHORTJMP_LEN, | |||||||||||
kp->kp_trampoline, trlen); | |||||||||||
} else { | |||||||||||
if (kinst_is_call_or_uncond_jmp(bytes)) | |||||||||||
trlen = KINST_JMP_LEN; | |||||||||||
else | |||||||||||
trlen = KINST_NEARJMP_LEN; | |||||||||||
memcpy(kp->kp_trampoline, bytes, opclen); | |||||||||||
displ = kinst_displ(instr - d86.d86_len + | |||||||||||
origdispl + trlen, kp->kp_trampoline, trlen); | |||||||||||
} | |||||||||||
memcpy(&kp->kp_trampoline[opclen], &displ, sizeof(displ)); | |||||||||||
} else if (d86.d86_got_modrm && | |||||||||||
KINST_MOD(bytes[d86.d86_rmindex]) == 0 && | |||||||||||
KINST_RM(bytes[d86.d86_rmindex]) == 5) { | |||||||||||
opclen = d86.d86_rmindex + 1; | |||||||||||
trlen = d86.d86_len; | |||||||||||
memcpy(&origdispl, &bytes[d86.d86_rmindex + 1], | |||||||||||
sizeof(origdispl)); | |||||||||||
memcpy(kp->kp_trampoline, bytes, d86.d86_rmindex + 1); | |||||||||||
/* | |||||||||||
* Create a new %rip-relative instruction with a | |||||||||||
* recalculated offset to %rip. | |||||||||||
*/ | |||||||||||
displ = kinst_displ(instr - d86.d86_len + | |||||||||||
origdispl + trlen, kp->kp_trampoline, trlen); | |||||||||||
memcpy(&kp->kp_trampoline[opclen], &displ, sizeof(displ)); | |||||||||||
} else { | |||||||||||
memcpy(kp->kp_trampoline, d86.d86_bytes, d86.d86_len); | |||||||||||
trlen = d86.d86_len; | |||||||||||
} | |||||||||||
/* | |||||||||||
* Encode a jmp back to the next instruction so that the thread | |||||||||||
* can continue execution normally. | |||||||||||
*/ | |||||||||||
kp->kp_trampoline[trlen] = KINST_JMP; | |||||||||||
displ = kinst_displ(instr, &kp->kp_trampoline[trlen], | |||||||||||
KINST_JMP_LEN); | |||||||||||
memcpy(&kp->kp_trampoline[trlen + 1], &displ, sizeof(displ)); | |||||||||||
kp->kp_id = dtrace_probe_create(kinst_id, lf->filename, | |||||||||||
symval->name, kp->kp_name, 3, kp); | |||||||||||
TAILQ_INSERT_TAIL(&kinst_probes, kp, kp_next); | |||||||||||
markjUnsubmitted Done Inline ActionsWhat happens if two different dtrace(1) instances create the same probe? We'll have two probe structures with the same address in this list. But kinst_invop() will only find the first one. markj: What happens if two different dtrace(1) instances create the same probe? We'll have two probe… | |||||||||||
} | |||||||||||
return (0); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused, | |||||||||||
struct thread *td __unused) | |||||||||||
{ | |||||||||||
return (0); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused, | |||||||||||
struct thread *td __unused) | |||||||||||
{ | |||||||||||
dtrace_condense(kinst_id); | |||||||||||
return (0); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr, | |||||||||||
int flags __unused, struct thread *td __unused) | |||||||||||
{ | |||||||||||
dtrace_kinst_probedesc_t *pd; | |||||||||||
int error = 0; | |||||||||||
switch (cmd) { | |||||||||||
case DTRACEIOC_KINST_MKPROBE: | |||||||||||
pd = (dtrace_kinst_probedesc_t *)addr; | |||||||||||
/* Loop over all functions in the kernel and loaded modules. */ | |||||||||||
error = linker_file_foreach(kinst_linker_file_cb, pd); | |||||||||||
break; | |||||||||||
default: | |||||||||||
error = ENOTTY; | |||||||||||
break; | |||||||||||
} | |||||||||||
return (error); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_linker_file_cb(linker_file_t lf, void *arg) | |||||||||||
{ | |||||||||||
/* | |||||||||||
* Invoke kinst_make_probe_function() once for each function symbol in | |||||||||||
* the module "lf". | |||||||||||
*/ | |||||||||||
return (linker_file_function_listall(lf, kinst_make_probe, arg)); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_dis_get_byte(void *p) | |||||||||||
{ | |||||||||||
int ret; | |||||||||||
uint8_t **instr = p; | |||||||||||
ret = **instr; | |||||||||||
(*instr)++; | |||||||||||
return (ret); | |||||||||||
} | |||||||||||
static int32_t | |||||||||||
kinst_displ(uint8_t *dst, uint8_t *src, int len) | |||||||||||
{ | |||||||||||
return (dst - (src + len)); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_is_call_or_uncond_jmp(uint8_t *bytes) | |||||||||||
{ | |||||||||||
return (bytes[0] == KINST_CALL || bytes[0] == KINST_JMP); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_is_short_jmp(uint8_t *bytes) | |||||||||||
{ | |||||||||||
/* | |||||||||||
* KINST_UNCOND_SHORTJMP could be kinst_is_call_or_uncond_jmp() but I | |||||||||||
* think it's easier to work with if we have it here. | |||||||||||
*/ | |||||||||||
return ((bytes[0] >= KINST_SHORTJMP_FIRST && | |||||||||||
bytes[0] <= KINST_SHORTJMP_LAST) || | |||||||||||
bytes[0] == KINST_UNCOND_SHORTJMP); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_is_near_jmp(uint8_t *bytes) | |||||||||||
{ | |||||||||||
return (bytes[0] == KINST_NEARJMP_PREFIX && | |||||||||||
bytes[1] >= KINST_NEARJMP_FIRST && | |||||||||||
bytes[1] <= KINST_NEARJMP_LAST); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_is_jmp(uint8_t *bytes) | |||||||||||
{ | |||||||||||
return (kinst_is_call_or_uncond_jmp(bytes) || | |||||||||||
kinst_is_short_jmp(bytes) || | |||||||||||
kinst_is_near_jmp(bytes)); | |||||||||||
} | |||||||||||
static void | |||||||||||
kinst_provide_module(void *arg, modctl_t *lf) | |||||||||||
{ | |||||||||||
} | |||||||||||
static void | |||||||||||
kinst_getargdesc(void *arg, dtrace_id_t id, void *parg, dtrace_argdesc_t *desc) | |||||||||||
{ | |||||||||||
/* TODO? */ | |||||||||||
markjUnsubmitted Not Done Inline ActionsSo this made me think about the regs array. If you look at dtrace.h, there are two definitions: #define DIF_VAR_REGS 0x0001 /* registers array */ #define DIF_VAR_UREGS 0x0002 /* user registers array */ The second one is the uregs (userspace registers) array: https://illumos.org/books/dtrace/chp-user.html#chp-user-uregs It's implemented here: https://github.com/freebsd/freebsd-src/blob/main/sys/cddl/contrib/opensolaris/uts/common/dtrace/dtrace.c#L3380 Clearly the implementors of dtrace intended to have a regs array as well, but it isn't implemented for some reason. But I think it will be useful to have access to the register file from kinst probes. Remember, kinst_invop() takes a pointer to a struct trapframe parameter; this is a saved copy of the CPU registers at the time that the breakpoint was executed. So if we can somehow make that available to the DIF_VAR_REGS handler, it'll be possible to access the CPU registers from a D script. DIF_VAR_UREGS is implemented by fetching curthread->td_frame. When a thread enters the kernel (e.g., via an interrupt or a system call), the kernel saves a pointer to the usermode registers in td_frame. I think the easiest way to implement DIF_VAR_REGS is to add a new field struct trapframe *td_bp_frame to struct kdtrace_thread. In kinst_invop() (and fbt_invop() too), set curthread->td_dtrace->td_bp_frame to the address of the trapframe before calling dtrace_probe(). You could implement and commit this mechanism independent of kinst: just do it for FBT. Then just modify kinst_invop() to use it. markj: So this made me think about the `regs` array. If you look at dtrace.h, there are two… | |||||||||||
} | |||||||||||
static void | |||||||||||
kinst_destroy(void *arg, dtrace_id_t id, void *parg) | |||||||||||
{ | |||||||||||
struct kinst_probe *kp; | |||||||||||
while (!TAILQ_EMPTY(&kinst_probes)) { | |||||||||||
markjUnsubmitted Done Inline ActionsI think this won't work with multiple dtrace(1) processes that are creating and destroying kinst probes simultaneously. We'll need something more clever. markj: I think this won't work with multiple dtrace(1) processes that are creating and destroying… | |||||||||||
markjUnsubmitted Done Inline ActionsIn my branch I added a mutex to serialize all trampoline allocations and deallocations, and that fixes the problem. markj: In my branch I added a mutex to serialize all trampoline allocations and deallocations, and… | |||||||||||
kp = TAILQ_FIRST(&kinst_probes); | |||||||||||
TAILQ_REMOVE(&kinst_probes, kp, kp_next); | |||||||||||
kinst_trampoline_dealloc(kp->kp_trampoline); | |||||||||||
free(kp, M_KINST); | |||||||||||
} | |||||||||||
} | |||||||||||
static void | |||||||||||
kinst_enable(void *arg, dtrace_id_t id, void *parg) | |||||||||||
{ | |||||||||||
struct kinst_probe *kp = parg; | |||||||||||
kinst_patch_tracepoint(kp, kp->kp_patchval); | |||||||||||
} | |||||||||||
static void | |||||||||||
kinst_disable(void *arg, dtrace_id_t id, void *parg) | |||||||||||
{ | |||||||||||
struct kinst_probe *kp = parg; | |||||||||||
kinst_patch_tracepoint(kp, kp->kp_savedval); | |||||||||||
} | |||||||||||
static void | |||||||||||
kinst_load(void *dummy) | |||||||||||
{ | |||||||||||
TAILQ_INIT(&kinst_probes); | |||||||||||
kinst_trampoline_init(); | |||||||||||
kinst_cdev = make_dev(&kinst_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, | |||||||||||
"dtrace/kinst"); | |||||||||||
if (dtrace_register("kinst", &kinst_attr, DTRACE_PRIV_USER, NULL, | |||||||||||
&kinst_pops, NULL, &kinst_id) != 0) | |||||||||||
return; | |||||||||||
markjUnsubmitted Done Inline ActionsLooks like the error is silently ignored? markj: Looks like the error is silently ignored? | |||||||||||
christosAuthorUnsubmitted Done Inline ActionsIs it better to convert to change the function's return value to int and return christos: Is it better to convert to change the function's return value to int and return
the result of… | |||||||||||
markjUnsubmitted Done Inline ActionsI think we can just pass the error up, but I didn't check. kinst_modevent() should return an errno value if an error occurs. markj: I think we can just pass the error up, but I didn't check. kinst_modevent() should return an… | |||||||||||
dtrace_invop_add(kinst_invop); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_unload(void) | |||||||||||
{ | |||||||||||
kinst_trampoline_deinit(); | |||||||||||
dtrace_invop_remove(kinst_invop); | |||||||||||
destroy_dev(kinst_cdev); | |||||||||||
return (dtrace_unregister(kinst_id)); | |||||||||||
} | |||||||||||
static int | |||||||||||
kinst_modevent(module_t mod __unused, int type, void *data __unused) | |||||||||||
{ | |||||||||||
int error = 0; | |||||||||||
switch (type) { | |||||||||||
case MOD_LOAD: | |||||||||||
break; | |||||||||||
case MOD_UNLOAD: | |||||||||||
break; | |||||||||||
case MOD_SHUTDOWN: | |||||||||||
break; | |||||||||||
default: | |||||||||||
error = EOPNOTSUPP; | |||||||||||
break; | |||||||||||
} | |||||||||||
return (error); | |||||||||||
} | |||||||||||
SYSINIT(kinst_load, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY, kinst_load, NULL); | |||||||||||
SYSUNINIT(kinst_unload, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY, kinst_unload, NULL); | |||||||||||
DEV_MODULE(kinst, kinst_modevent, NULL); | |||||||||||
MODULE_VERSION(kinst, 1); | |||||||||||
MODULE_DEPEND(kinst, dtrace, 1, 1, 1); | |||||||||||
MODULE_DEPEND(kinst, opensolaris, 1, 1, 1); |
I explained further in an email, but we need to synchronize access to this list. One CPU can be executing kinst_make_probe() while another is executing kinst_destroy(). We can deal with reader synchronization using an IPI barrier, but we need a mutex for writers.