Linux Kernel Development and Debugging on macOS

I use a MacBook Pro with Apple silicon and cross-compile the x86_64 kernel through a Linux VM, while all development and debugging happens natively on macOS. Linux Kernel VRED (Vulnerability Research and Exploit Development) usually doesn’t modify kernel code, so frequent rebuilds aren’t needed.

Create Disk Image

Linux kernel development requires a case-sensitive filesystem. The macOS filesystem is case-insensitive by default — follow Create a blank disk image for storage to create a blank case-sensitive disk image.

Mount the image you created and clone the Linux Kernel repository into it. Mounted images show up under /Volumes by default.

Create Linux VM

I use the Parallels Desktop hypervisor to create a Debian VM; UTM also works and is open source and free.

Install the Debian packages:

sudo apt update
sudo apt install build-essential crossbuild-essential-amd64 bc bison flex libncurses-dev libelf-dev libssl-dev
sudo apt install debootstrap qemu-user-static # syzkaller create-image.sh required

Share the case-sensitive disk with the VM. Parallels Tools must be installed inside the VM, otherwise the shared disk won’t be mounted automatically. It auto-mounts under /media/psf by default.

Compile

Set the environment variables needed for cross-compiling:

export ARCH=x86_64
export CROSS_COMPILE=x86_64-linux-gnu-
export CC=x86_64-linux-gnu-gcc

Generate the default kernel config with make defconfig, then edit the .config file to enable the following kernel config options:

# Debug info for symbolization.
CONFIG_DEBUG_INFO_DWARF4=y

# Required for Debian Stretch and later
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

Update the kernel config with make olddefconfig, then build the kernel: make -j`nproc`.

Code Navigation

I navigate the kernel with Ctags and Cscope — extremely fast. Neovim requires a third-party Cscope plugin; cscope_maps.nvim is recommended.

The Linux kernel Makefile provides tags and cscope targets that generate the respective index files. To generate indexes only for sources that have been compiled, set the COMPILED_SOURCE environment variable.

all_target_sources()
{
	if [ -n "$COMPILED_SOURCE" ]; then
		all_compiled_sources
	else
		all_sources
	fi
}

Debugging

Follow the syzkaller tutorial to create a Debian Linux image, then copy it to the shared disk.

Boot the Linux kernel with QEMU:

qemu-system-x86_64 \
	-m 2G \
	-smp 2 \
	-kernel <bzImage> \
	-append "console=ttyS0 root=/dev/sda net.ifnames=0" \
	-drive file=<stretch.img>,format=raw \
	-net user,host=10.0.2.31,hostfwd=tcp:127.0.0.1:2222-:22 \
	-net nic,model=e1000 \
	-nographic \
	-no-reboot \
	-pidfile vm.pid -s

See the official System Emulation documentation for what the options do.

Debug the kernel with gdb + pwndbg; installing pwndbg via Nix is recommended: nix profile install github:pwndbg/pwndbg.