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自身が持つデバイスドライバでハードウェアに直接制御する場合がある。
つまり自作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