ゼロからのOS自作入門 | day03

Kubernetesの沼にハマったムッキー!

Kubernetes大嫌いです。さてday03やっていきます

前回はメモリマップを読み込んでみるということをしました。正直いってほとんど理解してないですが、読み進めていきます。

今回は画面を塗りつぶしたOS本体を作りブートローダーで呼び出すことをやります。

レジスタ

CPUに内蔵されている記憶領域で、汎用レジスタと特殊レジスタがある。

汎用レジスタは一般の演算、値を記憶します。
特殊レジスタはCPUの設定を行う

外部のメインメモリと汎用レジスタは目的が同じでも速度が違う。もちろん汎用レジスタのほうが早い

参考サイト

汎用レジスタ

汎用レジスタで演算を行うには

add rax, rbx (rax += rbx)

の用に記載する。

汎用レジスタは64ビットだが、区分けしてより小さい型を使えるようになっている

例えばRAX(64ビット)のうち、EAXを32ビット、AXを16ビット、AHとALを8ビットとして扱える。

特殊レジスタ

値を読み書きすること自体に意味があったり、各ビットによって役割が異なるなどの特徴がある。

RIP(命令ポインタレジスタ)は次に実行する命令のメモリアドレスを保持
RFLAGS(フラグレジスタ)は命令の実行結果によって変化するフラグを集める
CR0(制御レジスタ?)はCPUの重要な設定を集める

この辺を読んでいたらこの動画を思い出した。

ロード命令って何だ? ポケモン第二世代任意コード実行講座 Part.02

カーネル

この本ではブートローダはUEFIアプリとして作るが、カーネルはELFバイナリとして別々に作っていくそうです。

カーネルに関してはUEFIの規格から自由になるためだそうです。

C++でC言語風に関数定義するにはextern “C”という記載をします。マングリングを防ぐ。

___asm___()は、C言語にアセンブリ言語の命令を埋め込む方法です。

hltはCPUに割り込みがない限りCPUを停止するコマンドです。

C++の動作環境はホスト環境とフリースタンディング環境があり、フリースタンディング環境向けにコンパイルする→OS自体はOSがない環境だから

前も出てきたリンカがまた出てきた。

ld.lldによってオブジェクトファイルから実行可能ファイルを作る。
流れとしては.cpp→.o→.elfですね

とりあえず進める

なんかgitの設定がバグってる・・・?

いつできたのかわからんので消します・・・

上手く戻せたと思う

-02オプション付けると、そんなのないって怒られてしまったので外しました

elfの作成まで完了

本の通り進めてるとうまくいかないので、source edksetup.shを実行しました。(あってんのかな?)

なんどかinfo registersをやってRIPの値が変わっていないことが確認できたので、永久ループしているようです(あれこれって駄目なんだっけ?)

メモリマップを確認してみる

