|
@@ -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:
|