LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Visual Studio Code で Linuxカーネルのコードリーディング

概要

Visual Studio Code (VSCode)では、短時間でエディタのセットアップすることができ、テキストエディタとして次の機能が使えることが判明した。

  • 関数定義を参照する
  • 関数呼び出し元を参照する
  • 入力補完機能
  • ホバーによるドキュメント表示
  • シンタックスチェック
  • シンボルの一斉置換

また、VSCodeではデバッグ機能が標準で提供されているので、VSCodeからGDBでアタッチしてデバッグすることもできた。
そこで本記事では、Linuxカーネルのコーディングに必要な設定手順について記載する。

はじめに

Visual Studio Code (VSCode)は、Microsoftが開発・提供しているオープンソースのエディタの一つである。
VSCodeでは、プログラマが必要とする機能 (補完機能やデバッグ機能など) がデフォルトで搭載されていることに加えて、拡張機能が豊富に提供されている。

そこで、VSCodeソースコードリーディングするために、「初期設定に手間がかかるかどうか」と「実際に使ってみてどうなのか」を確認していくことにする。

環境

今回、Windows 10のローカルマシンからVSCodeを利用して、Ubuntu 20.04のリモートマシンにアクセスして、リモートマシン上にあるソースコードやツールチェーンを利用して、コードリーディングをすることを想定する。

VSCode の Remote Development

ここでは、次のバージョンで動作を確認している。

Windows 10 Home

  • VSCode v1.63.2
  • Remote Development v0.21.0

Ubuntu 20.04.3

  • C/C++ Extension Pack v1.1.0

VSCode のインストール

Visual Studio Code のサイト VSCodeは、WIndowsに加えてMacOSLinuxにも対応している。

code.visualstudio.com

Remote Development のインストール

Remote Development は、コンテナ・リモートマシン・WSL内のファイルをアクセスできるVSCode拡張機能の一つ *1 である。

Remote Development のインストール

  1. アクティビティバーにある拡張機能アイコンを選択する
  2. 検索窓に「Remote Development」と入力する
  3. 検索候補にある「Remote Development」を選択する
  4. エディタグループにある「インストール」を選択する

インストールが完了すると、アクティビティバーに「リモート エクスプローラー」が追加される。

SSH 未設定の場合

  1. アクティビティバーにあるリモートエクスプローラーを選択する
  2. SSH Target」に変更する

現在、ローカルマシンにはリモートマシンにアクセスするための設定をしていないため、サイドバーには何も表示されていない。

リモートマシンに公開鍵を追加する

既にローカルマシンからリモートマシンにSSHでリモートアクセスできる場合、公開鍵認証が必要ない場合には省略可能。

ここでは、Windows PowerShell を利用して公開鍵を生成をする。*2

PowerShellSSH用の公開鍵を生成する

  1. ssh-keygen.exeを実行する
  2. 公開鍵の保存先を設定する
  3. 秘密鍵パスフレーズを設定する
  4. 確認用にパスフレーズを再度入力する

コマンドが成功すると指定されたパスに秘密鍵と公開鍵がローカルマシンに生成される。 (ここでは、秘密鍵id_rsa、公開鍵をid_rsa.pubとする)

公開鍵id_rsa.pubを、何らかの手段(SSH, USBメモリ経由, クリップボードなど) でリモートマシンにコピーし、リモートマシンの.ssh/authorized_keysに登録する。

その後、ローカルマシンのVSCodeに戻り、SSHの接続先を設定する。

SSH の接続先設定

  1. SSH TARGETS」にマウスオーバーすると表示される「Configure」を選択する

SSH の接続先を設定する

  1. SSH接続先を記述する
  2. サイドバーに記述したホスト名が出現するので、「Connect to Host in New Window」を選択する

リモートマシンにあるLinuxソースコードを開く

SSH接続に成功した場合、新規ウインドウでリモートマシン内のファイルにアクセスすることができる。

  1. ステータスバーに接続先のホスト名が出力されていることを確認する
  2. アクティビティバーの「エクスプローラー」を選択する
  3. サイドバーから「フォルダーを開く」を選択し、Linuxソースコードのトップディレクトリを選択する

リモートマシンのVSCode拡張機能をインストールする