sudo mount -o loop disk.img mnt
[sudo] misaka のパスワード: 
misaka@misaka:~/edk2$ cat mnt/memmap
Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute
0, 3, EfiBootServicesCode, 00000000, 1, F
1, 7, EfiConventionalMemory, 00001000, 9F, F
2, 7, EfiConventionalMemory, 00100000, 700, F
3, A, EfiACPIMemoryNVS, 00800000, 8, F
4, 7, EfiConventionalMemory, 00808000, 8, F
5, A, EfiACPIMemoryNVS, 00810000, F0, F
6, 4, EfiBootServicesData, 00900000, B00, F
7, 7, EfiConventionalMemory, 01400000, 3AB36, F
8, 4, EfiBootServicesData, 3BF36000, 20, F
9, 7, EfiConventionalMemory, 3BF56000, 270B, F
10, 1, EfiLoaderCode, 3E661000, 3, F
11, 4, EfiBootServicesData, 3E664000, 219, F
12, 3, EfiBootServicesCode, 3E87D000, B7, F
13, A, EfiACPIMemoryNVS, 3E934000, 12, F
14, 0, EfiReservedMemoryType, 3E946000, 1C, F
15, 3, EfiBootServicesCode, 3E962000, 10A, F
16, 6, EfiRuntimeServicesData, 3EA6C000, 5, F
17, 5, EfiRuntimeServicesCode, 3EA71000, 5, F
18, 6, EfiRuntimeServicesData, 3EA76000, 5, F
19, 5, EfiRuntimeServicesCode, 3EA7B000, 5, F
20, 6, EfiRuntimeServicesData, 3EA80000, 5, F
21, 5, EfiRuntimeServicesCode, 3EA85000, 7, F
22, 6, EfiRuntimeServicesData, 3EA8C000, 8F, F
23, 4, EfiBootServicesData, 3EB1B000, 4DA, F
24, 7, EfiConventionalMemory, 3EFF5000, 4, F
25, 4, EfiBootServicesData, 3EFF9000, 6, F
26, 7, EfiConventionalMemory, 3EFFF000, 1, F
27, 4, EfiBootServicesData, 3F000000, A1B, F
28, 7, EfiConventionalMemory, 3FA1B000, 1, F
29, 3, EfiBootServicesCode, 3FA1C000, 17F, F
30, 5, EfiRuntimeServicesCode, 3FB9B000, 30, F
31, 6, EfiRuntimeServicesData, 3FBCB000, 24, F
32, 0, EfiReservedMemoryType, 3FBEF000, 4, F
33, 9, EfiACPIReclaimMemory, 3FBF3000, 8, F
34, A, EfiACPIMemoryNVS, 3FBFB000, 4, F
35, 4, EfiBootServicesData, 3FBFF000, 201, F
36, 7, EfiConventionalMemory, 3FE00000, 8D, F
37, 4, EfiBootServicesData, 3FE8D000, 20, F
38, 3, EfiBootServicesCode, 3FEAD000, 20, F
39, 4, EfiBootServicesData, 3FECD000, 9, F
40, 3, EfiBootServicesCode, 3FED6000, 1E, F
41, 6, EfiRuntimeServicesData, 3FEF4000, 84, F
42, A, EfiACPIMemoryNVS, 3FF78000, 88, F
43, 6, EfiRuntimeServicesData, FFC00000, 400, 1

なるほど、そろそろ2進数とか16進数を覚える必要があるということですか。

EfiConventionalMemoryというのが十分な大きさのあるメモリだそうです??

まず前提として、AllocatePagesは確実に確保されたメモリのアドレスを指定する必要がある。

今回はカーネルファイルをメモリの0x100000番地に配置しているので、ここが空いている必要があるということです。

ん?0x100000番地ってどれ?

0xってのは16進数のことなのね。ってことは100000をなんとかして解読すればいいのか?

これか?これでいいのか?空いてるぞ??なにをどうしたらいいのかわからないのでとりあえず進めてみる。。

2, 7, EfiConventionalMemory, 00100000, 700, F

ちなみにソースで0x100000を指定しているのは以下で表しています。

  EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
  gBS->AllocatePages(
      AllocateAddress, EfiLoaderData,
      (kernel_file_size + 0xfff) / 0x1000, &kernel_base_addr);

kernel_file_size + 0xfff) / 0x1000ではkernel_file_sizeがバイト単位なのでページ単位に変換する

UEFIにおけるページ単位の大きさが4Kib=0x1000で切り上げのため0xfffをプラスしている。

→この説明で理解できるほど頭良くないぜ☆
とりあえずUEFIではページ単位でメモリとかやれってことかい?

とりあえずあとで読みます

とりあえずRIPが指すアドレスに入っている命令を調べる必要がある。書籍では

RIP=0000000000101011となっており、RPIが指すメモリ領域を x / 2i 0x101011 で表示していた

→なるほど、まったくわからん。とりあえず16進数の0xを頭につけてx/2iすりゃいいのか?やってみることにします。私の場合は

RIP=000000000010100a

なのでx /2i 0x10100aを入力してみる

(qemu) x /2i 0x10100a
0x0010100a:  e9 fa ff ff ff           jmp      0x101009
0x0010100f:  cc                       int3   

ん?書籍と同じようにjmpしてるので、そこを調べる。

(qemu) x /2i 0x101009
0x00101009:  f4                       hlt      
0x0010100a:  e9 fa ff ff ff           jmp      0x101009

ん?hltあるぞ?これってうまくいってる証拠じゃなかったか?

永久ループで止まっているのが正解なのか?それともて永久ループしているのが正解なのか?わからんが、もはや進めるしかない。。ついでにjmpも調べてみるか・・・ってこれ同じってことは永久ループしてるってことでいいんですよね?あれ書籍と同じなんですが。。。これって正解なんですか????????????????????????????????????????????????????????????????????????????

