Linux Kernel debugfs Tutorial: Building Debug Interfaces for Kernel Modules-Best Linux Device Driver Training Online

Linux Kernel debugfs Tutorial: Building Debug Interfaces for Kernel Modules
A beginner-friendly, hands-on guide to debugfs on modern Linux kernels (6.x) — part of EmbeddedPathashala’s free Linux kernel programming course
Difficulty: Beginner–Intermediate
Kernel: 6.x LTS
Reading Time: 14 min

If you have ever written a Linux kernel module and wished you had a quick, no-fuss way to peek inside its internal state while it was running, this Linux kernel debugfs tutorial is exactly what you need. debugfs is the kernel’s dedicated debugging filesystem, and it is one of the fastest ways for driver authors to expose runtime values, counters, and configuration knobs to user space without designing a full-blown driver interface. In this lesson — part of our free Linux kernel programming course and free Linux device drivers course — you will build a working debugfs-based debug interface from scratch, understand how it differs from procfs and sysfs, and learn the modern kernel APIs used to create it on current 6.x kernels.

In this Linux kernel debugfs tutorial, you’ll work with:
debugfs_create_dir debugfs_create_file debugfs_create_u32 file_operations i_private simple_read_from_buffer debugfs_remove_recursive

What You Will Learn

  • What debugfs is and why the kernel community built it purely for debugging
  • How debugfs compares to procfs and sysfs, and when to pick each one
  • How to create a debugfs directory and register debug pseudo-files from a kernel module
  • How to write your own read callback using a file_operations structure
  • How to use the i_private field to pass driver context data to your callbacks
  • How to expose a variable read/write with a single line using debugfs_create_u32()
  • How to clean up debugfs entries safely on module removal
  • Common mistakes, security notes, and best practices for production-safe debug code

Prerequisites

Before starting this tutorial, you should already be comfortable with:

  • Writing and building a basic Loadable Kernel Module (LKM) with insmod/rmmod
  • Basic C pointers, structures, and the Linux kernel module init/exit macros
  • A Linux system running a recent kernel (5.10+ recommended, examples verified on 6.x)
  • Root/sudo access, since debugfs is restricted to the root user by default

New to kernel modules? Start with our free Linux kernel programming course from the beginning — the first lecture link is at the top of this page.

What Is debugfs and Why Does It Exist?

debugfs is a special, lightweight, RAM-only virtual filesystem built into the Linux kernel with one goal: give kernel and driver developers a completely unrestricted way to export debug information to user space. Unlike procfs and sysfs, debugfs was never meant to be a stable, user-facing ABI. There are no rules about file formats, no “one value per file” convention, and no promise that entries will exist in every kernel build. That freedom is precisely what makes it so convenient during development and bring-up of new drivers.

On almost every modern distribution, debugfs is automatically mounted at boot. You can confirm this yourself:

$ mount | grep debugfs
debugfs on /sys/kernel/debug type debugfs (rw,relatime)

If it isn’t mounted, you (as root) can mount it manually:

$ sudo mount -t debugfs none /sys/kernel/debug
How a User-Space Read Reaches Your Kernel Module
User Space
cat / echo command
→
VFS Layer
system call routing
→
debugfs
/sys/kernel/debug/…
→
Your Callback
read / write function

debugfs vs procfs vs sysfs: Which Should You Use?

Most beginners in a free Linux device drivers course get confused about when to use debugfs versus procfs or sysfs. Here’s a quick comparison table to clear that up:

Aspect debugfs sysfs procfs
Primary purpose Ad-hoc debugging Device/driver model attributes Process and kernel-wide info
ABI stability None – can change anytime Stable, one value per file Mostly stable, historical
File format rules Whatever you like, including binary blobs One value per attribute Free-form text
Typical mount point /sys/kernel/debug /sys /proc

Step 1: Create a debugfs Directory for Your Module

Every well-behaved debugfs user creates its own subdirectory so its debug files stay organized and don’t clutter the top level. This uses debugfs_create_dir(), which takes a name and an optional parent directory (pass NULL for the top level):

#include <linux/debugfs.h>

#define DRVNAME "ep_debugfs_demo"

static struct dentry *ep_dbgfs_root;

static int __init ep_debugfs_demo_init(void)
{
    ep_dbgfs_root = debugfs_create_dir(DRVNAME, NULL);
    if (IS_ERR(ep_dbgfs_root)) {
        pr_err("%s: failed to create debugfs directory\n", DRVNAME);
        return PTR_ERR(ep_dbgfs_root);
    }

    pr_info("%s: debugfs directory created at /sys/kernel/debug/%s\n",
            DRVNAME, DRVNAME);
    return 0;
}