Linuxカーネルソースコードの多くはCプログラムとなっているため、リモートマシンにはC/C++開発用の拡張機能をインストールしていく。

C/C++開発用の拡張機能をインストールする

  1. アクティビティバーから「拡張機能」を選択する
  2. 検索的に「C++」と入力する
  3. C/C++ extension Pack」(C/C++を開発するための拡張機能がバンドルされている) をインストールする

その後、再読み込みを促されるので、「再読み込み」を選択する。

インテリセンスを設定する

Linuxカーネルでは、かなり複雑な依存関係を持つため、手動でコンパイルフラグを作成することは難しい。
幸いにも、Linuxカーネルではgen_compile_commands.py と呼ばれるスクリプトが用意されている。

    ~/linux:$ ./scripts/clang-tools/gen_compile_commands.py  

ビルド済みのカーネルツリーに対して、上記のコマンドを実行することでcompile_commands.jsonファイルが生成される。

上記の情報を渡してあげるためには、VSCodeの設定を変更する必要がある。

C/C++構成の設定

  1. Ctrl + Shift + Pを選択し、「C/C++; 構成の編集 (JSON)」を選択する

デフォルトのc_cpp_properities.jsonが表示されるので、compileCommandsフィールドを追加する。

// 1:
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64",
            "compileCommands": "${workspaceFolder}/compile_commands.json",
        }
    ],
    "version": 4
}

またクロスコンパイルを検討している場合には、compilerPathintelliSenseModeを設定する。

これによりVSCodeでは、プロジェクト内にあるcompile_commands.jsonを用いてインテリセンスを設定する。

VSCodeソースコードを確認する

関数定義を参照する

  • <Alt> + <F12>

関数名や変数名にカーソルを合わせ、<Alt> + <F12> を押下すると、その関数の定義元が表示される。

__ext4_handle_dirty_metadata関数の定義を表示する

関数呼び出し元を参照する

  • <Shift> + <F12>

関数名や変数名にカーソルを合わせ、<Shift> + <F12> を押下すると、その関数の呼び出し元が表示される。

__ext4_handle_dirty_metadata関数の呼び出し元を表示する

入力補完機能

  • <Ctrl> + <Space>

関数や変数を入力すると、自動でポップアップが表示される。また、<Ctrl> + <Space>を入力でも同様にポップアップが表示される。

inode構造体のメンバから入力候補をリストアップする

ホバーによるドキュメント表示

  • マウスオーバー

マウスアップして関数のドキュメント表示する

シンタックスチェック

Cプログラムの構文に合わないようなコードを記述する

シンボルの一斉置換

  • <Ctrl> + <F2>

シンボル (super_block) を一斉に置換する

カーネルのビルド

規模が大きい場合には、コードリーディングだけでプログラムの仕様を完全に理解することは難しい。
そこで、実際にプログラムを動かしてみることで、より詳細な挙動を確認することができる。

まずは、VSCodeからカーネルをビルドできるような設定をしていく。

VSCodeでは、"タスク"と呼ばれる単位で何かしらの処理を実行することができる。
まず初めに、カーネルのビルド/クリーンアップするタスクを追加していく。

タスクの設定

  1. 「ターミナル」を選択する
  2. 「タスクの構成」を選択する

テンプレートからtasks.jsonを生成する

  1. 「テンプレートからtasks.jsonを生成する」を選択する

これにより、新規エディタが立ち上がり、tasks.jsonのテンプレートが表示される。
次のようなmake -j$(nproc) bzImageを実行する Build bzImageタスクとmake cleanを実行するCleanタスクを追加する。

// 1:
{
        // See https://go.microsoft.com/fwlink/?LinkId=733558
        // for the documentation about the tasks.json format
        "version": "2.0.0",
        "type": "shell",
        "echoCommand": true,
        "tasks": [
                {
                        "label": "Build bzImage",
                        "command": "make",
                        "args": [
                                "-j$(nproc)",
                                "bzImage"
                        ],
                        "group": {
                                "kind": "build",
                                "isDefault": true
                        },
                }, 
                {
                        "label": "Clean",
                        "command": "make",
                        "args": [
                                "clean"
                        ],
                        "group": "none"
                },
        ]
}

これにより、ビルドタスクとしてデフォルトでmake -j$(nproc) bzImageが実行されるようになった。

