D言語でもデバッグができる！Visual Studio CodeでD言語デバッグ環境を構築する方法

本記事はVisual Studio Code上にD言語のデバッグ環境を整えるという趣旨の記事です。D言語に興味のない方は人生最良の行動を促す[こちらの記事からご確認ください](https://qiita.com/outlandkarasu@github/items/7bb095d786b849378084)。<font color=white>逃がさないよ？</font>

# Windowsのデバッグ環境
## 必要なもの
本記事はインストール方法を紹介するものではないので、各自インストールしてください。
- [Visual Studio Code](https://code.visualstudio.com/)
- [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) ※Visual Studio CodeのC/C++言語拡張。デバッグ実行はこれを流用する。
- [dlang_cpp.natviz](https://github.com/Pure-D/dlang-debug/blob/master/dlang_cpp.natvis) ※C/C++言語拡張でD言語の型を取り扱えるようにする設定ファイル。ダウンロードしてプロジェクトフォルダのどこかに置く。
- [code-d](https://marketplace.visualstudio.com/items?itemName=webfreak.code-d) ※Visual Studio CodeのD言語拡張。プログラミング教育サービスでも映画でもないので注意。
- [ldc2](https://github.com/ldc-developers/ldc/releases) ※LLVMベースのD言語コンパイラ

## ポイント
デバッグを行うには、以下について注意が必要です。
- LDC2コンパイラを使用してビルドを行う。
- ビルドの際64bitのバイナリにC言語形式のデバッグ情報を仕込む。
  具体的にはdubのプロジェクト設定にて`debugInfoC`という`buildOptions`を設定します。
- デバッガにはVisual Studio CodeのC/C++拡張のデバッグ機能を使う。
- C/C++拡張のデバッグ機能にD言語設定を施す。
  具体的には`visualizerFile`に、ダウンロードした`dlang_cpp.natvis`を指定します。

上記ポイントについて、以降の章で詳細な手順を示します。

ちなみに、コンパイラに`ldc2`ではなく`dmd`を使用したり、dubのプロジェクト設定にて`debugInfoC`の設定を行わなかったり、`dlang_cpp.natvis`ファイルを指定しなかったりしても、ブレークポイントを張ってステップ実行したりするくらいはできます。では、上記をすると何が強くなるかというと、以下の点がサポートされるようになります。

- 文字列がちゃんとウォッチできるようになる。ゼロ終端しなくてもちゃんと長さを解釈してくれる。
- 配列がちゃんとウォッチできるようになる。長さも、要素ごとの展開も可能になります。
- 連想配列もちゃんとウォッチできるようになる。

Before: ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/894090ef-d7e5-712d-4c89-a3113301cfd1.png)
After: ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/40ca57ff-5df6-40a6-2db9-3ef4180cccdc.png)

上記画面では連想配列の変数`aa`の中身や、文字列の変数`str`の中身がキチンと表示されていることがわかります。

## ビルド方法の手順
dubを使用してビルドを行います。その際、C言語形式のデバッグ情報を仕込むため、`debugInfoC`のオプションを設定します。

```json:dub.json
{
    "name": "testapp",
    "targetType": "executable",
	"buildTypes": {
		"vscode-unittest": { "buildOptions": ["debugMode", "debugInfoC", "unittests"] },
		"vscode-unittest-cov": { "buildOptions": ["debugMode", "debugInfoC", "unittests", "coverage"] },
		"vscode-debug": { "buildOptions": ["debugMode", "debugInfoC"] },
		"vscode-debug-cov": { "buildOptions": ["debugMode", "debugInfoC", "coverage"] }
	}
}
```

上記のように、`buildTypes`にて各デバッグ用ビルドタイプのビルドオプションに`"debugMode", "debugInfoC"`を与えます。これにより、

そして、以下のようにビルドを行います。

```bat:Windows
dub build --arch=x86_64 --build=vscode-debug --compiler=ldc2
```

ちなみに、カバレッジ付きの単体テストが実行されるバイナリを作るには以下のようにします。
```bat:Windows
dub build --arch=x86_64 --build=vscode-unittest-cov --config=unittest --compiler=ldc2
```

これをVisual Studio Codeから実行するため、`tasks.json`を設定します。
以下のようになるでしょう。

```json:tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "DebugBuild",
            "group": "build",
            "command": "dub build --arch=x86_64 --build=vscode-debug --compiler=ldc2",
            "type": "shell",
            "problemMatcher": "$dmd",
        },
        {
            "label": "UnittestBuild",
            "group": "build",
            "command": "dub build --arch=x86_64 --build=vscode-unittest-cov --config=unittest --compiler=ldc2",
            "type": "shell",
            "problemMatcher": "$dmd",
        }
    ]
}
```

あるいは、code-dが用意してくれている`dub`のビルドタイプを使用して以下のようにしてもよいかもしれません。

```json:tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "DebugBuild",
            "group": "build",
            "type": "dub",
            "compiler": "ldc2",
            "archType": "x86_64",
            "buildType": "vscode-debug",
            "problemMatcher": "$dmd"
        },
        {
            "label": "UnittestBuild",
            "group": "build",
            "type": "dub",
            "compiler": "ldc2",
            "archType": "x86_64",
            "buildType": "vscode-unittest-cov",
            "problemMatcher": "$dmd"
        }
    ]
}
```

## デバッグ方法の手順
デバッグ方法の`launch.json`の設定は以下のようになります。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug",
            "type": "cppvsdbg",
            "request": "launch",
            "preLaunchTask": "DebugBuild",
            "program": "${workspaceRoot}/testapp.exe",
            "cwd": "${workspaceRoot}",
            "visualizerFile": "${workspaceRoot}/dlang_cpp.natvis",
        },
        {
            "name": "Unittest",
            "type": "cppvsdbg",
            "request": "launch",
            "preLaunchTask": "UnittestBuild",
            "program": "${workspaceRoot}/testapp.exe",
            "cwd": "${workspaceRoot}",
            "args": ["--DRT-covopt=dstpath:cov"],
            "visualizerFile": "${workspaceRoot}/dlang_cpp.natvis",
        }
    ]
}
```

`"visualizerFile": "${workspaceRoot}/dlang_cpp.natvis"` として、D言語用の設定ファイルを指定してあげるのがポイントです。このファイルと`debugInfoC`のついたバイナリを使用することで、快適にデバッグができるようになります。また、単体テストでカバレッジを出す場合は、引数に `--DRT-covopt=dstpath:cov` を与えることでカバレッジファイルの出力先を指定することができます。

以下が上記設定を適用したデバッグの様子です。
各設定を確認→デバッグ実行→ブレーク確認→連想配列の中身確認→単体テスト実行→ブレーク確認→カバレッジ確認を行っています。

![Debug D on Windows](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/700d3d22-efeb-9215-9cf6-d4c35bc033aa.gif)


# Linuxのデバッグ環境
## 必要なもの
本記事はインストール方法を紹介するものではないので、各自インストールしてください。
- [Visual Studio Code](https://code.visualstudio.com/)
- [lldb](https://lldb.llvm.org/) ※LLVMのデバッガ
- [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) ※Visual Studio CodeのLLDB拡張。デバッグ実行はこれを使用する。
- [lldb_dlang.py](https://github.com/Pure-D/dlang-debug/blob/master/lldb_dlang.py) ※LLDB拡張でD言語の型を取り扱えるようにする設定ファイル。ダウンロードしてプロジェクトフォルダのどこかに置く。
- [code-d](https://marketplace.visualstudio.com/items?itemName=webfreak.code-d) ※Visual Studio CodeのD言語拡張。プログラミング教育サービスでも映画でもないので注意。
- [ldc2](https://github.com/ldc-developers/ldc/releases) ※LLVMベースのD言語コンパイラ

## ポイント
デバッグを行うには、以下について注意が必要です。
- LDC2コンパイラを使用してビルドを行う。
- デバッガにはVisual Studio CodeでLLDBを動かすためのCodeLLDBを使う。
- LLDBにD言語設定(lldb_dlang.py)を施す。ただし、修正が必要(2022/12/20現在)。

上記ポイントについて、以降の章で詳細な手順を示します。

ちなみに、コンパイラに`ldc2`ではなく`dmd`を使用したり、`lldb_dlang.py`ファイルを指定しなかったりしても、ブレークポイントを張ってステップ実行したりするくらいはできます。では、上記をすると何が強くなるかというと、以下の点がサポートされるようになります。

- 文字列がちゃんとウォッチできるようになる。ゼロ終端しなくてもちゃんと長さを解釈してくれる。
- 配列がちゃんとウォッチできるようになる。長さも、要素ごとの展開も可能になります。
- 連想配列もちゃんとウォッチできるようになる。

Before: ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/ef60bc89-44e8-7aa4-e41a-3e1a71340ee9.png)
After: ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/ffbfc33f-6b74-347c-0799-92e969ecd908.png)

上記画面では連想配列の変数`aa`の中身や、文字列の変数`str`の中身がキチンと表示されていることがわかります。

## ビルド方法の手順
dubを使用してビルドを行います。これといって特別な設定は必要ありません。

```json:dub.json
{
    "name": "testapp",
    "targetType": "executable"
}
```

そして、以下のようにビルドを行います。

```sh:Linux
dub build --arch=x86_64 --build=debug --compiler=ldc2
```

ちなみに、カバレッジ付きの単体テストが実行されるバイナリを作るには以下のようにします。
```sh:Linux
dub build --arch=x86_64 --build=unittest-cov --config=unittest --compiler=ldc2
```

これをVisual Studio Codeから実行するため、`tasks.json`を設定します。
以下のようになるでしょう。

```json:tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "DebugBuild",
            "group": "build",
            "command": "dub build --arch=x86_64 --build=debug --compiler=ldc2",
            "type": "shell",
            "problemMatcher": "$dmd",
        },
        {
            "label": "UnittestBuild",
            "group": "build",
            "command": "dub build --arch=x86_64 --build=unittest-cov --config=unittest --compiler=ldc2",
            "type": "shell",
            "problemMatcher": "$dmd",
        }
    ]
}
```

あるいは、code-dが用意してくれている`dub`のビルドタイプを使用して以下のようにしてもよいかもしれません。

```json:tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "DebugBuild",
            "group": "build",
            "type": "dub",
            "compiler": "ldc2",
            "archType": "x86_64",
            "buildType": "debug",
            "problemMatcher": "$dmd"
        },
        {
            "label": "UnittestBuild",
            "group": "build",
            "type": "dub",
            "compiler": "ldc2",
            "archType": "x86_64",
            "buildType": "unittest-cov",
            "problemMatcher": "$dmd"
        }
    ]
}
```

## デバッグ方法の手順
デバッグ方法の`launch.json`の設定は以下のようになります。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug",
            "type": "lldb",
            "request": "launch",
            "preLaunchTask": "DebugBuild",
            "program": "${workspaceRoot}/testapp",
            "cwd": "${workspaceRoot}",
            "initCommands": ["command script import \"${workspaceRoot}/lldb_dlang.py\""]
        },
        {
            "name": "Unittest",
            "type": "lldb",
            "request": "launch",
            "preLaunchTask": "UnittestBuild",
            "program": "${workspaceRoot}/testapp",
            "cwd": "${workspaceRoot}",
            //"args": ["--DRT-covopt=dstpath:${workspaceRoot}/cov"],
            "initCommands": ["command script import \"${workspaceRoot}/lldb_dlang.py\""]
        }
    ]
}
```

`"initCommands": ["command script import \"${workspaceRoot}/lldb_dlang.py\""]` として、D言語用の設定ファイルを指定してあげるのがポイントです。このファイルを使用することで、快適にデバッグができるようになります。

しかし、lldb_dlang.pyはそのままではうまく機能しないようでした。size_tが検索できないとかのエラーが出るようです。仕方ないのでlldb_dlang.py内に出てくる`size_t`をすべて`ulong`に書き換えます。

また、単体テストでカバレッジを出す場合は、引数に `--DRT-covopt=dstpath:${workspaceRoot}/cov` を与えることでカバレッジファイルの出力先を指定することができ…るのですが、VSCodeにカバレッジが表示されなくなるのでコメントアウトしています。

以下が上記設定を適用したデバッグの様子です。
各設定を確認→デバッグ実行→ブレーク確認→連想配列の中身確認→単体テスト実行→ブレーク確認→カバレッジ確認を行っています。

![Debug D on Linux](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/b0db88b0-cb53-32e1-6da4-af9ffdcce867.gif)