Note: On very old kernels, several debugfs creation functions returned error pointers on failure. On current 6.x kernels, most of the “helper” creation APIs return void and simply fail silently if debugfs is disabled, so always check your kernel documentation for the exact return type you are compiling against.

Step 2: Expose a Read-Only Debug File with a Custom Callback

Suppose your driver keeps a small runtime context structure and you want to dump it whenever someone reads a debugfs file. First, define the callback and wire it into a file_operations structure:

struct ep_ctx {
    u32 events_seen;
    u32 error_count;
    u64 last_ts;
};

static struct ep_ctx *g_ctx;
static DEFINE_MUTEX(ep_ctx_lock);

static ssize_t ep_dbgfs_dump_read(struct file *filp, char __user *ubuf,
                                   size_t count, loff_t *fpos)
{
    struct ep_ctx *ctx = filp->f_inode->i_private;
    char buf[200];
    int len;

    mutex_lock(&ep_ctx_lock);
    ctx->last_ts = jiffies;
    len = scnprintf(buf, sizeof(buf),
                     "events_seen:%u\nerror_count:%u\nlast_ts:%llu\n",
                     ctx->events_seen, ctx->error_count, ctx->last_ts);
    mutex_unlock(&ep_ctx_lock);

    return simple_read_from_buffer(ubuf, count, fpos, buf, len);
}

static const struct file_operations ep_dbgfs_dump_fops = {
    .owner = THIS_MODULE,
    .read  = ep_dbgfs_dump_read,
};

Notice the pattern: your read callback pulls its private data straight out of filp->f_inode->i_private. This lets you register the same callback for multiple instances of a device, each carrying its own context pointer, instead of relying on a single global variable.

Now register the file inside your init function, passing the context pointer as the fourth argument — it is stored automatically into i_private for you:

    g_ctx = kzalloc(sizeof(*g_ctx), GFP_KERNEL);
    if (!g_ctx)
        return -ENOMEM;

    debugfs_create_file("dump_context", 0440, ep_dbgfs_root,
                         g_ctx, &ep_dbgfs_dump_fops);

The mode 0440 means read-only for the owner (root) and root’s group — a sensible default for information that shouldn’t be writable.

Step 3: Skip the Boilerplate with debugfs_create_u32()

Writing a full file_operations structure for every single integer knob gets tedious fast. debugfs provides ready-made helper functions — debugfs_create_u8(), debugfs_create_u16(), debugfs_create_u32(), debugfs_create_u64(), and debugfs_create_bool() — that bind a debugfs file directly to a kernel variable. No callback code required:

static u32 ep_debug_level; /* controllable from user space */

    debugfs_create_u32("debug_level", 0640, ep_dbgfs_root, &ep_debug_level);

With this single line, reading the file returns the current value of ep_debug_level, and writing an integer to it updates the variable inside the kernel — debugfs handles all the parsing and formatting internally.

Step 4: Clean Up on Module Removal

Always remove your debugfs entries in the module’s exit function. The safest approach is a single recursive removal of the root directory, which takes every child file with it:

static void __exit ep_debugfs_demo_exit(void)
{
    debugfs_remove_recursive(ep_dbgfs_root);
    kfree(g_ctx);
    pr_info("%s: debugfs entries removed, module unloaded\n", DRVNAME);
}

module_init(ep_debugfs_demo_init);
module_exit(ep_debugfs_demo_exit);
MODULE_LICENSE("GPL");

Forgetting this step leaves stale dentries behind, and on some kernels this can trigger warnings or crashes the next time the module is reloaded — so treat cleanup as mandatory, not optional.

Testing Your debugfs Interface

After loading the module with insmod, verify the files exist and behave as expected:

$ sudo ls -l /sys/kernel/debug/ep_debugfs_demo
-r--r----- 1 root root 0 dump_context
-rw-r----- 1 root root 0 debug_level

$ sudo cat /sys/kernel/debug/ep_debugfs_demo/dump_context
events_seen:0
error_count:0
last_ts:4301928123

$ sudo cat /sys/kernel/debug/ep_debugfs_demo/debug_level
0

$ echo 3 | sudo tee /sys/kernel/debug/ep_debugfs_demo/debug_level
3

If any file is missing, double-check that CONFIG_DEBUG_FS is enabled in your kernel configuration — debugfs support can be compiled out entirely on hardened or minimal kernel builds.

Common Mistakes and Troubleshooting Tips

  • Forgetting to check CONFIG_DEBUG_FS: if it’s disabled, all debugfs calls silently become no-ops on newer kernels — your module will still load, but no files will appear.
  • Not locking shared data: debugfs callbacks can run concurrently from multiple processes; always protect shared state with a mutex or spinlock as shown above.
  • Using large on-stack buffers: the kernel stack is small (often just a few KB). Keep local buffers modest in size, or allocate on the heap for larger dumps.
  • Skipping debugfs_remove_recursive(): leads to dangling file entries and possible instability across repeated load/unload cycles.
  • Treating debugfs as a stable API: never let production user-space tools depend on specific debugfs file names or formats — they can change between kernel versions without notice.

