第 3 章前半。QEMU モニタを使ってみる。run_image.sh
内で qemu-system-x86_64
のオプションとして -monitor stdio
を渡していることで QEMU モニタとして標準入出力を使う形になるっぽい。
さて、起動して書籍に倣って RIP
レジスタ周辺のアセンブラのインストラクションを 2 つ表示する。詳細は QEMU Monitor とかを見たら良いのかもしれない。
(qemu) info registers ... RIP=000000003e665416 RFL=... ... (qemu) x /4xb 03e665416 000000003e665416: 0xeb 0xfe 0x55 0x41 (qemu) x /2i 03e665416 000000003e665416: jmp 0x3e665416 000000003e665418: push %rbp
となっていて、値は異なるが 同じアドレスにジャンプするということで while (1);
が実行されているらしいことが分かった。Busy loop のせいかは分からないけど、QEMU のプロセスが CPU の 80% を持っていっている。
カーネルのビルド。面倒くさいので Makefile
にやらせる。
CXX = clang++ LD = ld.lld TARGET = kernel.elf SRCS = main.cpp OBJS = $(SRCS:%.cpp=%.o) all: $(TARGET) $(TARGET): $(OBJS) $(LD) --entry KernelMain -z norelro --image-base 0x100000 --static -o $@ $(OBJS) .cpp.o: $(CXX) -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c $< .PHONY: clean clean: @rm -rf $(TARGET) $(OBJS)
相変わらずブートローダのところは写経しようもなくて MikanLoaderPkg
をコピー。
ページ数 = (kernel_file_size + 0xfff) / 0x1000
は 0xfff = 0x1000 - 1
に注意すると (x + n - 1) / n
の切り上げの計算とかでよく使うやつであることが分かる。
個人的には関数ポインタの typedef
は
typedef void EntryPointType(void);
ではなく、
typedef void (*EntryPointType)(void);
としてより気持ち悪さを表に出してしまいたい・・・。この場合にはキャストするところも (EntryPointType*)
ではなく (EntryPointType)
にしないとビルドエラーになる。まぁこの辺は趣味の問題だろうか。
source edksetup.sh
build
でブートローダをビルド。QEMU モニタから確認するとアドレス類は書籍と同じで以下のような感じだった。
(qemu) info registers ... RIP=0000000000101011 RFL=... ... (qemu) x /2i 0x101011 0x0000000000101011: jmp 0x101010 0x0000000000101013: int3 (qemu) x /2i 0x101010 0x0000000000101010: hlt 0x0000000000101011: jmp 0x101010
これで第 3 章前半(〜p.81)は終わり。
第 3 章真ん中はブートローダだけビルドして差し替えたら終わりなので、前半のカーネルと組み合わせて起動して画面が真っ白になることを見たら終わり。これで第 3 章真ん中(〜p.83)は終わり。