From: Nick Alcock Date: Wed, 14 Sep 2016 09:52:17 +0000 (+0100) Subject: dtrace: parse sdpd_args to handle sdt_getargdesc() rather than hardwiring X-Git-Tag: v4.1.12-111.0.20170907_2225~3^2~3^2~38 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=28da0b484fbfb78a3b995207583a94ed7bfc34df;p=users%2Fjedix%2Flinux-maple.git dtrace: parse sdpd_args to handle sdt_getargdesc() rather than hardwiring This takes the new sdpd_args element in the sdt_probedesc_t and translates it into argdesc array entries for sdt_getargdesc() to return. This is not really a parser but a tokenizer, splitting the input string into "tokens" which are arg strings, translators, etc; but tokenizers, like parsers, are often fairly squirrelly and full of annoying special cases, and that is no less true here. The most notable: - you can have *no* arguments, either via an empty translation with brackets "foo : ()" or without "foo : " or simply via having a probe with zero arguments to start with: this might be represented as an sdpd_args of NULL. - arg strings often end in trailing commas because of details of the macros that produce them, so arguments do not match up one-to-one with commas, even ignoring the rare case of empty translations (though, because translations also use commas for the same purpose, they nearly do). It's OK to overcount a bit when allocating memory for the array, but it has to be right by the time we're done. - perf probes have their arg strings in a subtly different format to native probes. The perf arg string is used by tracepoint.h to define a function, so it's not a string of types, but rather a string of arguments, complete with names. Thankfully you can't hand a perf probe a function pointer, and arrays have decayed to pointers by this stage, so we don't need to deal with the full horror of C declarator syntax: rather, we just need to note if a probe name starts with __perf_ and strip off the last run of alphanumeric and _ characters from each argument if so (followed by any trailing whitespace thus exposed). - native probes with no arguments simply have no arg string (NULL). Perf probes with no arguments have the string 'void', so identify that and strip it off. Once we have the argdesc constructed, adjusting sdt_getargdesc() is simple: it actually gets quite a lot shorter and faster because we can treat the sdp_argdesc array as an array rather than looping through it comparing provider and probe names and indices: the index in the array *is* the index of the (possibly translated) argument. Signed-off-by: Nick Alcock Acked-by: Kris Van Hees Orabug: 24661801 --- diff --git a/dtrace/sdt_dev.c b/dtrace/sdt_dev.c index b02154d8ad6ca..ad6557ce456d3 100644 --- a/dtrace/sdt_dev.c +++ b/dtrace/sdt_dev.c @@ -25,6 +25,7 @@ * Use is subject to license terms. */ +#include #include #include #include @@ -42,65 +43,221 @@ sdt_probe_t **sdt_probetab; int sdt_probetab_size; int sdt_probetab_mask; -static sdt_argdesc_t sdt_args[] = { +/* + * Return, in newly-allocated space, a version of the passed-in type which has + * been cleaned up suitably for CTF: leading and trailing spaces (if any) + * removed, and optionally a trailing argument removed as well. + * + * Type strings look like either + * + * type (for SDT, as in function prototypes), or + * + * type argname (for perf: as in function declarations). + * + * Translator components ": (foo, foo)", if any, have been removed by this + * stage. + */ +static char *cleanup_type(const char *type, int arg_strip) +{ + const char *cleaned; + const char *p; + + cleaned = type + strspn(type, " \t"); + for (p = cleaned + strlen(cleaned) - 1; p > cleaned && + isspace(*p); p--); + if (arg_strip) { + for (; p > cleaned && (isalnum(*p) || *p == '_'); p--); + for (; p > cleaned && isspace(*p); p--); + } + p++; + + return kstrndup(cleaned, p - cleaned, GFP_KERNEL); +} + +/* + * Set up the args lists, extracting them from their sdpd entry and parsing them + * into an sdt_argdesc array for each probe. + */ +static sdt_argdesc_t *sdt_setup_args(sdt_probedesc_t *sdpd, size_t *sdp_nargdesc) +{ + sdt_argdesc_t *args; + char *argstr; + char *p; + int arg_strip = 0; + char *next_arg = NULL; + size_t arg = 0, sarg = 0, i; + + *sdp_nargdesc = 0; + + if ((sdpd->sdpd_args == NULL) || (sdpd->sdpd_args[0] == '\0')) + return NULL; + /* - * { name, provider, ndx, mapping, native, xlate } + * Take a copy of the string so we can mutate it without causing trouble + * on module reload. */ - { "io", "done", 0, 0, "struct buffer_head *", "bufinfo_t *" }, - { "io", "done", 1, 0, "struct buffer_head *", "devinfo_t *" }, - { "io", "done", 2, 0, "struct buffer_head *", "fileinfo_t *" }, - { "io", "start", 0, 0, "struct buffer_head *", "bufinfo_t *" }, - { "io", "start", 1, 0, "struct buffer_head *", "devinfo_t *" }, - { "io", "start", 2, 0, "struct buffer_head *", "fileinfo_t *" }, - { "io", "wait-done", 0, 0, "struct buffer_head *", "bufinfo_t *" }, - { "io", "wait-done", 1, 0, "struct buffer_head *", "devinfo_t *" }, - { "io", "wait-done", 2, 0, "struct buffer_head *", "fileinfo_t *" }, - { "io", "wait-start", 0, 0, "struct buffer_head *", "bufinfo_t *" }, - { "io", "wait-start", 1, 0, "struct buffer_head *", "devinfo_t *" }, - { "io", "wait-start", 2, 0, "struct buffer_head *", "fileinfo_t *" }, - - { "proc", "create", 0, 0, "struct task_struct *", "psinfo_t *" }, - { "proc", "exec", 0, 0, "char *", }, - { "proc", "exec-failure", 0, 0, "int", }, - { "proc", "exit", 0, 0, "int", }, -#if 0 - { "proc", "fault", 0, 0, "int", }, - { "proc", "fault", 1, 1, "siginfo_t", }, -#endif - { "proc", "lwp-create", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "proc", "lwp-create", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "proc", "signal-clear", 0, 0, "int", }, - { "proc", "signal-discard", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "proc", "signal-discard", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "proc", "signal-discard", 2, 1, "int", }, - { "proc", "signal-handle", 0, 0, "int" }, - { "proc", "signal-handle", 1, 1, "siginfo_t *" }, - { "proc", "signal-handle", 2, 2, "void (*)(void)" }, - { "proc", "signal-send", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "proc", "signal-send", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "proc", "signal-send", 2, 1, "int", }, - - { "sched", "change-pri", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "change-pri", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "sched", "change-pri", 2, 1, "int", }, - { "sched", "dequeue", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "dequeue", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "sched", "dequeue", 2, 1, "cpuinfo_t *", }, - { "sched", "dequeue", 3, 2, "int", }, - { "sched", "enqueue", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "enqueue", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "sched", "enqueue", 2, 1, "cpuinfo_t *", }, - { "sched", "off-cpu", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "off-cpu", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "sched", "surrender", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "surrender", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "sched", "tick", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "tick", 1, 0, "struct task_struct *", "psinfo_t *" }, - { "sched", "wakeup", 0, 0, "struct task_struct *", "lwpsinfo_t *" }, - { "sched", "wakeup", 1, 0, "struct task_struct *", "psinfo_t *" }, - - { NULL, } -}; + argstr = kstrdup(sdpd->sdpd_args, GFP_KERNEL); + if (argstr == NULL) + goto oom; + + /* + * Handle the common case of a trailing comma before we allocate space, + * and elide it. + */ + p = argstr + strlen(argstr) - 1; + if (p[0] == ',' && p[1] == '\0') + *p = '\0'; + + /* + * This works for counting the number of args even though translator + * strings can contain commas, because each comma denotes a new probe + * argument. It may overcount in the case of elided arguments + * ("foo : ,"): we compensate for that further down, and ignore the tiny + * memory leak that results. + */ + for (p = argstr; p != NULL; p = strchr(p + 1, ',')) + (*sdp_nargdesc)++; + + args = kzalloc(*sdp_nargdesc * sizeof (struct sdt_argdesc), + GFP_KERNEL); + if (args == NULL) + goto oom_argstr; + + /* + * We need to transform each arg (stripping off a terminal argument + * name) if this is a perf probe. + */ + if (strncmp(sdpd->sdpd_name, "__perf_", strlen("__perf_")) == 0) + arg_strip = 1; + + next_arg = argstr; + do { + char *tok; + char *xlator = NULL, *p; + char *native; + int parens = 0; + int empty_xlation; + + /* + * Find the end of this arg, and figure out if it has any + * translators. Clean up the type of the arg (or native type, + * if this is a translated type). + */ + tok = next_arg; + next_arg = NULL; + p = strpbrk(tok, "():,"); + while (p && !next_arg) { + switch(*p) { + case '(': parens++; + break; + case ')': if (parens > 0) + parens--; + break; + case ':': *p = '\0'; + xlator = p + 1; + break; + case ',': if (parens == 0) { + *p = '\0'; + next_arg = p + 1; + } + break; + } + p = strpbrk(p + 1, "():,"); + } + + native = cleanup_type(tok, arg_strip); + if (native == NULL) { + args[arg].sda_native = args[arg].sda_xlate = NULL; + goto full_oom; + } + + /* + * Special case: perf's DECLARE_TRACE_NOARGS passes a single arg + * 'void'. Spot and skip it. + */ + if (!xlator && arg_strip && strcmp(native, "void") == 0) { + kfree(native); + (*sdp_nargdesc)--; + sarg++; + continue; + } + + /* + * No translator: straight mapping. + */ + if (xlator == NULL) { + ASSERT(arg < *sdp_nargdesc); + args[arg].sda_mapping = sarg; + args[arg].sda_native = native; + args[arg].sda_xlate = NULL; + arg++; + sarg++; + continue; + } + + /* + * If this is a perf probe, warn: translations cannot exist for + * these, and have no defined format yet in any case. We can + * struggle on by assuming they look like SDT translations. + */ + if (arg_strip) + pr_warn("Perf probe %s has at least one SDT translation, " + "which should be impossible.", sdpd->sdpd_name); + + /* + * Zero or more translations. (If there are zero, i.e. a pair + * of empty parentheses or a colon with nothing after it, we + * have to decrement the nargdesc.) + */ + + empty_xlation = 1; + while ((p = strsep(&xlator, "(,)")) != NULL) { + /* + * Skip the empty space before the ( or after the ). + */ + if (strspn(p, " \t") == strlen(p)) + continue; + + ASSERT(arg < *sdp_nargdesc); + + empty_xlation = 0; + args[arg].sda_mapping = sarg; + args[arg].sda_native = kstrdup(native, GFP_KERNEL); + args[arg].sda_xlate = cleanup_type(p, 0); + if ((args[arg].sda_native == NULL) || + (args[arg].sda_xlate == NULL)) { + pr_warn("Unable to create argdesc list for " + "probe %s: out-of-memory\n", + sdpd->sdpd_name); + kfree(native); + goto full_oom; + } + arg++; + } + if (empty_xlation) + (*sdp_nargdesc)--; + + kfree(native); + sarg++; + } while (next_arg != NULL); + + kfree(argstr); + return args; + +full_oom: + for (i = 0; i < arg; i++) { + kfree(args[i].sda_native); + kfree(args[i].sda_xlate); + } + kfree(args); +oom_argstr: + kfree(argstr); +oom: + *sdp_nargdesc = 0; + pr_warn("Unable to create argdesc list for probe %s: " + "out-of-memory\n", sdpd->sdpd_name); + return NULL; +} void sdt_provide_module(void *arg, struct module *mp) { @@ -181,6 +338,8 @@ void sdt_provide_module(void *arg, struct module *mp) sdp->sdp_namelen = len; sdp->sdp_provider = prov; + sdp->sdp_argdesc = sdt_setup_args(sdpd, &sdp->sdp_nargdesc); + if ((id = dtrace_probe_lookup(prov->dtmp_id, modname, sdpd->sdpd_func, nname)) != DTRACE_IDNONE) { @@ -260,36 +419,26 @@ void sdt_getargdesc(void *arg, dtrace_id_t id, void *parg, dtrace_argdesc_t *desc) { sdt_probe_t *sdp = parg; - int i; desc->dtargd_native[0] = '\0'; desc->dtargd_xlate[0] = '\0'; - for (i = 0; sdt_args[i].sda_provider != NULL; i++) { - sdt_argdesc_t *a = &sdt_args[i]; - - if (strcmp(sdp->sdp_provider->dtmp_name, a->sda_provider) != 0) - continue; - - if (a->sda_name != NULL && - strcmp(sdp->sdp_name, a->sda_name) != 0) - continue; - - if (desc->dtargd_ndx != a->sda_ndx) - continue; - - if (a->sda_native != NULL) - strcpy(desc->dtargd_native, a->sda_native); - - if (a->sda_xlate != NULL) - strcpy(desc->dtargd_xlate, a->sda_xlate); - - desc->dtargd_mapping = a->sda_mapping; - + if (sdp->sdp_nargdesc <= desc->dtargd_ndx) { + desc->dtargd_ndx = DTRACE_ARGNONE; return; } - desc->dtargd_ndx = DTRACE_ARGNONE; + if (sdp->sdp_argdesc[desc->dtargd_ndx].sda_native != NULL) + strlcpy(desc->dtargd_native, + sdp->sdp_argdesc[desc->dtargd_ndx].sda_native, + sizeof(desc->dtargd_native)); + + if (sdp->sdp_argdesc[desc->dtargd_ndx].sda_xlate != NULL) + strlcpy(desc->dtargd_xlate, + sdp->sdp_argdesc[desc->dtargd_ndx].sda_xlate, + sizeof(desc->dtargd_xlate)); + + desc->dtargd_mapping = sdp->sdp_argdesc[desc->dtargd_ndx].sda_mapping; } void sdt_destroy(void *arg, dtrace_id_t id, void *parg) @@ -301,6 +450,7 @@ void sdt_destroy(void *arg, dtrace_id_t id, void *parg) while (sdp != NULL) { sdt_probe_t *old = sdp, *last, *hash; int ndx; + size_t i; ndx = SDT_ADDR2NDX(sdp->sdp_patchpoint); last = NULL; @@ -317,6 +467,11 @@ void sdt_destroy(void *arg, dtrace_id_t id, void *parg) else sdt_probetab[ndx] = sdp->sdp_hashnext; + for (i = 0; i < sdp->sdp_nargdesc; i++) { + kfree(sdp->sdp_argdesc[i].sda_native); + kfree(sdp->sdp_argdesc[i].sda_xlate); + } + kfree(sdp->sdp_argdesc); kfree(sdp->sdp_name); sdp = sdp->sdp_next; kfree(old); diff --git a/dtrace/sdt_impl.h b/dtrace/sdt_impl.h index 3c03641828376..2b03eee30e5f6 100644 --- a/dtrace/sdt_impl.h +++ b/dtrace/sdt_impl.h @@ -7,6 +7,8 @@ extern struct module *dtrace_kmod; +struct sdt_argdesc; + typedef struct sdt_probe { dtrace_mprovider_t *sdp_provider; /* provider */ char *sdp_name; /* name of probe */ @@ -18,14 +20,13 @@ typedef struct sdt_probe { asm_instr_t *sdp_patchpoint;/* patch point */ asm_instr_t sdp_patchval; /* instruction to patch */ asm_instr_t sdp_savedval; /* saved instruction value */ + struct sdt_argdesc *sdp_argdesc; /* arguments for this probe */ + size_t sdp_nargdesc; /* number of arguments */ struct sdt_probe *sdp_next; /* next probe */ struct sdt_probe *sdp_hashnext; /* next on hash */ } sdt_probe_t; -typedef struct sdt_argdesc { - char *sda_provider; - char *sda_name; - int sda_ndx; +typedef struct sdt_argdesc { int sda_mapping; char *sda_native; char *sda_xlate;