Best Practices for Production-Safe Debug Code

  • Group every file for a module under a single named directory, never scatter files at the debugfs root.
  • Use the narrowest file permissions that still let you debug — read-only unless a write path is genuinely needed.
  • Prefer the built-in helper functions (debugfs_create_u32() and friends) whenever you’re just exposing a raw variable; write custom callbacks only when you need formatting or side effects.
  • Guard sensitive internal state behind conditional compilation if the module can also ship in production builds.
  • Document each debugfs file’s purpose in a comment right next to where it’s created — six months later, you’ll thank yourself.

Security Considerations

debugfs is deliberately restricted to root by default (/sys/kernel/debug is typically mode 0700), and that restriction exists for a reason: because debugfs has no format rules, it’s easy to accidentally leak sensitive kernel addresses, buffer contents, or hardware state to anyone who can read those files. On production and hardened systems, many distributions disable debugfs entirely or restrict it further with lockdown mode. When designing your debug interface, assume that anything you write to a debugfs file could eventually be read by someone debugging a live system, and avoid exposing raw pointers or security-sensitive values without good reason.

Real-World Use Cases

debugfs is used extensively throughout the mainline kernel itself. Wireless and Bluetooth drivers expose radio statistics and firmware state; GPU drivers dump memory allocation and scheduler queues; the CPU frequency governor exposes internal tuning knobs; and block I/O schedulers surface queue depth and latency histograms — all through debugfs, precisely because none of that information needs a stable, long-term ABI guarantee.

Summary and Key Takeaways

  • debugfs is a RAM-only, ABI-unstable filesystem meant purely for kernel and driver debugging.
  • debugfs_create_dir() gives your module its own namespace under /sys/kernel/debug.
  • debugfs_create_file() with a custom file_operations structure gives full control over formatting and behavior.
  • The i_private field lets a single callback serve multiple instances by carrying per-instance context.
  • Helper functions like debugfs_create_u32() eliminate boilerplate for simple variable exposure.
  • Always pair every debugfs directory with a matching debugfs_remove_recursive() call on exit.

Conclusion

This Linux kernel debugfs tutorial walked you through building a complete, working debug interface from an empty kernel module: creating a directory, wiring a custom read callback, using the i_private trick to pass context, and taking the fast path with helper APIs for simple variables. debugfs remains one of the quickest tools in a kernel developer’s toolbox for inspecting driver state without the overhead of designing a formal sysfs attribute set. Practice building your own debugfs files on a test VM, and continue with the next lecture in this free Linux kernel programming course to explore procfs and sysfs interfaces in depth.

Frequently Asked Questions

Q1. Is debugfs available on every Linux kernel?
Only if the kernel was built with CONFIG_DEBUG_FS=y. Most desktop and server distribution kernels enable it by default, but some minimal or hardened builds disable it.

Q2. Can non-root users access debugfs files?
By default, /sys/kernel/debug is only accessible to root, and individual files should keep restrictive permissions such as 0440 or 0640.

Q3. Should I ever rely on debugfs in a shipped, production user-space application?
No. debugfs offers no ABI stability guarantees, and file formats or names can change between kernel releases without warning.

Q4. What’s the difference between debugfs_create_file() and debugfs_create_u32()?
The former requires you to write your own file_operations callbacks for full control; the latter is a ready-made shortcut that directly binds a file to a numeric kernel variable with no extra code.

Q5. Why did some debugfs functions change their return type in newer kernels?
Kernel maintainers found that almost no callers checked the return values of debugfs creation helpers, so several were changed to return void to simplify the API. Always check the header for the kernel version you’re targeting.

Q6. How do I remove just one debugfs file instead of the whole directory?
Every debugfs creation call returns (or historically returned) a dentry * you can pass to debugfs_remove() for a single entry, though removing the whole module directory with debugfs_remove_recursive() is usually simpler and safer.

Q7. Can debugfs pass binary data, not just text?
Yes. debugfs supports arbitrary byte streams, including binary blob helpers, which is one of its biggest advantages over the strictly text-oriented conventions of sysfs.

Q8. Is debugfs a good starting point for learning free Linux device drivers course concepts?
Yes — debugfs is one of the simplest kernel-to-user-space interfaces to build, making it an excellent second or third exercise after writing your first loadable kernel module.

Continue Your Free Linux Kernel Programming Journey

This lesson is part of EmbeddedPathashala’s free Linux kernel programming course and free Linux device drivers course. Explore more lessons on kernel modules, procfs, sysfs, and embedded Linux development.

Browse the Full Course Join the Community

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *