【ゼロからのOS自作入門】MikanOSをRustに移植する 1章・2章

https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E3%81%AEOS%E8%87%AA%E4%BD%9C%E5%85%A5%E9%96%80-%E5%86%85%E7%94%B0-%E5%85%AC%E5%A4%AA/dp/4839975868

↑を進める傍らRustにも移植しているのでメモ書き程度に残しておく。

1章

uefi-rsが優秀なので特に言うことはない。

Cargo.toml

[package]
name = "rust-uefi"
version = "0.1.0"
authors = ["callus-corn <mtfm_ymzk@yahoo.co.jp>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
uefi = { git = "https://github.com/rust-osdev/uefi-rs.git" }

cargo/config.toml

[unstable]
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]

[build]
target = "x86_64-unknown-uefi"

main.rs

#![feature(abi_efiapi)]
#![no_std]
#![no_main]

use uefi::prelude::*;
use core::panic::PanicInfo;
use core::fmt::Write;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[entry]
fn efi_main(_handle: Handle, system_table: SystemTable<Boot>) -> Status {
    writeln!(system_table.stdout(), "Hello, world!").unwrap();

    loop {}
    //Status::SUCCESS
}

参考:

neriring.hatenablog.jp

os.phil-opp.com

2章

かなり詰まった。 詰まった個所は後述。

main.rs

#![feature(abi_efiapi)]
#![no_std]
#![no_main]

use uefi::prelude::*;
use uefi::table::boot::{MemoryType, MemoryAttribute};
use uefi::proto::loaded_image::LoadedImage;
use uefi::proto::media::fs::SimpleFileSystem;
use uefi::proto::media::file::{File, RegularFile, Directory, FileMode, FileAttribute};
use core::panic::PanicInfo;
use core::fmt::Write;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

//数値→ascii列変換
fn u32_to_ascii(number: u32) -> [u8;8] {
    let mut result: [u8;8] = [0;8];
    let radix = 16;
    let len = result.len();
    for i in 0..len {
        let target_4bit = ((number >> i*4) % radix) as u8;
        if target_4bit <= 0x9 {
            result[i] = 0x30 + target_4bit;
        } else if target_4bit >= 0xa && target_4bit <= 0xf {
            result[i] = 0x57 + target_4bit;
        }
    }
    result
}

//数値→ascii列変換
fn u64_to_ascii(number: u64) -> [u8;16] {
    let mut result: [u8;16] = [0;16];
    let radix = 16;
    let len = result.len();
    for i in 0..len {
        let target_4bit = ((number >> i*4) % radix) as u8;
        if target_4bit <= 0x9 {
            result[i] = 0x30 + target_4bit;
        } else if target_4bit >= 0xa && target_4bit <= 0xf {
            result[i] = 0x57 + target_4bit;
        }
    }
    result
}

