Creating debugfs files
Contents
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:
- name : Name of the file to be created
- mode : Access permissions of the file
- parent : Dentry of the folder under which we want to create this file
- data : Pointer value that is stored for later use. inode.i_private will be set to this value on a open() syscall.
- 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
|
|
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
Author Abdun Nihaal
LastMod 06-11-2021