Post

Writing a kernel module on Linux (1)

This post is an automatic translation from French. You can read the original version here.

C From Scratch Episode 24 (1/2)

Once again today, a new stream from Imil… And therefore, note-taking. Today’s topic is writing kernel modules on Linux: We’re going to write a nice little driver! As always, I strongly recommend watching the video directly.

Ready, set, compile!

To get started, we need to install the necessary tools. On Debian and its derivatives, two packages are required:

  • build-essential (The usual dev tools… like make and the compiler)
  • linux-headers (which contains the headers needed to build a kernel module)

And if you’re not on Debian… Well, just look for the right packages! Generally, it’s not very different!

Let’s start by checking that everything is in place with a simple ls:

$ls /lib/modules/$(uname -r)
total 4664
lrwxrwxrwx  1 root root      29 24 mars  11:40 build -> /usr/src/linux-5.15.12-gentoo
drwxr-xr-x 14 root root    4096 24 mars  11:40 kernel
-rw-r--r--  1 root root 1171504 24 mars  14:57 modules.alias
-rw-r--r--  1 root root 1141036 24 mars  14:57 modules.alias.bin
-rw-r--r--  1 root root    6739 24 mars  11:40 modules.builtin
-rw-r--r--  1 root root   18405 24 mars  14:57 modules.builtin.alias.bin
-rw-r--r--  1 root root    8892 24 mars  14:57 modules.builtin.bin
-rw-r--r--  1 root root   55902 24 mars  11:40 modules.builtin.modinfo
-rw-r--r--  1 root root  427461 24 mars  14:57 modules.dep
-rw-r--r--  1 root root  582025 24 mars  14:57 modules.dep.bin
-rw-r--r--  1 root root     453 24 mars  14:57 modules.devname
-rw-r--r--  1 root root  136889 24 mars  11:40 modules.order
-rw-r--r--  1 root root    1084 24 mars  14:57 modules.softdep
-rw-r--r--  1 root root  534278 24 mars  14:57 modules.symbols
-rw-r--r--  1 root root  648267 24 mars  14:57 modules.symbols.bin
lrwxrwxrwx  1 root root      29 24 mars  11:40 source -> /usr/src/linux-5.15.12-gentoo
drwxr-xr-x  2 root root    4096 24 mars  14:57 video

As you can see, there’s a symbolic link named “build” in there that points to the kernel sources. Inside it, you’ll find a general Makefile that allows you to compile kernel modules.

You’ll also find the include directory, with the headers needed to compile anything that can go into the kernel. We’ll be using that in a few minutes!

A very first kernel module

Let’s start with a simple first module that will just leave us a little message when it’s loaded or unloaded. Here is the magnificent kprout.c:

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


static int
prout_init(void)
{
	printk("Coucou CFS!\n") ;
	return 0 ;
}

static void
prout_exit(void)
{
	printk("ooooh ... tout triste :(\n" ) ;
}

module_init(prout_init)
module_exit(prout_exit)

The first thing you can notice is the presence of three includes at the beginning of the code. These includes give us access to the structures, definitions, and macros that we’ll use to build our module.

You can also notice that there’s no main in this code. Our module is actually made up of functions that the kernel will call. For this reason, we’ll use the macros defined in module.h to tell it which ones to use. Thus, this part of the code:

1
2
module_init(prout_init);
module_exit(prout_exit);

is actually a call to two macros that indicate which functions to use when loading and unloading the module.

It’s important to remind here that we cannot use the libC, since we’re in ring 0… so no stdio.h and no printf! Instead, we use printk, which is defined in the kernel. It’s simply the equivalent of printf, but defined in the kernel, and it allows us to output to dmesg.

Finally, an important detail: everything here is declared as static, which limits the scope of our functions to the current file. The goal is simply to avoid “polluting” the kernel with our functions, which could conflict with functions already defined elsewhere. Everyone in their own space! (I was about to say I’d be surprised to find “prout” in the kernel, but I just found prout_cmd so I’ll keep quiet…)

To compile our module, we’ll create a file named “Kbuild” in the same directory, and write:

1
obj-m	= kprout.o

The compilation command will be:

$make -C /lib/modules/$(uname -r)/build M=$(pwd)

And here we are again with make! The -C option lets us specify the path to the Makefile (remember? The one in the build directory we mentioned at the beginning!) and the M parameter is the path to our source file and our Kbuild. Normally, the compilation goes pretty smoothly:

