/* $OpenBSD: dtvar.h,v 1.20 2024/11/02 16:59:22 mpi Exp $ */ /* * Copyright (c) 2019 Martin Pieuchot * * 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. */ #ifndef _DT_H_ #define _DT_H_ #include #include #include /* * Length of provider/probe/function names, including NUL. */ #define DTNAMESIZE 16 /* * Length of process name, including NUL. */ #define DTMAXCOMLEN _MAXCOMLEN /* * Maximum number of arguments passed to a function. */ #define DTMAXFUNCARGS 10 #define DTMAXARGTYPES 5 /* * Event state: where to store information when a probe fires. */ struct dt_evt { unsigned int dtev_pbn; /* Probe number */ unsigned int dtev_cpu; /* CPU id */ pid_t dtev_pid; /* ID of current process */ pid_t dtev_tid; /* ID of current thread */ struct timespec dtev_tsp; /* timestamp (nsecs) */ /* * Recorded if the corresponding flag is set. */ struct stacktrace dtev_kstack; /* kernel stack frame */ struct stacktrace dtev_ustack; /* userland stack frame */ char dtev_comm[DTMAXCOMLEN]; /* current pr. name */ union { register_t E_entry[DTMAXFUNCARGS]; struct { register_t __retval[2]; int __error; } E_return; } _args; #define dtev_args _args.E_entry /* function args. */ #define dtev_retval _args.E_return.__retval /* function retval */ #define dtev_error _args.E_return.__error /* function error */ }; /* * States to record when a probe fires. */ #define DTEVT_EXECNAME (1 << 0) /* current process name */ #define DTEVT_USTACK (1 << 1) /* userland stack */ #define DTEVT_KSTACK (1 << 2) /* kernel stack */ #define DTEVT_FUNCARGS (1 << 3) /* function arguments */ #define DTEVT_FLAG_BITS \ "\020" \ "\001EXECNAME" \ "\002USTACK" \ "\003KSTACK" \ "\004FUNCARGS" \ struct dtioc_probe_info { uint32_t dtpi_pbn; /* probe number */ uint8_t dtpi_nargs; /* # of arguments */ char dtpi_prov[DTNAMESIZE]; char dtpi_func[DTNAMESIZE]; char dtpi_name[DTNAMESIZE]; }; struct dtioc_probe { size_t dtpr_size; /* size of the buffer */ struct dtioc_probe_info *dtpr_probes; /* array of probe info */ }; struct dtioc_arg_info { uint32_t dtai_pbn; /* probe number */ uint8_t dtai_argn; /* arguments number */ char dtai_argtype[DTNAMESIZE]; }; struct dtioc_arg { uint32_t dtar_pbn; /* probe number */ size_t dtar_size; /* size of the buffer */ struct dtioc_arg_info *dtar_args; /* array of arg info */ }; struct dtioc_req { uint32_t dtrq_pbn; /* probe number */ uint32_t dtrq_rate; /* number of ticks */ uint64_t dtrq_evtflags; /* states to record */ }; struct dtioc_stat { uint64_t dtst_readevt; /* events read */ uint64_t dtst_dropevt; /* events dropped */ }; struct dtioc_getaux { pid_t dtga_pid; /* process to inspect */ unsigned long dtga_auxbase; /* AUX_base value */ }; #define DTIOCGPLIST _IOWR('D', 1, struct dtioc_probe) #define DTIOCGSTATS _IOR('D', 2, struct dtioc_stat) #define DTIOCRECORD _IOW('D', 3, int) #define DTIOCPRBENABLE _IOW('D', 4, struct dtioc_req) #define DTIOCPRBDISABLE _IOW('D', 5, struct dtioc_req) #define DTIOCGARGS _IOWR('D', 6, struct dtioc_arg) #define DTIOCGETAUXBASE _IOWR('D', 7, struct dtioc_getaux) #ifdef _KERNEL #include #include #include /* Flags that make sense for all providers. */ #define DTEVT_COMMON (DTEVT_EXECNAME|DTEVT_KSTACK|DTEVT_USTACK) #define M_DT M_DEVBUF /* XXX FIXME */ struct dt_softc; /* * Probe control block, possibly per-CPU. * * At least a PCB is allocated for each probe enabled via the DTIOCPRBENABLE * ioctl(2). It will hold the events written when the probe fires until * userland read(2)s them. * * Locks used to protect struct members in this file: * D dt_lock * I immutable after creation * K kernel lock * K,S kernel lock for writing and SMR for reading * m per-pcb mutex * c owned (read & modified) by a single CPU */ struct dt_pcb { SMR_SLIST_ENTRY(dt_pcb) dp_pnext; /* [K,S] next PCB per probe */ TAILQ_ENTRY(dt_pcb) dp_snext; /* [K] next PCB per softc */ struct dt_softc *dp_sc; /* [I] related softc */ struct dt_probe *dp_dtp; /* [I] related probe */ uint64_t dp_evtflags; /* [I] event states to record */ /* Provider specific fields. */ struct clockintr dp_clockintr; /* [D] profiling handle */ uint64_t dp_nsecs; /* [I] profiling period */ struct cpu_info *dp_cpu; /* [I] on which CPU */ }; TAILQ_HEAD(dt_pcb_list, dt_pcb); struct dt_pcb *dt_pcb_alloc(struct dt_probe *, struct dt_softc *); void dt_pcb_free(struct dt_pcb *); void dt_pcb_purge(struct dt_pcb_list *); struct dt_evt *dt_pcb_ring_get(struct dt_pcb *, int); void dt_pcb_ring_consume(struct dt_pcb *, struct dt_evt *); /* * Probes are entry points in the system where events can be recorded. * * Locks used to protect struct members in this file: * I immutable after creation * K kernel lock * D dt_lock * D,S dt_lock for writing and SMR for reading * M dtp mutex */ struct dt_probe { SIMPLEQ_ENTRY(dt_probe) dtp_next; /* [K] global list of probes */ SMR_SLIST_HEAD(, dt_pcb) dtp_pcbs; /* [D,S] list of enabled PCBs */ struct dt_provider *dtp_prov; /* [I] its to provider */ const char *dtp_func; /* [I] probe function */ const char *dtp_name; /* [I] probe name */ uint32_t dtp_pbn; /* [I] unique ID */ volatile uint32_t dtp_recording; /* [d] is it recording? */ unsigned dtp_ref; /* [m] # of PCBs referencing the probe */ /* Provider specific fields. */ int dtp_sysnum; /* [I] related # of syscall */ const char *dtp_argtype[DTMAXARGTYPES]; /* [I] type of arguments */ int dtp_nargs; /* [I] # of arguments */ vaddr_t dtp_addr; /* [I] address of breakpoint */ }; /* * Providers expose a set of probes and a method to record events. */ struct dt_provider { const char *dtpv_name; /* [I] provider name */ volatile uint32_t dtpv_recording;/* [D] # of recording PCBs */ int (*dtpv_alloc)(struct dt_probe *, struct dt_softc *, struct dt_pcb_list *, struct dtioc_req *); int (*dtpv_enter)(struct dt_provider *, ...); void (*dtpv_leave)(struct dt_provider *, ...); int (*dtpv_dealloc)(struct dt_probe *, struct dt_softc *, struct dtioc_req *); }; extern struct dt_provider dt_prov_kprobe; int dt_prov_profile_init(void); int dt_prov_syscall_init(void); int dt_prov_static_init(void); int dt_prov_kprobe_init(void); struct dt_probe *dt_dev_alloc_probe(const char *, const char *, struct dt_provider *); void dt_dev_register_probe(struct dt_probe *); void dt_clock(struct clockrequest *, void *, void *); extern volatile uint32_t dt_tracing; /* currently tracing? */ #define DT_ENTER(provname, args...) do { \ extern struct dt_provider dt_prov_ ## provname ; \ struct dt_provider *dtpv = &dt_prov_ ## provname ; \ \ if (__predict_false(dt_tracing) && \ __predict_false(dtpv->dtpv_recording)) { \ dtpv->dtpv_enter(dtpv, args); \ } \ } while (0) #define DT_LEAVE(provname, args...) do { \ extern struct dt_provider dt_prov_ ## provname ; \ struct dt_provider *dtpv = &dt_prov_ ## provname ; \ \ if (__predict_false(dt_tracing) && \ __predict_false(dtpv->dtpv_recording)) { \ dtpv->dtpv_leave(dtpv, args); \ } \ } while (0) #define _DT_STATIC_P(func, name) (dt_static_##func##_##name) /* * Probe definition for the static provider. */ #define _DT_STATIC_PROBEN(func, name, arg0, arg1, arg2, arg3, arg4, n) \ struct dt_probe _DT_STATIC_P(func, name) = { \ .dtp_next = { NULL }, \ .dtp_pcbs = { NULL }, \ .dtp_prov = &dt_prov_static, \ .dtp_func = #func, \ .dtp_name = #name, \ .dtp_pbn = 0, \ .dtp_sysnum = 0, \ .dtp_argtype = { arg0, arg1, arg2, arg3, arg4 }, \ .dtp_nargs = n, \ } \ #define DT_STATIC_PROBE0(func, name) \ _DT_STATIC_PROBEN(func, name, NULL, NULL, NULL, NULL, NULL, 0) #define DT_STATIC_PROBE1(func, name, arg0) \ _DT_STATIC_PROBEN(func, name, arg0, NULL, NULL, NULL, NULL, 1) #define DT_STATIC_PROBE2(func, name, arg0, arg1) \ _DT_STATIC_PROBEN(func, name, arg0, arg1, NULL, NULL, NULL, 2) #define DT_STATIC_PROBE3(func, name, arg0, arg1, arg2) \ _DT_STATIC_PROBEN(func, name, arg0, arg1, arg2, NULL, NULL, 3) #define DT_STATIC_PROBE4(func, name, arg0, arg1, arg2, arg3) \ _DT_STATIC_PROBEN(func, name, arg0, arg1, arg2, arg3, NULL, 4) #define DT_STATIC_PROBE5(func, name, arg0, arg1, arg2, arg3, arg4) \ _DT_STATIC_PROBEN(func, name, arg0, arg1, arg2, arg3, arg4, 5) #define DT_STATIC_ENTER(func, name, args...) do { \ extern struct dt_probe _DT_STATIC_P(func, name); \ struct dt_probe *dtp = &_DT_STATIC_P(func, name); \ \ if (__predict_false(dt_tracing) && \ __predict_false(dtp->dtp_recording)) { \ struct dt_provider *dtpv = dtp->dtp_prov; \ \ dtpv->dtpv_enter(dtpv, dtp, args); \ } \ } while (0) #define _DT_INDEX_P(func) (dtps_index_##func) #define DT_INDEX_ENTER(func, index, args...) do { \ extern struct dt_probe **_DT_INDEX_P(func); \ \ if (__predict_false(dt_tracing) && \ __predict_false(index > 0) && \ __predict_true(_DT_INDEX_P(func) != NULL)) { \ struct dt_probe *dtp = _DT_INDEX_P(func)[index]; \ \ if(__predict_false(dtp->dtp_recording)) { \ struct dt_provider *dtpv = dtp->dtp_prov; \ \ dtpv->dtpv_enter(dtpv, dtp, args); \ } \ } \ } while (0) #endif /* !_KERNEL */ #endif /* !_DT_H_ */