2017/07/22 に行なわれた Kernel/VM 勉強会の発表資料です。
C 以外でのベアメタルプログラミングXXXJul 22th, 20171 / 27
View Slide
自己(事故)紹介oruminTwitter ID: @kotatsu_mi最近自宅のサーバの電源と PT3 が雷に撃たれました。雷サージ対策は忘れずにやろうね!アンテナ線にも電話線にもサージプロテクタは存在する2 / 27
緒言ベアメタルプログラミングいわゆる freestanding 環境libc 他が存在しないみなさんよくやってますよね?よくつかわれるのは C や C++どうして C をつかうのか3 / 27
ベアメタルプログラミングと Cメリット 生ポインタで特定のアドレスへの I/O 操作といったことが簡単にできる,また,この分野で枯れており,ノウハウも豊富デメリット 型システムが貧弱,最近のクールな言語機能が使えない4 / 27
C 以外でベアメタルプログラミング複数の代替手段D 言語,C♯,Rust …(いますぐ採用するとかなくても)今後のために C 以外でやるノウハウも得ておかないとそのうち死ぬのでRust既にかなりの規模のそれなりにちゃんと動く OS が作られている(Redox)実績C♯ も 2011 年ぐらいから Cosmos という OS があったりするC♯ でのベアメタルプログラミングも調べて話そうとすると話者の力量不足でどっちつかずの内容になるので割愛しましたそのうちやるかも?5 / 27
RustOCaml と C++ の血を感じる言語ML 系の強力な型システム所有権など,独特だが C++ を既に知ってる人間がわりと違和感なく使える強力な機能nightly コンパイラで freestanding をサポートstable 入りはまだですかー!様々な機能や設計が C++ のカウンターっぽいc.f.) 過去の KernelVM での @omasanori さんの発表https://speakerdeck.com/omasanori/rustru-men-yi-qian-fa-biao-ban6 / 27
Rust(nightly)のインストールとテスト$ curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=nightly -y$ . ~/.cargo/env$ cargo new --bin hello-rust7 / 27
Rust のテスト右のようなディレクトリツリーが出来あがる作成されるファイルは下例実行は cargo runcargo は gem みたいなもの。ビルドツールかつパッケージマネージャ。Rust のパッケージは https://crates.io にあるhello-rustCargo.tomlsrc/main.rs[package]name = ”hello-rust”version = ”0.1.0”authors = [”orumin ”][dependencies]fn main() {println!(”Hello, world!”);}8 / 27
Rust でベアメタルをするにはどうすれば良いかUEFI apps を例に step-by-step で紹介します!Linux の人は手元で試してみてくださいTL;DRhttps://github.com/orumin/rust-uefi-sample.git を clone して makerunrust-uefi-sample/MakefileCargo.tomlx86_64-unknown-efi.jsonsrc/lib.rs9 / 27
Cargo.toml[package]name = ”uefi-sample”version = ”0.1.0”authors = [”orumin ”][dependencies]uefi = { git = ”https://github.com/orumin/rust-uefi” }rlibc = ”1.0”[lib]crate-type = [”staticlib”][profile.dev]panic = ”abort”[profile.release]panic = ”abort”10 / 27
Cargo.tomlパッケージ名や依存パッケージを記述[lib] セクションをつくり,src/lib.rs を記述するとライブラリとして作られる今回はオブジェクトファイルだけ作成して手動でリンクしたかった依存パッケージは名前だけ書いておくと crates.io からもってくるpath = に続けてパスを記述すると他のディレクトリのプロジェクトを依存に指定git = に続けて Git repo を記述すると自動で clone とビルドしてくれます11 / 27
target jsonCargo は json で custom ターゲットを作れる{”arch”: ”x86_64”,”os”: ”efi”,”llvm-target”: ”x86_64-efi-none-gnu”,”target-endian”: ”little”,”target-pointer-width”: ”64”,”function-sections”: false,”no-compiler-rt”: true,”data-layout”: ”e-m:e-i64:64-f80:128-n8:16:32:64-S128”,”linker”: ”x86_64-efi-pe”,”linker-flavor”: ”ld”,”pre-link-args”: [”subsystem”,”10”]}12 / 27
target json今回は UEFI 向けに PE バイナリを作りたいので target の json を記述arm-none-eabi などでも記述ただの x86_64 の ELF なら要らないかも?13 / 27
ソースコード#![no_std]#![feature(asm)]#![feature(intrinsics)]#![feature(lang_items)]extern crate uefi;extern crate rlibc;use core::mem;#[allow(unreachable_code)]#[no_mangle]pub extern ”win64” fn efi_main(hdl: uefi::Handle, sys: uefi::SystemTable)-> uefi::Status {uefi::initialize_lib(&hdl, &sys);let bs = uefi::get_system_table().boot_services();let rs = uefi::get_system_table().runtime_services();14 / 27
ソースコードuefi::get_system_table().console().write(”Hello, World!\n\rvendor: ”);uefi::get_system_table().console().write_raw(uefi::get_system_table().vendor());uefi::get_system_table().console().write(”\n\r”);loop {}uefi::Status::Success}#[no_mangle]pub fn abort() -> ! {loop {}}#[no_mangle]pub fn breakpoint() -> ! {loop {}}#[no_mangle]pub extern ”C” fn _Unwind_Resume() -> ! {loop {}}#[lang = ”eh_personality”]#[no_mangle]pub extern fn rust_eh_personality() {}15 / 27
ソースコード#[lang = ”panic_fmt”]#[no_mangle]pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,_file: &’static str,_line: u32) -> ! {loop {}}16 / 27
ソースコード解説#![no_std]標準ライブラリをリンクしなくなる標準ライブラリのうち,アーキテクチャ非依存な便利機能は core ライブラリをuse すれば使えるものもある17 / 27
no_std でフォーマット出力e.g.)use core::fmt::Write;impl Write for Writer {fn write_str(&mut self, s: &str) -> core::fmt::Result {引数でうけた文字列 s について出力を自分で実装;Ok(()) // 失敗なら Err(foo) でエラーの値返す}}これだけで,write!() マクロの実装が得られるlet mut w = Writer {};write!(w, ”foo {}”, bar).unwrap();18 / 27
#![feature(lang_items)]eh_personality とか panic_fmt とかeh_personalityGCC の personality 関数の代わりのシンボルLLVM で言語の例外ハンドリングに使うunwindpanic_fmtRust で panic!() を使うときのシンボルデバッグ文字列出力とか作っておくと良い19 / 27
externextern “C” などとすると C 互換の ABI になる#[no_mangle] 付けると名前マングルをしない既存のライブラリを置き換える何かを作るのにもべんりかもUEFI は x86_64 だと Microsoft’s 64-bit call convention を使うextern “win64” で対応20 / 27
ではビルドをしてみましょう$ cargo install xargo$ xargo build --target x86_64-unknown-efiXargo を導入cargo build の際に core ライブラリなどをターゲットのアーキでビルドしてくれる素の cargo だとヘンなアーキテクチャ使うのに core をそこに合わせてビルドしないといけない21 / 27
binutils とリンク今回は UEFI ターゲットの PE バイナリを作りたいので普通の binutils だとダメ$ curl -O https://orum.in/distfiles/x86_64-efi-pe-binutils.tar.xz$ mkdir -p $PWD/toolchain$ tar xf x86_64-efi-pe-binutils.tar.xz -C $PWD/toolchain$ export PATH=$PATH:$PWD/toolchain/usr/bin/$ pushd$ cd target/x86_64-unknown-efi/debug$ ar x *.a$ popd$ x86_64-efi-pe-ld --gc-sections --oformat pei-x86-64 \--subsystem 10 -pie -e efi_main \-o bootx64.efi \target/x86_64-unknown-efi/debug/*.o22 / 27
実行$ dd if=/dev/zero of=fat.img bs=1k count=1440$ mformat -i fat.img -f 1440 ::$ mmd -i fat.img ::/EFI$ mmd -i fat.img ::/EFI/BOOT$ mcopy -i fat.img bootx64.efi ::/EFI/BOOT$ curl -O https://orum.in/distfiles/ovmf.fd$ qemu-system-x86_64 -enable-kvm -net none -m 1024 \-bios ovmf.fd -usb -usbdevice disk::fat.img23 / 27
これだと見えづらいので……https://github.com/orumin/rust-uefi-sample/blob/master/src/lib.rsGraphic Output Protocol の取得,設定BLT24 / 27
conclusionRust 便利C 以外でもベアメタルしたい!25 / 27
こぼれ話1質問:@nullpo_head さん「自作 OS 勉強会だと Box が使えなくて辛いとかあったけどどうやってるの?」回答:「まだそこらへんがっつりやるステージに入れてないが,そもそも Box とかは自前実装する方法があるので問題がないと思う。」https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/custom-allocators.html実は上記の通り公式ドキュメントがあるベアメタル以外でも使えるのでゲームエンジンとかやる人にも良いのではないか26 / 27
こぼれ話2Rust のコンパイラ機能#[start]特定のシンボルの前(たとえば pub fn app_main() とかの前の行)に付けておくと,エントリポイントにしてくれる#[link_section = “.test_section”]この行のところが ELF の.test_section になる。便利。27 / 27