Ecrire un module kernel sous Linux (1)

Next

C From Scratch Episode 24 (1/2)

Aujourd’hui encore, un nouveau stream d’Imil … Et donc une prise de notes. Le sujet d’aujourd’hui est l’écriture de modules kernel sous Linux : On va écrire un zoli ptit driver ! Comme toujours, je ne saurais trop vous recommander de visionner directement la vidéo.

A vos compilos … prêts … partez !

Pour commencer, on va devoir installer les outils nécessaires. Sous debian et consorts, deux packages sont nécessaires :

Et si vous n’êtes pas sous Debian … Et bien il n’y a qu’à chercher les paquets qui vont bien ! Généralement, ce n’est pas très différent !

On commence par vérifier que tout le monde est là avec un 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

Comme on peut le voir, il y a là-dedans un lien symbolique nommé “build” qui pointe vers les sources du kernel. On trouve à l’intérieur un Makefile general qui permet de compiler des modules kernel.

On y trouve également le repertoire include, avec les headers nécessaires pour compiler tout ce qui peut aller dans le kernel. Nous allons nous servir de cela dans quelques minutes !

Un tout premier module kernel

On commence par un premier module simple, qui va juste nous laisser un petit message lorsqu’il est chargé ou déchargé. Voici donc le magnifique 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)

La première chose que l’on peut noter est la présence de trois includes en début de code. Ces includes nous permettent de disposer des structures, définitions et macros que nous allons utiliser pour faire notre module.

On peut également noter qu’il n’y a pas de main dans ce code. Notre module se compose en fait de fonctions que le kernel appelera. Et pour cette raison, nous allons utiliser les macros définies dans module.h pour lui indiquer lesquelles utiliser. Ainsi, cette partie du code :

module_init(prout_init);
module_exit(prout_exit);

est en fait un appel à deux macros permettant d’indiquer quelles fonctions utiliser au chargement et au déchargement du module.

Il est important de rappeler ici que nous ne pouvons pas faire appel à la libC, puisque nous sommes en ring 0 … donc pas de stdio.h et pas de printf ! A la place, nous utilisons printk, qui lui est défini dans le kernel. Il s’agit tout simplement d’un équivalent de printf, mais qui est défini dans le kernel et nous permettra d’afficher dans le dmesg.

Enfin, détail important, tout est ici déclaré en static, ce qui permet de limiter la portée de nos fonctions au fichier courant. L’objectif est tout simplement d’éviter de “polluer” le kernel avec nos fonctions, qui pourraient entrer en conflit avec des fonctions déjà définies ailleurs. Chacun chez soi ! ( J’allais dire que ça m’etonnerait de trouver “prout” dans le kernel, mais je viens de trouver prout_cmd alors je vais me taire … )

Pour compiler notre module, on va créer un fichier nommé “Kbuild” dans le même repertoire, et écrire :

obj-m	= kprout.o

La ligne de compilation sera la suivante :

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

Et nous voilà reparti avec un make ! L’option -C nous permet d’indiquer le chemin vers le Makefile ( vous vous souvenez ? Celui dans le build dont on parlait au début ! ) et le paramètre M le chemin vers notre fichier source et notre Kbuild. Normalement, la compilation se passe plutôt bien :

$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 »

Arg ! Pourquoi ça marche po ? Tout simplement parce qu’il faudrait donner quelques petites infos sur notre module : l’auteur, la licence … On utilise donc trois petites macros pour indiquer tout ça !

#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)

Et cette fois-ci …

$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

CA Y EST ! prout.ko est notre premier module !!!!

On peut même vérifier qu’il contient les bonnes infos :

$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

Elle est pas belle la vie ? Et en plus il fonctionne :

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

On peut également le décharger :

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

Si on veut compiler un peu plus facilement, nous pouvons ajouter à notre repertoire un Makefile tout simple :

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

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

Et voilà ! un petit make et le tour est joué !!!!

Demain, promis, je mets au propre la deuxième partie du stream !