こんにちは、みんみんです。
最近アウトプットが全然できてないなと思っていたら、最後の記事が2ヶ月前でした。
今回はRustでOSを作るチュートリアルを見つけたので、まずは最小のバイナリを作ってみました。
今回使用したチュートリアルがこちらです。
この記事はRust初心者であるみんみんのアウトプット記事です。厳密に言えば異なる内容の記載もあるかと思われます。
バイナリって何?
そもそもバイナリとは、コンピュータが直接実行できる形式のファイル のことだそうです。
たとえばRustであればmain.rsというファイルに主な処理を書きますが、コンパイルを行うと処理の内容が実行ファイルに切り替わります。
コンパイル前のソースコードは、人間にとっては読みやすいですがコンピューター側は理解できません。
よって、コンパイラを通して機械語に翻訳する必要があります。
コンピューターが理解できる機械語に翻訳され、生成されたファイルが実行ファイル(バイナリ)です。
どうやって作るのか
詳しい全体のコードについてはチュートリアルを参照いただければと思います。
標準ライブラリstdを無効にする
まず以下のコマンドをターミナルに打ち込んで、新しいCargoプロジェクトを作ります。
cargo new blog_os --bin --edition 2024
そして、デフォルトで入っているライブラリ、stdを使えないようにします。
#![no_std] //これを追加
fn main() {
println!("Hello, world!");
}
こう書くと、いつもならcargo buildでコンパイルができるのに、このようにエラーが出ます。
error: cannot find macro `println!` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
printlnができないのもそうですが、まずは2つ目のエラーである#[panic_handler] 関数を作る必要があります。
#[panic_handler] 関数を作る
#[panic_handler] 関数とは、コードがパニックを起こした場合にコンパイルが呼び出す関数 のことです。
普段は標準ライブラリに入っていますが、stdを使えないようにしたことが原因でエラーが出ているので自力で作ります。
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
ただ上記の場合、パニックが起きるとプログラムはスタック(※)を遡り、出会った各関数のデータを片っ端からクリーンアップしますが、この処理を有効にしておくと今後とんでもない処理時間がかかってしまいます。
ですので、以下のソースコードをCargo.tomlに追加し、クリーンアップを行わずにプログラムを終了させるようにします。
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
スタックとは、データ構造の一つです。たとえばデータを新しく増やした後に1つデータを削除する場合、古いものから削除するのが普通ですが、スタックの場合は新しいものから消していきます。
私は積ん読本をイメージしています。
標準ライブラリmainをも無効にする
この状態からcargo buildでコンパイルすると、以下のエラーでまた失敗します。
error: requires `start` lang_item
そもそもプログラム上では、main関数を実行する前に初期化の処理などを行っています。
「プログラムを開始するためのスタート地点が分からない」というのが今回のエラー理由ですが、実はこのstartもstdが提供してくれています。
stdが無効になっているのでmain関数も使えません。
よって、#![no_main]を追加し、main関数を削除します。
#![no_std]
#![no_main]
use core::panic::PanicInfo;
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
そして、main関数の代わりに自前でプログラムのスタート時点を決めます。
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
loop {}
}
リンカエラーを修正する
「よし、これでいけるな!」と思ったらまたエラーです。
ちなみにこちら、OSによってエラーの出方が違うそうです。
// Linuxの場合(長いので一部のみ)
error: linking with `cc` failed: exit code: 1
// Windowsの場合(長いので一部のみ)
error: linking with `link.exe` failed: exit code: 1561
// macOSの場合(長いので一部のみ)
error: linking with `cc` failed: exit code: 1
これらはリンカエラー と言います。
実際にコードをコンパイルして、実行可能なファイル(バイナリ)を作るための紐付け用プログラムであるリンカ がエラーを起こしている状態です。
先ほどプログラムのスタート時点のために作った_start関数がリンカに伝わってないみたいなのですが、
そもそも自作のOSを作る場合、リンカにてデフォルトで設定されている(らしい)Cランタイムに依存しない設定へ変更する必要があります。
そこで、ターミナルで以下のコマンドを打ちます。
rustup target add thumbv7em-none-eabihf
cargo build --target thumbv7em-none-eabihf
そして直るかなと思ったらまた同じエラーが出たので、効いてないみたいです。今度は以下の処理、ビルドコマンドの再設定が必要です。
OSごとにコマンドが違います。
// Linuxの場合
cargo rustc -- -C link-arg=-nostartfiles
// Windowsの場合
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
// macOSの場合
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
そして、.cargo/config.tomlというファイルを作成し、以下のコードを書きます。
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-nostartfiles"]
[target.'cfg(target_os = "windows")']
rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
[target.'cfg(target_os = "macos")']
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
そして今後はcargo buildを使用せず、各OSのビルドコマンドを使えばコンパイルできるようになります。これでようやく完成です!
さいごに
今回はチュートリアルを参考にして、Rustでバイナリを作ってみました。
間違ってることもあるかもしれませんので、もしよろしければご指摘いただけますと嬉しいです!