Skip to content

Commit

Permalink
dyndbg: RFC add tracer facility RFC
Browse files Browse the repository at this point in the history
Sean Paul seanpaul@chromium.org proposed, in
https://patchwork.freedesktop.org/series/78133/
drm/trace: Mirror DRM debug logs to tracefs

The patchset goal is to be able to duplicate the debug stream to a
tracing destination, by splitting drm_debug_enabled() into syslog &
trace flavors, and enabling them separately.  That clashes rather
deeply with this patchset goal; to avoid drm_debug_enabled() using
dyndbg.

Instead, this puts the print-to-trace decision in dyndbg, after the
is-it-enabled test (which is a noop), so it has near zero additional
cost (other than memory increase); the print-to-trace test is only
done on enabled callsites.

The basic elements:

 - add a new struct _ddebug member: (*tracer)(char *format, ...)
 - add a new T flag to enable tracer
 - adjust the static-key-enable/disable condition for (p|T)
 - if (p) before printk, since T enables too.
 - if (T) call tracer if its true

 = int dynamic_debug_register_tracer(query, tracer);
 = int dynamic_debug_unregister_tracer(query, cookie);

This new interface lets clients set/unset a tracer function on each
callsite matching the query, for example: "drm:atomic:fail:".

Clients are expected to unregister the same callsites they register (a
cookie), allowing protection of each client's dyndbg-state setup
against overwrites by others.

Intended Behavior: (things are in flux, RFC)

- register sets empty slot, doesnt overwrite
  the query selects callsites, and sets +T (grammar requires +-action)

- register allows same-tracer over-set wo warn
  2nd register can then enable superset, subset, disjoint set

- unregister clears slot if it matches cookie/tracer
  query selects set, -T (as tested)
  tolerates over-clears

- dd-exec-queries(+/-T) can modify the enablements
  not sure its needed, but it falls out..

The code is currently in-line in ddebug_change(), to be moved to
separate fn, rc determines flow, may also veto/alter changes by
altering flag-settings - tbd.

TBD: Im not sure what happens when exec-queries(+T) hits a site wo a
tracer (silence I think. maybe not ideal).

internal call-chain gets a tracer param:
					New arg:
public: dynamic_debug_exec_queries	NULL
	ro-string copy moved ...
1	ddebug_exec_queries		tracer=NULL
	... to here
2	ddebug_exec_query		tracer=NULL

call-chain gets (re)used: with !NULL

public: dynamic_debug_register_tracer	tracer=client's
	w ro-string
1	ddebug_exec_queries		tracer
...

SELFTEST: test_dynamic_debug.ko:

Uses the tracer facility to do a selftest:

- A custom tracer counts the number of calls (of T-enabled pr_debugs),
- do_debugging(x) calls a set of categorized pr_debugs x times

- test registers the tracer on the function,
  then iteratively:
  manipulates dyndbg states via query-cmds
  runs do_debugging()
  counts enabled callsite executions
  reports mismatches

- modprobe test_dynamic_debug use_bad_tracer=1
  attaches a bad/recursive tracer
  Bad Things Happen.
  has thrown me interesting panics.

NOTES:

This needs more work. RFC.

ERRORS (or WARNINGS):

It should be an error to +T a callsite which has no aux_print set (ie
already registered with a query that selected that callsite).  This
tacitly enforces registration.

Then +T,-T can toggle those aux_print callsites (or subsets of them)
to tailor the debug-stream for the purpose.  Controlling flow is the
best work limiter.

---
v4+: (this patch sent after (on top of) v4)

. fix "too many arguments to function", and name the args:
  int (*aux_print)(const char *fmt, char *prefix, char *label, void *);
   prefix : is a slot for dynamic_emit_prefix, or for custom buffer insert
   label  : for builtin-caller used by drm-trace-print
   void*  : vaf, add type constraint later.

. fix printk (to syslog) needs if (+p), since +T also enables
. add prototypes for un/register_aux_print
. change iface names: s/aux_print/tracer/
. also s/trace_print/tracer/
. struct va_format *vaf - tighten further ?
  • Loading branch information
jimc committed Aug 12, 2021
1 parent b7a923f commit 03e1630
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 27 deletions.
32 changes: 31 additions & 1 deletion include/linux/dynamic_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ struct _ddebug {
const char *function;
const char *filename;
const char *format;
int (*tracer)(const char *fmt, char *prefix, char *label, struct va_format *vaf);
unsigned int lineno:18;
/*
* The flags field controls the behaviour at the callsite.
* The bits here are changed dynamically when the user
* writes commands to <debugfs>/dynamic_debug/control
*/
#define _DPRINTK_FLAGS_NONE 0
#define _DPRINTK_FLAGS_PRINT (1<<0) /* printk() a message using the format */
#define _DPRINTK_FLAGS_PRINT (1<<0) /* printk() a message */
#define _DPRINTK_FLAGS_PRINT_TRACE (1<<5) /* call (*tracer) */

#define _DPRINTK_ENABLED (_DPRINTK_FLAGS_PRINT | _DPRINTK_FLAGS_PRINT_TRACE)

#define _DPRINTK_FLAGS_INCL_MODNAME (1<<1)
#define _DPRINTK_FLAGS_INCL_FUNCNAME (1<<2)
#define _DPRINTK_FLAGS_INCL_LINENO (1<<3)
Expand Down Expand Up @@ -279,4 +284,29 @@ extern const struct kernel_param_ops param_ops_dyndbg;
#define _DD_cat_help_(pfx)
#endif

/**
* dynamic_debug_register_tracer - register a "printer" function
* @query: query-command string to select callsites getting the function
* @tracer: &vprintf-ish accepting 3 char* ptrs & a vaf
*
* Attach a printer function to callsites selected by query.
* If another printer is already attached, warn and skip, applying the
* rest of the query. This protects existing setups, while allowing
* maximal coexistence of (mostly) non-competing listeners. RFC.
*/
int dynamic_debug_register_tracer(char *query, const char *mod,
int (*tracer) (const char *fmt, char *prefix, char *label, struct va_format *vaf));
/**
* dynamic_debug_unregister_tracer - unregister your "printer" function
* @query: query-command string to select callsites to reset
* @cookie: reserved to validate unregisters against pirates
*
* Detach this printer function (@cookie) from callsites selected by
* the query, when @cookie == callsite. This protects existing
* setups, while allowing maximal coexistence of (mostly)
* non-competing listeners. RFC.
*/
int dynamic_debug_unregister_tracer(const char *query, const char *mod,
int (*cookie) (const char *fmt, char *prefix, char *label, struct va_format *vaf));

#endif
10 changes: 10 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -2486,6 +2486,16 @@ config TEST_STATIC_KEYS

If unsure, say N.

config TEST_DYNAMIC_DEBUG
tristate "Test DYNAMIC_DEBUG"
depends on m
depends on DYNAMIC_DEBUG
help
Test the print_aux registration with a counting fn,
then test expected counts during the execution.

If unsure, say N.

config TEST_KMOD
tristate "kmod stress tester"
depends on m
Expand Down
1 change: 1 addition & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ obj-$(CONFIG_TEST_SORT) += test_sort.o
obj-$(CONFIG_TEST_USER_COPY) += test_user_copy.o
obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
obj-$(CONFIG_TEST_DYNAMIC_DEBUG) += test_dynamic_debug.o
obj-$(CONFIG_TEST_PRINTF) += test_printf.o
obj-$(CONFIG_TEST_SCANF) += test_scanf.o
obj-$(CONFIG_TEST_BITMAP) += test_bitmap.o
Expand Down
109 changes: 83 additions & 26 deletions lib/dynamic_debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ static inline const char *trim_prefix(const char *path)

static struct { unsigned flag:8; char opt_char; } opt_array[] = {
{ _DPRINTK_FLAGS_PRINT, 'p' },
{ _DPRINTK_FLAGS_PRINT_TRACE, 'T' },
{ _DPRINTK_FLAGS_INCL_MODNAME, 'm' },
{ _DPRINTK_FLAGS_INCL_FUNCNAME, 'f' },
{ _DPRINTK_FLAGS_INCL_LINENO, 'l' },
Expand Down Expand Up @@ -146,7 +147,8 @@ static void vpr_info_dq(const struct ddebug_query *query, const char *msg)
* logs the changes. Takes ddebug_lock.
*/
static int ddebug_change(const struct ddebug_query *query,
struct flag_settings *modifiers)
struct flag_settings *modifiers,
int (*tracer)(const char *, char *, char *, struct va_format *))
{
int i;
struct ddebug_table *dt;
Expand Down Expand Up @@ -205,11 +207,43 @@ static int ddebug_change(const struct ddebug_query *query,
newflags = (dp->flags & modifiers->mask) | modifiers->flags;
if (newflags == dp->flags)
continue;

/* handle T flag */
if (newflags & _DPRINTK_FLAGS_PRINT_TRACE) {
if (!tracer)
v2pr_info("tracer enable\n");
else {
/* register attempt */
if (!dp->tracer) {
v2pr_info("register tracer\n");
dp->tracer = tracer;
}
else if (tracer == dp->tracer)
v2pr_info("tracer equal, ok\n");
else
pr_warn("tracer register error\n");
}
} else if (dp->flags & _DPRINTK_FLAGS_PRINT_TRACE) {
if (!tracer)
v2pr_info("ok: disabling\n");
else {
/* only unregister has a !!tracer */
if (!dp->tracer)
pr_warn("nok: tracer already unset\n");

else if (dp->tracer == tracer) {
v2pr_info("ok: cookie match, unregistering\n");
dp->tracer = NULL;
}
else
pr_warn("nok: tracer cookie match fail\n");
}
}
#ifdef CONFIG_JUMP_LABEL
if (dp->flags & _DPRINTK_FLAGS_PRINT) {
if (!(modifiers->flags & _DPRINTK_FLAGS_PRINT))
if (dp->flags & _DPRINTK_ENABLED) {
if (!(modifiers->flags & _DPRINTK_ENABLED))
static_branch_disable(&dp->key.dd_key_true);
} else if (modifiers->flags & _DPRINTK_FLAGS_PRINT)
} else if (modifiers->flags & _DPRINTK_ENABLED)
static_branch_enable(&dp->key.dd_key_true);
#endif
dp->flags = newflags;
Expand Down Expand Up @@ -482,7 +516,7 @@ static int ddebug_parse_flags(const char *str, struct flag_settings *modifiers)
return 0;
}

static int ddebug_exec_query(char *query_string, const char *modname)
static int ddebug_exec_query(char *query_string, const char *modname, void *tracer)
{
struct flag_settings modifiers = {};
struct ddebug_query query = {};
Expand All @@ -505,7 +539,7 @@ static int ddebug_exec_query(char *query_string, const char *modname)
return -EINVAL;
}
/* actually go and implement the change */
nfound = ddebug_change(&query, &modifiers);
nfound = ddebug_change(&query, &modifiers, tracer);
vpr_info_dq(&query, nfound ? "applied" : "no-match");

return nfound;
Expand All @@ -515,10 +549,19 @@ static int ddebug_exec_query(char *query_string, const char *modname)
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)
static int ddebug_exec_queries(const char *query_in, const char *modname, void *tracer)
{
char *split;
int i, errs = 0, exitcode = 0, rc, nfound = 0;
char *query; /* writable copy of query_in */

if (!query_in) {
pr_err("non-null query/command string expected\n");
return -EINVAL;
}
query = kstrndup(query_in, PAGE_SIZE, GFP_KERNEL);
if (!query)
return -ENOMEM;

for (i = 0; query; query = split) {
split = strpbrk(query, ";\n");
Expand All @@ -531,7 +574,7 @@ static int ddebug_exec_queries(char *query, const char *modname)

vpr_info("query %d: \"%s\" %s\n", i, query, (modname) ? modname : "");

rc = ddebug_exec_query(query, modname);
rc = ddebug_exec_query(query, modname, tracer);
if (rc < 0) {
errs++;
exitcode = rc;
Expand All @@ -540,6 +583,7 @@ static int ddebug_exec_queries(char *query, const char *modname)
}
i++;
}
kfree(query);
vpr_info("processed %d queries, with %d matches, %d errs\n",
i, nfound, errs);

Expand All @@ -560,20 +604,7 @@ static int ddebug_exec_queries(char *query, const char *modname)
*/
int dynamic_debug_exec_queries(const char *query, const char *modname)
{
int rc;
char *qry; /* writable copy of query */

if (!query) {
pr_err("non-null query/command string expected\n");
return -EINVAL;
}
qry = kstrndup(query, PAGE_SIZE, GFP_KERNEL);
if (!qry)
return -ENOMEM;

rc = ddebug_exec_queries(qry, modname);
kfree(qry);
return rc;
return ddebug_exec_queries(query, modname, NULL);
}
EXPORT_SYMBOL_GPL(dynamic_debug_exec_queries);

Expand Down Expand Up @@ -637,8 +668,20 @@ void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...)
vaf.fmt = fmt;
vaf.va = &args;

printk(KERN_DEBUG "%s%pV", dynamic_emit_prefix(descriptor, buf), &vaf);
if (descriptor->flags & _DPRINTK_ENABLED)
dynamic_emit_prefix(descriptor, buf);

if (descriptor->flags & _DPRINTK_FLAGS_PRINT)
printk(KERN_DEBUG "%s%pV", buf, &vaf);

if (descriptor->flags & _DPRINTK_FLAGS_PRINT_TRACE) {

if (descriptor->tracer) {
(*descriptor->tracer)("%s:%ps %pV", buf,
__builtin_return_address(0), &vaf);
}
/* else shouldnt matter, but maybe for consistency */
}
va_end(args);
}
EXPORT_SYMBOL(__dynamic_pr_debug);
Expand Down Expand Up @@ -783,7 +826,7 @@ static ssize_t ddebug_proc_write(struct file *file, const char __user *ubuf,
return PTR_ERR(tmpbuf);
vpr_info("read %d bytes from userspace\n", (int)len);

ret = ddebug_exec_queries(tmpbuf, NULL);
ret = ddebug_exec_queries(tmpbuf, NULL, NULL);
kfree(tmpbuf);
if (ret < 0)
return ret;
Expand Down Expand Up @@ -989,7 +1032,7 @@ static int ddebug_dyndbg_param_cb(char *param, char *val,
if (strcmp(param, "dyndbg"))
return on_err; /* determined by caller */

ddebug_exec_queries((val ? val : "+p"), modname);
ddebug_exec_queries((val ? val : "+p"), modname, NULL);

return 0; /* query failure shouldn't stop module load */
}
Expand Down Expand Up @@ -1124,7 +1167,7 @@ static int __init dynamic_debug_init(void)
/* apply ddebug_query boot param, dont unload tables on err */
if (ddebug_setup_string[0] != '\0') {
pr_warn("ddebug_query param name is deprecated, change it to dyndbg\n");
ret = ddebug_exec_queries(ddebug_setup_string, NULL);
ret = ddebug_exec_queries(ddebug_setup_string, NULL, NULL);
if (ret < 0)
pr_warn("Invalid ddebug boot param %s\n",
ddebug_setup_string);
Expand Down Expand Up @@ -1214,3 +1257,17 @@ const struct kernel_param_ops param_ops_dyndbg = {
};
/* support DEFINE_DYNAMIC_DEBUG_CATEGORIES users */
EXPORT_SYMBOL(param_ops_dyndbg);

int dynamic_debug_register_tracer(const char *query, const char *mod,
int (*tracer) (const char *fmt, char *prefix, char *label, struct va_format *vaf))
{
return ddebug_exec_queries(query, mod, tracer);
}
EXPORT_SYMBOL(dynamic_debug_register_tracer);

int dynamic_debug_unregister_tracer(const char *query, const char *mod,
int (*tracer) (const char *fmt, char *prefix, char *label, struct va_format *vaf))
{
return ddebug_exec_queries(query, mod, tracer);
}
EXPORT_SYMBOL(dynamic_debug_unregister_tracer);
Loading

0 comments on commit 03e1630

Please sign in to comment.