1日30分
よく使うコマンド
※「runqm」は「$HOME/osbook/devenv/run_qemu.sh」をエイリアス登録したもの。いちいち書くのがめんどくさいので。
1.ブランチのチェックアウト
# 本来はmikanosだが、自作用ディレクトリ「jisakuos」を指定している
cd $HOME/workspace/jisakuos
git checkout osbook_dayxx
2.ブートローダをビルドしてqemuで実行
cd $HOME/edk2
build
# ブートローダのみ実行
runqm Build/MikanLoaderx64/DEBUG_CLANG38/X64/Loader.efi
# ブートローダとカーネルを実行。本来はmikanosだが、自作用ディレクトリ「jisakuos」を指定している
runqm Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $HOME/workspace/jisakuos/kernel/kernel.elf
3.Make(コンパイル&リンク)用の環境変数(CPPFLAGSなど)を設定する
source $HOME/osbook/devenv/buildenv.sh
4.「build」コマンドのパスを通すコマンド
source edksetup.sh
5.コンパイル時にヘッダーファイルを含められるよう「bits/libc-header-start.h」などのパスを通すコマンド
export C_INCLUDE_PATH=/usr/include/aarch64-linux-gnu
export CPLUS_INCLUDE_PATH=/usr/include/aarch64-linux-gnu
1日目~4日目
開発環境準備などを実施したりしていた。
Ubuntuインストール
- Mac OS(Apple M1)
- UTM4.6.4
- Ubuntu22.04
- ※最新は25.04だがMikanOSのGithubにおいて22.04をサポートしているという一文を見かけたのであえて古いの入れている
ハマったところ
Mac-UTM間でクリップボード共有ができない。UTMのGithub Issueにも同じような質問が上がってた。スレッドにあった「spice-vdagent はディスプレイサーバーのクリップボード(X11 または Wayland)の実行を必要とするはずです。」という一文がヒントになり、Ubuntu-desktopをインストールしたら解決。ずっとCLIでやろうとしていたのが良くなかった。
5〜6日目
1章実施した。
特筆すべき点はなし。バイナリエディタってこんな風なんだな〜くらい。
7日目
2章「EDK2入門とメモリマップ」に突入。
UEFIブートローダをビルドしようとしたら必要なライブラリがないとのこと。
mikan@mikan-server:~/edk2$ build
Build environment: Linux-5.15.0-143-generic-aarch64-with-glibc2.35
Build start time: 09:35:08, Jul.20 2025
WORKSPACE = /home/mikan/edk2
EDK_TOOLS_PATH = /home/mikan/edk2/BaseTools
CONF_PATH = /home/mikan/edk2/Conf
PYTHON_COMMAND = /usr/bin/python3
Architecture(s) = X64
Build target = DEBUG
Toolchain = CLANG38
Active Platform = /home/mikan/edk2/MikanLoaderPkg/MikanLoaderPkg.dsc
Processing meta-data .
build.py...
/home/mikan/edk2/MikanLoaderPkg/MikanLoaderPkg.dsc(...): error 4000: Instance of library class [RegisterFilterLib] is not found
in [/home/mikan/edk2/MdePkg/Library/BaseLib/BaseLib.inf] [X64]
consumed by module [/home/mikan/edk2/MikanLoaderPkg/Loader.inf]
- Failed -
Build end time: 09:35:08, Jul.20 2025
Build total time: 00:00:00
ゼロからのOS入門サポートサイトに対処方法が載っていた。MikanLoaderPkg.dscRegisterFilterLibを追加して解決。
8日目
引き続き2章でビルドできない問題に対処。
長文になるのでエラー部分のみ抜粋。
Geminiに聞いてみたところ、AARCH64システム上でX64向けのクロスコンパイルを行おうとしていルガ、clangが呼び出しているリンカー(/usr/bin/ld)がAARCH64ネイティブのリンカーであり、X64のバイナリを生成するための「エミュレーションモード」(リンク形式)を認識できないために発生しているとのこと。
mikan@mikan-server:~/edk2$ build
/usr/bin/ld: unrecognised emulation mode: elf_x86_64
Supported emulations: aarch64linux aarch64elf aarch64elf32 aarch64elf32b aarch64elfb armelf armelfb aarch64linuxb aarch64linux32 aarch64linux32b armelfb_linux_eabi armelf_linux_eabi
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [GNUmakefile:329: /home/mikan/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/MikanLoaderPkg/Loader/DEBUG/Loader.dll] Error 1
この記事に同様のエラーメッセージと解決方法が書かれていた。Conf/tools_def.txtのDEBUG_CLANG38_X64_DLINK_FLAGSに-fuse-ld=lldを足して解決できた。
後続のエラーについても上記記事に書かれている手順で解決できた。神。
9日目
2章後半のメモリマップ部分に突入。
メモリマップ取得・保存のためC言語プログラムを写経する。
ポインタが出てきて、初めてまともに理解した。たとえば下記のようなコードがあるとする。
CHAR8* header ="Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\n";
変数「header」には、CHAR8型の「I」(※文章の最初の一文字)が格納されている0x7FFC0A3B5670のようなメモリアドレスが格納される。(※変数「header」自体には何文字でも入る。)ポインタを使用すると、データに高速なアクセスができたり、大容量のデータをいちいちメモリにコピーすることがないためメモリ節約が可能。C言語が速い速いと言われるのはこれが理由か。(JavaやPythonでもポインタのような概念はあるが開発者が自由にいじれないよう隠蔽されているらしい。)
10日目
引き続き写経する。
VOID**というポインタが出てきた。は型を指定せずにメモリアドレスを格納できる万能ポインタVOID*のメモリアドレスを格納する役割を持つらしい。
これができてなにが嬉しいのかはよくわからなかった。そのうち理解できればいいか。今は先に進もう。
11日目
引き続き写経。写経完了。
メモリマップを取得し、書き込んで保存するプログラムが完成した。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/DiskIo2.h>
#include <Protocol/BlockIo.h>
// #@@range_begin(struct_memory_map)
struct MemoryMap {
UINTN buffer_size;
VOID* buffer;
UINTN map_size;
UINTN map_key;
UINTN descriptor_size;
UINT32 descriptor_version;
};
// #@@range_end(struct_memory_map)
// #@@range_begin(get_memory_map)
EFI_STATUS GetMemoryMap(struct MemoryMap* map) {
if (map->buffer == NULL) {
return EFI_BUFFER_TOO_SMALL;
}
map->map_size = map->buffer_size;
return gBS->GetMemoryMap(
&map->map_size,
(EFI_MEMORY_DESCRIPTOR*)map->buffer,
&map->map_key,
&map->descriptor_size,
&map->descriptor_version);
}
// #@@range_end(get_memory_map)
// #@@range_begin(get_memory_type)
const CHAR16* GetMemoryTypeUnicode(EFI_MEMORY_TYPE type) {
switch (type) {
case EfiReservedMemoryType: return L"EfiReservedMemoryType";
case EfiLoaderCode: return L"EfiLoaderCode";
case EfiLoaderData: return L"EfiLoaderData";
case EfiBootServicesCode: return L"EfiBootServicesCode";
case EfiBootServicesData: return L"EfiBootServicesData";
case EfiRuntimeServicesCode: return L"EfiRuntimeServicesCode";
case EfiRuntimeServicesData: return L"EfiRuntimeServicesData";
case EfiConventionalMemory: return L"EfiConventionalMemory";
case EfiUnusableMemory: return L"EfiUnusableMemory";
case EfiACPIReclaimMemory: return L"EfiACPIReclaimMemory";
case EfiACPIMemoryNVS: return L"EfiACPIMemoryNVS";
case EfiMemoryMappedIO: return L"EfiMemoryMappedIO";
case EfiMemoryMappedIOPortSpace: return L"EfiMemoryMappedIOPortSpace";
case EfiPalCode: return L"EfiPalCode";
case EfiPersistentMemory: return L"EfiPersistentMemory";
case EfiMaxMemoryType: return L"EfiMaxMemoryType";
default: return L"InvalidMemoryType";
}
}
// #@@range_end(get_memory_type)
// #@@range_begin(save_memory_map)
EFI_STATUS SaveMemoryMap(struct MemoryMap* map, EFI_FILE_PROTOCOL* file) {
CHAR8 buf[256];
UINTN len;
CHAR8* header =
"Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\n";
len = AsciiStrLen(header);
file->Write(file, &len, header);
Print(L"map->buffer = %08lx, map->map_size = %08lx\n",
map->buffer, map->map_size);
EFI_PHYSICAL_ADDRESS iter;
int i;
for (iter = (EFI_PHYSICAL_ADDRESS)map->buffer, i = 0;
iter < (EFI_PHYSICAL_ADDRESS)map->buffer + map->map_size;
iter += map->descriptor_size, i++) {
EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)iter;
len = AsciiSPrint(
buf, sizeof(buf),
"%u, %x, %-ls, %08lx, %lx, %lx\n",
i, desc->Type, GetMemoryTypeUnicode(desc->Type),
desc->PhysicalStart, desc->NumberOfPages,
desc->Attribute & 0xffffflu);
file->Write(file, &len, buf);
}
return EFI_SUCCESS;
}
// #@@range_end(save_memory_map)
EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root) {
EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;
gBS->OpenProtocol(
image_handle,
&gEfiLoadedImageProtocolGuid,
(VOID**)&loaded_image,
image_handle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
gBS->OpenProtocol(
loaded_image->DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid,
(VOID**)&fs,
image_handle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
fs->OpenVolume(fs, root);
return EFI_SUCCESS;
}
EFI_STATUS EFIAPI UefiMain(
EFI_HANDLE image_handle,
EFI_SYSTEM_TABLE* system_table) {
Print(L"Hello, Mikan World!\n");
// #@@range_begin(main)
CHAR8 memmap_buf[4096 * 4];
struct MemoryMap memmap = {sizeof(memmap_buf), memmap_buf, 0, 0, 0, 0};
GetMemoryMap(&memmap);
EFI_FILE_PROTOCOL* root_dir;
OpenRootDir(image_handle, &root_dir);
EFI_FILE_PROTOCOL* memmap_file;
root_dir->Open(
root_dir, &memmap_file, L"\\memmap",
EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
SaveMemoryMap(&memmap, memmap_file);
memmap_file->Close(memmap_file);
// #@@range_end(main)
Print(L"All done\n");
while (1);
return EFI_SUCCESS;
}
上記プログラムをUEFIブートローダとしてqemuで実行し、作成されたメモリマップが下記になる。
‥‥結局なにやってるかよくわからなかった。
Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute
0, 3, , 00000000, 1, F
1, 7, , 00001000, 9F, F
2, 7, , 00100000, 700, F
3, A, , 00800000, 8, F
4, 7, , 00808000, 8, F
5, A, , 00810000, F0, F
6, 4, , 00900000, B00, F
7, 7, , 01400000, 3AB36, F
8, 4, , 3BF36000, 20, F
9, 7, , 3BF56000, 2726, F
10, 1, , 3E67C000, 2, F
11, 4, , 3E67E000, A, F
12, 9, , 3E688000, 1, F
13, 4, , 3E689000, 1F7, F
14, 3, , 3E880000, B4, F
15, A, , 3E934000, 12, F
16, 0, , 3E946000, 1C, F
17, 3, , 3E962000, 10A, F
18, 6, , 3EA6C000, 5, F
19, 5, , 3EA71000, 5, F
20, 6, , 3EA76000, 5, F
21, 5, , 3EA7B000, 5, F
22, 6, , 3EA80000, 5, F
23, 5, , 3EA85000, 7, F
24, 6, , 3EA8C000, 8F, F
25, 4, , 3EB1B000, 6F7, F
26, 7, , 3F212000, 4, F
27, 4, , 3F216000, 6, F
28, 7, , 3F21C000, 1, F
29, 4, , 3F21D000, 7FE, F
30, 7, , 3FA1B000, 1, F
31, 3, , 3FA1C000, 17F, F
32, 5, , 3FB9B000, 30, F
33, 6, , 3FBCB000, 24, F
34, 0, , 3FBEF000, 4, F
35, 9, , 3FBF3000, 8, F
36, A, , 3FBFB000, 4, F
37, 4, , 3FBFF000, 201, F
38, 7, , 3FE00000, 8D, F
39, 4, , 3FE8D000, 20, F
40, 3, , 3FEAD000, 20, F
41, 4, , 3FECD000, 9, F
42, 3, , 3FED6000, 1E, F
43, 6, , 3FEF4000, 84, F
44, A, , 3FF78000, 88, F
45, 6, , FFC00000, 400, 1
12日目
- ポインタについての補足説明を読む。
- ポインタのポインタという概念がどんな時に嬉しいのか結局よくわからなかった。
- 無理やり3章に突入。QUMEモニタのデバッグ方法を学ぶ。よくわからない言葉がいろいろ出てきたが先に進む
- CPUの記憶領域であるレジスタには2種類あるらしい。
- 汎用レジスタ‥計算に使用する。メインメモリより容量小さくて読み書きが速い
- 特殊レジスタ‥メモリアドレスを保持したりフラグが入っていたりCPUの重要な設定が入っていたりする
- 書いてあることがマジで1ミリも理解できないので、とにかく手を動かしていく。次はカーネルの作成
13日目
- カーネルのコンパイルとリンクを行った。
- コンパイル‥人間用のソースコードを機械が読めるように「オブジェクトファイル」(1と0の組み合わせのファイル)に変換する。1つのソースコードは1つのオブジェクトファイルになる。
- リンク‥オブジェクトファイルを結合して一つの実行ファイルにする
今回で言うと下記のようなイメージ。
[ソースコード]main.cpp →(コンパイル)→ [オブジェクトファイル]main.o →(リンク) → [実行ファイル]kernel.elf
次に、ブートローダからカーネルを呼び出すような動作を行う。
また写経する。
写経完了。とりあえずブートローダからkernel呼び出すところまでは実行できた。
14日目
- 「3.3 初めてのカーネル」が完了。コードの解説を読み終わった。カーネルファイルを読み込む→ブートローダは邪魔なのでカーネル起動前に停止する→カーネルのエントリポイント(あるプログラムの入り口。今回で言うとKernelMain()が該当)を呼び出す‥という流れ。
- どんなことをやるにしても、そのプログラムファイルを実行するためのメモリを確保する必要があるんだなと思った。大変そう。
- git checkoutで、あるコミット時点のものとローカルファイルを一緒にできる。git stashで、ローカルに加えた変更を破棄できる。git checkoutでローカルに落としてきたファイルを変更している場合、そのままgit checkoutで別コミットに移ることはできないのでgit stashを実行してあげる必要がある。
カーネルファイルを読み込む部分。gBS->AllocatePagesの部分でメモリを確保
// #@@range_begin(read_kernel)
EFI_FILE_PROTOCOL* kernel_file;
root_dir->Open(
root_dir, &kernel_file, L"\\kernel.elf",EFI_FILE_MODE_READ, 0);
UINTN file_info_size = sizeof(EFI_FILE_INFO) + sizeof(CHAR16) * 12;
UINT8 file_info_buffer[file_info_size];
kernel_file->GetInfo(
kernel_file, &gEfiFileInfoGuid,&file_info_size, file_info_buffer);
EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
UINTN kernel_file_size = file_info->FileSize;
EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
gBS->AllocatePages(
AllocateAddress, EfiLoaderData,
(kernel_file_size + 0xfff) / 0x1000,&kernel_base_addr);
kernel_file->Read(kernel_file, &kernel_file_size, (VOID*)kernel_base_addr);
Print(L"Kernel: 0x%0lx (%lu bytes)\n",kernel_base_addr, kernel_file_size);
// #@@range_end(read_kernel)
15日目
- 画面を白く塗りつぶしてみる。まずはブートローダで実行。
- Main.cのこの部分。255が白を指すらしい。
/ #@@range_begin(gop)
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
OpenGOP(image_handle, &gop);
Print(L"Resolution: %ux%u, Pixel Format: %s, %u pixels/line\n",
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
GetPixelFormatUnicode(gop->Mode->Info->PixelFormat),
gop->Mode->Info->PixelsPerScanLine);
UINT8* frame_buffer = (UINT8*)gop->Mode->FrameBufferBase;
for (UINTN i = 0; i < gop->Mode->FrameBufferSize; ++i){
frame_buffer[i] = 255;
}
// #@@range_end(gop)
-
この章から急に、efiファイルのビルドコマンド〜qemu実行コマンドが記載されなくなっていたので「よく使うコマンド」の章を設置。メモしておく。
-
C言語において、Include <>で指定したライブラリはコンパイル時に読み込めるようにする必要があるらしい。今回は筆者が用意してくれたスクリプトを下記のように実行して環境変数にライブラリのパスを読み込ませ、コンパイル・リンク時に上記環境変数を指定するようにしている。
mikan@mikan-server:~/workspace/mikanos/kernel$ cat $HOME/osbook/devenv/buildenv.sh
# Usage: source buildenv.sh
BASEDIR="$HOME/osbook/devenv/x86_64-elf"
EDK2DIR="$HOME/edk2"
if [ ! -d $BASEDIR ]
then
echo "$BASEDIR \u304c\u5b58\u5728\u3057\u307e\u305b\u3093\u3002"
echo "\u4ee5\u4e0b\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3001$(dirname $BASEDIR)\u306b\u5c55\u958b\u3057\u3066\u304f\u3060\u3055\u3044\u3002"
echo "https://github.com/uchan-nos/mikanos-build/releases/download/v2.0/x86_64-elf.tar.gz "
else
export CPPFLAGS="\
-I$BASEDIR/include/c++/v1 -I$BASEDIR/include -I$BASEDIR/include/freetype2 \
-I$EDK2DIR/MdePkg/Include -I$EDK2DIR/MdePkg/Include/X64 \
-nostdlibinc -D__ELF__ -D_LDBL_EQ_DBL -D_GNU_SOURCE -D_POSIX_TIMERS \
-DEFIAPI='__attribute__((ms_abi))'"
export LDFLAGS="-L$BASEDIR/lib"
fi
mikan@mikan-server:~/workspace/mikanos/kernel$ source $HOME/osbook/devenv/buildenv.sh
mikan@mikan-server:~/workspace/mikanos/kernel$ clang++ $CPPFLAGS -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
mikan@mikan-server:~/workspace/mikanos/kernel$ ld.lld $LDFLAGS --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
16日目
急にbuildコマンドが使用できなくなって焦った。
mikan@mikan-server:~/edk2$ build
Command 'build' not found, did you mean:
command 'pbuild' from deb pbuilder-scripts (22)
command 'guild' from deb guile-2.2-dev (2.2.7+1-6build2)
command 'guild' from deb guile-3.0-dev (3.0.7-1)
command 'sbuild' from deb sbuild (0.81.2ubuntu6.1)
command 'xbuild' from deb mono-xbuild (6.8.0.105+dfsg-3.2)
command 'buildd' from deb buildd (0.81.2ubuntu6.1)
command 'obuild' from deb ocaml-obuild (0.1.10-3build1)
どうやらUTM上のUbuntu再起動?などによりPATHが通らなくなってしまったらしい。
下記コマンドを実行後、buildコマンドが正常に実行できるようになった。
eishu@mikan-server:~/edk2$ source edksetup.sh
Loading previous configuration from /home/mikan/edk2/Conf/BuildEnv.sh
Using EDK2 in-source Basetools
WORKSPACE: /home/mikan/edk2
EDK_TOOLS_PATH: /home/mikan/edk2/BaseTools
CONF_PATH: /home/mikan/edk2/Conf
あとはUbuntuの画面がスタックしたのでUTM上から停止→起動しようとしたら、Ubuntu22.04が「開始中」のままとなってしまい、どうしようもなくなって発狂していた。option + command + esc同時押しでUTMそのものを強制終了後、再び起動したら無事に復旧できた。

上記の対応をしていたので今日は進捗なし。「3.5 カーネルからピクセルを描く」を完了したくらい。明日は「3.6 エラー処理をしよう」をやっていく。
17日目
- 3章は消化不良で終わった感がある
- 「3.6 エラー処理をしよう」のところは写経しようかと思ったが流石にやっていることがわかったので読むだけにとどめた。
- 「ポインタのキャスト」は脳が理解することを諦めた。
- 「ポインタとアセンブリ言語」についてはアドレスを入れたり出したりしているんだね‥。ということはわかったがそれ以上は脳が理解することを諦めた。
- このままだと挫折しそうなので先に進む。とりあえず重要なのは最後まで行くことだと思うので。そもそもこの分厚い本をやってるだけ偉いやろの精神。最悪2週目やればいいか(フロム脳)
18日目
- 無理やり「4章 ピクセル描画とmake入門」に入った
- Makefileを使用すると、今まで手打ちで長いコマンドを打ってコンパイル・リンクしていたのを一つのファイルにまとめ、短いコマンドで実行することができる
- 「make」コマンドを実行したところライブラリが見つからないエラーになったがこの記事を読んで解決
19日目
- 画面描画できるように下記を実施
- ブートローダのプログラム「Main.c」にてframebufferの情報をkernelに渡すよう修正
- kernelのプログラム「main.cpp」にてframebufferを用いて画面描画するよう処理追加←ここの写経している時に時間切れ
今日写経したもの
◾️Main.cのmain()の一部分で、kernelにframebufferの情報を渡すところ
UINT64 entry_addr = *(UINT64*)(kernel_base_addr + 24);
// #@@range_begin(pass_frame_buffer_config)
struct FrameBufferConfig config = {
(UINT8*)gop->Mode->FrameBufferBase,
gop->Mode->Info->PixelsPerScanLine,
gop->Mode->Info->HorizontalResolution,
gop->Mode->Info->VerticalResolution,
0
};
switch (gop->Mode->Info->PixelFormat) {
case PixelRedGreenBlueReserved8BitPerColor:
config.pixel_format = kPixelRGBResv8BitPerColor;
break;
case PixelBlueGreenRedReserved8BitPerColor:
config.pixel_format = kPixelBGRResv8BitPerColor;
break;
default:
Print(L"Unimplemented pixel format: %d\n", gop->Mode->Info->PixelFormat);
Halt();
}
typedef void EntryPointType(const struct FrameBufferConfig*);
EntryPointType* entry_point = (EntryPointType*)entry_addr;
entry_point(&config);
// #@@range_end(pass_frame_buffer_config)
// #@@range_end(pas_frame_buffer_config)
◾️ヘッダーファイル「frame_buffer_config.hpp」
フレームバッファ(コンピュータの画面表示に用いられる、表示内容を記憶しておくためのメモリ領域)を定義する。
最初はなぜこの定義だけ独立させるのかわからなかったが、Main.cだけでなくmain.cppからもincludeされていたので、何らかの定義を共通化したい場合に便利だということがわかった。
#pragma once
#include <stdint.h>
enum PixelFormat{
KPixelRGBResv8BitPerColor,
KPixelBGRResv8BitPerColor,
};
struct FrameBufferConfig{
uint8_t* frame_buffer;
uint32_t pixels_per_scan_line;
uint32_t horizontal_resolution;
uint32_t vertical_resolution;
enum PixelFormat pixel_format;
};
20日目
- ビルドを実行したところ下記のエラーが発生してしまった。osbook_day04bで、buildを実行したところ、が見つからないエラーが出るを参考にして明日から対処する
In file included from /usr/lib/llvm-14/lib/clang/14.0.0/include/stdint.h:52:
/usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found
#include <bits/libc-header-start.h>
^~~~~~~~~~~~~~~~~~~~~~~~~~
今日写経したもの
◾️カーネルプログラム「main.cpp」
/**
* @ file main.cpp
*
*
*/
# include <cstdint>
# include <cstddef>
# include "frame_buffer_config.hpp"
// #@@range_begin(write_pixel)
struc PixelColor {
uint8_t r, g, b;
};
/** WritePixelは1つの点を描画します.
* @retval 0 成功
* @retval 非0 失敗
*/
int WritePixel(const FrameBufferConfig& config,
int x, int y, const PixelColor& c){
const int pixel_position = config.pixels_per_scan_line * y + x;
if (config.pixel_format == kPixelRGBResv8BitPerColor) {
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.r;
p[1] = c.g;
p[2] = c.b;
} else if (config.pixel_format == kPixelBGRResv8BitPerColor){
uint8_t* p = &config.frame_buffer[4 * pixel_position];
p[0] = c.b;
p[1] = c.g;
p[2] = c.r;
} else {
return -1;
}
return 0;
}
// #@@range_end(write_pixel)
// #@@range_begin(call_write_pixel)
extern "C" void KernelMain(const FrameBufferConfig& frame_buffer_config) {
for (int x = 0: x < frame_buffer_config.horizontal_rezolution; ++x) {
for (int y = 0; y < frame_buffer_config.vertical_resolution; ++y) {
WritePixel(frame_buffer_config, x, y, (255, 255, 255);)
}
}
for (int x = 0; x < 200; ++x) {
for (int y = 0; y < 100; ++y) {
WritePixel(frame_buffer_config, 100 + x, 100 + y, {0, 255, 0});
}
}
while (1) __asm__("hlt");
}
// #@@range_end(call_write_pixel)
21日目
-
osbook_day04bで、buildを実行したところ、が見つからないエラーが出るに答えが載っておりビルドエラーは下記コマンドを実行することで解決できた。
- コンパイルする時に参考にするヘッダーファイルのリストに、上記のディレクトリ・ファイルが含むことができる。
export C_INCLUDE_PATH=/usr/include/aarch64-linux-gnu
export CPLUS_INCLUDE_PATH=/usr/include/aarch64-linux-gnu
- 無事ビルドできたブートローダとカーネルを実行したが、表示される画面が本で示されているもの(白地の画面に緑の四角形が描画されるはず)と違った。全く同じ事象がIssudeで報告されており解決策も記載されていたのでこれを元に対処する
22日目
- リンカのバージョンが上がっていることで、kernel.elfが
0x100000に読み込まれずズレてしまうことで挙動がおかしくなっているらしい - ld.lldに
-z separate-codeを追加することで解決するらしいが、肝心の対象ファイルが文字化けしておりどうすればいいんだ‥となったがld.lld自体はバイナリファイルなので意味がなかった。 - 実際は「ld.lld」コマンドでリンクする時に、オプションに
-z separate-codeを追加するという意味だった。 -
$HOME/workspace/mikanos/kernel/Makefileを下記のように改修
TARGET = kernel.elf
OBJS = main.o
CXXFLAGS += -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone \
-fno-exceptions -fno-rtti -std=c++17
LDFLAGS += --entry KernelMain -z norelro --image-base 0x100000 --static
.PHONY: all
all: $(TARGET)
.PHONY: clean
clean:
rm -rf *.o
kernel.elf: $(OBJS) Makefile
ld.lld $(LDFLAGS) -o kernel.elf -z separate-code $(OBJS)
%.o: %.cpp Makefile
clang++ $(CPPFLAGS) $(CXXFLAGS) -c $<
-
-z separate-codeは、リンクの際に命令とデータを別のメモリページに配置するオプションらしい。このオプションをつけることで、kernel.elfが0x100000に近いところで読み込まれるようだ。下記のように実際に確認できた。(ぶっちゃけ、解決策実施後もそこまで0x100000に近いところに配置されているとは思えないが、とにかくこの差が大事らしい) -
この話は「4.5 ローダを改良する」でも出てくるとのこと。
・解決策実施前(Entry pointが0x101180)
mikan@mikan-server:~/workspace/mikanos$ readelf -l kernel/kernel.elf
Elf file type is EXEC (Executable file)
Entry point 0x101180
There are 4 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000100040 0x0000000000100040
0x00000000000000e0 0x00000000000000e0 R 0x8
LOAD 0x0000000000000000 0x0000000000100000 0x0000000000100000
0x0000000000000120 0x0000000000000120 R 0x1000
LOAD 0x0000000000000120 0x0000000000101120 0x0000000000101120
0x0000000000000133 0x0000000000000133 R E 0x1000
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x0
Section to Segment mapping:
Segment Sections...
00
01
02 .text
03
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x23
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
・解決策実施後(Entry pointは0x101060)
mikan@mikan-server:~/workspace/mikanos$ readelf -l kernel/kernel.elf
Elf file type is EXEC (Executable file)
Entry point 0x101060
There are 4 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000100040 0x0000000000100040
0x00000000000000e0 0x00000000000000e0 R 0x8
LOAD 0x0000000000000000 0x0000000000100000 0x0000000000100000
0x0000000000000120 0x0000000000000120 R 0x1000
LOAD 0x0000000000001000 0x0000000000101000 0x0000000000101000
0x0000000000001000 0x0000000000001000 R E 0x1000
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x0
Section to Segment mapping:
Segment Sections...
00
01
02 .text
03
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x23
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
readelf: Warning: Unrecognized form: 0x22
23日目
- 「4.3 C++の機能を使って書き直す」に突入
- main.cppの写経をした。抽象クラス「PixelWriter」を作成。
- 抽象クラスで処理を定義し、それを継承するクラスにも処理を行わせるよう強制することでそれぞれのクラスで行う処理がバラバラにならないようにする。今回の例で言うと、データ形式が「RGB」と「BGR」のものでそれぞれクラスを作り、抽象クラスを継承でもさせるのだろう。
-
PixelWriter(const FrameBufferConfig& config): config_{config}はコンストラクタを表し、メモリ上にインスタンスを配置する役割をになっている。ついでにメンバ変数を初期化している。 -
~PixelWriterの~はデストラクタであることを示している。 -
virtualをつけると「仮想関数」となり、基底クラスのメソッドではなく、子どものクラスのメソッドを呼び出すようにすることができる。 -
virtual void Write(int x, int y, const PixelColor& c) = 0;は純仮想関数と言い、子どものクラスでこの関数の処理を行うことを強制できるらしい。抽象クラスとなる条件のうちの一つが、この関数があることのようだ。
struct PixelColor {
uint8_t r, g, b;
};
// #@@range_begin(pixel_writer)
class PixelWriter {
public:
PixelWriter(const FrameBufferConfig& config): config_{config} {
}
virtual ~PixelWriter() = default;
virtual void Write(int x, int y, const PixelColor& c) = 0;
protected:
uint8_t* PixelAt(int x, int y){
return config_.frame_buffer + 4 * (config_.pixels_per_scan_line * y + x);
}
Private:
const FrameBufferConfig& config_;
};
// #@@range_end(pixel_writer)
24日目
* 昨日かいた基底クラスを継承する2種類のクラスを定義する
-
using PixelWriter::PixelWriter;がコンストラクタの役割を果たしている。
// #@@range_begin(derived_pixel_writer)
class RGBResv8BitPerColorPixelWriter : public PixelWriter{
public:
using PixelWriter::PixelWriter;
virtual void Write(int x, int y, const PixelColor& c) override{
auto p = PixelAt(x, y);
p[0] = c.r;
p[1] = c.g;
p[2] = c.b;
}
};
class BGRResv8BitperColorPixeWriter : public PixelWriter {
public:
using PixelWriter::PixelWriter;
virtual void Write(int x, int y, const PixelColor& c) override {
auto p = PixelAt(x, y);
p[0] = c.b;
p[1] = c.g;
p[2] = c.r;
}
};
// #@@range_end(derived_pixel_writer)
- これは配置newという書き方らしい。まだOSがメモリを管理できていないので、一般的なnewは使えない(ヒープ領域に確保するためプログラムがOSにメモリ管理要求を出すが、OS側は応えられない)ので、保存して欲しいメモリ領域を明示的に指定するnewを行なっているらしい。
// #@@range_begin(placement_new)
void* operator new(size_t size, void* buf) {
return buf;
}
void operator delete(void* obj) noexcept {
}
// #@@range_end(placement_new)
- メイン処理の部分はクラスを使うことで前よりもすっきりした
- 相変わらず細かいところはなにやっているかわからないが、大まかな処理の流れはわかるようになってきた気がする。成長。
- 明日は実際にコンパイル、リンク、ビルドして動かし、「4.4 vtable」に突入しよう
// #@@range_begin(call_pixel_writer)
extern "C" void KernelMain(const FrameBufferConfig& frame_buffer_config) {
switch (frame_buffer_config.pixel_format) {
case kPixelRGBResv8BitPerColor:
pixel_writer = new(pixel_writer_buf)
RGBResv8BitPerColorPixelWriter{frame_buffer_config};
break;
case kPixelBGRResv8BitPerColor:
pixel_writer = new(pixel_writer_buf)
BGRResv8BitperColorPixeWriter{frame_buffer_config};
break;
}
for (int x = 0; x < frame_buffer_config.horizontal_resolution; ++x) {
for (int y = 0; y < frame_buffer_config.vertical_resolution; ++y) {
pixel_writer->Write(x, y, {255, 255, 255});
}
}
for (int x = 0; x < 200; ++x){
for (int y = 0; y < 100; ++y) {
pixel_writer->Write(x, y, {0, 255, 0});
}
}
while (1) __asm__("hlt");
}
// #@@range_end(call_pixel_writer)
25日目
- 写経したコードを使用してピクセル描画することができた。
- 写経時に色々とタイポしていてその修正だけで時間が潰れた
- 写経だけして雰囲気掴むだけでもいいかも‥。わざわざ実行の必要あるのかな?
26日目
- ひたすらよくわからんことが書いてあった
- とりあえず、メモリを読み込む処理に不具合がある状態なので修正が必要らしい
- 難しい言葉だらけで処理が追いつかない‥。とりあえず写経しつつ、Geminiに意味を聞いていってる。
27日目
- そもそもELFについて何かわからなくなってきたので調べた。同じように悩んでいた人が解説記事を出してくれていたので参考にした。
- Executable and Linking Formatの略。C言語のプログラムをコンパイル&リンクすると出来上がる実行ファイルがこれ。(別にUEFIブートローダ用の実行ファイル形式というわけではない)
-
.elfのみだけでなく、.O(コンパイルによって作成されるオブジェクトファイル)などもELFに該当する - 本で説明されていたファイル構造も説明されていた。
- ファイルヘッダー‥ファイル全体の情報
- プログラムヘッダー‥ローダ向けの情報
- セクション本体
- セクションヘッダー‥リンカ向けの情報
- 概念は何となくわかったので、引き続き調べる
ファイルヘッダー
mikan@mikan-server:~/workspace/mikanos/kernel$ readelf -h kernel.elf
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x101020
Start of program headers: 64 (bytes into file)
Start of section headers: 15288 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 5
Size of section headers: 64 (bytes)
Number of section headers: 18
Section header string table index: 16
プログラムヘッダー
mikan@mikan-server:~/workspace/mikanos/kernel$ readelf -l kernel.elf
Elf file type is EXEC (Executable file)
Entry point 0x101020
There are 5 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000100040 0x0000000000100040
0x0000000000000118 0x0000000000000118 R 0x8
LOAD 0x0000000000000000 0x0000000000100000 0x0000000000100000
0x00000000000001a8 0x00000000000001a8 R 0x1000
LOAD 0x0000000000001000 0x0000000000101000 0x0000000000101000
0x00000000000001c9 0x00000000000001c9 R E 0x1000
LOAD 0x0000000000002000 0x0000000000102000 0x0000000000102000
0x0000000000000000 0x0000000000000018 RW 0x1000
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x0
Section to Segment mapping:
Segment Sections...
00
01 .rodata
02 .text
03 .bss
04
セクションヘッダー
mikan@mikan-server:~/workspace/mikanos/kernel$ readelf -S kernel.elf
There are 18 section headers, starting at offset 0x3bb8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .rodata PROGBITS 0000000000100158 00000158
0000000000000050 0000000000000000 A 0 0 8
[ 2] .text PROGBITS 0000000000101000 00001000
00000000000001c9 0000000000000000 AX 0 0 16
[ 3] .bss NOBITS 0000000000102000 00002000
0000000000000018 0000000000000000 WA 0 0 16
[ 4] .debug_loclists PROGBITS 0000000000000000 00002000
00000000000000cb 0000000000000000 0 0 1
[ 5] .debug_abbrev PROGBITS 0000000000000000 000020cb
00000000000002d5 0000000000000000 0 0 1
[ 6] .debug_info PROGBITS 0000000000000000 000023a0
0000000000000690 0000000000000000 0 0 1
[ 7] .debug_rnglists PROGBITS 0000000000000000 00002a30
0000000000000033 0000000000000000 0 0 1
[ 8] .debug_str_o[...] PROGBITS 0000000000000000 00002a63
00000000000001cc 0000000000000000 0 0 1
[ 9] .debug_str PROGBITS 0000000000000000 00002c2f
0000000000000656 0000000000000001 MS 0 0 1
[10] .debug_addr PROGBITS 0000000000000000 00003285
0000000000000088 0000000000000000 0 0 1
[11] .comment PROGBITS 0000000000000000 0000330d
0000000000000042 0000000000000001 MS 0 0 1
[12] .debug_frame PROGBITS 0000000000000000 00003350
0000000000000160 0000000000000000 0 0 8
[13] .debug_line PROGBITS 0000000000000000 000034b0
00000000000001ee 0000000000000000 0 0 1
[14] .debug_line_str PROGBITS 0000000000000000 0000369e
000000000000019b 0000000000000001 MS 0 0 1
[15] .symtab SYMTAB 0000000000000000 00003840
0000000000000150 0000000000000018 17 2 8
[16] .shstrtab STRTAB 0000000000000000 00003990
00000000000000c4 0000000000000000 0 0 1
[17] .strtab STRTAB 0000000000000000 00003a54
0000000000000164 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
28日目
- 引き続きELF調べつつ、写経した下記コードの理解を進める。
- 言葉の意味はわかったけど実感ができていない。先に進もう
// #@@range_begin(calc_addr_func)
void CalcLoadAddressRange(f64_Ehdr* ehdr, UINT64* first, UINT64* last) {
// ELFヘッダからプログラムヘッダテーブルのアドレスを計算する。ehdr->e_phoffはファイル先頭からプログラム
Elf64_Phdr* phdr = (Elf64_Phdr*)((UINT64)ehdr + ehdr->e_phoff);
*first = MAX_UINT64;
*last = 0;
for (Elf64_Half i = 0; i < ehdr->e_phnum; ++i){
if(phdr[i].p_type != PT_LOAD) continue;
*first = MIN(*first, phdr[i].p_vaddr);
*last = MAX(*last, phdr[i].p_vaddr + phdr[i].p_memsz);
}
}
// #@@range_end(calc_addr_func)
29日目
- 写経を続けている
- 相変わらずわからない表現ばかりだが、
CalcLoadAddressRange(kernel_ehdr, &kernel_first_addr, &kernel_last_addr);のところでは、kernel_first_addrとkernel_first_addrのポインタを渡し、渡した先の関数で計算結果をポインタに代入することで、実データのやりとりをしないで済んでいる(ポインタを介してやりとりしている)ことが直感的にわかった。前よりは成長しているはず‥。
// #@@range_begin(copy_segm_func)
void CopyLoadSegments(Elf64_Ehdr* ehdr){
Elf64_Phdr phdr = (Elf64_Phdr*)((UINT64)ehdr + ehdr->e_phoff);
// e_phnum(プログラムヘッダテーブルのエントリ数)分だけ繰り返し処理する
for (Elf64_Half i = 0; i < ehdr->e_phnum; ++i) {
// p_type(セグメント種別)がPT_LOAD(ロード可能なセグメント種別)なら処理続行。それ以外は処理中断。
if(phdr[i].p_type != PT_LOAD) continue;
// phdr[i].p_offsetは、ファイル先頭からセグメントのデータまでのオフセット
// ロード対象となるセグメントが、ファイル内のどこに位置しているかを計算
UINT64 segm_in_file = (UINT64)ehdr + phdr[i].p_offset;
// ファイルからメモリへのセグメントのコピーを行う。
// p_vaddrセグメントがロードされるべきメモリ上の仮想アドレス
// segm_in_fileは、ファイル内のセグメントの開始アドレス
CopyMem((VOID*)phdr[i].p_vaddr, (VOID*)segm_in_file, phdr[i].p_filesz);
// BSSセグメント(初期化されていないデータ)のサイズを計算。p_memszがp_fileszよりも大きい場合、その差分がBSSセグメントでありゼロで埋める必要がある。
UINTN remain_bytes = phdr[i].p_memsz - phdr[i].p_filesz;
// BSSセグメントのゼロクリアを行う。
setMem((VOID*)(phdr[i].p_vaddr + phdr[i].p_filesz), remain_bytes, 0);
}
}
// #@@range_end(copy_segm_func)
// #@@range_begin(alloc_pages)
Elf64_Ehdr* kernel_ehdr = (Elf64_Ehdr*)kernel_buffer;
UINT64 kernel_first_addr, kernel_last_addr;
CalcLoadAddressRange(kernel_ehdr, &kernel_first_addr, &kernel_last_addr);
UINTN num_pages = (kernel_last_addr - kernel_first_addr + 0xfff) / 0x1000;
status = gBS->AllocatePages(AllocatedAddress, EfiLoaderData,
num_pages, &kernel_first_addr);
if (EFI_ERROR(status)){
Print(L"failed to allocate pages: %r\n",status);
Halt();
}
// #@@range_end(alloc_pages)
30日目
- とりあえず写経は終わらせた。この章についてもう嫌になったので先に進むことにした。
- いつか強くなったワイが読み解いてくれるだろう‥。
- せっかく写経したファイルだが、タイポによってコンパイルエラー発生して修正して進み遅くなるのもしんどいのでとりあえずosbook_day04dのコード群をmakeして動かせるところまでを確認する
- とりあえずやってるだけ偉いので。
- 5章「文字表示とコンソールクラス」に突入した
- 「AA」と表示するだけで、8 * 16ピクセルで表現するための下記のようなコードが必要になるらしい。「普通に文字表示すればええやん」と思ったけどそもそも文字表示機能とかないところからやっているので全て一からやらなければならないことが実感できた。
- 何やっているかはある程度直感的にわかるのでここは写経しない。
↓文字ひとつ表示するだけで大変‥
// #@@range_begin(font_a)
const uint8_t kFontA[16] = {
0b00000000, //
0b00011000, // **
0b00011000, // **
0b00011000, // **
0b00011000, // **
0b00100100, // * *
0b00100100, // * *
0b00100100, // * *
0b00100100, // * *
0b01111110, // ******
0b01000010, // * *
0b01000010, // * *
0b01000010, // * *
0b11100111, // *** ***
0b00000000, //
0b00000000, //
};
// #@@range_end(font_a)
// (中略)
// #@@range_begin(write_ascii)
void WriteAscii(PixelWriter& writer, int x, int y, char c, const PixelColor& color) {
if (c != 'A') {
return;
}
for (int dy = 0; dy < 16; ++dy) {
for (int dx = 0; dx < 8; ++dx) {
if ((kFontA[dy] << dx) & 0x80u) {
writer.Write(x + dx, y + dy, color);
}
}
}
}
// #@@range_end(write_ascii)



