Preparation

Kernelのソースコードを読める・デバッグできる環境を作る

はじめに

このページでは、Kernelのソースコードを読んだり、実際にデバッグビルドしてGDBで動かすための環境を作っていきます。

Source Code

Kernelのソースコードが出来る環境を作ります。 ソースコードはexploitを書いたり、kernelの処理の内容を調べたりするのに頻繁に必要になります。 コードリーディングに使う環境は正直なんでも良いですが、一般的には以下ができることが望ましいです:

  • Go To Definitionできる
  • XREFが見れる
  • シンボルが検索できる
  • 特定のアーキテクチャのシンボルが探せる
  • 容易にバージョンをswitchできる

以下ではこれらを満たす環境として、VSCode + Clangdを使う方法を紹介します。 これ以外の方法としては、bootlin Elixir Cross Referencerもオンライン上で気軽に使えるのでおすすめです。

Linuxソースツリーのクローン

git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git あたりからソースコードをcloneしてきましょう。

cloneには異常時間かかります。コーヒーブレイクをしながら気軽に待ちましょう。

compile_commands.jsonの生成

cloneしたら、適当なタグにcheckoutします。 バージョンは何でも良いですが、特に断らない限りはUbuntu 22.04 LTSで使われている5.19.XX系を使うことにします。 あんまり古すぎてもリアリティがなくなりますし、 あんまり新しすぎてもexploitに使いやすいattack surfaceが消されていることがあるので、 そこだけ注意してください。

1
git checkout v5.19.2

そのあと、configを設定します。Linuxのコンフィグはmake menuconfigによって設定できます。 しかし今回は取り敢えずビルドできればいいので、make alldefconfigで適当に設定してしまってもいいでしょう。

1
make alldefconfig

続いてkernelをビルドします:

1
make -j$(nproc)

このビルドもマシンスペックによっては少し時間がかかります。 ビルドが終わったら、続いてclangdに食べさせる用のcompile_commands.jsonを生成します:

1
python3 ./scripts/clang-tools/gen_compile_commands.py

これによって、ソースツリーのルートにcompile_commands.jsonが生成されます。

clangd

必要に応じてLLVM/clangdをインストールしてください。 また、VSCode拡張のvscode-clangdをインストールしてください。 その後、/vscode/c_cpp_properties.jsonを以下のように設定します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"configurations": [
    {
        "name": "Linux",
        "includePath": [
            "${workspaceFolder}/include/**",
            "${workspaceFolder}/arch/x86/**",
            "${default}"
        ],
        "defines": [
            "__GNUC__",
            "__KERNEL__"
        ],
        "compilerPath": "/bin/clang-15",
        "cStandard": "c17",
        "cppStandard": "c++14",
        "intelliSenseMode": "linux-clang-x64",
        "compileCommands": "${workspaceFolder}/compile_commands.json"
    }
],
"version": 4
}

この状態でclangdを起動すると、compile_commands.jsonに従ってソースコードをindexingしてくれるはずです。

試しにVSCodeを開いて、シンボルの検索やGo To Definition等ができるかどうかを確かめてみてください。 また、アーキ依存のシンボルに関してはx86用のシンボルが表示されることを確かめてください。

Build

ソースコードを動かせたので次は任意のバージョンのカーネルをビルドしてみます。 先程のLinuxソースツリーを使ってもいいですが、 折角なので今回はbuildrootを使ってみましょう。

まずはbuildrootをダウンロードします。 展開後、ソースのルートディレクトリにてmake menuconfigを実行することで設定ができます。 ここで、buildrootの設定とLinuxの設定は別ものであることに注意してください。 今回はただビルドするだけなのでとりわけ追加で設定する必要はありませんが、 簡単のため以下のコンフィグだけ設定してください:

  • BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE: 任意のカーネルバージョン
  • BR2_TARGET_ROOTFS_CPIO: yを選択
  • BR2_TARGET_ROOTFS_EXT2: yを選択

buildrootはあらかじめいくつかのビルドターゲット用の設定を用意してくれています。 その一覧はmake list-defconfigsで確認することができます。 今回はQEMU上で動かす用のx64ビルドをしたいため、以下のコマンドでコンフィグをしてください。

1
make qemu_x86_64_defconfig

あとはビルドするのみです。 もともとbuildrootはクロスビルドにも対応したツールのため、ホストツールも含めて全てダウンロード・ビルドします。 そのため先程のkernelのビルドよりも時間がかかりますが、 これは初回だけなので気長に待ちましょう。

1
make -j$(nproc)

ビルドが終わると、/output/images以下に以下のファイルが生成されます:

  • bzImage: ビルドしたカーネルのバイナリ
  • rootfs.cpio: ビルドしたカーネルのルートファイルシステムのバイナリ(rootfs)
  • rootfs.ext2: ビルドしたカーネルのルートファイルシステムのバイナリ(EXT2)

あとはQEMU上で動かすだけです。 buildrootは/output/images/start_qemu.shにQEMUを起動するスクリプトを生成してくれているので、 これを動かせばOKです。

1
./output/images/start_qemu.sh

kernelが起動してネットワーク設定等諸々したあとにログインプロンプトが出るかと思います。 configからルートログイン設定をすることもできますが、めんどくさいので今回はinittabを修正することで 勝手にルートログインしてくれるようにしましょう。

まず/output/images/rootfs.ext2をマウントして/etc/inittabを修正します:

1
2
3
sudo mkdir /mnt/hoge
sudo mount ./output/images/rootfs.ext2 /mnt/hoge
vim /mnt/hoge/etc/inittab

inittab中の以下の部分を修正してください

1
2
3
4
5
6
# このようになっている箇所を2行ともコメントアウト
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL
tty1::respawn:/sbin/getty -L  tty1 0 vt100 # QEMU graphical window

# 以下を追加
::respawn:-/bin/sh

rootfs.ext2をアンマウントして、再度QEMUを起動してください。 今度は勝手にルートとしてシェルが起動するはずです。


Exercise

1. ソースコードが便利に読める状態にする

Source Codeの章を参考にして、ソースコードが読める状態にしてください。 なお、Web上のクロスリファレンサーを使うのも全く問題ありません。

2. 任意のバージョンのカーネルをビルドする

Buildの章を参考にして、任意のバージョンのカーネルをビルドして動かしてみてください。

Last modified November 15, 2023: add warning about challenge server (55ad5da)