summaryrefslogtreecommitdiffstats
path: root/lib/dynamic_debug.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dynamic_debug.c')
-rw-r--r--lib/dynamic_debug.c542
1 files changed, 383 insertions, 159 deletions
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index 75ca78f3a8c..7ca29a0a301 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -10,11 +10,12 @@
* Copyright (C) 2011 Bart Van Assche. All Rights Reserved.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
+
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kallsyms.h>
-#include <linux/version.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
@@ -30,6 +31,8 @@
#include <linux/jump_label.h>
#include <linux/hardirq.h>
#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
extern struct _ddebug __start___verbose[];
extern struct _ddebug __stop___verbose[];
@@ -38,7 +41,6 @@ struct ddebug_table {
struct list_head link;
char *mod_name;
unsigned int num_ddebugs;
- unsigned int num_enabled;
struct _ddebug *ddebugs;
};
@@ -58,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)
@@ -66,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 */
@@ -81,58 +96,76 @@ 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(fmt, ...) \
+ if (verbose) do { pr_info(fmt, ##__VA_ARGS__); } while (0)
+
+#define vpr_info_dq(q, msg) \
+do { \
+ /* trim last char off format print */ \
+ vpr_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 */
@@ -148,29 +181,20 @@ static void ddebug_change(const struct ddebug_query *query,
newflags = (dp->flags & mask) | flags;
if (newflags == dp->flags)
continue;
-
- if (!newflags)
- dt->num_enabled--;
- else if (!dp->flags)
- dt->num_enabled++;
dp->flags = newflags;
- if (newflags)
- dp->enabled = 1;
- else
- dp->enabled = 0;
- if (verbose)
- printk(KERN_INFO
- "ddebug: changed %s:%d [%s]%s %s\n",
- dp->filename, dp->lineno,
- dt->mod_name, dp->function,
- ddebug_describe_flags(dp, flagbuf,
- sizeof(flagbuf)));
+ vpr_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)));
}
}
mutex_unlock(&ddebug_lock);
if (!nfound && verbose)
- printk(KERN_INFO "ddebug: no matches for query\n");
+ pr_info("no matches for query\n");
+
+ return nfound;
}
/*
@@ -190,8 +214,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++)
@@ -203,8 +229,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)
@@ -215,10 +241,10 @@ static int ddebug_tokenize(char *buf, char *words[], int maxwords)
if (verbose) {
int i;
- printk(KERN_INFO "%s: split into words:", __func__);
+ pr_info("split into words:");
for (i = 0 ; i < nwords ; i++)
- printk(" \"%s\"", words[i]);
- printk("\n");
+ pr_cont(" \"%s\"", words[i]);
+ pr_cont("\n");
}
return nwords;
@@ -283,6 +309,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:
@@ -294,55 +333,64 @@ 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)
+ struct ddebug_query *query, const char *modname)
{
unsigned int i;
+ int rc;
/* check we have an even number of words */
if (nwords % 2 != 0)
return -EINVAL;
memset(query, 0, sizeof(*query));
+ if (modname)
+ /* support $modname.dyndbg=<multiple queries> */
+ query->module = modname;
+
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)
- printk(KERN_ERR "%s: unknown keyword \"%s\"\n",
- __func__, words[i]);
+ pr_err("unknown keyword \"%s\"\n", words[i]);
return -EINVAL;
}
+ if (rc)
+ return rc;
}
-
- if (verbose)
- printk(KERN_INFO "%s: q->function=\"%s\" q->filename=\"%s\" "
- "q->module=\"%s\" q->format=\"%s\" q->lineno=%u-%u\n",
- __func__, query->function, query->filename,
- query->module, query->format, query->first_lineno,
- query->last_lineno);
-
+ vpr_info_dq(query, "parsed");
return 0;
}
@@ -367,8 +415,7 @@ static int ddebug_parse_flags(const char *str, unsigned int *flagsp,
default:
return -EINVAL;
}
- if (verbose)
- printk(KERN_INFO "%s: op='%c'\n", __func__, op);
+ vpr_info("op='%c'\n", op);
for ( ; *str ; ++str) {
for (i = ARRAY_SIZE(opt_array) - 1; i >= 0; i--) {
@@ -380,10 +427,7 @@ static int ddebug_parse_flags(const char *str, unsigned int *flagsp,
if (i < 0)
return -EINVAL;
}
- if (flags == 0)
- return -EINVAL;
- if (verbose)
- printk(KERN_INFO "%s: flags=0x%x\n", __func__, flags);
+ vpr_info("flags=0x%x\n", flags);
/* calculate final *flagsp, *maskp according to mask and op */
switch (op) {
@@ -400,70 +444,186 @@ static int ddebug_parse_flags(const char *str, unsigned int *flagsp,
*flagsp = 0;
break;
}
- if (verbose)
- printk(KERN_INFO "%s: *flagsp=0x%x *maskp=0x%x\n",
- __func__, *flagsp, *maskp);
+ vpr_info("*flagsp=0x%x *maskp=0x%x\n", *flagsp, *maskp);
return 0;
}
-static int ddebug_exec_query(char *query_string)
+static int ddebug_exec_query(char *query_string, const char *modname)
{
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);
if (nwords <= 0)
return -EINVAL;
- if (ddebug_parse_query(words, nwords-1, &query))
+ if (ddebug_parse_query(words, nwords-1, &query, modname))
return -EINVAL;
if (ddebug_parse_flags(words[nwords-1], &flags, &mask))
return -EINVAL;
/* actually go and implement the change */
- ddebug_change(&query, flags, mask);
+ 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, const char *modname)
+{
+ 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;
+
+ vpr_info("query %d: \"%s\"\n", i, query);
+
+ rc = ddebug_exec_query(query, modname);
+ if (rc < 0) {
+ errs++;
+ exitcode = rc;
+ } else
+ nfound += rc;
+ i++;
+ }
+ vpr_info("processed %d queries, with %d matches, %d errs\n",
+ i, nfound, errs);
+
+ if (exitcode)
+ return exitcode;
+ return nfound;
+}
+
+#define PREFIX_SIZE 64
+
+static int remaining(int wrote)
+{
+ if (PREFIX_SIZE - wrote > 0)
+ return PREFIX_SIZE - wrote;
return 0;
}
+static char *dynamic_emit_prefix(const struct _ddebug *desc, char *buf)
+{
+ int pos_after_tid;
+ int pos = 0;
+
+ pos += snprintf(buf + pos, remaining(pos), "%s", KERN_DEBUG);
+ if (desc->flags & _DPRINTK_FLAGS_INCL_TID) {
+ if (in_interrupt())
+ pos += snprintf(buf + pos, remaining(pos), "%s ",
+ "<intr>");
+ else
+ pos += snprintf(buf + pos, remaining(pos), "[%d] ",
+ task_pid_vnr(current));
+ }
+ pos_after_tid = pos;
+ if (desc->flags & _DPRINTK_FLAGS_INCL_MODNAME)
+ pos += snprintf(buf + pos, remaining(pos), "%s:",
+ desc->modname);
+ if (desc->flags & _DPRINTK_FLAGS_INCL_FUNCNAME)
+ 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);
+ if (pos - pos_after_tid)
+ pos += snprintf(buf + pos, remaining(pos), " ");
+ if (pos >= PREFIX_SIZE)
+ buf[PREFIX_SIZE - 1] = '\0';
+
+ return buf;
+}
+
int __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...)
{
va_list args;
int res;
+ struct va_format vaf;
+ char buf[PREFIX_SIZE];
BUG_ON(!descriptor);
BUG_ON(!fmt);
va_start(args, fmt);
- res = printk(KERN_DEBUG);
- if (descriptor->flags & _DPRINTK_FLAGS_INCL_TID) {
- if (in_interrupt())
- res += printk(KERN_CONT "<intr> ");
- else
- res += printk(KERN_CONT "[%d] ", task_pid_vnr(current));
- }
- if (descriptor->flags & _DPRINTK_FLAGS_INCL_MODNAME)
- res += printk(KERN_CONT "%s:", descriptor->modname);
- if (descriptor->flags & _DPRINTK_FLAGS_INCL_FUNCNAME)
- res += printk(KERN_CONT "%s:", descriptor->function);
- if (descriptor->flags & _DPRINTK_FLAGS_INCL_LINENO)
- res += printk(KERN_CONT "%d ", descriptor->lineno);
- res += vprintk(fmt, args);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+ res = printk("%s%pV", dynamic_emit_prefix(descriptor, buf), &vaf);
va_end(args);
return res;
}
EXPORT_SYMBOL(__dynamic_pr_debug);
-static __initdata char ddebug_setup_string[1024];
+int __dynamic_dev_dbg(struct _ddebug *descriptor,
+ const struct device *dev, const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+ int res;
+ char buf[PREFIX_SIZE];
+
+ BUG_ON(!descriptor);
+ BUG_ON(!fmt);
+
+ va_start(args, fmt);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+ res = __dev_printk(dynamic_emit_prefix(descriptor, buf), dev, &vaf);
+ va_end(args);
+
+ return res;
+}
+EXPORT_SYMBOL(__dynamic_dev_dbg);
+
+#ifdef CONFIG_NET
+
+int __dynamic_netdev_dbg(struct _ddebug *descriptor,
+ const struct net_device *dev, const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+ int res;
+ char buf[PREFIX_SIZE];
+
+ BUG_ON(!descriptor);
+ BUG_ON(!fmt);
+
+ va_start(args, fmt);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+ res = __netdev_printk(dynamic_emit_prefix(descriptor, buf), dev, &vaf);
+ va_end(args);
+
+ return res;
+}
+EXPORT_SYMBOL(__dynamic_netdev_dbg);
+
+#endif
+
+#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) {
- pr_warning("ddebug boot param string too large\n");
+ 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;
}
@@ -473,26 +633,32 @@ __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)
- printk(KERN_INFO "%s: read %d bytes from userspace\n",
- __func__, (int)len);
+ vpr_info("read %d bytes from userspace\n", (int)len);
- ret = ddebug_exec_query(tmpbuf);
- if (ret)
+ ret = ddebug_exec_queries(tmpbuf, NULL);
+ kfree(tmpbuf);
+ if (ret < 0)
return ret;
*offp += len;
@@ -551,9 +717,7 @@ static void *ddebug_proc_start(struct seq_file *m, loff_t *pos)
struct _ddebug *dp;
int n = *pos;
- if (verbose)
- printk(KERN_INFO "%s: called m=%p *pos=%lld\n",
- __func__, m, (unsigned long long)*pos);
+ vpr_info("called m=%p *pos=%lld\n", m, (unsigned long long)*pos);
mutex_lock(&ddebug_lock);
@@ -577,9 +741,8 @@ static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)
struct ddebug_iter *iter = m->private;
struct _ddebug *dp;
- if (verbose)
- printk(KERN_INFO "%s: called m=%p p=%p *pos=%lld\n",
- __func__, m, p, (unsigned long long)*pos);
+ vpr_info("called m=%p p=%p *pos=%lld\n",
+ m, p, (unsigned long long)*pos);
if (p == SEQ_START_TOKEN)
dp = ddebug_iter_first(iter);
@@ -599,11 +762,9 @@ 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)
- printk(KERN_INFO "%s: called m=%p p=%p\n",
- __func__, m, p);
+ vpr_info("called m=%p p=%p\n", m, p);
if (p == SEQ_START_TOKEN) {
seq_puts(m,
@@ -611,10 +772,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");
@@ -627,9 +788,7 @@ static int ddebug_proc_show(struct seq_file *m, void *p)
*/
static void ddebug_proc_stop(struct seq_file *m, void *p)
{
- if (verbose)
- printk(KERN_INFO "%s: called m=%p p=%p\n",
- __func__, m, p);
+ vpr_info("called m=%p p=%p\n", m, p);
mutex_unlock(&ddebug_lock);
}
@@ -641,18 +800,18 @@ 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)
{
struct ddebug_iter *iter;
int err;
- if (verbose)
- printk(KERN_INFO "%s: called\n", __func__);
+ vpr_info("called\n");
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (iter == NULL)
@@ -696,20 +855,57 @@ int ddebug_add_module(struct _ddebug *tab, unsigned int n,
}
dt->mod_name = new_name;
dt->num_ddebugs = n;
- dt->num_enabled = 0;
dt->ddebugs = tab;
mutex_lock(&ddebug_lock);
list_add_tail(&dt->link, &ddebug_tables);
mutex_unlock(&ddebug_lock);
- if (verbose)
- printk(KERN_INFO "%u debug prints in module %s\n",
- n, dt->mod_name);
+ vpr_info("%u debug prints in module %s\n", n, dt->mod_name);
return 0;
}
EXPORT_SYMBOL_GPL(ddebug_add_module);
+/* helper for ddebug_dyndbg_(boot|module)_param_cb */
+static int ddebug_dyndbg_param_cb(char *param, char *val,
+ const char *modname, int on_err)
+{
+ char *sep;
+
+ sep = strchr(param, '.');
+ if (sep) {
+ /* needed only for ddebug_dyndbg_boot_param_cb */
+ *sep = '\0';
+ modname = param;
+ param = sep + 1;
+ }
+ if (strcmp(param, "dyndbg"))
+ return on_err; /* determined by caller */
+
+ ddebug_exec_queries((val ? val : "+p"), modname);
+
+ return 0; /* query failure shouldnt stop module load */
+}
+
+/* handle both dyndbg and $module.dyndbg params at boot */
+static int ddebug_dyndbg_boot_param_cb(char *param, char *val,
+ const char *unused)
+{
+ vpr_info("%s=\"%s\"\n", param, val);
+ return ddebug_dyndbg_param_cb(param, val, NULL, 0);
+}
+
+/*
+ * modprobe foo finds foo.params in boot-args, strips "foo.", and
+ * passes them to load_module(). This callback gets unknown params,
+ * processes dyndbg params, rejects others.
+ */
+int ddebug_dyndbg_module_param_cb(char *param, char *val, const char *module)
+{
+ vpr_info("module: %s %s=\"%s\"\n", module, param, val);
+ return ddebug_dyndbg_param_cb(param, val, module, -ENOENT);
+}
+
static void ddebug_table_free(struct ddebug_table *dt)
{
list_del_init(&dt->link);
@@ -726,9 +922,7 @@ int ddebug_remove_module(const char *mod_name)
struct ddebug_table *dt, *nextdt;
int ret = -ENOENT;
- if (verbose)
- printk(KERN_INFO "%s: removing module \"%s\"\n",
- __func__, mod_name);
+ vpr_info("removing module \"%s\"\n", mod_name);
mutex_lock(&ddebug_lock);
list_for_each_entry_safe(dt, nextdt, &ddebug_tables, link) {
@@ -779,46 +973,76 @@ static int __init dynamic_debug_init(void)
{
struct _ddebug *iter, *iter_start;
const char *modname = NULL;
+ char *cmdline;
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++;
+ int n = 0, entries = 0, modct = 0;
+ int verbose_bytes = 0;
+
+ 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++) {
+ entries++;
+ verbose_bytes += strlen(iter->modname) + strlen(iter->function)
+ + strlen(iter->filename) + strlen(iter->format);
+
+ if (strcmp(modname, iter->modname)) {
+ modct++;
+ ret = ddebug_add_module(iter_start, n, modname);
+ if (ret)
+ goto out_err;
+ 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_err;
+
+ ddebug_init_success = 1;
+ vpr_info("%d modules, %d entries and %d bytes in ddebug tables,"
+ " %d bytes in (readonly) verbose section\n",
+ modct, entries, (int)( modct * sizeof(struct ddebug_table)),
+ verbose_bytes + (int)(__stop___verbose - __start___verbose));
- /* ddebug_query boot param got passed -> set it up */
+ /* apply ddebug_query boot param, dont unload tables on err */
if (ddebug_setup_string[0] != '\0') {
- ret = ddebug_exec_query(ddebug_setup_string);
- if (ret)
- pr_warning("Invalid ddebug boot param %s",
- ddebug_setup_string);
- else
- pr_info("ddebug initialized with string %s",
+ pr_warn("ddebug_query param name is deprecated,"
+ " change it to dyndbg\n");
+ ret = ddebug_exec_queries(ddebug_setup_string, NULL);
+ if (ret < 0)
+ pr_warn("Invalid ddebug boot param %s",
ddebug_setup_string);
+ else
+ pr_info("%d changes by ddebug_query\n", ret);
}
+ /* now that ddebug tables are loaded, process all boot args
+ * again to find and activate queries given in dyndbg params.
+ * While this has already been done for known boot params, it
+ * ignored the unknown ones (dyndbg in particular). Reusing
+ * parse_args avoids ad-hoc parsing. This will also attempt
+ * to activate queries for not-yet-loaded modules, which is
+ * slightly noisy if verbose, but harmless.
+ */
+ cmdline = kstrdup(saved_command_line, GFP_KERNEL);
+ parse_args("dyndbg params", cmdline, NULL,
+ 0, 0, 0, &ddebug_dyndbg_boot_param_cb);
+ kfree(cmdline);
+ return 0;
-out_free:
- if (ret)
- ddebug_remove_all_tables();
- else
- ddebug_init_success = 1;
+out_err:
+ ddebug_remove_all_tables();
return 0;
}
/* Allow early initialization for boot messages via boot param */
-arch_initcall(dynamic_debug_init);
+early_initcall(dynamic_debug_init);
+
/* Debugfs setup must be done later */
-module_init(dynamic_debug_init_debugfs);
+fs_initcall(dynamic_debug_init_debugfs);