debugfs

debugfs is a pseudo-filesystem used for kernel debugging. It is usually mounted at /sys/kernel/debug. debugfs contains files that allow us to read debugging information.

By default, only the root user can cd into the /sys/kernel/debug directory.

To change it to allow the current user to cd into debugfs, we can remount it with uid set to the current user’s uid.

sudo umount /sys/kernel/debug
sudo mount -t debugfs none /sys/kernel/debug -o uid=`echo $UID`
cd /sys/kernel/debug

Creating debugfs entries

Creating debugfs files is similar to creating character device files. It is done by defining functions and storing pointers to these functions in a file_operations structure which is then passed to the kernel.

  • Create a debugfs directory using debugfs_create_dir function This functions takes the directory name and the parent dentry. If the parent dentry is set to NULL, the directory is created directly under /sys/kernel/debug, otherwise it is created under the given parent directory. The return value of the function is a pointer to a dentry or an error value. We need to store this dentry pointer to create files in that directory and also to remove the directory in the exit function.

      struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
    
  • In some functions like debugfs_create_dir, the return value is either a normal kernel space pointer or an error code. The return value of these functions must be checked with IS_ERR macro to check if it is an error code or a valid pointer.

      root_dentry = debugfs_create_dir("hello", NULL);
      if (IS_ERR(root_dentry))
        return -ENODEV;
    
  • Create debugfs files using the functions available in include/linux/debugfs.h.

      struct dentry *debugfs_create_file(const char *name, umode_t mode,
                                         struct dentry *parent, void *data,
                                         const struct file_operations *fops);
    

    Arguments of the above function:

    1. name : Name of the file to be created
    2. mode : Access permissions of the file
    3. parent : Dentry of the folder under which we want to create this file
    4. data : Pointer value that is stored for later use. inode.i_private will be set to this value on a open() syscall.
    5. fops : The file_operations struct initialized with pointers to the defined functions
  • To expose the values of simple variables, we have functions for some basic types like u8, u16, etc.

      void debugfs_create_u8(const char *name, umode_t mode, struct dentry *parent,
                             u8 *value);
      void debugfs_create_u16(const char *name, umode_t mode, struct dentry *parent,
                              u16 *value);
      void debugfs_create_u32(const char *name, umode_t mode, struct dentry *parent,
                              u32 *value);
      void debugfs_create_u64(const char *name, umode_t mode, struct dentry *parent,
                              u64 *value);
      void debugfs_create_ulong(const char *name, umode_t mode, struct dentry *parent,
                                unsigned long *value);
    
  • In the exit function of the kernel modules, we remove the debugfs entries using debugfs_remove function. We pass the dentry pointer of debugfs directory we created. The function removes debugfs entries recursively, i.e. all files under the directory will be removed.

      void debugfs_remove(struct dentry *dentry);
    

An example: Echo file

The source code shown below is a kernel module that creates a simple debugfs file that

  • When written to, will store upto a page of data
  • When read from, will return the data that was last written

This is the same functionality that was implemented using char device in a previous post. I have highlighted the lines that are related to debugfs operations.

echo.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/debugfs.h>
#include<linux/slab.h>
#include<linux/rwsem.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nihaal");
MODULE_DESCRIPTION("Creating debugfs files");
MODULE_VERSION("1.0");

static char *echo_buf;
static DECLARE_RWSEM(echo_rwlock);
static struct dentry *root_dentry;

ssize_t echo_read (struct file *filp, char __user * buf, size_t size, loff_t * f_pos) {
	size_t len, ret;

	if ((*f_pos) >= PAGE_SIZE) {
		return 0;
	}

	down_read(&echo_rwlock);
	len = (size > PAGE_SIZE)? PAGE_SIZE: size;
	ret = copy_to_user(buf, echo_buf, len);
	len = len - ret;
	*f_pos += len;
	up_read(&echo_rwlock);
	return len;
}

ssize_t echo_write (struct file *filp, const char __user * buf, size_t size, loff_t * f_pos) {
	size_t len, ret;
	if ((*f_pos) >= PAGE_SIZE) {
		return -ENOMEM;
	}

	down_write(&echo_rwlock);
	memset(echo_buf, 0, PAGE_SIZE);
	len = (size > PAGE_SIZE)? PAGE_SIZE: size;
	ret = copy_from_user(echo_buf, buf, len);
	len = len - ret;
	*f_pos += len;
	up_write(&echo_rwlock);
	return len;
}

static struct file_operations echo_fops = {
	.read	= &echo_read,
	.write	= &echo_write,
};

static int __init echo_init(void)
{
	// Create "hello" directory
	root_dentry = debugfs_create_dir("hello", NULL);
	if (IS_ERR(root_dentry))
		return -ENODEV;

	// Allocate buffer
	echo_buf = (char*) kzalloc(PAGE_SIZE, GFP_KERNEL);
	if (!echo_buf) {
		return -ENOMEM;
	}

	// Create echo file
	if (IS_ERR(debugfs_create_file("echo", (S_IRUGO|S_IWUGO), root_dentry, NULL, &echo_fops))) {
		kfree(echo_buf);
		return -ENODEV;
	}
	return 0;
}

static void __exit echo_exit(void)
{
	kfree(echo_buf);

	// Recursively remove debugfs entries
	debugfs_remove(root_dentry);
}

module_init(echo_init);
module_exit(echo_exit);

Testing echo

  • Build and load the module

      make
      sudo insmod echo.ko
      ls /sys/kernel/debug/hello
      #.rw-rw-rw- 0 root  5 Nov 22:26 echo
    
  • Write to the device

      echo "Good Morning!" > /sys/kernel/debug/hello/echo
    
  • Read from the device. It will return what was last written to it.

      cat /sys/kernel/debug/hello/echo
      # Good Morning!
    
  • Unload the module

      sudo rmmod echo
    

References