Dynamic Debug
pr_debug and dev_dbg: enabling kernel debug messages at runtime without recompilation
The problem with pr_debug
pr_debug() is controlled in two ways:
- With
CONFIG_DYNAMIC_DEBUG=y: eachpr_debug()becomes a dynamically-controllable site (NOP by default, enabled at runtime). - Without
CONFIG_DYNAMIC_DEBUG:pr_debug()expands toprintk(KERN_DEBUG ...)if theDEBUGC preprocessor macro is defined for that compilation unit (viaCFLAGS_file.o += -DDEBUGin the Makefile), or tono_printk()(a compile-time no-op) ifDEBUGis not defined.
Without CONFIG_DYNAMIC_DEBUG, this is an all-or-nothing compile-time switch that floods the log with messages from every subsystem simultaneously.
CONFIG_DYNAMIC_DEBUG replaces this with a per-call-site on/off switch controlled at runtime through debugfs. The cost when disabled is a single NOP instruction. There is no need to recompile or reboot to enable debug messages for a specific driver or function.
How it works
For every pr_debug() and dev_dbg() call site in the kernel, the compiler emits:
- A
static struct _ddebugdescriptor in the__dyndbgsection of the object file, containing metadata about that call site. - A NOP instruction at the call site in
.text, via the jump label (static key) infrastructure.
Dynamic debug uses the jump label (static key) infrastructure (CONFIG_JUMP_LABEL). Each pr_debug() call site has an associated struct static_key embedded in its _ddebug descriptor. When disabled, the site is a NOP (via a jump_label that doesn't branch). When enabled, static_key_enable() / jump_label_update() patches the NOP to a short jump. The patching is done by the jump label subsystem — dynamic debug itself just calls static_key_enable() or static_key_disable(). When disabled, the call site is a NOP with zero runtime overhead.
struct _ddebug
Defined in include/linux/dynamic_debug.h:
struct _ddebug {
const char *modname; /* module name */
const char *function; /* enclosing function name */
const char *filename; /* source file path */
const char *format; /* the format string literal */
unsigned int lineno:18; /* source line number */
unsigned int class_id:6;
unsigned int flags:8; /* _DPRINTK_FLAGS_* bitmask */
#ifdef CONFIG_JUMP_LABEL
union {
struct static_key_true dd_key_true;
struct static_key_false dd_key_false;
} key;
#endif
} __attribute__((aligned(8)));
The flags field tracks which output decorations are enabled for this site (print, line, file, module, thread info, and so on). The kernel maintains an array of all _ddebug entries and searches it when processing control commands.
The control interface
The control file is at /sys/kernel/debug/dynamic_debug/control. Writing a query string to it enables or disables matching call sites.
# Enable all pr_debug() calls in a module
echo "module mymodule +p" > /sys/kernel/debug/dynamic_debug/control
# Enable by source file (relative path from kernel root)
echo "file drivers/net/ethernet/intel/e1000/e1000_main.c +p" \
> /sys/kernel/debug/dynamic_debug/control
# Enable a line range within a file
echo "file drivers/net/ethernet/intel/e1000/e1000_main.c line 100-200 +p" \
> /sys/kernel/debug/dynamic_debug/control
# Enable by function name
echo "func tcp_recvmsg +p" > /sys/kernel/debug/dynamic_debug/control
# Enable with additional output decorations
# +p print the message
# +f prefix with filename
# +l prefix with line number
# +m prefix with module name
# +t prefix with thread/task name and PID
echo "module mymodule +pmfl" > /sys/kernel/debug/dynamic_debug/control
# Disable all call sites in a module
echo "module mymodule -p" > /sys/kernel/debug/dynamic_debug/control
# Show current state — one line per registered call site
cat /sys/kernel/debug/dynamic_debug/control | grep mymodule
# Show only currently active (printing) sites
grep "=p" /sys/kernel/debug/dynamic_debug/control
Query syntax
A control string has the form:
Where match-spec can be:
- module <name> — match by module name
- file <path> — match by source file (substring or exact)
- func <name> — match by function name
- line <N> or line <N>-<M> — match by line number or range
- format <string> — match if the format string contains this substring
Multiple match specs are ANDed. The flags-spec is + or - followed by flag letters.
Output flag letters
| Flag | Meaning |
|---|---|
p |
Enable printing (required to see output) |
f |
Prefix output with source filename |
l |
Prefix output with line number |
m |
Prefix output with module name |
t |
Prefix output with task name and PID |
Boot-time enablement
Some debug messages are needed before debugfs is mounted. Two boot-time mechanisms exist:
# Kernel command line — applies to all call sites matching the query
dyndbg="module mymodule +p"
# Per-module parameter — applies only to that module at load time
# Passed via the kernel command line or modprobe.conf
mymodule.dyndbg="+p"
The dyndbg kernel parameter is processed during early boot and again when modules are loaded.
Module integration
When a module is loaded, load_module() registers its __dyndbg section entries with the dynamic debug core:
/* Called from load_module() */
int dynamic_debug_setup(struct module *mod, struct _ddebug *debug, unsigned int num);
If a dyndbg module parameter was passed at load time (e.g., insmod mymodule.ko dyndbg=+p), the dynamic debug core applies it immediately after registration, before mod->init() runs. This means debug messages from the init function itself can be captured.
When a module is unloaded, its _ddebug entries are unregistered.
pr_debug vs dev_dbg
Both use the dynamic debug infrastructure and are controlled through the same interface.
/* Generic — not tied to a device */
pr_debug("packet count: %d\n", count);
/* Device-aware — prepends the device name to the output */
dev_dbg(&pdev->dev, "DMA transfer complete, status=%#x\n", status);
dev_dbg() is preferred in driver code because the device name in the output immediately identifies which hardware instance produced the message. Both are no-ops (NOPs) when disabled.
print_hex_dump_debug
For bulk data, print_hex_dump_debug() dumps a buffer in hex + ASCII format and is also controlled by dynamic debug:
This produces output like:
Combining with ftrace for lightweight tracing
Enable thread info (+t) in dynamic debug output and compare timestamps against ftrace's function graph tracer to correlate debug messages with kernel function calls — without writing a custom tracepoint:
# Enable dynamic debug with thread info
echo "module mymodule +pt" > /sys/kernel/debug/dynamic_debug/control
# Enable ftrace function graph for the same module
echo mymodule > /sys/kernel/debug/tracing/set_ftrace_filter
echo function_graph > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace_pipe &
# Run your workload — pr_debug output and ftrace appear on the same timeline
Implementation: jump label patching
When a _ddebug entry's p flag is set, the kernel:
- Updates the
flagsfield in the_ddebugstructure. - Calls
static_key_enable()on thestruct static_keyembedded in the_ddebugdescriptor'skeyfield. - The jump label subsystem (
jump_label_update()) patches the NOP at the call site to a short branch, directing execution to__dynamic_pr_debug()(or__dynamic_dev_dbg()).
The call site address and all metadata are stored directly in the _ddebug descriptor at compile time — no runtime ELF debug info lookup occurs. When disabled, static_key_disable() causes jump_label_update() to patch the branch back to a NOP. Because the patch is applied by the jump label subsystem, it is safe with respect to concurrent execution on other CPUs.
Checking what is enabled
# All registered call sites — shows =p for enabled, =_ for disabled
cat /sys/kernel/debug/dynamic_debug/control
# format: file:line [module]function flags "format-string"
# example:
# drivers/net/e1000/e1000_main.c:1502 [e1000]e1000_configure =p "configured\n"
# drivers/net/e1000/e1000_main.c:1510 [e1000]e1000_open =_ "irq %d\n"
# Count enabled sites
grep -c "=p" /sys/kernel/debug/dynamic_debug/control
# Disable everything
echo "module mymodule -p" > /sys/kernel/debug/dynamic_debug/control
Further reading
- Writing and Loading Modules — pr_* logging overview
- Module Loading Internals — how __dyndbg is registered at load time
Documentation/admin-guide/dynamic-debug-howto.rst— kernel documentation for dynamic debuginclude/linux/dynamic_debug.h— struct _ddebug and macro definitions