D言語+FreeRTOSなプロジェクトで快適デバッグ

# はじめに

前の記事> [STM32CubeMXで生成したFreeRTOSとD言語を連携する方法](https://qiita.com/mono_shoo/items/db71975ba0a1d7b85db2)

前の記事はとにかくプロジェクトを作ってビルドできるしLEDチカらせられるってところまで書いたが、続いてデバッグできるようにしていく。
主に以下について説明していく。

- Visual Studio Codeでのビルド環境を整える
- cortex-debug 使ってブレークポイント張りながらデバッグできるようにする
- Semihosting使ってprintfデバッグできるようにする


# 必要なもの
- [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code/) : IDEです
  - [code-d](https://marketplace.visualstudio.com/items?itemName=webfreak.code-d) : D言語のハイライトや補完ができます
  - [cortex-debug](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug) : ARMマイコンのデバッグを行うための拡張機能です
    - [STM32F401.svd](https://github.com/posborne/cmsis-svd/blob/master/data/STMicro/STM32F401.svd) : デバッグ時のレジスタ定義用のファイルです
- [OpenOCD](https://openocd.sourceforge.io/pages/getting-openocd.html) : NucleoボードにくっついているST-Link(デバッガ)をgdbで使えるようにします。
- [Zadig](https://zadig.akeo.ie/) : ST-LinkをOpenOCDで使うためのWindows用ドライバをインストールするためのツールです。

※前回の記事で紹介したビルドツール等はインストールされている前提です。
※インストール作業の解説は省略します。


# Visual Studio Codeでのビルド環境を整える
インストールだの起動方法だのはもちろん省略します。
ここでは、D言語でARMマイコン開発する際のtasks.json設定やlaunch.json設定について説明していきます。

まずはtasks.jsonです。 ldcは`C:/ldc`に、GNU Arm Embedded Toolchainは `C:/gcc-arm-none-eabi`に、clang(LLVM)は`C:/llvm`に、msys2は`C:/msys64`にそれぞれインストールされていて、msys2ではmingw32-makeが使えるようなインストールはあらかじめ済ませている前提でお話しします。

以下のようなtasks.jsonを作成することで、`build-os`でC言語側の`*.a`ファイル、`build-debug`でD言語側をビルドし、そのままdubのpostGenerateCommandsで`*.elf`を作成します。なお、llvmはなくても、すべてGNU Arm Embedded Toolchainで代替可能です。頭いい最適化かけてくれそうな気がするのでなんとなくllvm使ってますが、必須じゃありません。

```json:tasks.json
{
	// See https://go.microsoft.com/fwlink/?LinkId=733558
	// for the documentation about the tasks.json format
	"version": "2.0.0",
	"tasks": [
		{
			"label": "build-debug",
			"type": "dub",
			"archType": "arm-none-eabi",
			"buildType": "debug",
			"compiler": "C:/ldc/bin/ldc2.exe",
			"cwd": "${workspaceFolder}"
		},
		{
			"label": "build-os",
			"type": "process",
			"command": "C:/msys64/mingw64/bin/mingw32-make.exe",
			"args": [
				"lib",
				"-j",
				"8",
				"CC=C:/llvm/bin/clang.exe --sysroot=C:/gcc-arm-none-eabi/arm-none-eabi --target=arm-none-eabi -fshort-enums -nodefaultlibs ",
				"AS=C:/llvm/bin/clang.exe --sysroot=C:/gcc-arm-none-eabi/arm-none-eabi --target=arm-none-eabi -fshort-enums -nodefaultlibs -x assembler-with-cpp",
				"LD=C:/gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe",
				"CP=C:/llvm/bin/llvm-objcopy.exe",
				"SZ=C:/llvm/bin/llvm-size.exe",
				"AR=C:/llvm/bin/llvm-ar.exe",
				"ASMOPTS=",
				"MKDIR=cmd /C mkdir"
			],
			"problemMatcher": "$gcc"
		}
	]
}
```

code-dのおかげで、tasksで `"type": "dub"` が使えます。
`"archType": "arm-none-eabi"` をすると`dub build --arch=arm-none-eabi`になります。
`"buildType": "debug"`としているのは、今回はデバッグすることも目標にしているからで、リリース用のビルド設定ではここが`"buildType": "release"`になります。

もちろん、 `"type": "dub"` を使わずに、 `"type": "process"` にしてcommandやargsの設定でdubを起動するようにしてもいいです。その場合は `"problemMatcher": "$dmd"` とするといいでしょう。

build-osのほうはふつうのmakeです。設定内容は[前回記事参照](https://qiita.com/mono_shoo/items/db71975ba0a1d7b85db2#%E3%83%93%E3%83%AB%E3%83%89)。


# cortex-debug 使ってブレークポイント張りながらデバッグできるようにする

デバッグするには、launch.jsonが必要です。
ここではD言語はあまり関係がなくて、単純にelfファイルの中にデバッグ情報が含まれていればあとはcortex-debugの設定に従って項目を埋めていくだけでデバッグできるようになります。せいぜい、ちゃんと`dub build -b=debug`でビルドされていて、あとは一応[dub設定でdebugInfoCを選んだ](https://qiita.com/mono_shoo/items/db71975ba0a1d7b85db2#d%E8%A8%80%E8%AA%9E%E5%81%B4%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E8%A8%AD%E5%AE%9A)くらいでしょうか。debugInfoCじゃなくてもうまくいくかも。

以下の例はOpenOCDを`C:/openocd`にインストールしている前提で書かれています。

```json:launch.json
{
	"version": "0.2.0",
	"configurations": [
		{
			"name": "Launch",
			"type": "cortex-debug",
			"request": "launch",
			"servertype": "openocd",
			"cwd": "${workspaceRoot}",
			"armToolchainPath": "C:/gcc-arm-none-eabi/bin",
			"preLaunchTask": "build-debug",
			"executable": "${workspaceFolder}/application.elf",
			"device": "STM32F401RE",
			"svdFile": "${workspaceRoot}/STM32F401.svd",
			"breakAfterReset": true,
			"runToEntryPoint": "D_main",
			"serverpath": "C:/openocd/bin/openocd.exe",
			"configFiles": [
				"interface/stlink.cfg",
				"target/stm32f4x.cfg"
				//"board/st_nucleo_f4.cfg"
			],
			"postLaunchCommands": ["monitor arm semihosting enable"],
		}
	]
}

```

要点をざっくり解説します。

- `"request": "launch"` : すでにOpenOCDが起動しているなら`attach`としますが、`launch`とすることで、勝手にOpenOCDを起動してくれます。
- `"armToolchainPath": "C:/gcc-arm-none-eabi/bin"` : 要するにgdbとobjcopyがどこにあるのかを教えています。パスが通っていれば不要だと思います。
- `"preLaunchTask": "build-debug"` : tasks.jsonでbuild-debugを実行すると`*.elf`が作成されるので、これを事前実行するように指示しています。毎回ビルドするのが時間かかっていやだなっていう場合はここを指定しないという手もあります。
- `"executable": "${workspaceFolder}/application.elf"` : build-debugを実行するとこのファイルが生成されます。このファイルを指定することで、デバッグ開始時に自動的にダウンロード[^1]してくれます。
- `"device": "STM32F401RE"` : Nucleo-F401REの使っているマイコンを指定しています。ボードによって指定を変える必要があります。
- `"svdFile": "${workspaceRoot}/STM32F401.svd"` : レジスタの定義ファイルを指定します。この内容は有志が作成していて [ココ](https://github.com/posborne/cmsis-svd) から自分のマイコンにあったものを選んでダウンロードしておきます。なければあきらめます。なくてもブレークした時にレジスタの状態がわからない程度なので、printfデバッグなどで代替できます。
- `"serverpath": "C:/openocd/bin/openocd.exe"` : OpenOCDをどこにインストールしたか教えてあげます。パスが通っていれば不要だと思います。
- `"configFiles": [ ... ]` : OpenOCDに対して、デバッガとマイコンについてどんな設定をすればいいかをまとめたファイルを指定してあげます。この場合、`C:/openocd/scripts/target/stm32f4x.cfg`にSTM32F401RE(STM32F4x系マイコン)の設定を、`C:/openocd/scripts/interface/stlink.cfg` にST-Linkのデバッガ設定を記載したファイルがある(OpenOCDインストールだけであらかじめ用意されている)ので、これを指定しています。ちなみに、今回はマイコンとST-Linkが一体化しているNucleoボードを使用しており、 `C:/openocd/scripts/board/st_nucleo_f4.cfg` などというファイルも実は用意されているので、これでもいいです。
- `"runToEntryPoint": "D_main"` : 実行時、D言語側の`D_main`関数にブレークを張ってスタートします。もちろんC言語側の`main`とかにしてもいいですし、スタートアップから見たい場合はアセンブラの`Reset_Handler`とかにしてもいいでしょう。
- `"postLaunchCommands": ["monitor arm semihosting enable"]` 後述のSemihostingを有効にする場合にこれを記載します。

[^1]: ここでいうダウンロードは、マイコンにプログラムを書き込むこと

# Semihosting使ってprintfデバッグできるようにする

デバッグを行うのに、コードの途中にprintfで標準出力に現在の状態を書き出してみる、というprintfデバッグは昔も今も強力なデバッグ方法の一つです。
Nucleo-F401REボード(ARMマイコンやST-Linkの機能を使う)では、これを実現するのにいくつかの方法があります。

1. Semihostingを使う
2. UART(シリアル通信)を使う
3. SWOを使う

今回は、[D言語Wikiに載っている](https://wiki.dlang.org/Minimal_semihosted_ARM_Cortex-M_%22Hello_World%22)、Semihostingの方法を使用しました。

なお、このやり方は過去に[kubo39さんがやっていて、ライブラリ化している](https://github.com/kubo39/semihosting)ので、それを使うのも手かもしれません。 …が、せいぜい数十行程度のコード追加で済むので、私は適当にD_main関数のうしろあたりに追加しました。

```d
extern (C) void SendSemihostCommand(int command, void* message) @trusted
{
	version(LDC)
	{
		import ldc.llvmasm;
		__asm("mov r0, $0; mov r1, $1; bkpt #0xAB", "r,r,~{r0},~{r1}", command, message);
	}
	else static assert(0);
}

extern (C) int _write_r(void* ptr, int fd, const char* buf, uint cnt)
{
	uint[3] message = [
		1,
		cast(uint)buf,
		cnt
	];
	SendSemihostCommand(0x05, &message[0]);
	return cnt;
}
```

`_write_r` というのが、printfやputsの出力先を決定づけるための関数で、これを定義することでprintfやputsといった標準出力への出力関数が使用できるようになります。`_write`という関数もありますが、どうやら今回作成したアーキテクチャでは_r版(リエントラント版)が呼ばれるようでした。ホントはリエントラント可能[^2]なように、割禁なりクリティカルセクションなり使用してスレッドセーフで作るのが安全ですが、デバッグ用だしまあいいかなと。

[^2]: 関数の中で処理をしている最中にほかのどこかから(別スレッドや割り込み処理などによって)同じ関数を呼んでも正しく動作する性質

なお、cortex-debugを使ってデバッグをする場合、出力タブの"Adapter Output"というログのところにprintfした内容が出力されます。

# まとめ
VSCodeでは、必要な拡張機能(code-d, cortex-debug)をインストールし、tasks.json, launch.jsonを記載することでビルドしてダウンロードしてブレークポイントを張りながらデバッグすることができるようになります。
さらに、Semihostingを使用することで、printfの出力先を設定して、printfデバッグすることもできるようになります。

ビルドしてダウンロードしてデバッグしてprintfする動画を撮影しましたので、ご覧ください。

![参考動画](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11147/eeefb0cc-ad13-676d-2456-6ad949b84340.gif)


なお、今回の記事で使用したソースコードは以下のリポジトリ(タグ=v0.2.0)です。
https://github.com/shoo/Nucleo-F401RE-FreeRTOS-Demo/tree/v0.2.0