ネタがあまりに思いつかないので、もう小ネタで。
Legacy biosはオワコンだよね、ということでUEFIでOS開発や!ということで、とりあえず簡単にやってみることのできる分量のものを探してみると、Writing OS in Nimというものを見つけました。Shopifyの人が趣味で書いてるものみたいです。
Nimでできるなら当然D言語でもできます(!?)し、とっかかりにも丁度よい分量のようにみえるのでこれをちょっと移植していきます。
レポジトリはコ↑コ↓においてます。
開発環境
- LDC
- lld
- qemu
- ovmf(Open Virtual Machine Firmware)
NimはCへのトランスパイラとしての利用になってるので利用するCコンパイラとしてclangを指定していましたが、LDCでは不要です。
あと必須のものではないですが、参考元と同様にコマンドランナーとしてjustを使ってます。makeでも別にいいんですが、FAQの思想とかわかるなーみたいな感じになったので使ってみました。
UEFIターゲット
今回はldc2.confに全部寄せました。クロスコンパイルするあたりはswitchesらへんですね。
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)
を定義してるのが違いくらいでしょうか。
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
つくわ。
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!までできました。簡単ですね!