OS自作入門|Macで環境構築からアセンブリでHello Worldまで

自作コンパイラに一段落がついて「もっと低レイヤを知りたい」と思ったので、今日から自作OSに取り組む事にした。

ただ、OS自作のための環境構築やHello Worldまでの道のりが思ったよりキツかったので、備忘録としてまとめていく。

僕の開発環境

僕の開発環境は以下の通り。

  • Mac 10.14.6
  • GNU assembler version 2.24
  • GNU ld (GNU Binutils) 2.24
  • gcc version 9.3.0
  • QEMU 4.2.0(自作OSを動かす仮想マシン)

開発環境の準備

もし、あなたが使っている環境がLinuxであれば、仮想マシンqumuを用意するだけで開発環境を揃えることができる。

しかし、Macの場合は少々手間がかかる。なぜなら、Macは標準のCコンパイラにclangを採用しているが、Linuxのasコマンドやldコマンドでは使えるオプションが使えない等、自作OSを行う上ですごく不便な環境と言えるからだ。

なので、Macのclangとは別にgccとかas等を用意し、そのgccを自作OSに使えるように準備する必要がある。

また、自作OSを動かすための仮想マシンqemuも一緒に用意する。

qemuのインストール

qemuのインストールはHomebrewを使えばコマンド一発でできる。

brew install qemu

うまくインストールできたかの確認のため、qemu-system-x86_64 --versionを実行してバージョンが表示されればOK.

gccのインストール

gccをインストールするためにgccに必要なライブラリをインストールしておく必要がある。僕の場合は、下記の通りインストールすればOKだった。

brew install gmp mpfr libmpc gcc

この時点でインストールしているgccは自作OS用に使うものではなく、後述するbinutilや自作OS用のgccの環境を構築するためのもの。

binutilのインストール

次にbinutilと言う、低レイヤよりのツールを集めたライブラリをインストールする。

そのためにもbinutilのビルドの時にclangではなく、先ほど入れたgccを使うようにするための準備をする。僕の場合はgcc-9がインストールされていたので、下記のようにコマンドを実行する。

export CC=/usr/local/bin/gcc-9
export LD=/usr/local/bin/gcc-9

次に以下のコマンドを実行。これはPREFIXで自作OS用のツールをビルドする場所を指定し、TARGETでツール名に名前を追加するようにしている。

例えば、asと言う名前のままビルドすると、Macで標準に入っているasと衝突する可能性があるので、TARGETを指定する事でi386-elf-asと言う名前でビルドするようにしている。

export PREFIX="/usr/local/i386elfgcc"
export TARGET=i386-elf
export PATH="$PREFIX/bin:$PATH"

今回はディレクトリ名をi386elfgccとかツールの名前をi386-elfに指定しているが、ここの部分は好みで変えて良い。

次に下記のコマンドを順番に実行していけば、binutilのビルドは環境する。

mkdir /tmp/src
cd /tmp/src
curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz
tar xf binutils-2.24.tar.gz
mkdir binutils-build
cd binutils-build
../binutils-2.24/configure --target=$TARGET --enable-interwork --enable-multilib --disable-nls --disable-werror --prefix=$PREFIX 2>&1 | tee configure.log
make all install 2>&1 | tee make.log

上手くいけば、i386-elf-as --versionでバージョンが表示されるはず。

もし、makeとかconfigure時に「ここのソースコードはコンパイルできないよ」と言ったエラーが発生した場合は、binutilのビルドに必要なライブラリが入っていなかったり、バージョンが古い可能性があるので、その時はHomebrewでインストールorバージョンアップをしよう。

自作OS用のgccのインストール

次に自作OS用のgccのインストールを行う。

gccのバージョンは今回は9.3.0に設定しているが、ここも好みのバージョンを入れれば良い。

cd /tmp/src
curl -O https://ftp.gnu.org/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.bz2
tar xf gcc-9.3.0.tar.bz2
mkdir gcc-build
cd gcc-build
../gcc-9/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers
make all-gcc 
make all-target-libgcc 
make install-gcc 
make install-target-libgcc 

上手くいけばi386-elf-gcc --versionでバージョンが表示される。

Hello Worldを表示させる

自作OSの環境構築が終わったので、ここからアセンブリでHello Worldを表示させるようにする。

ネットの記事ではnasmを使っているものが多いが、今回はGNU asを使っていく。

そもそもOSはどうやって動いているのか?

実際のコードの説明の前に、そもそもOSはどのように動いているかの説明をしていく。

僕たちがパソコンを開いて電源を付けると、セキュリティソフトが起動したりデスクトップに画面が表示されたり音が出たりするわけだが、最初は「BIOS」と言う512byteで表現されたメモリの部分を読み取っているのだ。

Wikipediaにも、下記のようにBIOSが説明されている。

BIOSソフトウェアはパーソナルコンピュータ (PC) に組み込まれており、電源投入と同時に実行される。主な働きはハードウェアを初期化して記憶装置からブートローダーを呼び出すことで、そのほかにキーボードやディスプレイなどの入出力装置とプログラムが相互に作用するための抽象化した層(英: abstract layer)を提供する。システムのハードウェアの差異はBIOSによって隠され、プログラムはハードウェアに直接アクセスするのではなくBIOSが提供するサービスを利用する。近代的なオペレーティングシステム (OS) はこの抽象化した層を使用せず、OS自身が持つデバイスドライバでハードウェアに直接制御する場合がある。

Basic Input/Output System - Wikipedia

つまり自作OSで最初にやるべきことは、BIOSを自作することだと言える。

どのようにBIOSを作れば良いか。

ではどのようにBIOSを作れば良いかと言うと、結論から言うと下記のアセンブリコードを作れば良い。

.global _start
_start:
mov $0x0e, %ah
mov $'H', %al
int $0x10
mov $'e', %al
int $0x10
mov $'l', %al
int $0x10
int $0x10
mov $'o', %al
int $0x10

.org 510
.word 0xaa55 

最初のmovとかintの部分でHelloが表示されるようにし、.org 510で510byteまでを0で埋める処理を行っている。

そして最後の.word 0xaa55だが、BIOSは最後の511byte目と512byte目をチェックして、「今実行しようとしているプログラムは何なのか?」を判断している。

511byte目を55、512byte目をaaと表現することで「これはOSなんだな」と判断してくれて処理を行ってくれる。(0xaa55と書いているが、リトルエンディアン形式なので55が先になる)

アセンブリでコードを作れば、あとは下記のコマンドでバイナリを作成しよう。

i386-elf-as -o bios.o bios.s
i386-elf-ld --oformat=binary -o bios.img bios.o

あとはqemu-system-x86_64 bios.imgで仮想マシンを実行すれば、コンソール画面にHelloが表示されるはず。(Hello worldじゃないけど)

参考文献

os-tutorial/11-kernel-crosscompiler at master · cfenollosa/os-tutorial

Prerequisites for GCC - GNU Project - Free Software Foundation (FSF)

Using as - Assembler Directives

reverse engineering - Disassembling A Flat Binary File Using objdump - Stack Overflow