From 259d115c48d715bdf54ecbdbfc9ed31fa12467ee Mon Sep 17 00:00:00 2001 From: Kris Van Hees Date: Tue, 17 Dec 2013 18:06:57 -0500 Subject: [PATCH] dtrace: implement SDT in kernel modules Full implementation of SDT probes in kernel modules. The dtrace_sdt.sh script has been modified to handle both the creation of the SDT stubs and the SDT info. It's syntax has therefore changed: dtrace_sdt.sh sdtstub * or dtrace_sdt.sh sdtinfo vmlinux.o or dtrace_sdt.sh sdtinfo vmlinux.o .tmp_vmlinux1 or dtrace_sdt.sh sdtinfo .o kmod The first form generates a stub file in assembler to ensure that the (fake) functions that are called from SDT probe points will not longer be reported as undefined symbols, and to ensure that when SDT is not enabled, the probes become calls to a function that simply returns. The second form creates the initial (dummy) SDT info file for the kernel linking process, mainly to ensure that its size is known. The third form then creates the true SDT info file for the kernel, based on the kernel object file and the first stage linked kernel image. The fourth and final form generates SDT info for a kernel module, based on its initial linked object. This commit also enables the test probes in the dt_test module. Orabug: 17851716 Reviewed-by: Jamie Iles Signed-off-by: Kris Van Hees --- dtrace/dt_test_dev.c | 2 + dtrace/dt_test_mod.c | 5 +++ dtrace/dtrace_dev.c | 89 ++++++++++++++++++++----------------------- dtrace/dtrace_probe.c | 37 ++++++++++++++---- dtrace/sdt_dev.c | 59 +++++++++++++++++++++++----- 5 files changed, 128 insertions(+), 64 deletions(-) diff --git a/dtrace/dt_test_dev.c b/dtrace/dt_test_dev.c index 2a5f82973ea8..30a4131caa84 100644 --- a/dtrace/dt_test_dev.c +++ b/dtrace/dt_test_dev.c @@ -82,6 +82,8 @@ static long dt_test_ioctl(struct file *file, return 0; } + DTRACE_PROBE(sdt__test); + return -EAGAIN; } diff --git a/dtrace/dt_test_mod.c b/dtrace/dt_test_mod.c index d200a45f8f9d..b7e059bd57ea 100644 --- a/dtrace/dt_test_mod.c +++ b/dtrace/dt_test_mod.c @@ -60,3 +60,8 @@ static dtrace_pops_t dt_test_pops = { }; DT_PROVIDER_MODULE(dt_test, DTRACE_PRIV_USER) + +void foo(void) +{ + DTRACE_PROBE(sdt__test2); +} diff --git a/dtrace/dtrace_dev.c b/dtrace/dtrace_dev.c index 18be9e2bf98c..081d329fbf84 100644 --- a/dtrace/dtrace_dev.c +++ b/dtrace/dtrace_dev.c @@ -1125,33 +1125,22 @@ static struct miscdevice helper_dev = { .fops = &helper_fops, }; -static void -dtrace_module_loaded(struct module *module) +static void dtrace_module_loaded(struct module *mod) { dtrace_provider_t *prv; mutex_lock(&dtrace_provider_lock); - /* FIXME: mutex_lock(&mod_lock); */ - - //ASSERT(ctl->mod_busy); /* - * We're going to call each providers per-module provide operation - * specifying only this module. + * Give all providers a chance to register probes for this module. */ for (prv = dtrace_provider; prv != NULL; prv = prv->dtpv_next) - prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, module); + prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, mod); - /* FIXME: mutex_unlock(&mod_lock); */ mutex_unlock(&dtrace_provider_lock); /* * If we have any retained enablings, we need to match against them. - * Enabling probes requires that cpu_lock be held, and we cannot hold - * cpu_lock here -- it is legal for cpu_lock to be held when loading a - * module. (In particular, this happens when loading scheduling - * classes.) So if we have any retained enablings, we need to dispatch - * our task queue to do the match for us. */ mutex_lock(&dtrace_lock); @@ -1160,39 +1149,18 @@ dtrace_module_loaded(struct module *module) return; } -#ifdef FIXME - /* FIXME: maybe convert to a Linux workqueue */ - (void) taskq_dispatch(dtrace_taskq, - (task_func_t *)dtrace_enabling_matchall, NULL, TQ_SLEEP); -#else - dtrace_enabling_matchall(); -#endif - mutex_unlock(&dtrace_lock); - - /* - * And now, for a little heuristic sleaze: in general, we want to - * match modules as soon as they load. However, we cannot guarantee - * this, because it would lead us to the lock ordering violation - * outlined above. The common case, of course, is that cpu_lock is - * _not_ held -- so we delay here for a clock tick, hoping that that's - * long enough for the task queue to do its work. If it's not, it's - * not a serious problem -- it just means that the module that we - * just loaded may not be immediately instrumentable. - */ - udelay(jiffies_to_usecs(1)); + dtrace_enabling_matchall(); } -static void -dtrace_module_unloaded(struct module *module) +static void dtrace_module_unloaded(struct module *mod) { dtrace_probe_t template, *probe, *first, *next; dtrace_provider_t *prov; - template.dtpr_mod = module->name; + template.dtpr_mod = mod->name; mutex_lock(&dtrace_provider_lock); - /* FIXME: mutex_lock(&mod_lock); */ mutex_lock(&dtrace_lock); if (dtrace_bymod == NULL) { @@ -1201,16 +1169,14 @@ dtrace_module_unloaded(struct module *module) * we don't have any work to do. */ mutex_unlock(&dtrace_lock); - /* FIXME: mutex_unlock(&mod_lock); */ mutex_unlock(&dtrace_provider_lock); return; } for (probe = first = dtrace_hash_lookup(dtrace_bymod, &template); - probe != NULL; probe = probe->dtpr_nextmod) { + probe != NULL; probe = probe->dtpr_nextmod) { if (probe->dtpr_ecb != NULL) { mutex_unlock(&dtrace_lock); - /* FIXME: mutex_unlock(&mod_lock); */ mutex_unlock(&dtrace_provider_lock); /* @@ -1225,7 +1191,7 @@ dtrace_module_unloaded(struct module *module) */ if (dtrace_err_verbose) { pr_warning("unloaded module '%s' had " - "enabled probes", module->name); + "enabled probes", mod->name); } return; @@ -1235,9 +1201,7 @@ dtrace_module_unloaded(struct module *module) probe = first; for (first = NULL; probe != NULL; probe = next) { -//TBD ASSERT(dtrace_probes[probe->dtpr_id - 1] == probe); - -//TBD dtrace_probes[probe->dtpr_id - 1] = NULL; + dtrace_probe_remove_id(probe->dtpr_id); next = probe->dtpr_nextmod; dtrace_hash_remove(dtrace_bymod, probe); @@ -1268,12 +1232,10 @@ dtrace_module_unloaded(struct module *module) kfree(probe->dtpr_mod); kfree(probe->dtpr_func); kfree(probe->dtpr_name); -//FIXME vmem_free(dtrace_arena, (void *)(uintptr_t)probe->dtpr_id, 1); kfree(probe); } mutex_unlock(&dtrace_lock); - /* FIXME: mutex_unlock(&mod_lock); */ mutex_unlock(&dtrace_provider_lock); } @@ -1349,6 +1311,31 @@ int dtrace_istoxic(uintptr_t kaddr, size_t size) return 0; } +static int dtrace_mod_notifier(struct notifier_block *nb, unsigned long val, + void *args) +{ + struct module *mod = args; + + if (!mod) + return NOTIFY_DONE; + + switch (val) { + case MODULE_STATE_LIVE: + dtrace_module_loaded(mod); + break; + + case MODULE_STATE_GOING: + dtrace_module_unloaded(mod); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block dtrace_modmgmt = { + .notifier_call = dtrace_mod_notifier, +}; + /* * Initialize the DTrace core. * @@ -1398,8 +1385,12 @@ int dtrace_dev_init(void) return rc; } + register_module_notifier(&dtrace_modmgmt); + +#ifdef FIXME dtrace_modload = dtrace_module_loaded; dtrace_modunload = dtrace_module_unloaded; +#endif dtrace_helpers_cleanup = dtrace_helpers_destroy; dtrace_helpers_fork = dtrace_helpers_duplicate; #ifdef FIXME @@ -1522,8 +1513,12 @@ void dtrace_dev_exit(void) dtrace_probe_exit(); + unregister_module_notifier(&dtrace_modmgmt); + +#ifdef FIXME dtrace_modload = NULL; dtrace_modunload = NULL; +#endif dtrace_helpers_cleanup = NULL; dtrace_helpers_fork = NULL; #ifdef FIXME diff --git a/dtrace/dtrace_probe.c b/dtrace/dtrace_probe.c index ccda810fbc38..524b4d2516db 100644 --- a/dtrace/dtrace_probe.c +++ b/dtrace/dtrace_probe.c @@ -169,9 +169,7 @@ void dtrace_probe_description(const dtrace_probe_t *prp, void dtrace_probe_provide(dtrace_probedesc_t *desc, dtrace_provider_t *prv) { -#ifdef FIXME struct module *mod; -#endif int all = 0; if (prv == NULL) { @@ -190,21 +188,46 @@ void dtrace_probe_provide(dtrace_probedesc_t *desc, dtrace_provider_t *prv) * no need for code duplication in handling such probe points. */ prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, dtrace_kmod); + + /* + * We need to explicitly make the call for the 'dtrace' module + * itself, because the following loop does not actually process + * the 'dtrace' module (it is used as a sentinel). + */ + prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, THIS_MODULE); + #ifdef FIXME -/* - * This needs work because (so far) I have not found a way to get access to the - * list of modules in Linux. - */ mutex_lock(&module_mutex); +#else + rcu_read_lock(); +#endif + + list_for_each_entry(mod, &(THIS_MODULE->list), list) { + /* + * Skip over the modules list header, because it cannot + * validly be interpreted as a 'struct module'. It is + * a basic list_head structure. + */ +#ifdef MODULES_VADDR + if ((uintptr_t)mod < MODULES_VADDR || + (uintptr_t)mod >= MODULES_END) + continue; +#else + if ((uintptr_t)mod < VMALLOC_START || + (uintptr_t)mod >= VMALLOC_END) + continue; +#endif - list_for_each_entry(mod, &modules, list) { if (mod->state != MODULE_STATE_LIVE) continue; prv->dtpv_pops.dtps_provide_module(prv->dtpv_arg, mod); } +#ifdef FIXME mutex_unlock(&module_mutex); +#else + rcu_read_unlock(); #endif } while (all && (prv = prv->dtpv_next) != NULL); } diff --git a/dtrace/sdt_dev.c b/dtrace/sdt_dev.c index 661c03fba122..e4b668ddbf83 100644 --- a/dtrace/sdt_dev.c +++ b/dtrace/sdt_dev.c @@ -129,7 +129,19 @@ void sdt_provide_module(void *arg, struct module *mp) dtrace_mprovider_t *prov; sdt_probedesc_t *sdpd; sdt_probe_t *sdp, *prv; - int len; + int idx, len; + + /* + * Nothing to do if the module SDT probes were already created. + */ + if (mp->sdt_nprobes != 0) + return; + + /* + * Nothing to do if there are no SDT probes. + */ + if (mp->num_dtrace_probes == 0) + return; /* * Do not provide any probes unless all SDT providers have been created @@ -140,13 +152,8 @@ void sdt_provide_module(void *arg, struct module *mp) return; } - /* - * Nothing to do if the module SDT probes were already created. - */ - if (mp->sdt_nprobes != 0) - return; - - for (sdpd = mp->sdt_probes; sdpd != NULL; sdpd = sdpd->sdpd_next) { + for (idx = 0, sdpd = mp->sdt_probes; idx < mp->num_dtrace_probes; + idx++, sdpd++) { char *name = sdpd->sdpd_name, *nname; int i, j; dtrace_mprovider_t *prov; @@ -223,7 +230,26 @@ int _sdt_enable(void *arg, dtrace_id_t id, void *parg) { sdt_probe_t *sdp = parg; - dtrace_invop_enable(sdp->sdp_patchpoint); + /* + * Ensure that we have a reference to the module. + */ + if (!try_module_get(sdp->sdp_module)) + return -EAGAIN; + + /* + * If at least one other enabled probe exists for this module, drop the + * reference we took above, because we only need one to prevent the + * module from being unloaded. + */ + sdp->sdp_module->mod_nenabled++; + if (sdp->sdp_module->mod_nenabled > 1) + module_put(sdp->sdp_module); + + while (sdp != NULL) { + dtrace_invop_enable(sdp->sdp_patchpoint); + sdp = sdp->sdp_next; + } + return 0; } @@ -231,7 +257,20 @@ void _sdt_disable(void *arg, dtrace_id_t id, void *parg) { sdt_probe_t *sdp = parg; - dtrace_invop_disable(sdp->sdp_patchpoint, sdp->sdp_savedval); + /* + * If we are disabling a probe, we know it was enabled, and therefore + * we know that we have a reference on the module to prevent it from + * being unloaded. If we disable the last probe on the module, we can + * drop the reference. + */ + sdp->sdp_module->mod_nenabled--; + if (sdp->sdp_module->mod_nenabled == 0) + module_put(sdp->sdp_module); + + while (sdp != NULL) { + dtrace_invop_disable(sdp->sdp_patchpoint, sdp->sdp_savedval); + sdp = sdp->sdp_next; + } } void sdt_getargdesc(void *arg, dtrace_id_t id, void *parg, -- 2.50.1