Linux Kernel / Modules Debugging Techniques – Part 1: Proc File System

When programming in kernel space, we always end up checking some variables for its value, alter the variable, dynamic input to kernel, etc. (Linux Kernel used is version 3.16 ) For most of the time to get to know the values or what is happening in a specific case, we use printk. I am pretty sure you are aware of printk (not covered in this article)  where we print outputs from kernel to console, /var/log/messages/ or dmesg. But there are more clean and persistent options where we can list the values of variables and status from kernel/module which can be accessible from user space or even input a value to kernel module during its run time. We will go through three aspects of kernel with these series of articles, which will help us to achieve the above.

1. Read and Write in proc filesystem

2. seq_file API interface to access proc files

3. debugfs

The proc filesystem

The proc filesystem is a virtual filesystem exsist only in the memory. It is created when Linux is booted and removed when machine is switched off. The primary intention of proc filesystem is to keep process and other related information in structured filesystem format for easier access. Soon it is developed to a point were development community started using it as an interface to kernel for debugging and parameter passing. Let us see how to use it for our advantage ;) To start with lets create a basic module and Makefile: simple_proc.c

#include <linux/module.h>
#include <linux/kernel.h>

static int __init init_simpleproc (void)
{
        printk(KERN_INFO "init simple proc\n");
        return 0;
}
static void __exit exit_simpleproc(void)
{
        printk(KERN_INFO "exit simple proc\n");
}

module_init(init_simpleproc);
module_exit(exit_simpleproc);


MODULE_AUTHOR("Soorej P");
MODULE_LICENSE("GPL v3");
MODULE_DESCRIPTION("A simple module to see how to use proc filesystem");

Makefile:

obj-m += simple_proc.o

all:
make -C /usr/lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /usr/lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

When you ‘make‘ the above code and ‘insmod simple_proc.ko‘ you can see ‘init simple_proc’ at the end of ‘dmesg‘. Also after you’ve done ‘rmmod simple_proc‘, the module will be remove from kernel leaving the message ‘exit simple proc‘. Lets expand this simple module and modify as following.

#include <linux/proc_fs.h>

static struct file_operations fops;

static int __init init_simpleproc (void)
{
printk(KERN_INFO "init simple proc\n");
proc_create("simpleproc",0666,NULL,&fops);
return 0;
}
static void __exit exit_simpleproc(void)
{
 remove_proc_entry("simpleproc1",NULL);
 printk(KERN_INFO "exit simple proc\n");
}

We have added the function proc_create to create simpleproc file in /proc folder and remove_proc_entry to remove this entry

proc_create : creates a file entry in /proc directory.
 params:
 1. name - filename which will show up in proc directory
 2. mode - file creation mode (w,r,x for user,group,others)
 3. parent directory - pointer to parent directory in /proc where this file will be created (we used NULL to indicate /proc/ directory)
 4. file operations - pointer to file operations structure

remove_proc_entry : remove the file entry from /proc directory
 params:
 1. name - filename as in /proc
 2. parent - parent dir pointer (same NULL here)

for now we have not initialized fops. Once we insmod this module we can see simpleproc file in /proc folder.

Lets add open, read, write and release handler to file_operations structure
int simple_proc_open(struct inode * sp_inode, struct file *sp_file)
{
printk(KERN_INFO "proc called open\n");
return 0;
}
int simple_proc_release(struct inode *sp_indoe, struct file *sp_file)
{
printk(KERN_INFO "proc called release\n");
return 0;
}
int simple_proc_read(struct file *sp_file,char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "proc called read\n");
return 0;
}
int simple_proc_write(struct file *sp_file,const char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "proc called write\n");
return size;
}

struct file_operations fops = {
.open = simple_proc_open,
.read = simple_proc_read,
.write = simple_proc_write,
.release = simple_proc_release
};

Now when we compile and insmod the module Screenshot from 2014-10-23 20:42:27 The the return value of read and write functions should be noted: The read function will be called repeatedly till the return value becomes 0. This is to help reading the complete buffer if the size of buffer is smaller than total size of data to read. i.e, If you try to use ‘cat /proc/sample_proc‘ to display the value of buf to console, the read function will display it in console only if return value is +ve. hmmm.. the moment read function return 0, the ‘cat proc/sample_proc’ will stop display (i.e if beginning itself if you return 0, nothing will be displayed).. if +ve it will keep on calling read in loop until it returns 0 (chance of infinite-loop!).. tricky huh!.. Also, the write function will be called repeatedly until the return value equals the size attribute value with that much character removed from buf during the next recursive write call.

Let us extend the read and write to get value from user and write it back:

 

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <asm-generic/uaccess.h>

static char msg[128];
static int len = 0;
static int len_check = 1;
int simple_proc_open(struct inode * sp_inode, struct file *sp_file)
{
printk(KERN_INFO "proc called open\n");
return 0;
}
int simple_proc_release(struct inode *sp_indoe, struct file *sp_file)
{
printk(KERN_INFO "proc called release\n");
return 0;
}

int simple_proc_read(struct file *sp_file,char __user *buf, size_t size, loff_t *offset)
{

if (len_check)
 len_check = 0;
else {
 len_check = 1;
 return 0;
}
printk(KERN_INFO "proc called read %d\n",size);
copy_to_user(buf,msg,len);
return len;
}
int simple_proc_write(struct file *sp_file,const char __user *buf, size_t size, loff_t *offset)
{

printk(KERN_INFO "proc called write %d\n",size);
len = size;
copy_from_user(msg,buf,len);
return len;
}

struct file_operations fops = {
.open = simple_proc_open,
.read = simple_proc_read,
.write = simple_proc_write,
.release = simple_proc_release
};
static int __init init_simpleproc (void)
{
printk(KERN_INFO "init simple proc\n");
if (! proc_create("simpleproc",0666,NULL,&fops)) {
printk(KERN_INFO "ERROR! proc_create\n");
remove_proc_entry("simpleproc1",NULL);
return -1;
}
return 0;
}
static void __exit exit_simpleproc(void)
{
remove_proc_entry("simpleproc1",NULL);
printk(KERN_INFO "exit simple proc\n");
}

module_init(init_simpleproc);
module_exit(exit_simpleproc);
MODULE_AUTHOR("Soorej P");
MODULE_LICENSE("GPL v3");
MODULE_DESCRIPTION("A simple module to input/output using proc filesystem");

Checkout the use of :

if (len_check)
 len_check = 0;
else {
 len_check = 1;
 return 0;
}

This is a tweak (not a good one) to print the proc file. The /proc/simpleproc can be printed in console using ‘cat /proc/simpleproc’ only if read returns +ve value, when read returns 0 the read loop will be over and no more data is read. this is not the optimal logic to input from user and write it back when file is read, but we have easier methods available as seq_files (in next article). Capture1 Hope this helps!. Let me know your comments.   Next: Using proc filesystem using seq_api (coming up soon!)

Leave a Reply

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

15 − thirteen =