]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
dtrace: parse sdpd_args to handle sdt_getargdesc() rather than hardwiring
authorNick Alcock <nick.alcock@oracle.com>
Wed, 14 Sep 2016 09:52:17 +0000 (10:52 +0100)
committerNick Alcock <nick.alcock@oracle.com>
Fri, 28 Oct 2016 13:01:13 +0000 (14:01 +0100)
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 <nick.alcock@oracle.com>
Acked-by: Kris Van Hees <kris.van.hees@oracle.com>
Orabug: 24661801

dtrace/sdt_dev.c
dtrace/sdt_impl.h

index b02154d8ad6ca68e04d0243e1fae1a1cab445805..ad6557ce456d3f21a0867f6e72da5eeb4f09b3f8 100644 (file)
@@ -25,6 +25,7 @@
  * Use is subject to license terms.
  */
 
+#include <linux/ctype.h>
 #include <linux/fs.h>
 #include <linux/miscdevice.h>
 #include <linux/sdt.h>
@@ -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);
index 3c036418283767dc0a1f55dad5e9ffb256c2220f..2b03eee30e5f626871fa1b083792115e70c4523e 100644 (file)
@@ -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;