(qemu) x /2i 0x10100a
0x0010100a:  e9 fa ff ff ff           jmp      0x101009
0x0010100f:  cc                       int3     
(qemu) x /2i 0x101009
0x00101009:  f4                       hlt      
0x0010100a:  e9 fa ff ff ff           jmp      0x101009
(qemu) x /2i 0x101009
0x00101009:  f4                       hlt      
0x0010100a:  e9 fa ff ff ff           jmp      0x101009

というわけでRIPの値が変わってないですが先に進めます

ループ書いてループしてんなら、ええやろ☆

レッドゾーン

カーネルのコンパイルオプション -mno-red-zoneの説明が入りますが、読んでもわからんのでスルーします。とりあえず開発中はレッドゾーンを無効にしておくのが良いそうです。

ブートローダからピクセル描画

UEFIのGOPを使ってピクセル描画します。

1ピクセルが8ビットなら256色、各色8ビットなら1677万色の表示が可能

カーネルからピクセルを描画

今度はカーネルに処理を書くので書いたら、ブートローダからフレームバッファの先頭アドレスとサイズという情報をカーネルに渡します。

また、新しく環境変数を設定します

misaka@misaka:~/workspace/mikanos/kernel$ echo $CPPFLAGS

misaka@misaka:~/workspace/mikanos/kernel$ source $HOME/osbook/devenv/buildenv.sh
misaka@misaka:~/workspace/mikanos/kernel$ echo $CPPFLAGS
-I/home/misaka/osbook/devenv/x86_64-elf/include/c++/v1 -I/home/misaka/osbook/devenv/x86_64-elf/include -I/home/misaka/osbook/devenv/x86_64-elf/include/freetype2 -I/home/misaka/edk2/MdePkg/Include -I/home/misaka/edk2/MdePkg/Include/X64 -nostdlibinc -D__ELF__ -D_LDBL_EQ_DBL -D_GNU_SOURCE -D_POSIX_TIMERS -DEFIAPI='__attribute__((ms_abi))'

ソースのuintとunitって書いててエラー起きたので誤字には気をつけますということでコンパイルします。

misaka@misaka:~/workspace/mikanos/kernel$ clang++ $CPPFLAGS --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
main.cpp:2:28: error: unknown type name 'unit64_t'
extern "C" void KernelMain(unit64_t frame_buffer_base, unit64_t frame_buffer_size) {
                           ^
main.cpp:2:56: error: unknown type name 'unit64_t'
extern "C" void KernelMain(unit64_t frame_buffer_base, unit64_t frame_buffer_size) {
                                                       ^
main.cpp:3:1: error: unknown type name 'unit8_t'
unit8_t* frame_buffer = reinterpret_cast<unit8_t*>(frame_buffer_base);
^
main.cpp:3:42: error: unknown type name 'unit8_t'
unit8_t* frame_buffer = reinterpret_cast<unit8_t*>(frame_buffer_base);
                                         ^
main.cpp:4:5: error: unknown type name 'unit64_t'
for(unit64_t i = 0; i < frame_buffer_size; ++i){
    ^
5 errors generated.
misaka@misaka:~/workspace/mikanos/kernel$ clang++ $CPPFLAGS --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp

また、新しい環境変数を使ってelfを作る

misaka@misaka:~/workspace/mikanos/kernel$ ld.lld $LDFLAGS --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o

ブートローダの記載を終えたらbuildコマンドでビルドして、いつもどおりrun_qemu.shで起動させます。

エラー処理

わざとエラーにしたらきちんとエラー処理されました

ポインタとアセンブリの説明

ここでの記載は省略します。

今後もよく使いそうなコマンドメモ

misaka@misaka:~/workspace/mikanos/kernel$ ls
main.cpp  usb
misaka@misaka:~/workspace/mikanos/kernel$ clang++ $CPPFLAGS --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
misaka@misaka:~/workspace/mikanos/kernel$ ld.lld $LDFLAGS --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
misaka@misaka:~/workspace/mikanos/kernel$ ls
kernel.elf  main.cpp  main.o  usb
build
misaka@misaka:~/edk2$ $HOME/osbook/devenv/run_qemu.sh Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $HOME/workspace/mikanos/kernel/kernel.elf

感想

ここにきてようやく進め方というのがわかってきた感じがする。

あとはコード理解と、裏でバチバチ動かしてる設定系が気になっているところである。。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする