ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

WebAssembly.instantiateStreamingの第二引数

WebAssemblyがなかなか動かず、一つ分かったことがあるのでメモ書き。

emccが出力するファイル

www.manning.com

WebAssembly In ActionよりEmscriptenが出力するファイルは、以下の3つパターンのどれかになります。

  • WebAssembly module, JavaScript plumbing file, HTML template
  • WebAssembly module, JavaScript plumbing file
  • WebAssembly module

一番最後のWebAssembly Moduleだけを出力した場合は、実行時にdynamic linkingが行われます。dynamic linkingを実現するためには、side moduleを作る事必要があります。

side moduleを作るには、emccの-s SIDE_MODULE = 2 のオプションを指定し、Cの標準ライブラリとJavaScript JavaScript plumbing fileを使わない事を指示します。関数名をExportするために、emccに -s EXPORTED_FUNCTIONS=['_ai'] のようなオプションも必要です。

ここで、JavaScriptが良く分からないのでミニマムな環境を作ろうと思い、一番下の方法でやってみたらずいぶん長い間はまってしまいました。

WebAssembly.instantiateStreaming

本を見たり、他のサイトを見ているとこんな感じで、wasmファイルを呼び出していると思います。

const importObject = {
  env: {
    __memory_base: 0,
  }
};


WebAssembly.instantiateStreaming(fetch("ai.wasm"), importObject).then(result =>  {
    const value = result.instance.exports._ai();
    console.log("ai.wasm returns" + value)
  }).catch (err => {
    console.log(err)
  }
  )

これを実行すると、instantiateStreamingの第二引数(importObject)でエラーになります。

例えばこんなエラー

TypeError: WebAssembly.instantiate(): Import #3 module="GOT.mem" error: module is not an object or function

検索してみるとimportObject は省略可能と書いてあったり、env.tableが要るんだよとかいろんな情報が出てきます。

例えばこういうのです。

const importObj = {
  module: {},
  env: {
    memory: new WebAssembly.Memory({ initial: 256 }),
    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
  }
};

いろいろためしても全く動かないし、そもそもGOT.memって何だろうと悩み続けてようやく分かりました。

instantiateStreamingの二つめの引数は、一つ目の引数でfetchしたwasmファイルが動作するのに必要な環境を渡す必要があります。 何が正解かというのは、wasmファイルの作り方であったり、その内容によって変ってきます。

ちなみに、GOT.memでエラーが出ていたwasmファイルをwasm2watで見てみると、確かにmoduleの中で何か使おうとしています。

