diff options
Diffstat (limited to 'lib/dynamic_debug.c')
| -rw-r--r-- | lib/dynamic_debug.c | 270 | 
1 files changed, 187 insertions, 83 deletions
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c index dcdade39e47f..310c753cf83e 100644 --- a/lib/dynamic_debug.c +++ b/lib/dynamic_debug.c @@ -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:  | 
