D言語でもuefi bootしたい！

ネタがあまりに思いつかないので、もう小ネタで。

Legacy biosはオワコンだよね、ということでUEFIでOS開発や！ということで、とりあえず簡単にやってみることのできる分量のものを探してみると、[Writing OS in Nim](https://github.com/khaledh/khaledh.github.io/tree/main/docs/osdev)というものを見つけました。Shopifyの人が趣味で書いてるものみたいです。

Nimでできるなら当然D言語でもできます(!?)し、とっかかりにも丁度よい分量のようにみえるのでこれをちょっと移植していきます。

レポジトリは[コ↑コ↓](https://github.com/kubo39/fusion/tree/fc860a2335963d8d13bcb1f5f900cd1ae57bc1e9)においてます。

## 開発環境

- LDC
- lld
- qemu
- ovmf(Open Virtual Machine Firmware)

NimはCへのトランスパイラとしての利用になってるので利用するCコンパイラとしてclangを指定していましたが、LDCでは不要です。

あと必須のものではないですが、参考元と同様にコマンドランナーとしてjustを使ってます。makeでも別にいいんですが、[FAQの思想](https://just.systems/man/en/chapter_75.html)とかわかるなーみたいな感じになったので使ってみました。

## UEFIターゲット

今回はldc2.confに全部寄せました。クロスコンパイルするあたりはswitchesらへんですね。

```conf
default:
{
    switches = [
        "-mtriple=x86_64-unknown-windows",
        "--linker=lld-link",
        "-L/nodefaultlib",
        "-L/subsystem:EFI_APPLICATION",
        "-L/entry:efi_main",
        "--betterC",
        "--boundscheck=off",
        "--defaultlib=",
        "--debuglib=",
        "--platformlib=",
        "-mattr=-mmx,-sse,+soft-float",
        "-disable-red-zone",
        "-relocation-model=static",
        "-code-model=large"
    ];
    post-switches = [
        "-I%%ldcbinarypath%%/../import",
    ];
    lib-dirs=[];
}
```

本家だとOSのメモリアロケーションやlibcの関数への参照まわりでもろもろの言及がありますが、こちらでは `-betterC` とか `--defaultlib=` とか使ってランタイムへの依存を排除しているのでここは特に問題になりません。

## UEFI bootloader

UEFI関連の定義は以下のようになっています。ここはあまりみるべきところはないですね。トップレベルで `extern (C)` を定義してるのが違いくらいでしょうか。

```d
module uefi;

extern (C):

alias EfiStatus = uint;
alias EfiHandle = void*;

struct EfiTableHeader
{
	ulong signature;
	uint revision;
	uint headerSize;
	uint crc32;
	uint reserved;
}

struct EfiSystemTable
{
	EfiTableHeader header;
	wchar* firmwareVendor;
	uint firmwareRevision;
	EfiHandle consoleInHandle;
	void* conIn;
	EfiHandle consoleOutHandle;
	SimpleTextOutput* conOut;
	EfiHandle standardErrorHandle;
	void* stdErr;
	void* runtimeServices;
	void* bootServices;
	uint numTableEntries;
	void* configTable;
}

struct SimpleTextOutput
{
	void* reset;
	EfiStatus function(SimpleTextOutput*, wchar*) outputString;
	void* testString;
	void* queryMode;
	EfiStatus function(SimpleTextOutput*, uint) setMode;
	void* setAttribute;
	EfiStatus function(SimpleTextOutput*) clearScreen;
	void* setCursorPos;
	void* enableCursor;
	void** mode;
}

enum : EfiStatus
{
	EfiSuccess = 0,
	EfiLoadError = 1
}
```

本体のコードはこんな感じです。

インラインアセンブリが書けるとか `naked function` が使えるとかはシステムプログラミング言語として必須要件ですね。

outputStringはnull-terminatedなUTF-16文字列を要求しますが、D言語はビルトインでUTF-16な文字列型のwstringを持っているので楽です。~~最後に `\0` を入れるように注意して `wchar*` へキャストして使いましょう。~~ 忘れてたけど文字列リテラルは `\0` つくわ。

```d
import ldc.attributes : naked;
import ldc.llvmasm;

import uefi;

extern (C):

@naked void exit(int status)
{
	__asm(`
	.loop:
		cli
		hlt
		jmp .loop
	`, "");
}

void d_main() {}

EfiStatus efi_main(EfiHandle imgHandle, EfiSystemTable* sysTable)
{
	d_main();
	wchar* msg = cast(wchar*) "Hello, World"w.ptr;
	sysTable.conOut.clearScreen(sysTable.conOut);
	sysTable.conOut.outputString(sysTable.conOut, msg);
	exit(0);
	return EfiLoadError;
}
```

これでHello,World!までできました。簡単ですね！

![uefi-bootloader-ldc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1493/06e48897-1644-744f-ace4-ea03753ab726.png)