natu@Honoka:~/q/myproj/wasm-qumico/app_client/public$ wasm2wat ai.wasm
    (module
  (type (;0;) (func))
  (type (;1;) (func (result i32)))
  (import "env" "__stack_pointer" (global (;0;) (mut i32)))
  (import "env" "__memory_base" (global (;1;) i32))
  (import "env" "__table_base" (global (;2;) i32))
  (import "GOT.mem" "x" (global (;3;) (mut i32)))
  (import "env" "memory" (memory (;0;) 0))
  (import "env" "__indirect_function_table" (table (;0;) 0 funcref))
  (func (;0;) (type 0)
    call 1)

で、作ったwasmファイルに適切なimportObjectを作るのが大変なので、emccはwasmファイルと同時にJavaScript plumbing fileを生成してくれます。

実際に生成されたJavaScriptを見てみると、単にimportObjectを作るだけでなく、初期化など複雑な処理もやっていました。

というわけで振り出しに戻ります。

ゼロからのOS自作入門(その4)osbook_day03c

natsutan.hatenablog.com

の続きです。

    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でフレームバッファの塗りつぶしに成功 f:id:natsutan:20210416231124p:plain

ゼロからのOS自作入門(その3) Rustで書いたカーネルをブートさせる

ゼロからのOS自作入門(その2) メモリマップの取得 の続きです。

natsutan.hatenablog.com

Cで書かれたフレームバッファを塗りつぶすカーネルを作っていたら、UEFIアプリは無理でもOSだけならRustで書けるんじゃと思ってやってみました。Rustで書かれたカーネルに制御が移りフレームバッファを書き換えて画面表示を変えるところまで動きました。 後から続く人に参考になると思い、試行錯誤の手順を書いておきます。Rustは初心者です。

参考Webサイト こちらの記事が非常に役に立ちました。ありがとうございます。

www.akiradeveloper.com

この記事のベースとなっているバージョンは day03bです。

github.com

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してみたら気がつきました。

github.com

ここからOVMF_VARS.fdを持ってきて、$HOME/osbook/devenv のファイルを上書きすればOKです。無事qemuが立ち上がるようになりました。

ブート画面

f:id:natsutan:20210414094238p:plain

左上に一行開けて白い線が表示されています。やりました。

この後

どうも最初に暴走していた所が、このループのようです。

    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で落ちてそうです。

pdf2audiobook

Twitter で気がついた佐藤さんのpdf読み上げシステムの動画を見ました。

www.youtube.com

PDFをテキストにして読み上げサービスを使用して音声ファイルに変換しています。

このシステムで使用しているGoogleのテクノロジーです。ひとつひとつは聞いたことあります。

  • Cloud functions
  • Cloud Storage
  • OCR with Vision API
  • AutoML Table
  • Text to Speech

ここで上手いなと思ったところがAutoML Tableによるtext(paragraph)の識別です。

20年くらい前に、FPGAのデータシートを読むのにPDFをテキストに変換してPDAで読むというのをやっていました。 ここで問題になるのが、各ページにはいるヘッダー、ページ番号、図表です。

最初は戸惑ったのですが、なれてくると自力で飛ばせるようになります。ヘッダーはほぼ同じ内容ですし、ページ番号は一定間隔で数字が出てきて、図表は改行だらけのテキストが来たら図表だなと自分で判断して飛ばすことが出来るようになります。今回、ここの部分をAutoML Tableで上手くやっているのが驚きのポイントですね。

自分が苦労しただけあって、あーここでDeep Learning使うんだという感動がありました。 社内のDX推進とかそういうのも良いけど、今の生活がちょっと良くなる技術の使い方はもっともっと可能性あると信じています。

詳しく知りたい人は今すぐGoogle I/Oに登録しましょう。

events.google.com

emsdk のアップデート

emsdk メモ

バージョン等を確認するとき

emsdk list

update するとき

emsdk update

githubから持ってきた時はgit pullで最新にできる。

WSLに入っていたemsdkをアップデートした

emsdk listでバージョンを確認。

アップデート前

The *recommended* precompiled SDK download is 2.0.9 (d8e430f9a9b6e87502f826c39e7684852f59624f).

アップデート後

The *recommended* precompiled SDK download is 2.0.16 (80d9674f2fafa6b9346d735c42d5c52b8cc8aa8e).

アップデート成功

ゼロからのOS自作入門(その2) メモリマップの取得

ゼロからのOS自作入門、その1の続き

natsutan.hatenablog.com

ゼロからのOS自作入門 | マイナビブックス

本を読みながら、メモリマップの取得プログラムを書き写してみた。 ブートしたらメモリマップを取得して、memmapという名前のファイルにメモリマップをCSVで出力します。

実行時にメモリマップを取得できるのは凄い。加えて、OSが立ち上がってないのにファイルシステムが動いているのが凄い。Qemuと実機ではメモリマップがだいぶ違う事が分かったことが収穫です。

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

実機(OneMix 2S)でのメモリマップ

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 FileのKnown sections

WebAssembly in ActionからWebAssembly Fileの構造についての続き。

www.manning.com

その1の続き

natsutan.hatenablog.com

本はバイナリのフォーマットで説明されていますが、S式に変換した方が分かりやすい。 wasm2watコマンドを使うとリーダブルなS式に変換できるので、それで中身を確認しました。 S式に変換するときに、ちょっと変わっている気がするので後でちゃんと調査したい。

Type

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

Import sectionは、モジュールで使われるFunction、Table、Memory、Global Importの宣言です。

(import "wasi_snapshot_preview1" "proc_exit" (func (;0;) (type 3)))

Function

関数のlist。S式には見つからず。

Table

型付き配列のリファレンスを記載しています。

  (table (;0;) 2 2 funcref)

Memory

リニアなメモリの記載です。

 (memory (;0;) 256 256)

Global

グローバル変数の定義です。

 (global (;0;) (mut i32) (i32.const 5243936))

Export

Exportされるシンボル情報です。

(export "memory" (memory 0))
(export "__indirect_function_table" (table 0))

Start

このセクションは手持ちのファイルには見つからず。 多分、最初に呼ばれる関数のインデックス。普通にCで書くとmainになりそう。(未確認)

Element

インスタンスが生成されるときにロードされる値。Global変数。

(elem (;0;) (i32.const 1) func 1))

Code

関数の中身。多分S式に出てくるfuncだと思う。

(func (;1;) (type 2)
    nop)

ここのtype2が、Type section の (type (;2;) (func)) に対応しています。 この場合はC言語の void f(void) {} ですかね。

Data

こちらも見つからず。インスタンスが生成されるときにロードされる値と書いてあるが、Elmentとの違いは良く分からない。