#[entry]
fn efi_main(handle: Handle, system_table: SystemTable<Boot>) -> Status {

    writeln!(system_table.stdout(), "Hello, world!").unwrap();

    //↓メモリマップの取得
    //メモリマップを書き込むバッファ(サイズは適当)
    let memory_map_buffer: &mut [u8] = &mut [0; 4096*4];
    //帰ってくるのはmap_keyとdescriptorのイテレータ(イテレータの中にメモリマップがある)
    //このResultはuefi-rs独自実装のためunwrap_successを使う。
    let (_memory_map_key, descriptor_iter) = system_table.boot_services().memory_map(memory_map_buffer).unwrap_success();
    //↑メモリマップの取得
    
    //↓ルートディレクトリを開く
    //ほしいプロトコルを指定してHandleを渡す。帰ってくるのはUnsafeCell<プロトコル>なのでgetで中身を取り出す
    let loaded_image = system_table.boot_services().handle_protocol::<LoadedImage>(handle).unwrap_success().get();
    //生ポインタを解決するのでunsafe
    let device;
    unsafe {
        device = (*loaded_image).device();
    }
    let file_system = system_table.boot_services().handle_protocol::<SimpleFileSystem>(device).unwrap_success().get();
    //再度生ポインタ
    let mut root_dir: Directory;
    unsafe {
        root_dir = (*file_system).open_volume().unwrap_success();
    }
    //↑ルートディレクトリを開く

    //↓メモリマップの保存
    //保存するファイルの作成とFileHandleの取得
    let memory_map_file_handle = root_dir.open("\\memmap",FileMode::CreateReadWrite,FileAttribute::empty()).unwrap_success();
    //RegularFileに変換する必要あり(unsafe)
    let mut memory_map_file: RegularFile;
    unsafe {
        memory_map_file = RegularFile::new(memory_map_file_handle);
    }
    //ヘッダの書き込み
    let header: &[u8] = "Type, PhysicalStart, NumberOfPages, Attribute\n".as_bytes();
    memory_map_file.write(header).unwrap_success();
    //メモリディスクリプタの書き込み
    for descriptor in descriptor_iter {
        let memory_type:u32 = match descriptor.ty {
            MemoryType::RESERVED => 0,
            MemoryType::LOADER_CODE => 1,
            MemoryType::LOADER_DATA => 2,
            MemoryType::BOOT_SERVICES_CODE => 3,
            MemoryType::BOOT_SERVICES_DATA => 4,
            MemoryType::RUNTIME_SERVICES_CODE => 5,
            MemoryType::RUNTIME_SERVICES_DATA => 6,
            MemoryType::CONVENTIONAL => 7,
            MemoryType::UNUSABLE => 8,
            MemoryType::ACPI_RECLAIM => 9,
            MemoryType::ACPI_NON_VOLATILE => 10,
            MemoryType::MMIO => 11,
            MemoryType::MMIO_PORT_SPACE => 12,
            MemoryType::PAL_CODE => 13,
            MemoryType::PERSISTENT_MEMORY => 14,
            _ => 0xffff_ffff,
        };
        let physical_start = descriptor.phys_start;
        let number_of_pages = descriptor.page_count;        
        let attribute: u64 = match descriptor.att {
            MemoryAttribute::UNCACHEABLE => 0x1,
            MemoryAttribute::WRITE_COMBINE => 0x2,
            MemoryAttribute::WRITE_THROUGH => 0x4,
            MemoryAttribute::WRITE_BACK => 0x8,
            MemoryAttribute::UNCACHABLE_EXPORTED => 0x10,
            MemoryAttribute::WRITE_PROTECT => 0x1000,
            MemoryAttribute::READ_PROTECT => 0x2000,
            MemoryAttribute::EXECUTE_PROTECT => 0x4000,
            MemoryAttribute::NON_VOLATILE => 0x8000,
            MemoryAttribute::MORE_RELIABLE => 0x10000,
            MemoryAttribute::READ_ONLY => 0x20000,
            MemoryAttribute::RUNTIME => 0x8000_0000_0000_0000,
            _ => 0,
        };

        //上手く変換できなかったのでゴリ押し
        //絶対にもっといい方法がある
        let buffer: &mut [u8] = &mut [0;63];
        let memory_type = u32_to_ascii(memory_type);
        let physical_start = u64_to_ascii(physical_start);
        let number_of_pages = u64_to_ascii(number_of_pages);
        let attribute = u64_to_ascii(attribute);

        //memory_typeゴリ押し
        let memory_type_len = memory_type.len();
        //下駄。paddingといっていいんだろうか?
        let padding = 0;
        for i in 0..memory_type_len {
            buffer[padding+i] = memory_type[memory_type_len-i-1];
        }
        buffer[padding+memory_type_len] = 0x2c;//,
        buffer[padding+memory_type_len+1] = 0x20;//空白

        //physical_startゴリ押し
        let physical_start_len = physical_start.len();
        let padding = memory_type_len + 2;
        for i in 0..physical_start_len {
            buffer[padding+i] = physical_start[physical_start_len-i-1];
        }
        buffer[padding+physical_start_len] = 0x2c;//,
        buffer[padding+physical_start_len+1] = 0x20;//空白

        //number_of_pagesゴリ押し
        let number_of_pages_len = number_of_pages.len();
        let padding = memory_type_len + 2 + physical_start_len + 2;
        for i in 0..number_of_pages_len {
            buffer[padding + i] = number_of_pages[number_of_pages_len-i-1];
        }
        buffer[padding+number_of_pages_len] = 0x2c;//,
        buffer[padding+number_of_pages_len+1] = 0x20;//空白

        //attributeゴリ押し
        let attribute_len = attribute.len();
        let padding = memory_type_len + 2 + physical_start_len + 2 + number_of_pages_len + 2;
        for i in 0..attribute_len {
            buffer[padding+i] = attribute[attribute_len-i-1];
        }
        buffer[padding+attribute_len] = 0x0a;//LF

        memory_map_file.write(buffer).unwrap_success();
    }
    //書き込みの反映。自分の環境ではこれを書かないと変更が反映されなかった
    memory_map_file.flush().unwrap_success();
    //↑メモリマップの保存

    writeln!(system_table.stdout(), "Kernel did not execute").unwrap();

    loop {}
    //Status::SUCCESS
}

(cargo関係は変更なし)

詰まったところ

・対応する実装が分からない

例えばgBS->OpenProtocol()を移植しようと思ってもuefi-rsからは直接使えません。

SystemTable.boot_services().handle_protocol::(handle)とか、uefi-rsでいい感じに移植されている実装を使う必要があります。

そのためには実装に結構目を通さなくては行けなくて、最初の一回目としてはなかなかしんどかったです。

ただ、実装に目を通していくうちにuefi-rsは中々使いやすそうだということもわかってきたのでやったかいはあったかなと思います。(2章の段階でで言うには早いかもしれません)

また、恐らく extern "efiapi" とかで直接UEFIを叩くことはできると思います。

しかし、それはそれでポインタを結構使わないといけないのでなかなか修羅の道だと思います。

・そもそも何を移植しているのかわからない

これはProtocol関係の移植をしているときに結構悩みました。

何を移植しようとしているのか?ということと、どうすれば移植できるのか?ということの両方が欠けていて途方に暮れた形になります。

結局UEFI関係の調べ物を少ししていくうちにぼんやりと概念を理解して、uefi-rsからも比較的簡単に使えることが分かって、ようやく実装にこぎつけられました。

・stdが使えない

これは結構苦痛でした。 出来上がったコードにも苦しさがにじみ出ています。

「format!マクロが使えなくて文字列に変換できない」とか「Stringが使えなくてto_string()が使えない」とか

ここら辺を解決するには結局Rust力が必要で、私は力及ばずゴリ押しする羽目に…。

stdに限らずuefi-rsのエラーがRust力不足で理解できず実装方針を変更することも度々ありました。

感想

ここまでの感想としては

uefi-rs は結構すごい

・「何を作るのか?」「どうやって作るのか?」ということのうち最低でも片方は分かってないと結構しんどい

・rustはコンパイルに失敗するとうるさいのに、成功するとすごく静かでキュンとする

という感じです。