Bill's Super Duper Amazing Blog

A blog about programming, technology, and more!

Blog

Building and Testing Linux Kernel Modules Using Multipass & VS Code

A practical step-by-step guide to building and testing a Linux kernel module safely
Iain (Bill) Wiseman

Iain (Bill) Wiseman

Morning Windy

linux multipass

20 Jan 2025

4 min read

Building and Testing Linux Kernel Modules Using Multipass & VS Code

Kernel development can feel intimidating when there is a real chance of locking up your machine. Multipass gives you a disposable Ubuntu VM, so you can experiment safely while keeping your host OS clean.

This guide is ordered as a full workflow:

  • Create and prepare a Multipass VM
  • Connect to it from VS Code
  • Install kernel build dependencies
  • Write and build a module
  • Load, test, unload, and clean up

Create a Multipass VM

Install Multipass on the host:

sudo snap install multipass

Launch a VM named kernel-dev and then enter it:

multipass launch --name kernel-dev
multipass shell kernel-dev

At this point you are inside the Ubuntu VM.

Optional: Bridge Mode Networking

By default, Multipass uses NAT (network address translation), which only allows the VM to reach the host and external networks, but other machines on your network cannot directly access the VM.

If you need the VM to be accessible from other machines on your network, or if you're running on Windows with remote networking, use bridge mode instead:

First, find your network interface name on the host. Run:

ip link show

Look for the interface connected to your network (usually named something like eth0, enp4s0, or wlan0). Skip lo (loopback) and docker interfaces. Example output:

1: lo: <LOOPBACK,UP,LOWER_UP> ...
2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...    <- Use this one
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...

Then on the host, before creating the VM:

multipass set local.bridged-network=enp4s0
multipass launch --name kernel-dev --bridged

Replace enp4s0 with your actual network interface from the ip link show output.

Why use bridge mode:

  • Windows host + remote Linux: If you're running Windows and connecting to a remote Linux machine, bridge mode gives the VM a direct network connection
  • Multi-machine access: Other machines on your network can SSH directly to the VM without port forwarding
  • Network simulations: Useful if your kernel module testing involves network communication
  • Stable remote access: More reliable than NAT when working across network boundaries

The rest of this guide works the same way regardless of which networking mode you chose.

Enable SSH access for VS Code

Still inside the VM, allow password authentication and set a password for user ubuntu.

Edit SSH config:

sudo nano /etc/ssh/sshd_config

Ensure this line is set:

KbdInteractiveAuthentication yes

Restart SSH and set password:

sudo systemctl restart ssh
sudo passwd ubuntu

Get the VM IP address:

hostname -I

Connect from VS Code

Install the VS Code extension pack named Remote Development.

From your host terminal, verify SSH works:

ssh ubuntu@<vm-ip>

Then use VS Code Remote SSH to open that VM as your development environment.

Install kernel module build dependencies

Inside the VM:

sudo apt update
sudo apt install -y build-essential linux-headers-$(uname -r) kmod

What these provide:

  • Compiler and build tools
  • Headers for your running kernel
  • Module management tools such as insmod and rmmod

Create the module source

Create a working folder:

mkdir -p ~/pyjama
cd ~/pyjama

Create pyjama.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("bibble");
MODULE_DESCRIPTION("Simple /proc example module");

static struct proc_dir_entry *pyjama_entry;

static ssize_t pyjama_read(struct file *file, char __user *buffer, size_t count,
                           loff_t *offset)
{
    char msg[] = "Hello from me\n";
    size_t len = strlen(msg);

    if (*offset >= len)
        return 0;

    if (copy_to_user(buffer, msg, len))
        return -EFAULT;

    *offset += len;
    return len;
}

static const struct proc_ops pyjama_ops = {
    .proc_read = pyjama_read,
};

static int __init pyjama_init(void)
{
    pyjama_entry = proc_create("pyjama", 0444, NULL, &pyjama_ops);
    if (!pyjama_entry)
        return -ENOMEM;

    pr_info("pyjama: loaded\n");
    return 0;
}

static void __exit pyjama_exit(void)
{
    proc_remove(pyjama_entry);
    pr_info("pyjama: unloaded\n");
}

module_init(pyjama_init);
module_exit(pyjama_exit);

Add the Makefile

Create Makefile in the same folder:

obj-m += pyjama.o

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

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

Build the module

From ~/pyjama:

make

If successful, you will get pyjama.ko.

Load and test it

Load the module:

sudo insmod pyjama.ko

Check kernel logs:

dmesg | tail -n 20

Read your /proc entry:

cat /proc/pyjama

Expected output:

Hello from me

Unload and clean

Unload:

sudo rmmod pyjama

Confirm unload message:

dmesg | tail -n 20

Clean build artifacts:

make clean

Useful Multipass commands

On the host machine:

multipass list
multipass shell kernel-dev
multipass stop kernel-dev
multipass start kernel-dev

This workflow gives you a repeatable, low-risk setup for kernel module experimentation.

Related articles