$ls
total 8
-rw-r--r-- 1 rancune rancune  17  2 avril 18:03 Kbuild
-rw-r--r-- 1 rancune rancune 279  2 avril 17:56 kprout.c

$make -C /lib/modules/$(uname -r)/build M=$(pwd)
make : on entre dans le répertoire « /usr/src/linux-5.15.12-gentoo »
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-pc-linux-gnu-gcc (Gentoo 11.2.1_p20211127 p3) 11.2.1 20211127
  You are using:           gcc (Gentoo 11.2.1_p20211127 p3) 11.2.1 20211127
  CC [M]  /home/rancune/test/kprout.o
  MODPOST /home/rancune/test/Module.symvers
ERROR: modpost: missing MODULE_LICENSE() in /home/rancune/test/kprout.o
make[1]: *** [scripts/Makefile.modpost:134 : /home/rancune/test/Module.symvers] Erreur 1
make[1]: *** Suppression du fichier « /home/rancune/test/Module.symvers »
make: *** [Makefile:1783 : modules] Erreur 2
make : on quitte le répertoire « /usr/src/linux-5.15.12-gentoo »

Ouch! Why doesn’t it work? Simply because we need to provide a few details about our module: the author, the license… So we use three little macros to specify all that!

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

MODULE_DESCRIPTION("Le prout du kernel");
MODULE_AUTHOR("CFS");
MODULE_LICENSE("WTFPL");

static int
prout_init(void)
{
	printk("Coucou CFS!\n") ;
	return 0 ;
}

static void
prout_exit(void)
{
	printk("ooooh ... tout triste :(\n" ) ;
}

module_init(prout_init)
module_exit(prout_exit)

And this time…

$make -C /lib/modules/$(uname -r)/build M=$(pwd)
make : on entre dans le répertoire « /usr/src/linux-5.15.12-gentoo »
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-pc-linux-gnu-gcc (Gentoo 11.2.1_p20211127 p3) 11.2.1 20211127
  You are using:           gcc (Gentoo 11.2.1_p20211127 p3) 11.2.1 20211127
  CC [M]  /home/rancune/test/kprout.o
  MODPOST /home/rancune/test/Module.symvers
  CC [M]  /home/rancune/test/kprout.mod.o
  LD [M]  /home/rancune/test/kprout.ko
make : on quitte le répertoire « /usr/src/linux-5.15.12-gentoo »

$ls
total 36
-rw-r--r-- 1 rancune rancune   17  2 avril 18:03 Kbuild
-rw-r--r-- 1 rancune rancune  366  2 avril 18:29 kprout.c
-rw-r--r-- 1 rancune rancune 5000  2 avril 18:30 kprout.ko
-rw-r--r-- 1 rancune rancune   29  2 avril 18:30 kprout.mod
-rw-r--r-- 1 rancune rancune  817  2 avril 18:30 kprout.mod.c
-rw-r--r-- 1 rancune rancune 3112  2 avril 18:30 kprout.mod.o
-rw-r--r-- 1 rancune rancune 2456  2 avril 18:30 kprout.o
-rw-r--r-- 1 rancune rancune   29  2 avril 18:30 modules.order
-rw-r--r-- 1 rancune rancune    0  2 avril 18:30 Module.symvers

THERE WE GO! prout.ko is our first module!!!!

We can even check that it contains the right information:

$modinfo kprout.ko
filename:       /home/rancune/test/kprout.ko
license:        WTFPL
author:         CFS
description:    Le prout du kernel
srcversion:     B32927FF35204F404021BC9
depends:
retpoline:      Y
name:           kprout
vermagic:       5.15.12-gentoo-x86_64 SMP mod_unload modversions

Isn’t life beautiful? And on top of that, it works:

$sudo insmod kprout.ko
$sudo dmesg | tail -1
[ 1835.388568] Coucou CFS!

We can also unload it:

$sudo rmmod kprout
$sudo dmesg | tail -1
[ 1990.493121] ooooh ... tout triste :(

If we want to compile a bit more easily, we can add a simple Makefile to our directory:

KDIR=/lib/modules/`uname -r`/build

kbuild:
	make -C $(KDIR) M=`pwd`
clean:
	make -C $(KDIR) M=`pwd` clean

And there you go! A little make and we’re done!!!!

Tomorrow, I promise, I’ll clean up the second part of the stream!

This post is licensed under CC BY 4.0 by the author.