]> Pileus Git - ~andy/linux/blobdiff - lib/dynamic_debug.c
Merge branch 'x86-atomic-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[~andy/linux] / lib / dynamic_debug.c
index dcdade39e47fa4aebf0745c651cd389e18889e3a..310c753cf83e27788e80ae81088b48ad824149be 100644 (file)
@@ -60,6 +60,7 @@ struct ddebug_iter {
 static DEFINE_MUTEX(ddebug_lock);
 static LIST_HEAD(ddebug_tables);
 static int verbose = 0;
+module_param(verbose, int, 0644);
 
 /* Return the last part of a pathname */
 static inline const char *basename(const char *path)
@@ -68,12 +69,24 @@ static inline const char *basename(const char *path)
        return tail ? tail+1 : path;
 }
 
+/* Return the path relative to source root */
+static inline const char *trim_prefix(const char *path)
+{
+       int skip = strlen(__FILE__) - strlen("lib/dynamic_debug.c");
+
+       if (strncmp(path, __FILE__, skip))
+               skip = 0; /* prefix mismatch, don't skip */
+
+       return path + skip;
+}
+
 static struct { unsigned flag:8; char opt_char; } opt_array[] = {
        { _DPRINTK_FLAGS_PRINT, 'p' },
        { _DPRINTK_FLAGS_INCL_MODNAME, 'm' },
        { _DPRINTK_FLAGS_INCL_FUNCNAME, 'f' },
        { _DPRINTK_FLAGS_INCL_LINENO, 'l' },
        { _DPRINTK_FLAGS_INCL_TID, 't' },
+       { _DPRINTK_FLAGS_NONE, '_' },
 };
 
 /* format a string into buf[] which describes the _ddebug's flags */
@@ -83,58 +96,74 @@ static char *ddebug_describe_flags(struct _ddebug *dp, char *buf,
        char *p = buf;
        int i;
 
-       BUG_ON(maxlen < 4);
+       BUG_ON(maxlen < 6);
        for (i = 0; i < ARRAY_SIZE(opt_array); ++i)
                if (dp->flags & opt_array[i].flag)
                        *p++ = opt_array[i].opt_char;
        if (p == buf)
-               *p++ = '-';
+               *p++ = '_';
        *p = '\0';
 
        return buf;
 }
 
+#define vpr_info_dq(q, msg)                                            \
+do {                                                                   \
+       if (verbose)                                                    \
+               /* trim last char off format print */                   \
+               pr_info("%s: func=\"%s\" file=\"%s\" "                  \
+                       "module=\"%s\" format=\"%.*s\" "                \
+                       "lineno=%u-%u",                                 \
+                       msg,                                            \
+                       q->function ? q->function : "",                 \
+                       q->filename ? q->filename : "",                 \
+                       q->module ? q->module : "",                     \
+                       (int)(q->format ? strlen(q->format) - 1 : 0),   \
+                       q->format ? q->format : "",                     \
+                       q->first_lineno, q->last_lineno);               \
+} while (0)
+
 /*
- * Search the tables for _ddebug's which match the given
- * `query' and apply the `flags' and `mask' to them.  Tells
- * the user which ddebug's were changed, or whether none
- * were matched.
+ * Search the tables for _ddebug's which match the given `query' and
+ * apply the `flags' and `mask' to them.  Returns number of matching
+ * callsites, normally the same as number of changes.  If verbose,
+ * logs the changes.  Takes ddebug_lock.
  */
