の続きです。
for i in 0..fb_size { unsafe { *adr.offset(i as isize) = (255 % i) as u8; } }
このループで暴走していると思ってましたが、(255 % i)が逆。
(i % 256) as u8;
が正解でした。元のコードだとi=0で0除算が発生していて暴走していました。分かってしまえばたいした事はありませんでした。
無事Rustでフレームバッファの塗りつぶしに成功
の続きです。
for i in 0..fb_size { unsafe { *adr.offset(i as isize) = (255 % i) as u8; } }
このループで暴走していると思ってましたが、(255 % i)が逆。
(i % 256) as u8;
が正解でした。元のコードだとi=0で0除算が発生していて暴走していました。分かってしまえばたいした事はありませんでした。
無事Rustでフレームバッファの塗りつぶしに成功
ゼロからのOS自作入門(その2) メモリマップの取得 の続きです。
Cで書かれたフレームバッファを塗りつぶすカーネルを作っていたら、UEFIアプリは無理でもOSだけならRustで書けるんじゃと思ってやってみました。Rustで書かれたカーネルに制御が移りフレームバッファを書き換えて画面表示を変えるところまで動きました。 後から続く人に参考になると思い、試行錯誤の手順を書いておきます。Rustは初心者です。
参考Webサイト こちらの記事が非常に役に立ちました。ありがとうございます。
この記事のベースとなっているバージョンは day03bです。
UEFIアプリは本のコードを写経して、カーネルだけRustで書いています。 とりあずRustのカーネルがブートしたと自信持てるまでは、Cで書いた確実にブートするカーネルを用意して比較するのが良かったです。
修正したのは二箇所です。
// #@@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の起動スクリプト内で、上手いことリネームしてくれていると勝手に思い込んでいました。ちゃんと自分で作ったカーネルのファイル名に変更しましょう。
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初心者なので良く分からないです)
[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"
[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", ]
参考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ファイルのヘッダーをCのカーネルと、Rustのカーネルで比較しました。 ヘッダーの表示には、readelfコマンドを使います。
だいたい一緒になるはずです。 ヘッダーに含まれるEntry point addressが、UFEIアプリの中でちゃんと読めていることを確認しました。
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
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のカーネルを逆アセンブルして、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ですね。
最初の頃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で落ちてそうです。
Twitter で気がついた佐藤さんのpdf読み上げシステムの動画を見ました。
Google I/Oが5/18 - 20にオンライン開催。私はpdf2audiobookデモ紹介とMLOps AMAで参加します! #gcpja https://t.co/tLpBgTB4Xo
— Kazunori Sato (@kazunori_279) 2021年4月7日
PDFをテキストにして読み上げサービスを使用して音声ファイルに変換しています。
このシステムで使用しているGoogleのテクノロジーです。ひとつひとつは聞いたことあります。
ここで上手いなと思ったところがAutoML Tableによるtext(paragraph)の識別です。
20年くらい前に、FPGAのデータシートを読むのにPDFをテキストに変換してPDAで読むというのをやっていました。 ここで問題になるのが、各ページにはいるヘッダー、ページ番号、図表です。
最初は戸惑ったのですが、なれてくると自力で飛ばせるようになります。ヘッダーはほぼ同じ内容ですし、ページ番号は一定間隔で数字が出てきて、図表は改行だらけのテキストが来たら図表だなと自分で判断して飛ばすことが出来るようになります。今回、ここの部分をAutoML Tableで上手くやっているのが驚きのポイントですね。
自分が苦労しただけあって、あーここでDeep Learning使うんだという感動がありました。 社内のDX推進とかそういうのも良いけど、今の生活がちょっと良くなる技術の使い方はもっともっと可能性あると信じています。
詳しく知りたい人は今すぐGoogle I/Oに登録しましょう。
バージョン等を確認するとき
emsdk list
update するとき
emsdk update
githubから持ってきた時はgit pullで最新にできる。
emsdk listでバージョンを確認。
アップデート前
The *recommended* precompiled SDK download is 2.0.9 (d8e430f9a9b6e87502f826c39e7684852f59624f).
アップデート後
The *recommended* precompiled SDK download is 2.0.16 (80d9674f2fafa6b9346d735c42d5c52b8cc8aa8e).
アップデート成功
ゼロからのOS自作入門、その1の続き
本を読みながら、メモリマップの取得プログラムを書き写してみた。 ブートしたらメモリマップを取得して、memmapという名前のファイルにメモリマップをCSVで出力します。
実行時にメモリマップを取得できるのは凄い。加えて、OSが立ち上がってないのにファイルシステムが動いているのが凄い。Qemuと実機ではメモリマップがだいぶ違う事が分かったことが収穫です。
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, 270C, F 10, 1, EfiLoaderCode, 3E662000, 2, 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
Index, type, Type(name), PhysicalStart, NumberOfPages, Attribute 0, 7, EfiConventionalMemory, 00000000, 58, F 1, 0, EfiReservedMemoryType, 00058000, 1, F 2, 7, EfiConventionalMemory, 00059000, 45, F 3, 0, EfiReservedMemoryType, 0009E000, 2, F 4, 7, EfiConventionalMemory, 00100000, 7559A, F 5, 4, EfiBootServicesData, 7569A000, 40, F 6, 7, EfiConventionalMemory, 756DA000, E74B, F 7, 4, EfiBootServicesData, 83E25000, 302, F 8, 3, EfiBootServicesCode, 84127000, 1F, F 9, 4, EfiBootServicesData, 84146000, 30, F 10, 3, EfiBootServicesCode, 84176000, 2, F 11, 4, EfiBootServicesData, 84178000, 1388, F 12, 3, EfiBootServicesCode, 85500000, C, F 13, 4, EfiBootServicesData, 8550C000, 25, F 14, 3, EfiBootServicesCode, 85531000, 10, F 15, 4, EfiBootServicesData, 85541000, 21, F 16, 3, EfiBootServicesCode, 85562000, B, F 17, A, EfiACPIMemoryNVS, 8556D000, 1, F 18, 6, EfiRuntimeServicesData, 8556E000, 1, F 19, 4, EfiBootServicesData, 8556F000, 1, F 20, 3, EfiBootServicesCode, 85570000, 24, F 21, 4, EfiBootServicesData, 85594000, 81, F 22, 3, EfiBootServicesCode, 85615000, B, F 23, 4, EfiBootServicesData, 85620000, 10, F 24, 3, EfiBootServicesCode, 85630000, 13, F 25, 4, EfiBootServicesData, 85643000, 10, F 26, 3, EfiBootServicesCode, 85653000, 2, F 27, 4, EfiBootServicesData, 85655000, 18, F 28, 3, EfiBootServicesCode, 8566D000, 1D, F 29, 7, EfiConventionalMemory, 8568A000, D, F 30, 1, EfiLoaderCode, 85697000, 2, F 31, 4, EfiBootServicesData, 85699000, 5FE1, F 32, 7, EfiConventionalMemory, 8B67A000, 17A, F 33, 3, EfiBootServicesCode, 8B7F4000, 5E8, F 34, 0, EfiReservedMemoryType, 8BDDC000, C17, F 35, 9, EfiACPIReclaimMemory, 8C9F3000, 52, F 36, A, EfiACPIMemoryNVS, 8CA45000, 60, F 37, 6, EfiRuntimeServicesData, 8CAA5000, 4B7, F 38, 5, EfiRuntimeServicesCode, 8CF5C000, A3, F 39, 4, EfiBootServicesData, 8CFFF000, 1, F 40, 7, EfiConventionalMemory, 100000000, 16F000, F 41, 0, EfiReservedMemoryType, 000A0000, 60, 0 42, 0, EfiReservedMemoryType, 8D000000, 3000, 0 43, B, EfiMemoryMappedIO, E0000000, 10000, 0 44, B, EfiMemoryMappedIO, FE000000, 11, 1 45, B, EfiMemoryMappedIO, FEC00000, 1, 1 46, B, EfiMemoryMappedIO, FED00000, 1, 1 47, B, EfiMemoryMappedIO, FEE00000, 1, 100D 48, B, EfiMemoryMappedIO, FF000000, 1000, 100D
WebAssembly in ActionからWebAssembly Fileの構造についての続き。
その1の続き
本はバイナリのフォーマットで説明されていますが、S式に変換した方が分かりやすい。 wasm2watコマンドを使うとリーダブルなS式に変換できるので、それで中身を確認しました。 S式に変換するときに、ちょっと変わっている気がするので後でちゃんと調査したい。
Tyep sectionは、関数のシグネチャを記載しています。 引数と戻り値が記載されています。
(type (;0;) (func (result i32))) (type (;1;) (func (param i32) (result i32))) (type (;2;) (func)) (type (;3;) (func (param i32))) (type (;4;) (func (param i32 i32 i32) (result i32))) (type (;5;) (func (param i32 i64 i32) (result i64)))
Import sectionは、モジュールで使われるFunction、Table、Memory、Global Importの宣言です。
(import "wasi_snapshot_preview1" "proc_exit" (func (;0;) (type 3)))
関数のlist。S式には見つからず。
型付き配列のリファレンスを記載しています。
(table (;0;) 2 2 funcref)
リニアなメモリの記載です。
(memory (;0;) 256 256)
グローバル変数の定義です。
(global (;0;) (mut i32) (i32.const 5243936))
Exportされるシンボル情報です。
(export "memory" (memory 0)) (export "__indirect_function_table" (table 0))
このセクションは手持ちのファイルには見つからず。 多分、最初に呼ばれる関数のインデックス。普通にCで書くとmainになりそう。(未確認)
インスタンスが生成されるときにロードされる値。Global変数。
(elem (;0;) (i32.const 1) func 1))
関数の中身。多分S式に出てくるfuncだと思う。
(func (;1;) (type 2) nop)
ここのtype2が、Type section の (type (;2;) (func)) に対応しています。 この場合はC言語の void f(void) {} ですかね。
こちらも見つからず。インスタンスが生成されるときにロードされる値と書いてあるが、Elmentとの違いは良く分からない。
WebAssemblyファイルの構造まとめ。
WebAssembly in Actionから。
最初にpreambleとして、0x00, 0x61, 0x73, 0x6dがあります。文字列で"\0asm"です。 その次にバージョン1を示す、0x01, 0x00, 0x00, 0x00が続きます。
その後にセクション情報が続きますが、known sectionとcustom sectionの二種類があります。sectionは全てoptionです。
known sectionはリンク先の表に一覧があります。
実際にバイナリエディタでファイルの先頭を見てみたら、そんな感じになってました。