Compiler le Kernel Linux sur Mac
Le 4 février 2026 01:44.
*N'hésitez pas à m'envoyer un message pour une quelconque remarque ou suggestion sur ce post !*
Cet hiver je me suis prise d'une passion pour le noyau, *kernel*, linux. J'ai voulu comprendre comment les développeurs.es pouvait développer tous en commun sur un projet si gros et si je pouvais moi aussi les rejoindre !
Pour cela, j'ai d'abord lu une partie des pages de la [documentation officielle](https://docs.kernel.org) notamment sur le processus de développement. Complétement optionnel pour ce qu'on va faire ici mais un + pour la culture. La prochaine étape est d'essayer de compiler le kernel sur notre machine.
Je possède un Macbook Air M4, donc avec une **architecture arm64** et avec **macOS** comme système d'exploitation. Cette configuration va avoir de nombreuses conséquences dans la suite : l'utilisation de [Docker](https://www.docker.com/products/docker-desktop/) et de volumes spéciaux.
En effet, la solution que j'ai trouvé peut se résumer comme il suit :
- On va créer un volume `LinuxBuilder.dmg` case-sensitive qui va se monter en `linux_builder/`, c'est dedans que l'on va faire tout notre travail.
- On va utiliser un container Docker afin de compiler le kernel comme si on était sur une machine Debian (Trixie)
- On va tester le kernel sur une machine virtuelle grâce à QEMU et le débugger grâce à GDB.
## Infos bonnes à savoir
Pour rédiger ce post, j'ai arpenté beaucoup de blogs et ce que j'ai retenu est qu'il faut faire très attention au dates de publications ainsi qu'à la version du kernel qu'ils ou elles compilent, en particulier le hash du commit qu'ils ou elles utilisent. Ici, nous utiliserons le commit `944aacb68baf` (vous pouvez suivre les commits avec `git log` et aller à ce commit avec `git checkout 944aacb68baf`).
## 0. Cloner le repo : Problème de fichiers
Première étape est de cloner le [repo officiel](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git) du kernel, mais cela serait **trop** simple pour commencer (= ne le faites pas encore):
```bash
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Cloning into 'linux'...
remote: Enumerating objects: 11275310, done.
remote: Counting objects: 100% (229/229), done.
remote: Compressing objects: 100% (169/169), done.
remote: Total 11275310 (delta 75), reused 102 (delta 60), pack-reused 11275081 (from 1)
Receiving objects: 100% (11275310/11275310), 3.07 GiB | 17.68 MiB/s, done.
Resolving deltas: 100% (9273059/9273059), done.
Updating files: 100% (92194/92194), done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:
'include/uapi/linux/netfilter/xt_CONNMARK.h'
'include/uapi/linux/netfilter/xt_connmark.h'
'include/uapi/linux/netfilter/xt_DSCP.h'
'include/uapi/linux/netfilter/xt_dscp.h'
'include/uapi/linux/netfilter/xt_MARK.h'
'include/uapi/linux/netfilter/xt_mark.h'
'include/uapi/linux/netfilter/xt_RATEEST.h'
'include/uapi/linux/netfilter/xt_rateest.h'
'include/uapi/linux/netfilter/xt_TCPMSS.h'
'include/uapi/linux/netfilter/xt_tcpmss.h'
'include/uapi/linux/netfilter_ipv4/ipt_ECN.h'
'include/uapi/linux/netfilter_ipv4/ipt_ecn.h'
'include/uapi/linux/netfilter_ipv4/ipt_TTL.h'
'include/uapi/linux/netfilter_ipv4/ipt_ttl.h'
'include/uapi/linux/netfilter_ipv6/ip6t_HL.h'
'include/uapi/linux/netfilter_ipv6/ip6t_hl.h'
'net/netfilter/xt_DSCP.c'
'net/netfilter/xt_dscp.c'
'net/netfilter/xt_HL.c'
'net/netfilter/xt_hl.c'
'net/netfilter/xt_RATEEST.c'
'net/netfilter/xt_rateest.c'
'net/netfilter/xt_TCPMSS.c'
'net/netfilter/xt_tcpmss.c'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+pooncelock+pombonce.litmus'
```
Ici on voit la première limitation : le système de fichier de base de macOS ([APFS](https://fr.wikipedia.org/wiki/Apple_File_System)) est *Case Preservating*. C'est à dire que vous pouvez sauvegarder un fichier sous le nom `Video.mp4` (notez la majuscule) mais vous ne pourrez plus ajouter de fichier de même nom avec différentes majuscules/minuscules comme `video.mp4` ou `vIdEo.mp4`. C'est exactement pour cela que se plaint `git` : il y a des fichiers différents qui se différenties uniquement par la présence ou non de majuscule.
Heureusement, nous pouvons créer des volumes qui sont *Case Sensitive*, ce qu'attend git ([commandes tirées de ce post](https://dev.to/fazibear/how-to-cross-compile-linux-kernel-on-macos-4306)):
```bash
hdiutil create -size 20g -fs "Case-sensitive APFS" -volname LinuxBuilder LinuxBuilder.dmg
created: /Users/elo/LinuxBuilder.dmg
```
```bash
hdiutil attach LinuxBuilder.dmg -mountpoint linux_builder -nobrowse -readwrite
/dev/disk4 GUID_partition_scheme
/dev/disk4s1 EFI
/dev/disk4s2 Apple_APFS
/dev/disk5 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXX
/dev/disk5s1 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXX /Users/elo/linux_builder
```
```bash
cd linux_builder
```
- La première commande crée un volume de partition Case-sensitive APFS nommé LinuxBuilder.dmg de 20Go (vous pouvez l'augmenter, je pense que ce serait une bonne chose à faire vu que rien que le `.git` prend énormément de place)
- La seconde monte le volume au dossier `linux_builder` en lecture écriture
- La troisième fait se déplacer dans le volume crée.
On peut maintenant cloner le kernel :
```bash
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Cloning into 'linux'...
remote: Enumerating objects: 11275310, done.
remote: Counting objects: 100% (229/229), done.
remote: Compressing objects: 100% (169/169), done.
remote: Total 11275310 (delta 75), reused 102 (delta 60), pack-reused 11275081 (from 1)
Receiving objects: 100% (11275310/11275310), 3.07 GiB | 17.68 MiB/s, done.
Resolving deltas: 100% (9273059/9273059), done.
```
## 1. Compiler le kernel directement depuis macOS : la bataille infinie
J'ai d'abord voulu compiler nativement depuis macOS en suivant [ce post](https://seiya.me/blog/building-linux-on-macos-natively), me disant que je gagnerai en temps et en batterie à ne pas développer sur une machine virtuelle. Cependant, j'ai eu beaucoup de mal à le faire pour plusieurs raisons :
- Je n'avais pas le même commit que l'auteur (eh oui)
- Il y a plusieurs [patch](https://comprendre-git.com/fr/commandes/git-patch/) à faire afin de pouvoir compiler en `allnoconfig`(ça veut dire qu'on ne compile pas grand chose comme les drivers etc avec le kernel)
- Ces mêmes patchs sont différents de version en version du kernel donc compliqué de suivre le rythme
- Un manque de dépendance sur macOS : par exemple la bibliothèque [glibc](https://fr.wikipedia.org/wiki/GNU_C_Library) est très souvent utilisées mais pas présente sur macOS, même avec [Homebrew](https://brew.sh).
- Encore plus de problèmes quand on veut compiler pour une autre architecture comme x86_64
Je vous laisse la possibilité d'essayer mais j'ai pas trouvé ça vraiment concluant.
Vient alors la solution à tous mes problèmes : [Docker](https://www.docker.com/products/docker-desktop/).
### Utilisation de Docker
Pour l'instant, l'arborescence de notre projet ressemble à ça :
```
linux_builder
| linux
```
On se place dans le dossier `linux_builder`, on crée un dossier `linuxKernelCompiler` puis on va vouloir y crée deux fichiers `Dockerfile` ainsi que `docker-compose.yml` (inspiré en grande partie de [ce repo](https://github.com/rockavoldy/linux-kernel-cross-compile/tree/main#)):
**Dockerfile**
```bash
FROM debian:trixie
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get clean && \
apt-get install -y \
# Useful packages
git nano wget make bc libncurses-dev flex bison \
# Packages needed to build / crossbuild to ARCH=x86_64
gcc gcc-x86-64-linux-gnu libelf-dev libssl-dev
RUN mkdir /work
WORKDIR /work
CMD ["bash"]
```
**docker-compose.yml**
```bash
---
services:
cross-compile:
build: .
container_name: cross-compile
# Allow attaching to the container.
stdin_open: true
tty: true
volumes:
- ~/linux_builder:/work
```
Ces fichiers vont permettre de créer une image docker ainsi qu'un container possédant la majorité des paquets pour compiler le noyau et même faire de la compilation vers x86_64. Il [bind](https://docs.docker.com/engine/storage/bind-mounts/) le dossier `linux_builder/` de notre machine au dossier `work/` du container, c'est à dire que les fichiers du dossier `work/` sont exactement ceux de `linux_builder/`, si une modification est faite dans l'un, elle est aussi faite dans l'autre. Plutôt pratique !
Pour obtenir tous les bienfaits de ces fichiers, on va créer cette image et ce container :
```bash
docker compose up -d --build
```
- `-d` pour lancer le container crée en arrière plan
- `--build` pour compiler de 0
Pour accéder au container, on peut faire :
```bash
docker attach cross-compile
root@276dd584f3af:/work#
```
Vous pouvez alors faire un `ls` pour observer que tous nos fichiers sont là !
### Compilation du Noyau
Rentrez dans le dossier linux... nous sommes prêt.es, il est temps de compiler notre kernel :)
Toutefois, prenons rapidement le temps pour expliquer certaines choses.
#### Les .config
Vous allez les voir partout, sauf dans votre explorateur de fichier :( En effet, le . devant fait que vous ne le voyez pas, cependant ces fichiers sont bien là, faites un `ls -a` vous allez voir. Bref les `.config` contiennent exactement tout ce qui doit ou ne doit pas être compiler dans le kernel.
Heureusement pour nous, on n'a pas besoin de le modifier à la main (#l'enfer), on peut simplement faire `make menuconfig`. Vous devez voir un menu dans lequel vous pourrez vous déplacer avec vos flèches, la touche entrée et echap (double echap pour sortir d'un menu).
Vous remarquerez dès que vous ferez des modifications et des changements de branches, votre `make` va vous poser plein de questions ! C'est toutes les options de configurations qui ne sont pas définies dans votre `.config`. En général, laisser appuyer sur `entrée` met toutes les valeurs non définies à leur valeur de base et ça marche bien.
Ce problème se pose aussi quand vous changez les paramètres dans votre `make` ! Si vous faites un `make defconfig` (cf paragraphe suivant) sans rien puis que vous ajoutez un paramètre pour spécifier l'architecture de sortie à la compilation : `make ARCH=x86_64 ...` il va vous reposez toutes les questions !! donc pensez bien à mettre tous vos paramètres dès le début.
On cherche ici à faire une configuration par défaut et pour cela on va choisir de faire `make defconfig`, cependant il y a d'autres configurations mais je n'ai pas tout bien compris à leur sujet.
#### Cross-compile
Un point important de la compilation du kernel et celle de la cross-compilation, c'est à dire compiler le kernel pour une cible différente de son architecture. Les Mac M* ont des processeurs arm64 =/= x86_64 qui sont ceux du grand public. Pour régler ça, on va vouloir utiliser un compilateur `gcc` fait pour la cible : `x86_64-linux-gnu-gcc`, vous trouverez d'autres compilateur pour vos différentes cibles.
Une fois la compilation faite, il faut pas oublier de lancer QEMU avec la bonne architecture hein :)
#### Vraie compilation
On va chercher à compiler pour la cible x86_64 pour attester du fonctionnement de notre cross-compile ! On va de plus faire une mini-modification pour montrer que c'est bien notre kernel à nous, pour cela, on commence par prendre la configuration de base :
```bash
make defconfig -j10 ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu-
HOSTCC scripts/kconfig/conf.o
HOSTLD scripts/kconfig/conf
*** Default configuration is based on 'x86_64_defconfig'
#
# configuration written to .config
#
```
- `-j10` dit à `make` d'utiliser 10 coeurs du processeur disponible.
- `ARCH=x86_64` lui dit l'architecture cible
- `CROSS_COMPILE=x86_64-linux-gnu-` lui dit le prefix du compilateur à utiliser (on a juste enlever le gcc de la commande de la précédente sous-partie)
Puis on va lancer le `menuconfig` pour modifier `Général -> Local Version` et mettre le nom que vous voulez, moi j'ai mis "EloKernel".
```bash
make menuconfig -j10 ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu-
configuration written to .config
*** End of the configuration.
*** Execute 'make' to start the build or try 'make help'.
```
Et finalement, la commande ultime qui lance la compilation :
```bash
make -j10 ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu-
SYNC include/config/auto.conf
GEN arch/x86/include/generated/asm/orc_hash.h
...
LD arch/x86/boot/setup.elf
OBJCOPY arch/x86/boot/setup.bin
BUILD arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready (#1)
```
Et maintenant on attend un bon moment avant d'avoir notre kernel ! Vous ne devriez avoir aucun message d'erreur ou de warning, c'est pas beau tout ça
## 2. Test du noyau
Pour tester le noyau sur macOS (faire `exit` pour sortir du conteneur docker), on va vouloir utiliser QEMU que l'on installe via [Homebrew](https://brew.sh) :
```bash
brew install qemu
```
Vous avez deux solutions en fonction de ce que vous voulez faire :
- Simplement tester via une vm et avancer de votre côté
- Paramétrer une VM pour régler des bugs avec [Syzbot/Syzkaller](https://hackerbikepacker.com/syzbot). Cette option est bonne pour débuter dans le développement du noyau.
### Tests avec une image syteme
On peut utiliser [les images fournies par Debian](https://www.debian.org/distrib/) spécialement pour QEMU (le système étant déjà installée) pour tester le kernel :
```bash
curl -Lo debian-x86_64.qcow2 https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2
```
On lance alors la commande suivante :
```bash
qemu-system-x86_64 \
-nographic \
-kernel linux/arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/sda1 earlyprintk=serial net.iframes=0" \
-drive file="debian-x86_64.qcow2",format=qcow2 \
-m 2G \
-net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
-net nic,model=e1000 \
-s
```
- `-nographic` : lance QEMU sans fenêtre
- `-kernel` : chemin vers le kernel nouvellement crée
- `-append` : informations supplémentaires à fournir au kernel lors du démarrage
- `console=ttyS0 earlyprintk=serial` : utilise le terminal pour avoir le retour des messages du kernel
- `net`: configuration réseau pour faire un ssh
- `-drive` : chemin vers le fichier de stockage du système d'exploitation
- `-m 2G` : Mémoire vive de 2G passée à la machine virtuelle
- `-net` : configuration réseau pour faire un ssh sur le port 10021
- `-s` : permet d'utiliser GDB pour débugger le noyau dans un autre terminal, voir [GDB Usage](https://qemu-project.gitlab.io/qemu/system/gdb.html)
On utilise le login `root` et on peut alors vérifier la version du noyau :
```bash
uname -a
Linux localhost 6.19.0-rc5EloKernel-00042-g944aacb68baf #1 SMP PREEMPT_DYNAMIC Thu Jan 15 18:17:57 UTC 2026 x86_64 GNU/Linux
```
On a le `EloKernel` !!
### Tests pour Syzbot
Rapidement, Sysbot est un bot qui compile et exécute le kernel linux avec beaucoup de configurations différentes et qui rapporte les problèmes trouvés sur [syzkaller.appspot.com](https://syzkaller.appspot.com/upstream), comme par exemple des fuites de mémoires, des pointeurs NULL ou des problèmes très précis que je ne développerai pas plus.
Pour chaque problème sur le site, comme [celui-ci](https://syzkaller.appspot.com/bug?extid=1f53a30781af65d2c955), un `.config` ainsi que la version du commit est fournie. Pour reproduire un bug, on va utiliser le même disque (càd avec les mêmes paquets) que le bot : c'est ce que l'on va faire.
On va utiliser la machine virtuelle de la précédente sous-partie pour créer une image `trixie.img` contenant les packages minimum pour faire les tests et on va recompiler le kernel avec quelques paramètres nécessaires pour que la machine virtuelle lance cette image.
#### Ajouter de l'espace sur l'image QEMU
On commence par ajouter de l'espace sur le disque de la précédente machine virtuelle, pour cela, éteignons la, redimensionnons l'image et relançons la machine:
```bash
poweroff
...
[ 717.326327] reboot: Power down
```
```bash
qemu-img resize debian-x86_64.qcow2 +2G
Image resized.
```
```bash
qemu-system-x86_64 \
-nographic \
-kernel linux/arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/sda1 earlyprintk=serial net.iframes=0" \
-drive file="debian-x86_64.qcow2",format=qcow2 \
-m 4G \
-net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
-net nic,model=e1000 \
-s
```
Ici, on a ajouté 2Go à l'image et on a augmenté la mémoire vive de la machine virtuelle.
On peut voir que Debian n'a pas augmenté la taille de la partition (2.9Go) après notre augmentation de l'image (5Go):
```bash
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 5G 0 disk
├─sda1 8:1 0 2.9G 0 part /
├─sda14 8:14 0 3M 0 part
└─sda15 8:15 0 124M 0 part /boot/efi
sr0 11:0 1 1024M 0 rom
```
Modifions ça : installons `resize2fs` et `growpart` pour modifier les partitions de l'image et augmenter celle qui nous intéresse ([source](https://serverfault.com/questions/1127700/extend-primary-linux-partition-to-use-all-available-space)):
```bash
apt update && apt install e2fsprogs cloud-guest-utils
```
```bash
growpart /dev/sda 1
CHANGED: partition=1 start=262144 old: size=6027264 end=6289407 new: size=10223583 end=10485726
```
```bash
resize2fs /dev/sda1
resize2fs 1.47.2 (1-Jan-2025)
Filesystem at /dev/sda1 is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
[ 319.462202] EXT4-fs (sda1): resizing filesystem from 753408 to 1277947 blocks
[ 319.599258] EXT4-fs (sda1): resized filesystem to 1277947
The filesystem on /dev/sda1 is now 1277947 (4k) blocks long.
```
Finalement, on peut `reboot` pour affecter nos changement.
On peut alors vérifier par `lsblk` que la partition a bien été augmentée.
#### Créer l'image
Créons alors l'image finale, on commence par installer les dépendances :
```bash
apt update && apt install git sudo debootstrap openssh-server rsync
```
- `git` : pour cloner le repo
- `sudo` : car il n'est pas installé de base sur Debian et est nécessaire pour le script
- `debootstrap` : installeur Debian
- `openserver-ssh rsync` : pour transférer l'image de la machine virtuelle vers macOS via ssh
(On peut ajouter `qemu-user-static` pour cross-compiler une autre architecture que celle de la machine virtuelle, càd x86_64)
On clone ensuite le projet (on va seulement utiliser le fichier `tools/create-image.sh` du projet (indépendant du reste du projet) mais Syskaller peut être utilisé pour faire des tests automatiques du kernel, voir le `README`):
```bash
git clone https://github.com/google/syzkaller.git
Cloning into 'syzkaller'...
remote: Enumerating objects: 133242, done.
remote: Counting objects: 100% (282/282), done.
remote: Compressing objects: 100% (169/169), done.
remote: Total 133242 (delta 175), reused 113 (delta 113), pack-reused 132960 (from 2)
Receiving objects: 100% (133242/133242), 293.05 MiB | 4.46 MiB/s, done.
Resolving deltas: 100% (87943/87943), done.
Updating files: 100% (4321/4321), done.
```
```bash
cd syzkaller
```
Puis on créer une image disque pour l'architecture x86_64 avec Debian Trixie :
```bash
sh tools/create-image.sh --distribution trixie --arch x86_64
+ PREINSTALL_PKGS=openssh-server,curl,tar,gcc,libc6-dev,time,strace,sudo,less,psmisc,selinux-utils,policycoreutils,checkpolicy,selinux-policy-default,firmware-atheros,debian-ports-archive-keyring
...
+ sudo cp -a trixie/. /mnt/trixie/.
+ sudo umount /mnt/trixie
```
On peut alors récupérer le fichier via `rsync` et l'utiliser pour lancer la VM. Si aucun utilisateur n'est disponible sur la machine virtuelle, on va modifier les paramètres du serveur SSH pour autoriser les connections via `root` ([source](https://stackoverflow.com/questions/18395622/remote-login-as-root-in-ubuntu)):
```bash
nano /etc/ssh/sshd_config
```
Puis on va chercher `#PermitRootLogin prohibit-password` que l'on va remplacer par `PermitRootLogin yes`. On relance le serveur ssh par `service sshd restart` et on met un mot de passe au compte `root` avec `passwd root` et nous sommes prêt.es !
On lance un 2nd terminal dans lequel on exécute :
```bash
rsync -e "ssh -p 10021" --progress root@localhost:/root/syzkaller/trixie.img linux_builder/trixie.img
root@localhost's password:
trixie.img
2,147,483,648 100% 21.31MB/s 0:01:36 (xfr#1, to-chk=0/1)
```
- `--progress` pour afficher une bar de progression
- `-e` pour ajouter des paramètres via ssh
#### Modifier le kernel
Conformément à [ce document](https://github.com/google/syzkaller/blob/master/docs/linux/setup_ubuntu-host_qemu-vm_x86-64-kernel.md) de Syskaller, on doit modifier notre fichier `.config` afin de modifier les valeurs suivante :
```bash
# Coverage collection.
CONFIG_KCOV=y
# Debug info for symbolization.
CONFIG_DEBUG_INFO_DWARF4=y
# Memory bug detector
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
# Required for Debian Stretch and later
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y
```
On peut utiliser `nano .config` pour modifier le fichier et `Ctrl W` pour chercher les lignes correspondantes. Pour sortir, on fait `Ctrl + X`, `Y` et `Entrée`.
On peut alors recompiler avec la même commande que précédemment et là on a par exemple un cas où on nous redemande des questions sur une partie de la configuration.
On peut alors lancer la machine virtuelle avec cette nouvelle image disque (le paramètre `root` a changé pour correspondre avec le nouveau disque) :
```bash
qemu-system-x86_64 \
-nographic \
-kernel linux/arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/sda earlyprintk=serial net.iframes=0" \
-drive file="trixie.img",format=raw \
-m 4G \
-net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
-net nic,model=e1000 \
-s
```
TADAAAAMMMMMM
Pour une suite sur l'utilisation de Syzbot, j'ai utilisé [ce post](https://hackerbikepacker.com/syzbot) pour vraiment m'introduire dans le développement linux, j'ai simplement développé ici la partie non expliquée du début du post.
À vos claviers et bon courage !