-static void ddebug_change(const struct ddebug_query *query,
-                          unsigned int flags, unsigned int mask)
+static int ddebug_change(const struct ddebug_query *query,
+                       unsigned int flags, unsigned int mask)
 {
        int i;
        struct ddebug_table *dt;
        unsigned int newflags;
        unsigned int nfound = 0;
-       char flagbuf[8];
+       char flagbuf[10];
 
        /* search for matching ddebugs */
        mutex_lock(&ddebug_lock);
        list_for_each_entry(dt, &ddebug_tables, link) {
 
                /* match against the module name */
-               if (query->module != NULL &&
-                   strcmp(query->module, dt->mod_name))
+               if (query->module && strcmp(query->module, dt->mod_name))
                        continue;
 
                for (i = 0 ; i < dt->num_ddebugs ; i++) {
                        struct _ddebug *dp = &dt->ddebugs[i];
 
                        /* match against the source filename */
-                       if (query->filename != NULL &&
+                       if (query->filename &&
                            strcmp(query->filename, dp->filename) &&
-                           strcmp(query->filename, basename(dp->filename)))
+                           strcmp(query->filename, basename(dp->filename)) &&
+                           strcmp(query->filename, trim_prefix(dp->filename)))
                                continue;
 
                        /* match against the function */
-                       if (query->function != NULL &&
+                       if (query->function &&
                            strcmp(query->function, dp->function))
                                continue;
 
                        /* match against the format */
-                       if (query->format != NULL &&
-                           strstr(dp->format, query->format) == NULL)
+                       if (query->format &&
+                           !strstr(dp->format, query->format))
                                continue;
 
                        /* match against the line number range */
@@ -151,13 +180,9 @@ static void ddebug_change(const struct ddebug_query *query,
                        if (newflags == dp->flags)
                                continue;
                        dp->flags = newflags;
-                       if (newflags)
-                               dp->enabled = 1;
-                       else
-                               dp->enabled = 0;
                        if (verbose)
-                               pr_info("changed %s:%d [%s]%s %s\n",
-                                       dp->filename, dp->lineno,
+                               pr_info("changed %s:%d [%s]%s =%s\n",
+                                       trim_prefix(dp->filename), dp->lineno,
                                        dt->mod_name, dp->function,
                                        ddebug_describe_flags(dp, flagbuf,
                                                        sizeof(flagbuf)));
@@ -167,6 +192,8 @@ static void ddebug_change(const struct ddebug_query *query,
 
        if (!nfound && verbose)
                pr_info("no matches for query\n");
+
+       return nfound;
 }
 
 /*
@@ -186,8 +213,10 @@ static int ddebug_tokenize(char *buf, char *words[], int maxwords)
                buf = skip_spaces(buf);
                if (!*buf)
                        break;  /* oh, it was trailing whitespace */
+               if (*buf == '#')
+                       break;  /* token starts comment, skip rest of line */
 
-               /* Run `end' over a word, either whitespace separated or quoted */
+               /* find `end' of word, whitespace separated or quoted */
                if (*buf == '"' || *buf == '\'') {
                        int quote = *buf++;
                        for (end = buf ; *end && *end != quote ; end++)
@@ -199,8 +228,8 @@ static int ddebug_tokenize(char *buf, char *words[], int maxwords)
                                ;
                        BUG_ON(end == buf);
                }
-               /* Here `buf' is the start of the word, `end' is one past the end */
 
+               /* `buf' is start of word, `end' is one past its end */
                if (nwords == maxwords)
                        return -EINVAL; /* ran out of words[] before bytes */
                if (*end)
@@ -279,6 +308,19 @@ static char *unescape(char *str)
        return str;
 }
 
+static int check_set(const char **dest, char *src, char *name)
+{
+       int rc = 0;
+
+       if (*dest) {
+               rc = -EINVAL;
+               pr_err("match-spec:%s val:%s overridden by %s",
+                       name, *dest, src);
+       }
+       *dest = src;
+       return rc;
+}
+
 /*
  * Parse words[] as a ddebug query specification, which is a series
  * of (keyword, value) pairs chosen from these possibilities:
@@ -290,11 +332,15 @@ static char *unescape(char *str)
  * format <escaped-string-to-find-in-format>
  * line <lineno>
  * line <first-lineno>-<last-lineno> // where either may be empty
+ *
+ * Only 1 of each type is allowed.
+ * Returns 0 on success, <0 on error.
  */
 static int ddebug_parse_query(char *words[], int nwords,
                               struct ddebug_query *query)
 {
        unsigned int i;
+       int rc;
 
        /* check we have an even number of words */
        if (nwords % 2 != 0)
@@ -303,41 +349,43 @@ static int ddebug_parse_query(char *words[], int nwords,
 
        for (i = 0 ; i < nwords ; i += 2) {
                if (!strcmp(words[i], "func"))
-                       query->function = words[i+1];
+                       rc = check_set(&query->function, words[i+1], "func");
                else if (!strcmp(words[i], "file"))
-                       query->filename = words[i+1];
+                       rc = check_set(&query->filename, words[i+1], "file");
                else if (!strcmp(words[i], "module"))
-                       query->module = words[i+1];
+                       rc = check_set(&query->module, words[i+1], "module");
                else if (!strcmp(words[i], "format"))
-                       query->format = unescape(words[i+1]);
+                       rc = check_set(&query->format, unescape(words[i+1]),
+                               "format");
                else if (!strcmp(words[i], "line")) {
                        char *first = words[i+1];
                        char *last = strchr(first, '-');
+                       if (query->first_lineno || query->last_lineno) {
+                               pr_err("match-spec:line given 2 times\n");
+                               return -EINVAL;
+                       }
                        if (last)
                                *last++ = '\0';
                        if (parse_lineno(first, &query->first_lineno) < 0)
                                return -EINVAL;
-                       if (last != NULL) {
+                       if (last) {
                                /* range <first>-<last> */
-                               if (parse_lineno(last, &query->last_lineno) < 0)
+                               if (parse_lineno(last, &query->last_lineno)
+                                   < query->first_lineno) {
+                                       pr_err("last-line < 1st-line\n");
                                        return -EINVAL;
+                               }
                        } else {
                                query->last_lineno = query->first_lineno;
                        }
                } else {
-                       if (verbose)
-                               pr_err("unknown keyword \"%s\"\n", words[i]);
+                       pr_err("unknown keyword \"%s\"\n", words[i]);
                        return -EINVAL;
                }
+               if (rc)
+                       return rc;
        }
-
-       if (verbose)
-               pr_info("q->function=\"%s\" q->filename=\"%s\" "
-                       "q->module=\"%s\" q->format=\"%s\" q->lineno=%u-%u\n",
-                       query->function, query->filename,
-                       query->module, query->format, query->first_lineno,
-                       query->last_lineno);
-
+       vpr_info_dq(query, "parsed");
        return 0;
 }
 
@@ -375,8 +423,6 @@ static int ddebug_parse_flags(const char *str, unsigned int *flagsp,
                if (i < 0)
                        return -EINVAL;
        }
-       if (flags == 0)
-               return -EINVAL;
        if (verbose)
                pr_info("flags=0x%x\n", flags);
 
@@ -405,7 +451,7 @@ static int ddebug_exec_query(char *query_string)
        unsigned int flags = 0, mask = 0;
        struct ddebug_query query;
 #define MAXWORDS 9
-       int nwords;
+       int nwords, nfound;
        char *words[MAXWORDS];
 
        nwords = ddebug_tokenize(query_string, words, MAXWORDS);
@@ -417,8 +463,47 @@ static int ddebug_exec_query(char *query_string)
                return -EINVAL;
 
        /* actually go and implement the change */
-       ddebug_change(&query, flags, mask);
-       return 0;
+       nfound = ddebug_change(&query, flags, mask);
+       vpr_info_dq((&query), (nfound) ? "applied" : "no-match");
+
+       return nfound;
+}
+
+/* handle multiple queries in query string, continue on error, return
+   last error or number of matching callsites.  Module name is either
+   in param (for boot arg) or perhaps in query string.
+*/
+static int ddebug_exec_queries(char *query)
+{
+       char *split;
+       int i, errs = 0, exitcode = 0, rc, nfound = 0;
+
+       for (i = 0; query; query = split) {
+               split = strpbrk(query, ";\n");
+               if (split)
+                       *split++ = '\0';
+
+               query = skip_spaces(query);
+               if (!query || !*query || *query == '#')
+                       continue;
+
+               if (verbose)
+                       pr_info("query %d: \"%s\"\n", i, query);
+
+               rc = ddebug_exec_query(query);
+               if (rc < 0) {
+                       errs++;
+                       exitcode = rc;
+               } else
+                       nfound += rc;
+               i++;
+       }
+       pr_info("processed %d queries, with %d matches, %d errs\n",
+                i, nfound, errs);
+
+       if (exitcode)
+               return exitcode;
+       return nfound;
 }
 
 #define PREFIX_SIZE 64
@@ -452,7 +537,8 @@ static char *dynamic_emit_prefix(const struct _ddebug *desc, char *buf)
                pos += snprintf(buf + pos, remaining(pos), "%s:",
                                        desc->function);
        if (desc->flags & _DPRINTK_FLAGS_INCL_LINENO)
-               pos += snprintf(buf + pos, remaining(pos), "%d:", desc->lineno);
+               pos += snprintf(buf + pos, remaining(pos), "%d:",
+                                       desc->lineno);
        if (pos - pos_after_tid)
                pos += snprintf(buf + pos, remaining(pos), " ");
        if (pos >= PREFIX_SIZE)
@@ -527,14 +613,16 @@ EXPORT_SYMBOL(__dynamic_netdev_dbg);
 
 #endif
 
-static __initdata char ddebug_setup_string[1024];
+#define DDEBUG_STRING_SIZE 1024
+static __initdata char ddebug_setup_string[DDEBUG_STRING_SIZE];
+
 static __init int ddebug_setup_query(char *str)
 {
-       if (strlen(str) >= 1024) {
+       if (strlen(str) >= DDEBUG_STRING_SIZE) {
                pr_warn("ddebug boot param string too large\n");
                return 0;
        }
-       strcpy(ddebug_setup_string, str);
+       strlcpy(ddebug_setup_string, str, DDEBUG_STRING_SIZE);
        return 1;
 }
 
@@ -544,25 +632,33 @@ __setup("ddebug_query=", ddebug_setup_query);
  * File_ops->write method for <debugfs>/dynamic_debug/conrol.  Gathers the
  * command text from userspace, parses and executes it.
  */
+#define USER_BUF_PAGE 4096
 static ssize_t ddebug_proc_write(struct file *file, const char __user *ubuf,
                                  size_t len, loff_t *offp)
 {
-       char tmpbuf[256];
+       char *tmpbuf;
        int ret;
 
        if (len == 0)
                return 0;
-       /* we don't check *offp -- multiple writes() are allowed */
-       if (len > sizeof(tmpbuf)-1)
+       if (len > USER_BUF_PAGE - 1) {
+               pr_warn("expected <%d bytes into control\n", USER_BUF_PAGE);
                return -E2BIG;
-       if (copy_from_user(tmpbuf, ubuf, len))
+       }
+       tmpbuf = kmalloc(len + 1, GFP_KERNEL);
+       if (!tmpbuf)
+               return -ENOMEM;
+       if (copy_from_user(tmpbuf, ubuf, len)) {
+               kfree(tmpbuf);
                return -EFAULT;
+       }
        tmpbuf[len] = '\0';
        if (verbose)
                pr_info("read %d bytes from userspace\n", (int)len);
 
-       ret = ddebug_exec_query(tmpbuf);
-       if (ret)
+       ret = ddebug_exec_queries(tmpbuf);
+       kfree(tmpbuf);
+       if (ret < 0)
                return ret;
 
        *offp += len;
@@ -668,7 +764,7 @@ static int ddebug_proc_show(struct seq_file *m, void *p)
 {
        struct ddebug_iter *iter = m->private;
        struct _ddebug *dp = p;
-       char flagsbuf[8];
+       char flagsbuf[10];
 
        if (verbose)
                pr_info("called m=%p p=%p\n", m, p);
@@ -679,10 +775,10 @@ static int ddebug_proc_show(struct seq_file *m, void *p)
                return 0;
        }
 
-       seq_printf(m, "%s:%u [%s]%s %s \"",
-                  dp->filename, dp->lineno,
-                  iter->table->mod_name, dp->function,
-                  ddebug_describe_flags(dp, flagsbuf, sizeof(flagsbuf)));
+       seq_printf(m, "%s:%u [%s]%s =%s \"",
+               trim_prefix(dp->filename), dp->lineno,
+               iter->table->mod_name, dp->function,
+               ddebug_describe_flags(dp, flagsbuf, sizeof(flagsbuf)));
        seq_escape(m, dp->format, "\t\r\n\"");
        seq_puts(m, "\"\n");
 
@@ -708,10 +804,11 @@ static const struct seq_operations ddebug_proc_seqops = {
 };
 
 /*
- * File_ops->open method for <debugfs>/dynamic_debug/control.  Does the seq_file
- * setup dance, and also creates an iterator to walk the _ddebugs.
- * Note that we create a seq_file always, even for O_WRONLY files
- * where it's not needed, as doing so simplifies the ->release method.
+ * File_ops->open method for <debugfs>/dynamic_debug/control.  Does
+ * the seq_file setup dance, and also creates an iterator to walk the
+ * _ddebugs.  Note that we create a seq_file always, even for O_WRONLY
+ * files where it's not needed, as doing so simplifies the ->release
+ * method.
  */
 static int ddebug_proc_open(struct inode *inode, struct file *file)
 {
@@ -846,33 +943,40 @@ static int __init dynamic_debug_init(void)
        int ret = 0;
        int n = 0;
 
-       if (__start___verbose != __stop___verbose) {
-               iter = __start___verbose;
-               modname = iter->modname;
-               iter_start = iter;
-               for (; iter < __stop___verbose; iter++) {
-                       if (strcmp(modname, iter->modname)) {
-                               ret = ddebug_add_module(iter_start, n, modname);
-                               if (ret)
-                                       goto out_free;
-                               n = 0;
-                               modname = iter->modname;
-                               iter_start = iter;
-                       }
-                       n++;
+       if (__start___verbose == __stop___verbose) {
+               pr_warn("_ddebug table is empty in a "
+                       "CONFIG_DYNAMIC_DEBUG build");
+               return 1;
+       }
+       iter = __start___verbose;
+       modname = iter->modname;
+       iter_start = iter;
+       for (; iter < __stop___verbose; iter++) {
+               if (strcmp(modname, iter->modname)) {
+                       ret = ddebug_add_module(iter_start, n, modname);
+                       if (ret)
+                               goto out_free;
+                       n = 0;
+                       modname = iter->modname;
+                       iter_start = iter;
                }
-               ret = ddebug_add_module(iter_start, n, modname);
+               n++;
        }
+       ret = ddebug_add_module(iter_start, n, modname);
+       if (ret)
+               goto out_free;
 
        /* ddebug_query boot param got passed -> set it up */
        if (ddebug_setup_string[0] != '\0') {
-               ret = ddebug_exec_query(ddebug_setup_string);
-               if (ret)
+               ret = ddebug_exec_queries(ddebug_setup_string);
+               if (ret < 0)
                        pr_warn("Invalid ddebug boot param %s",
                                ddebug_setup_string);
                else
-                       pr_info("ddebug initialized with string %s",
-                               ddebug_setup_string);
+                       pr_info("%d changes by ddebug_query\n", ret);
+
+               /* keep tables even on ddebug_query parse error */
+               ret = 0;
        }
 
 out_free: