RustでOSを書いてみる(環境構築編)

本記事は[自作OS Advent Calendar 2016](http://www.adventar.org/calendars/1666) 16日目の 記事です。

# はじめに

大学に入った頃からOS自作に興味があり、「[30日でできる！　OS自作入門](https://book.mynavi.jp/ec/products/detail/id=22078)」を見ながらはりぼてOSもどきをつくって遊んでいました(OS自作を始めたのは本が出版される少し前です)。

時は流れて2016年・・・RustでOSが作れるという話をどこかで知って以来、RustでOSを作ってみたいという気持ちが強くなり、作り始めることにしました。

作り始めてみたものの、いまだにほとんど何もできてない状態ですが、環境構築してブートプログラムからRustのコードを呼び出すくらいはできたので、本記事ではRustを使ってOS自作したい人に向けて環境構築の手順について解説していきたいと思います。

# 注意事項

本記事で解説する手順はi686(Intel 32bit CPU)環境向けとなります。また、Mac OS X El Capitan、及びDebian or Ubuntu上でビルドすることを念頭に置いているため、特にWindows環境で構築したい方については、別途読みかえが必要になります。

# Let's 環境構築!

## rustupのインストール

Rustをインストールする方法は様々ありますが、Rustにもpyenvやrbenvのようなツールがあります。それが`rustup`です。将来的にi686以外の環境向けにクロスコンパイルしたい場合に大変便利なので、rustupを使ってRustをインストールしてみたいと思います。

ここでは解説していない使い方などは、下記GitHubリポジトリのReadme.mdに書いてあります。
https://github.com/rust-lang-nursery/rustup.rs

rustupのインストールには、下記コマンドを打つだけです(Mac, Linuxの場合)。

```bash
curl https://sh.rustup.rs -sSf | sh
```

このコマンドを打つことで、インストールスクリプトをダウンロードして、あとはスクリプトが勝手にインストールしてくれます。スクリプトの実行が終わった頃には、rustupのインストールも完了です！

rustupがインストールできたら、環境にもよりますが、シェルを立ち上げた際に

```
~/.cargo/env
```

が読み込まれるように設定をしておくと良いでしょう。

## Rustのインストール

さて、次はrustupを使ってRustをインストールしていきます。

インストールに当たって注意してほしいのは、RustでOSを自作するために必要な言語機能は`nightly `ビルドのRustを使わなければならないということです。

この辺はできればstableで作れるようにしてくれると良いのですが・・・。

というわけで、まずはnightlyビルドのものを使えるよう、下記コマンドを実行してください。

```bash
rustup install nightly
```

また、環境にもよりますが、ターゲット環境と開発環境のアーキテクチャが異なる場合、本記事でいうとi686以外の環境(x86_64など)でi686向けに開発したい方は、i686向けのクロスコンパイル環境を構築する必要があります。ただ、rustupを使えば下記コマンド1つで構築できます!

```bash
rustup target add i686-unknown-linux-gnu
```

インストールが完了したら、デフォルトで使用するRustコンパイラをnightlyにするため、下記コマンドを実行してください。

```bash
rustup default nightly
```

ここまでできたら、シェル上で`rustc --version`と打ってみてrustcがバージョン情報を出力すれば、インストールは完了です。

もう少し詳細なインストール情報が見たい場合は、`rustup show`と打ってみてください。環境によりますが、下記のような情報が出てくるはずです。

```
Default host: x86_64-apple-darwin

installed toolchains
--------------------

stable-x86_64-apple-darwin
nightly-2016-07-03-x86_64-apple-darwin
nightly-x86_64-apple-darwin (default)

installed targets for active toolchain
--------------------------------------

i686-unknown-linux-gnu
x86_64-apple-darwin
x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-x86_64-apple-darwin (default)
rustc 1.15.0-nightly (d9bdc636d 2016-11-24)
```

## NASM、binutils、QEMUのインストール

Rust言語だけでOSが書けるわけではなく、特にブート周りはアセンブラが必要になります。さらに、これらプログラムとRustで書いたプログラムをリンクするためのリンカ(ld)やコンパイラが出力したオブジェクトファイルを解析するための`objdump`や`readelf`といったコマンドが必要となります。objdumpやreadelfなどのコマンドは、`binutils`というツールに含まれています。

これらをインストールしていきましょう。まずはNASMです。

アセンブラといえば、他にもgas(GNU Assembler)もありますが、自分はintel記法に慣れ親しんだ人間なので、やっぱりintel記法で書けるNASMを選択したくなります。

NASMについてはクロスコンパイルなどは考えなくて良いので、各OS環境ごとにHomebrewやapt-getなどを使ってインストールしてください。参考までに、Homebrew(Mac)とapt-get(Ubuntu、Debian)の場合のインストールコマンドを書いておきます。

Homebrewの場合

```
brew install nasm
```

apt-getの場合

```
apt-get install nasm
```

次にbinutilsのインストールについてですが、こちらは環境によってはNASMと同じようにインストール可能ですが、ここではソースコードからのビルドを行いたいと思います。

まずは下記からソースをダウンロードしましょう。
http://ftp.gnu.org/gnu/binutils/

ソースを展開したら、ソースディレクトリへ移動して下記コマンドでビルドします。なお./configureの際のオプションについては、i686用のバイナリが欲しいので、`i686-unknown-linux-gnu`を指定してください。
※ version 2.27の場合です。

```sh
tar zxvf binutils-2.27.tar.gz
cd binutils-2.27
./configure --target=i686-unknown-linux-gnu
make
sudo make install
```

そして、作ったOSを動かすためのエミュレータ(QEMU)をインストールします。QEMUはnasmと同じくHomebrew(Mac)とapt-get(Ubuntu、Debian)でインストール可能です。

Homebrewの場合

```
brew install qemu
```

apt-getの場合

```
apt-get install qemu
```

これでNASMとbinutils、QEMUを使えるようになりました。

## mformat, mcopyのインストール

最後は、ディスクイメージを作成するために使うmformatとmcopyのインストールです。これらのコマンドは`mtools`というツール群の中に入っているので、mtoolsをインストールします。

Homebrewの場合

```
brew install mtools
```

apt-getの場合

```
apt-get install mtools
```

これで、OS開発に必要な最低限の道具は揃いました。

# ビルドできるか試してみる

## ブートローダーを用意する

ブート周りの話を解説し始めるとAdvent Calendar1日分かそれ以上かかるので、ここではサクッと既存のアセンブリコードを持ってくることにします。

まず下記2つのコードをダウンロードしてください。

- [ipl.asm](https://github.com/kotetuco/ructiss/blob/master/kernel/arch/i686-unknown-linux-gnu/asm/ipl.asm) 
- [secondboot.asm](https://github.com/kotetuco/ructiss/blob/master/kernel/arch/i686-unknown-linux-gnu/asm/secondboot.asm)

ダウンロードしたら、下記コマンドでこれら2つのコードをアセンブルします。

```bash
nasm -f bin -o ipl.bin ipl.asm
nasm -f bin -o secondboot.bin secondboot.asm
```

## Rust Core Libraryを用意する

さて、ここからはRustのコードを書いていくことになるのですが、その前に、Rustのコードを書くためにはRustの基本的なデータ型などが定義された`Rust Core Library(libcore)`というライブラリが必要なので、先にビルドしておきましょう。

まず、Rustのコード一式をダウンロードします。
https://github.com/rust-lang/rust

ダウンロードしたら、以下のコマンドを使って、Coreライブラリのみコンパイルしてください。

```sh
rustc --verbose --target=i686-unknown-linux-gnu --crate-type=rlib --emit=link,dep-info -C opt-level=2 -C no-prepopulate-passes -C no-stack-check -Z no-landing-pads -o libcore.rlib ./rust-lang/src/libcore/lib.rs
```

注意：Rustのソースコードのバージョンとインストール済みRustのバージョンによっては、コンパイルに失敗することがあります。その場合は、ダウンロードするRustのコードのバージョンを上げるなり下げるなりしてください。

## Rustで書いたOSのコードをコンパイルする

ここまで出来たら、いよいよRustのコードを書いてみたいと思います。init_os.rsという名前で、次のようなコードを書いてみてください。

```Rust
#![feature(lang_items)]
#![feature(start)]
#![no_main]
#![feature(no_std)]
#![no_std]
#![feature(asm)]

#[no_mangle]
fn hlt() {
    unsafe {
        asm!("hlt");
    }
}

#[no_mangle]
#[start]
pub extern fn init_os() {
    loop {
        hlt()
    }
}

#[lang = "eh_personality"]
extern fn eh_personality() {}

#[lang = "panic_fmt"]
extern fn panic_fmt() -> ! { loop {} }
```

詳しく解説しませんが、何をやっているかというと、単純に`hlt`というアセンブリ命令を打ち続けているだけです。なので、仮にビルドできたとしても、QEMUの画面に出てくるのは真っ黒な画面です :disappointed_relieved:

書けたら、上記コードをコンパイルしましょう。

```bash
rustc --target=i686-unknown-linux-gnu --crate-type=staticlib --emit=obj -C lto -C opt-level=2 -C no-prepopulate-passes -C no-stack-check -Z verbose -Z no-landing-pads -o init_os.o init_os.rs --extern core=libcore.rlib
```

## リンク・OSイメージ作成

さて、あとはリンクしてOSのイメージを作るだけです。

まずここまでコンパイルして作成したオブジェクトファイル一式をリンクしましょう・・・と言っても、実はinit_os.rsをコンパイルした際に既にlibcoreをリンクしているため、ここでは単純にリンカスクリプトで定義したオブジェクトファイルの形式に変換しているだけという感じです。

リンカスクリプトについても、奥が深いため、ここでは詳しく解説せず、既存のリンカスクリプトを持ってきましょう。

[kernel.ld](https://github.com/kotetuco/ructiss/blob/master/kernel/arch/i686-unknown-linux-gnu/kernel.ld)
※ もしリンカやリンカスクリプトについて詳しく知りたい方がいれば、「[リンカ・ローダ実践開発テクニック](http://shop.cqpub.co.jp/hanbai/books/38/38071.html)」という本を読むと良いと思います。

ダウンロードできたら、下記コマンドでリンクを行います。

```bash
i686-unknown-linux-gnu-ld -v -nostdlib -Tdata=0x00310000 init_os.o -T kernel.ld -o kernel.bin
```

次に、最初にアセンブルしたオブジェクトファイルのうち、secondboot.binを今リンクしたkernel.binの前にくっつけます。くっつけるには下記コマンドを使います。

```bash
cat secondboot.bin kernel.bin > first_os.sys
```

ここまでくれば、あと一息です。最後は、最初にアセンブルしたオブジェクトファイルのうちipl.binとfirst_os.sysを用いて、フロッピーディスクイメージを作ります。
※ 「今時フロッピーイメージかよ・・・」とか、「フロッピーディスクって何？」という指摘はごもっともです。。この辺も今後isoイメージとかに対応していきたいです。

```bash
mformat -f 1440 -C -B ipl.bin -i first_os.img ::
mcopy -i first_os.img first_os.sys ::
```

ここまで出来れば、OSのビルドは大成功です！

せっかくなので、QEMUで動かしてみましょうか。

```bash
qemu-system-i386 -m 32 -localtime -vga std -fda first_os.img -monitor stdio
```
※ QEMUだけ、`i386`なので注意。

下のような真っ黒なQEMUの画面が出てくれば、(全く見栄えは良くないですが)大成功です！

![スクリーンショット 2016-12-16 1.01.36.png](https://qiita-image-store.s3.amazonaws.com/0/122081/61941e87-278d-0c65-57e6-f7aaca3d5f05.png)

# あとがき

なるべく、OS自作に興味があり、かつRustを使う方を念頭に置いて書いてみました。少しでもお役に立てたら幸いです。

なお、上記環境構築手順をDockerfileにまとめたものがあるので、よかったらどうぞ。
https://github.com/kotetuco/rust-baremetal

本当はRustでOSを作る利点について話をしたいと思いましたが、利点を話せるほどRustに触れてるわけではないので、またの機会に話をしたいと思います。