VSCode+C#で標準以外のデバッガを使ってみる

# はじめに

特に必要に迫られたという事ではないが、デバッガについて色々調べている過程で、標準以外のnetcoreデバッガを見つけたので記事にしておく。
リポジトリリンクは以下。
https://github.com/Samsung/netcoredbg

# vscode上のnetcoreappデバッグについて

さて、vscodeでのC#アプリのデバッグはomnisharp-vscodeという拡張が担当しているが、これがどういう仕組みで動いているかというと、C#用デバッガが、[Debug Adapter Protocol(DAP)](https://microsoft.github.io/debug-adapter-protocol/)というHTTP(のようなもの)+JSONなプロトコルを使用して、vscodeと通信してあれこれしている。詳細は[DAP公式ページ](https://microsoft.github.io/debug-adapter-protocol/overview)を参照。
[Language Server Protocol(LSP)](https://microsoft.github.io/language-server-protocol/)のデバッガ版だと思えば良いと思う。設計者が同じMSだし。

ちなみにDAPと同じような役割を持つプロトコルがgdbにもあり、そちらは[GDB/MI](ftp://ftp.gnu.org/pub/old-gnu/Manuals/gdb/html_node/gdb_211.html)と呼ばれている。デバッガ関連のドキュメントでちょくちょくMIという名前が出てくるのはこれが由来。
GDB/MIの方が当然歴史は長いが、DAPがHTTP(のようなもの)+JSONであるのに対し、MIが行ベースのやり取りなので、jsベースのvscodeでは、DAPのようなプロトコルの方が扱いやすいというのが、DAPを作った動機だろうと推測される。

さて、現在omnisharp-vscodeでは、デバッガに当たる部分にvsdbgというものを使用している。しかしこのvsdbg、機能的には問題ないのだが、ソースが公開されていない。
各種ドキュメントを見るに、恐らくVisual Studioの一部としてビルドされていると思われるので、分離もしにくく、またしたところであまりメリット無さそうという判断なのかもしれない。
そういうわけで、実際の細かい挙動はあまり把握できないので、実際にどう動いているのかという所はわからずじまいとなる。

そこで代替実装はないかと探したところ、[netcoredbg](https://github.com/Samsung/netcoredbg)というものが見つかった(SamsungといえばTizenがあるのでそれ関連?)。
こちらはオープンソースで、かつ[aCute(eclipse上でc#を使うための拡張)](https://github.com/eclipse/aCute)でも使用されていると聞いたので、実績も期待できる。というわけで、こちらを動かしてみる。

# バイナリの準備

netcoredbgはスタンドアローンのプログラムになっているが、バイナリの取得方法についてはいくつか方法があるので記述する。
なお、バイナリの動作確認は`/path/to/netcoredbg --help`でヘルプが表示されればOKということになる。

## 公式ビルド

[githubのリリースページ](https://github.com/Samsung/netcoredbg/releases)からバイナリをDLして展開する。

## ソースからビルド

ソースからコンパイルして使う場合、以下の手順でバイナリをビルドする。要はcmakeとdotnet sdkという理解でOK。

### 準備(Windows)

以下のソフトが必要

* Visual Studio 2019以降
    * VC++とWindows SDKを有効にしておく
* cmake
    * VSが2019以降の場合、3.14以降が必要
* git
    * .NET SDKとdotnet/runtimeのソースコードを取得するため
* [最新安定版の.NET SDK](https://dotnet.microsoft.com/download)
    * .NET SDKのダウンロードをしたくない場合

### 準備(Linux)

* clang
* cmake
* autotools
    * [ninja](https://ninja-build.org/)でも可かもしれない
* git
    * .NET SDKとdotnet/runtimeのソースコードを取得するため
* [最新安定版の.NET SDK](https://dotnet.microsoft.com/download)
    * .NET SDKのダウンロードをしたくない場合


### ビルド

必要なソフトが揃ったら、以下の手順でビルドする

1. ビルド出力ディレクトリを作成
    * `build`ディレクトリ等
    * ツリーの外でも可
2. カレントディレクトリを作成したディレクトリに変更
3. `cmake -G [ジェネレーター] -DCMAKE_INSTALL_PREFIX=/path/to/install/dir [ソースルートディレクトリ]` でConfigure
    * この時にどこにインストールするか決まるので注意
    * ジェネレーター引数(`-G [ジェネレーター]`)は無くても可
    * `-G`で使える引数は、`cmake --help`で閲覧可能
    * この時、ソースルートに`.dotnet`と`.coreclr`というディレクトリ名でcoreclrのソースとdotnetバイナリがダウンロードされるため、既存のものを利用したい場合は`-DCORECLR_DIR`と`-DDOTNET_DIR`で指定すること
4. ソースルートディレクトリに戻る
5. `cmake --build [ビルド出力ディレクトリ]`でビルド
6. `cmake --build [ビルド出力ディレクトリ] --target install`でバイナリが`-DCMAKE_INSTALL_PREFIX`で指定したディレクトリにコピーされる

# VSCodeでの使用

vscode上で使用する場合、`launch.json`のエントリに以下のように指定する

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "${workspaceFolder}/path/to/debuggee.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "console": "internalConsole",
            "stopAtEntry": false,
            "pipeTransport": {
                "pipeCwd": "${workspaceFolder}",
                "pipeProgram": "cmd",
                "pipeArgs": ["/c"],
                "debuggerPath": "/path/to/netcoredbg"
            }
        }
    ]
}
```

前後に設定が色々書いてあるが、重要なのは`pipeTransport`の部分である。
ここに、pipeProgramとpipeArgsには、デバッガを起動するシェルを指定するものという理解でいいと思う。詳しくは、[vscode-cpptoolsのドキュメント](https://code.visualstudio.com/docs/cpp/pipe-transport)に書いてある。

後は通常通りvscodeのデバッグを実行すれば、netcoredbgを用いてデバッグが実行される。

## サポートされる機能

netcoredbgでは、ブレークポイントで停止したり、条件付きのブレークポイントの設置等、基本的な機能はサポートされているが、それでも全ての機能をサポートしているわけではない。何ができて何ができないか、という詳細は以下のページを参照。
https://github.com/Samsung/netcoredbg/wiki/Features
目立ったところでは、"全ての例外でブレークする"という機能がまだ動かない(ハンドルされてない例外でのブレークは可能)。

また、サポートされるのはあくまでも`netcoreapp*`のデバッグのみで、`net4*`なアプリに対してのデバッグはできないので注意が必要。正確には起動自体はできるものの、ブレークポイント等の機能が利用できない。

# ログ出力

さて、細かい挙動を確認したい場合や、トラブルシューティングしたい場合、ログ出力が必要となる。
標準入出力はDAPでやり取りするため使えないので、別途ログ出力機構を利用する必要がある。

## DAP部分のログ出力

netcoredbgでログ出力したい場合は、launch.jsonに`logging`オプションを追加する。具体的には以下のようになる。

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            ..
            "logging": {
              "engineLogging":true
            }
        }
    ]
}
```

上記設定を適用すると、vscodeのデバッグウィンドウにDAPのやり取りログが出力される

## netcoredbg内部のログ出力

"engineLogging"では、主にプロトコル部分のログ出力を取得可能だが、netcoredbg自体の内部的な動作をログ出力したい場合、netcoredbgのコマンド引数に`--log=file`を指定する。
具体的には以下のようになる。

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            ..
            "pipeTransport": {
                ..
                "debuggerPath": "/path/to/netcoredbg --log=file"
            }
        }
    ]
}
```

上記を指定すると、`[tmpディレクトリ]/netcoredbg_*`という名前でログファイルが出力される。なお、ファイル名に開始時点の時刻情報が入るため、複数回実行する場合はファイル数の増大に注意すること。また、ローリング(ファイルサイズに応じて新しくファイルを作成すること)機能はないため、ファイルサイズの増大に注意すること。

# 終わりに

まあ正直な話、vsdbg自体に機能的な不満はあまりないので、大抵の人は使う必要は無いと思う。だが、デバッガの具体的な挙動が知りたい場合、あるいはvsdbgでは動作しないような特殊環境でデバッグしたい場合、netcoredbgのようなツールが有用になるだろうと思う。

更に、デバッギングについてのあれやこれやを知りたい場合、[netcoredbgのリンク集が参考になるかもしれない](https://github.com/Samsung/netcoredbg/wiki/Links)