M1 MacでQEMUでARM64用Linuxカーネルを起動する。

M1 Mac上のCLionでカーネルとか組み込みデバイスをいい感じにデバッグしたい。
そのためにLinuxカーネルに対して、qemuやkgdbでデバッグの設定をしてみる。

環境:
- マシン: M1 Pro, MacBook Pro
- Linux on Macの環境: UTM (仮想化, Apple仮想化, Ubuntu 24.04)
用意するもの:
- Linuxカーネルのソースコード https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.10.5.tar.xz
- BusyBoxのソースコード https://busybox.net/downloads/busybox-1.36.1.tar.bz2

Linuxカーネルのビルド

デフォルト設定でビルドする。
$ tar -xvf linux-6.10.4.tar.xz
$ cd linux-6.10.4/
$ make defconfig
$ make -j4

とりあえず起動してみる。
$ qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-kernel ./linux-6.10.4/arch/arm64/boot/Image \
-nographic \
-append "console=ttyAMA0"
Kernel Panicとなった。
[ 0.588364] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---
Ctrl-a xでQEMUを終了できる。

initramfsを準備する。

簡単なinitramfsを準備する。
$ vim init.c
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
for(;;) {
42;
}
return 0;
}
$ gcc -static -o init init.c -O0
$ echo init | cpio -o -H newc | gzip > initramfs.cpio.gz

起動してみる。
$ qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-kernel ./linux-6.10.4/arch/arm64/boot/Image \
-nographic \
-append "console=ttyAMA0" \
-initrd ./initramfs.cpio.gz
Hello, World!が表示される。
Hello, World!
Ctrl-a xでQEMUを終了できる。

BusyBoxのビルド

busyboxを設定してビルドする。
$ tar -jxvf busybox-1.36.1.tar.bz2
$ cd busybox-1.36.1/
$ make defconfig
$ make menuconfig
Setting->Build static binary (no shared libs)
を有効にする
$ vim .config
CONFIG_TC=y
をCONFIG_TC=n
に変更する。
$ make
$ make install
_install
にバイナリが作成される。

必要なファイルを作成する。
$ cd _install
$ mkdir proc
$ mkdir sys
$ mkdir dev
$ sudo mknod dev/null c 1 3 # これはなくても起動した
$ vim init
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s
exec /bin/sh
$ chmod +x init
$ find . | cpio -o --format=newc > ../rootfs.img
最終的にinitramfsを作成する。

busyboxをinitrdとして起動してみる。
$ qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-kernel ./linux-6.10.4/arch/arm64/boot/Image \
-nographic \
-append "console=ttyAMA0" \
-initrd ./busybox-1.36.1/rootfs.img
lsやpwdなどが利用できる。

CLionによるLinuxカーネルのデバッグ

CLionのリモート開発機能のSSHを利用して、UTMのUbuntu仮想マシンにSSHする。

Linuxカーネルのオプションを有効かしてビルドしなおす。
$ make menuconfig
DEBUG_INFO=y
DEBUG_KERNEL=y
RELOCATABLE=n
CONFIG_DEBUG_INFO_DWARF4=y
オプションを有効化したら再度ビルド
$ make -j

QEMUでLinuxを起動する。
$ qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-kernel ./linux-6.10.4/arch/arm64/boot/Image \
-nographic \
-append "console=ttyAMA0" \
-initrd ./busybox-1.36.1/rootfs.img \
-s \
-S
-
-s
: gdbサーバーがport 1234で起動する。 -
-S
: gdbが接続されるまで待機する。

CLionを設定する。
リモートデバッグ
- デバッガー:
バンドルされたGDB
- target remote 引数:
:1234
- シンボルファイル:
linux-6.10.4/vmlinux

linux-6.10.4/init/main.c
内のstart_kernel
関数にブレイクポイントを設定してデバッグボタンを押す。
GUIでステップ実行できるようになった。

devcontainerを利用する方法

.devcontainer/devcontainer.json
{
"name": "ubuntu",
"dockerFile": "Dockerfile",
"remoteUser": "vscode",
}
.devcontainer/Dockerfile
FROM ubuntu:latest
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& apt-get update \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
RUN apt-get update \
&& apt-get install -y build-essential bc bison flex libncurses-dev libelf-dev libssl-dev git wget gawk openssl libudev-dev libpci-dev libiberty-dev autoconf llvm qemu-system cpio
USER $USERNAME

UTMの場合と同様にLinuxやBusyBoxをビルドする。
- ソースコードを解凍するときは、Mac上ではなくてLinuxコマンドを用いる。
- rootfs.imgを作成するときのmknodコマンドはスキップしても起動した。

docker内でQEMUを起動する。
$ qemu-system-aarch64 -M virt -cpu cortex-a53 -kernel ./linux-6.10.5/arch/arm64/boot/Image -nographic -append "console=ttyAMA0" -initrd ./busybox-1.36.1/rootfs.img
Mac上にインストールしたQEMUでも起動可能

QEMUのgdbserver機能