以前、「xv6-riscvのビルド&QEMUでの実行環境をMacに導入する」という記事を書きました。

MIT純正のxv6にはx86版(Intel 32bit)とRISC-V版があり、以前の記事では、買い替え前のIntel Mac上でRISC-V版をクロスコンパイラを用いてコンパイルし、QEMUで動かしていました。

しかしこの度、とある事情により、M1 MacBook Air上にx86版のxv6のビルド環境を整えなくてはならなくなりました。ところが、xv6は3年ほど前にx86版の開発を終了しRISC-V版の開発に移行しており、わざわざM1 Macでx86版をビルドする手法はどこにも載っていません。
というわけで今回は、M1でx86版のxv6をビルドできるようにした経緯を書き残しておきます。

実行環境

  • MacBook Air 2020
    • Chip: Apple M1
    • OS: macOS Monterey 12.4
    • RAM: 8GB
    • コンパイラ: i386-jos-elf-gcc (GCC) 4.6.1
    • エミュレータ: QEMU emulator 6.2.0

方法

arm64たるM1 Macでx86用のプログラムをビルドするのだから当然、通常通りには行きません。そもそもxv6のコンパイルにはクロスコンパイラが必要です。

リポジトリ[1]のREADMEを見てみると、Macではi386-jos-elf-gccというクロスコンパイラが推奨されていることが分かります。しかし、このクロスコンパイラはintel Macにしか対応しておらず、M1 Macでは利用できません。

幸い、M1 MacにはRosetta2があります。Rosetta2によりx86_64のバイナリも実行可能になるため、これを利用すればi386-jos-elf-gccも動くはずです。
というわけで、まずはx86_64版のhomebrewを手に入れ、x86_64版のhomebrewでx86版のgccクロスコンパイラをインストールし、それを使ってビルドします。今回導入するコンパイラは次の通り。

  • i386-jos-elf-gcc
  • i386-jos-elf-gdb (デバッグ用)

また、xv6を動かすにはエミュレータが必要ですが、今回はQEMU (qemu-system-i386)を使用します。QEMUに関してはarm64版で問題ありません。

※注:以下、zshを利用しているものとして進めます。

1. x86_64版のhomebrewを手に入れる

まずはターミナルを起動し、プロセスがRosetta2を通してx86_64のプロセスとして実行されるようにするために以下のコマンドを実行します。

% arch -x86_64 -e PATH= /bin/zsh --login

次にhomebrewをインストールします。

% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

こうすることで、/usr/local/bin/brewにx86_64版homebrewがインストールされます。現状、homebrew版はarm64版とx86_64版が以下のように併存している状態です。

アーキテクチャ パス
arm64 /opt/homebrew/bin/brew
x86_64 /usr/local/bin/brew

このままにしておくと、zshをx86_64として実行しているとき、PATHに/usr/local/bin/brewより先に/opt/homebrew/bin/brewが来てしまうため、arm64版のhomebrewを起動しようとしてしまいます。
そこで、~/.zshrcの末尾に以下を追記します。

if [ "$(arch)" = "arm64" ]; then
    export PATH="/opt/homebrew/bin:$PATH"
else
    export PATH="/usr/local/bin:$PATH"
fi

これにより、x86_64版とarm64版で別々のパスが優先され、homebrewが各々のアーキテクチャで使い分けられます。

(x86_64で実行した場合:)
% uname -m
x86_64
% which brew
/usr/local/bin/brew

(arm64で実行した場合:)
% uname -m                          
arm64
% which brew
/opt/homebrew/bin/brew

2. x86のgccクロスコンパイラをインストール

先述の通り、以下の2つをインストールします。

  • i386-jos-elf-gcc
  • i386-jos-elf-gdb (デバッグ用)

先程homebrewをインストールしたターミナルで、以下を実行。

% brew tap liudangyi/i386-jos-elf-gcc
% brew install i386-jos-elf-gcc i386-jos-elf-gdb

インストールが完了したら準備完了です。

3. xv6のビルド

まずはxv6をGitHubのリポジトリ[1]からクローン。

% git clone https://github.com/mit-pdos/xv6-public.git
% cd xv6-public

そしてビルド。同時にQEMUで実行もしてみます。

% make qemu

すると…. スクリーンショット 2022-07-15 14.22.03
無事起動!

手前のターミナルと後ろのQEMUで同じコマンド入力画面が2つ表示されてるけど…
リファレンス[2]曰く、どうもmake qemuではGUIウィンドウも出てくる仕様みたい。

以下のように、make qemu-noxでCUIオンリーにできるっぽいです。

% make qemu-nox

すると、こちらも無事動きました。
スクリーンショット 2022-07-15 1.19.26

とりあえずxv6のlsコマンドを実行。
スクリーンショット 2022-07-15 1.19.32
コマンドの実行も問題なさそうです。

ちなみに

今回はzshを% arch -x86_64 -e PATH= /bin/zsh --loginでRosetta2(x86-64)に切り替えて実行しましたが、この操作が必要なのはインストール時のみです。以後は、いちいち切り替えなくてもxv6のビルドと実行が可能です。

おわりに

今回はx86版のxv6をM1 Mac上で起動しましたが、ほぼ同じ流れでxv6-riscvもビルド環境を整えることが可能です(こちらも導入済み)。気が向いたらそちらも記事に書こうと思います。

なお、今回はクロスコンパイラの実行のためにRosetta2を使いましたが、xv6がビルドできるarm64版のgccクロスコンパイラがあればRosetta2がなくてもできる気はします。ただ、i386-jos-elf-gccはarm64に対応しておらず、代わりにarm64に対応したx86_64-elf-gcc-m32オプションを追加してコンパイルしたところ、なぜかxv6の起動時に無限ループが発生してしまいました。

まだまだ調査の余地はありそうですが、とりあえずRosetta2でi386-jos-elf-gccが問題なく動いてるしいっか、という結論に。いずれにせよ、公式で推奨されていると思われるi386-jos-elf-gccを使うに越したことはないと思います。

参考文献