Debugfs kernel debugging
Introduction to debugfs filesystem
This wiki is a tutorial on how to use the debugfs filesystem to perform kernel debugging or add debug files to obtain or perform functions of a driver.
Debugfs provides an easy way without rules for developers to expose internal data to user space, which allows developers to create custom files for reading and writing kernel data.
Debugs is currently automatically mounted at /sys/kernel/debug
, in case it is not mounted, you can use the following command:
mount -t debugfs none /sys/kernel/debug
Also, debugfs can be automatically mounted at boot by adding the following line to the /etc/fstab
file:
debugfs /sys/kernel/debug debugfs defaults 0 0
You can check if debugfs is running:
mount | grep debugfs
Debugfs filesystem functions to create and handle debug files are provided in the include <linux/debugfs.h>
.
Creating debugfs files
Initialize the debugfs filesystem by creating a directory to hold the debugfs files. The directory will be created in the debugfs root path underneath the selected parent. If the parent is NULL
, it will be created in the debugfs root path, which typically is mounted at /sys/kernel/debug
. Use the following method to create the main directory:
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
To create a file that will allow you to create a function to handle and expose a value, use the following method with the following arguments:
- name: File's name.
- mode: File's access permissions.
- parent: Dentry of the directory to store the file.
- data: Pointer with driver data.
- fops: Set of file operations.
struct dentry *debugfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops);
Also, in the created directory, you can use the following helper functions to create a file and expose the value of a variable of types like integers, hexadecimal, bool, and arrays into a file. Check debugfs kernel documentation for other helper functions.
//Integer 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_ulong(const char *name, umode_t mode, struct dentry *parent, unsigned long *value); //Hexadecimal void debugfs_create_x8(const char *name, umode_t mode, struct dentry *parent, u8 *value); //Bool void debugfs_create_bool(const char *name, umode_t mode, struct dentry *parent, bool *value); //Array void debugfs_create_u32_array(const char *name, umode_t mode, struct dentry *parent, struct debugfs_u32_array *array);
When the module is removed, it is important to clean up all directories created. The following function will recursively remove the directory and all files created within that directory.
void debugfs_remove_recursive(struct dentry *dentry);
File Operators
When using a general debugfs file, you can use fops to define a series of functions to implement the file's behavior.
To define the file operations (fops), the struct file_operations
can be used to at least provide the read() and write() operations; others can also be included.
static struct file_operations example_fops = { .read = &read_function, .write = &write_function, };
Some of the operations that can be used are listed in the following table:
Operation | Function | Description |
---|---|---|
.read | ssize_t (*read)(struct file *, char __user *, size_t, loff_t *) | Allows reading data from the file |
.write | ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *) | Allows writing data to the file |
.open | int (*open)(struct inode *, struct file *) | Open the file when open() is used |
.release | int (*release)(struct inode *, struct file *) | Close the file when close() is used |
.llseek | loff_t (*llseek)(struct file *, loff_t, int) | Changes the read/write position in the file |
Also, the macro DEFINE_SIMPLE_ATTRIBUTE
provided by <linux/fs.h>
can be used. This macro only allows the read and write operations.
/* * fops_name: file_operations struct name * get_function: read function * set_function: write function * format: Data value type (%llu, $ld, etc) */ DEFINE_SIMPLE_ATTRIBUTE(fops_name, get_function, set_function, format);
Debugfs files use
Debugfs files will be available in the created directory at debugfs root path, which typically is mounted at /sys/kernel/debug
To interact with the files, commands like cat or echo can be used. The following table shows some of the files usage:
Use | Command example |
---|---|
Read file value | cat <file> |
Read file value | echo "value" > <file> |
Inspect hexdump | hexdump -C <file> |
Use C function calls | open(), read(), write(), close(), lseek() |
Use Python function calls | open("archivo", "r+") |
Example code
Create the functions with the read and write logic:
static int test_write_call(void *data, u64 val) { //Add write logic pr_info("%s\n", __func__); return 0; } static int test_read_call(void *data, u64 *val) { //Add read logic pr_info("%s\n", __func__); return 0; }
Create a fops to define the read and write functions:
DEFINE_SIMPLE_ATTRIBUTE(test_fops, test_read_call, test_write_call, "%lld\n");
In the driver's probe function, create the debugfs directory to save the files:
static struct dentry *debugfs_dir; debugfs_dir = debugfs_create_dir("tests", NULL); if (debugfs_dir == NULL) return -ENOMEM; if (!debugfs_create_file("file_test", 0644, debugfs_dir, NULL, &test_fops)) goto error; error: debugfs_remove_recursive(debugfs_dir) return -ENOMEM;
For direct inquiries, please refer to the contact information available on our Contact page. Alternatively, you may complete and submit the form provided at the same link. We will respond to your request at our earliest opportunity.
Links to RidgeRun Resources and RidgeRun Artificial Intelligence Solutions can be found in the footer below.