ゼロからのOS自作入門(その2) メモリマップの取得 の続きです。
Cで書かれたフレームバッファを塗りつぶすカーネルを作っていたら、UEFIアプリは無理でもOSだけならRustで書けるんじゃと思ってやってみました。Rustで書かれたカーネルに制御が移りフレームバッファを書き換えて画面表示を変えるところまで動きました。 後から続く人に参考になると思い、試行錯誤の手順を書いておきます。Rustは初心者です。
参考Webサイト こちらの記事が非常に役に立ちました。ありがとうございます。
この記事のベースとなっているバージョンは day03bです。
UEFIアプリは本のコードを写経して、カーネルだけRustで書いています。 とりあずRustのカーネルがブートしたと自信持てるまでは、Cで書いた確実にブートするカーネルを用意して比較するのが良かったです。
UEFIアプリの修正
修正したのは二箇所です。
読み込むカーネルのファイル名を修正
// #@@range_begin(read_kernel) EFI_FILE_PROTOCOL* kernel_file; // root_dir->Open(root_dir, &kernel_file, L"\\kernel.elf", EFI_FILE_MODE_READ, 0); root_dir->Open(root_dir, &kernel_file, L"\\rust_kernel", EFI_FILE_MODE_READ, 0);
Qemuの起動スクリプト内で、上手いことリネームしてくれていると勝手に思い込んでいました。ちゃんと自分で作ったカーネルのファイル名に変更しましょう。
EntryPointの表示
UINT64 entry_addr = *(UINT64*)(kernel_base_addr + 24); typedef void EntryPointType(UINT64, UINT64); EntryPointType* entry_point = (EntryPointType *)entry_addr; Print(L"Entry point: 0x%0lx\n", entry_point); entry_point(gop->Mode->FrameBufferBase, gop->Mode->FrameBufferSize);
読み込んだカーネルのエントリーポイントを表示するようにしました。ここが表示されなければ、カーネル(ファイル)を読み込めてません。 ブートするかどうかを確認するのにUEFIは止める必要が無いので、UEFIの無効化もいったんentry_point()の後に回しました。 カーネルのブートが確認出来たら元に戻しましょう。
Rust側
ここは動いたファイルだけあれば十分ですね。これを参考に動かしてください。(Rust初心者なので良く分からないです)
cargo.toml
[package] name = "rust_kernel" version = "0.1.0" authors = ["natu"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rlibc = "1" panic-halt = "0.2"
.cargo/config.toml
[build] target = "x86_64-unknown-linux-gnu" [profile.release] panic = "abort" [profile.dev] panic = "abort" [target.x86_64-unknown-linux-gnu] linker = "ld.lld" rustflags = [ # Build Options "-C", "no-redzone=yes", "-C", "relocation-model=static", # Linker Options "-C", "link-arg=--entry=KernelMain", "-C", "link-arg=--image-base=0x100000", # "-C", "link-arg=-n", # "-C", "link-arg=-nmagic", #"-C", "link-arg=-znorelro", ]
main.rs
参考BlogのAkira Hayakawaさんは、Rustで書いたUEFIアプリからRustのカーネルを呼び出していましたが、僕はCのUEFIアプリからRustのカーネルを呼び出そうとしています。 UFEIアプリからはCのカーネルと同じように見える必要があります。ですので、関数宣言はpub extern "C"としています。
どうもiterationで落ちるようで、ループ無しでフレームバッファをいじってみました。abi_efiapiは要らない気がしますが、とりあえず動いた状態そのまま。
#![no_std] #![no_main] #![feature(asm)] #![feature(abi_efiapi)] extern crate rlibc; extern crate panic_halt; #[no_mangle] pub extern "C" fn KernelMain(fb_addr:u64, _fb_size: u64) -> ! { let adr = fb_addr as *mut u8; unsafe { *adr.offset(3200 + 0) = 255 as u8; *adr.offset(3200 + 1) = 255 as u8; *adr.offset(3200 + 2) = 255 as u8; *adr.offset(3200 + 3) = 255 as u8; *adr.offset(3200 + 4) = 255 as u8; *adr.offset(3200 + 5) = 255 as u8; *adr.offset(3200 + 6) = 255 as u8; *adr.offset(3200 + 7) = 255 as u8; *adr.offset(3200 + 8) = 255 as u8; *adr.offset(3200 + 9) = 255 as u8; *adr.offset(3200 + 10) = 255 as u8; *adr.offset(3200 + 11) = 255 as u8; *adr.offset(3200 + 12) = 255 as u8; *adr.offset(3200 + 13) = 255 as u8; *adr.offset(3200 + 14) = 255 as u8; *adr.offset(3200 + 15) = 255 as u8; } // for i in 0..fb_size { // unsafe { // *adr.offset(i as isize) = (255 % i) as u8; // } // } loop { unsafe { asm!("hlt") } } }
動かすために確認したこと
ELFヘッダーの確認
生成されたELFファイルのヘッダーをCのカーネルと、Rustのカーネルで比較しました。 ヘッダーの表示には、readelfコマンドを使います。
だいたい一緒になるはずです。 ヘッダーに含まれるEntry point addressが、UFEIアプリの中でちゃんと読めていることを確認しました。
Cカーネルのヘッダー
natu@Honoka:~/q/myproj/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: 0x101000 Start of program headers: 64 (bytes into file) Start of section headers: 11936 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 4 Size of section headers: 64 (bytes) Number of section headers: 16 Section header string table index: 14
Rustカーネルのヘッダー
natu@Honoka:~/q/myproj/mikanos/rust_kernel$ readelf -h target/x86_64-unknown-linux-gnu/debug/rust_kernel 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: 0x101000 Start of program headers: 64 (bytes into file) Start of section headers: 10520 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 6 Size of section headers: 64 (bytes) Number of section headers: 15 Section header string table index: 13
Rustカーネルのエントリーポイントの確認
Rustのカーネルを逆アセンブルして、Entry pointがRustのKernelMainになっているかを確認しました。 逆アセンブル表示は、objdump -d を使います。
natu@Honoka:~/q/myproj/mikanos/rust_kernel$ objdump -d target/x86_64-unknown-linux-gnu/debug/rust_kernel target/x86_64-unknown-linux-gnu/debug/rust_kernel: file format elf64-x86-64 Disassembly of section .text: 0000000000101000 <KernelMain>: 101000: 48 81 ec a8 00 00 00 sub $0xa8,%rsp 101007: 48 89 bc 24 80 00 00 mov %rdi,0x80(%rsp) 10100e: 00 10100f: 48 89 bc 24 90 00 00 mov %rdi,0x90(%rsp)
OKですね。
Qemuが動かなくなったとき
最初の頃Rustで作ったカーネルが暴走して、その後Qemuが起動中に止まってしまうようになりました。 実績のあるCカーネルも起動しなくなったのでQemuに問題があると判断、良く分からないですがOVMF_VARS.fdの内容が変ってしまっているようです。何気なくgit statusしてみたら気がつきました。
ここからOVMF_VARS.fdを持ってきて、$HOME/osbook/devenv のファイルを上書きすればOKです。無事qemuが立ち上がるようになりました。
ブート画面
左上に一行開けて白い線が表示されています。やりました。
この後
どうも最初に暴走していた所が、このループのようです。
for i in 0..fb_size { unsafe { *adr.offset(i as isize) = (255 % i) as u8; } }
逆アセを見ると、iterっぽい関数が呼ばれていてそこで死んでそうでした。(動いてからの想像)
stackが上手く設定で来てなくて関数呼び出しが出来ないのか、rangeで作られるIteratorがnew的なサポートが必要なのか、そんな想像はしています。 次はrangeを動かして、画面を塗りつぶしたいです。
追記:loopではなく、(255 % i) as u8で落ちてそうです。