ビルドタスクを実行するためには、<Ctrl> + Shift + B がショートカットとして割り当てられている。

カーネルをビルドする

QEMUで指定したカーネルをブートする

ビルドしたカーネルをお試しで動作確認する場合、QEMU*3でエミュレートすると効率が良い。
そこで、VSCodeからQEMUを利用して、カーネルをブートできるような設定をしておく。

カーネルのビルドと同様にQEMUでブートするようなタスクを作成する。

// 1:
{
        // See https://go.microsoft.com/fwlink/?LinkId=733558
        // for the documentation about the tasks.json format
        "version": "2.0.0",
        "type": "shell",
        "echoCommand": true,
        "tasks": [
                {
                        "label": "Build bzImage",
                        "command": "make",
                        "args": [
                                "-j$(nproc)",
                                "bzImage"
                        ],
                        "group": {
                                "kind": "build",
                                "isDefault": true
                        },
                }, 

                {
                        "label": "Clean",
                        "command": "make",
                        "args": [
                                "clean"
                        ],
                        "group": "none"
                },

                {
                        "label": "Boot kernel in QEMU",
                        "command": "qemu-system-x86_64",
                        "args": [
                                "-kernel",
                                "arch/x86_64/boot/bzImage",
                                "-drive",
                                "file=rootfs.ext4,if=ide,format=raw",
                                "-nographic",
                                "-append",
                                "'root=/dev/sda console=ttyS0 init=/bin/sh'",
                                "-s",
                                "-S"
                        ],
                        "group": "none"
                },
        ]
}

タスクの実行を選択する

  1. タブバーから「ターミナル」を選択する
  2. 「タスクの実行」を選択する

QEMUを実行するタスクを選択する

作成したタスクのラベル (Boot kernel in QEMU) を選択すると、ターミナルに実行結果が出力される。

QEMUでビルドしたカーネルをブートする

GDBでターゲットにアタッチする

上記の手順にて、ビルドしたカーネルを起動させているのでVSCodeからGDBにアタッチしてみる。

VSCodeでは、デバッグ機能が標準として提供されており、GDBを用いたテンプレートが用意されている。
今回は、そのテンプレートを利用して、QEMUで起動させたカーネルにアタッチする。

デバッグのための設定ファイルを作成する

  1. 「実行とデバッグ」を選択する
  2. 「launch.jsonファイルを作成します」を選択する
  3. C++ (GDB/LLDB)」を選択する

127.0.0.1:1234GDBで接続するためにlaunch.jsonを編集する。

// 1:
{
        // IntelliSense を使用して利用可能な属性を学べます。
        // 既存の属性の説明をホバーして表示します。
        // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
                {
                        "name": "kernel-debug",
                        "type": "cppdbg",
                        "request": "launch",
                        "miDebuggerServerAddress": "127.0.0.1:1234",
                        "program": "${workspaceFolder}/vmlinux",
                        "args": [],
                        "stopAtEntry": false,
                        "cwd": "${workspaceFolder}",
                        "environment": [],
                        "externalConsole": false,
                        "logging": {
                            "engineLogging": false
                        },
                        "MIMode": "gdb",
                }
        ]
}

上記のファイルを保存した結果、launch.jsonで作成したサイドバーが更新される。

ext4_fill_super関数にブレークポイントを設置する

試しに、ext4_fill_super関数にブレークポイントを設置して、GDBデバッグしてみる。

  1. 行番号の左に選択すると、ブレークポイントが設置される
  2. 「実行とデバッグ」に作成した kernel-debug が選択されているので、「デバッグの開始」を選択する

VSCode からGDBを実行する

おわりに

VSCodeは非常に便利なテキストエディタであり、豊富な機能が提供されている。
今回の場合では、1時間足らずで次のような機能が有効で確認することができた。

  • 関数定義を参照する
  • 関数呼び出し元を参照する
  • 入力補完機能
  • ホバーによるドキュメント表示
  • シンタックスチェック
  • シンボルの一斉置換

変更履歴

  • 2022/2/1: 記事公開

参考

*1:Remote - Containers, Remote -SSH, Remote - WSL の 3つから成る拡張機能となっている

*2:PuTTYやGit Bashなど生成する手段は何でもよい

*3:VirtualBoxなども含む