Linux
assembly
OS
30日でできる!OS自作入門

30日でOS自作入門挑戦記(Linux) Day1

概要

謎のきっかけで、常々読んでみたいと思っていた魔導書『30日でできる!OS自作入門』が手元にやってきたので、挑戦してみる話です。
ただ、書籍内ではWindows環境が前提とされていて、著者様の用意した各種ツールや環境もすべてWindows向けのものだったり、出版が2006年であるため情報が古すぎたりするため、それをLinuxで現代的(?)になんとかやってみた内容を書いていきます。
本の内容そのものを書いていくわけではありません。

環境

  • ArchLinux

1日目(11/6)

Chapter1 『PCの仕組みからアセンブラ入門まで』

まず、本に理解不能な16進数の羅列が載っていて、それをバイナリエディタで写経せよと言われました。本ではBzを利用していますが、私はLinuxで利用可能なwxHexEditorを使って作業しました。ghex等でも良いと思います。

DeepinScreenshot_select-area_20181107133730.png

ここで写経した十数万行に及ぶ16進数の羅列(ほとんど0)は、起動可能なドライブのraw image dataのようです。本に従ってhelloos.imgという名前で保存しています。次にこのイメージをフロッピーに書き込めと書いてあるのですが、フロッピーなぞ存在しませんのでUSBに焼いてBootable USBを作っていきます。

# USBへの書き込みコマンドsdXは環境に合わせて調べて変更してください。
$ dd if=./helloos.img of=/dev/sdX

作成したBootable USBを適当なマシンに挿し、BIOSやUEFIから選択して起動すると、真っ黒な画面に"Hello, World"が表示されました。これがLinuxでもWindowsでもMacでもないOSで出力された"Hello, World"か……。

毎回USBに焼いてPCを再起動するのは面倒なので、書中ではqemuを用いてエミュレータ環境で実行をしています。でも私は手元にVirtualBoxがいたのでVirtualBoxでやってみました。
VirtualBoxはraw imageである.imgを起動できないので、予めVirtualBoxの仮想ドライブイメージであるVDIに変換してやる必要があります。この変換はVirtualBoxに付属してくるVBoxManageで行えます。

# 変換
$ VBoxManage convertfromraw --format VDI ./helloos.img ./helloos.vdi

すると、helloos.imgと同一のディレクトリにhelloos.vdiが生成されますので、これを適当に作った仮想マシンのドライブとして選択してやります。
DeepinScreenshot_VirtualBox_20181107135129.png
いざ起動。
DeepinScreenshot_VirtualBox Machine_20181107135233.png

やったね!実機のマシンでUSBから起動したときと同じ画面を表示できました。

書中では、ここから簡単なCPUの話とかが並んでいました。この部分には特にLinux特有の話はありません。

続いて、さっきは写経のように16進数を打ち込んで作成したバイナリファイルを、アセンブラをアセンブルして生成してみるパートです。なんと書中では作者様が自作されたアセンブラであるnaskを利用しているのですが、私はその原型であるNASMを使ってやってみました。基本的に同じ命令が利用できるので大した手間ではないのですが、一部互換性がない命令や推奨されない命令があったため修正を加えています。人生で初めてNASMを触りました。

helloos.asm
; hello-os

; 以下は標準的なFAT12フォーマットフロッピーディスクのための記述(と書いてあるけどこれでUSBでも動いた)

    DB      0xeb, 0x4e, 0x90
    DB      "HELLOIPL"      ; ブートセクタの名前を自由に書いてよい(8バイト)
    DW      512             ; 1セクタの大きさ(512にしなければいけない)
    DB      1               ; クラスタの大きさ(1セクタにしなければいけない)
    DW      1               ; FATがどこから始まるか(普通は1セクタ目からにする)
    DB      2               ; FATの個数(2にしなければいけない)
    DW      224             ; ルートディレクトリ領域の大きさ(普通は224エントリにする)
    DW      2880            ; このドライブの大きさ(2880セクタにしなければいけない)
    DB      0xf0            ; メディアのタイプ(0xf0にしなければいけない)
    DW      9               ; FAT領域の長さ(9セクタにしなければいけない)
    DW      18              ; 1トラックにいくつのセクタがあるか(18にしなければいけない)
    DW      2               ; ヘッドの数(2にしなければいけない)
    DD      0               ; パーティションを使ってないのでここは必ず0
    DD      2880            ; このドライブ大きさをもう一度書く
    DB      0,0,0x29        ; よくわからないけどこの値にしておくといいらしい
    DD      0xffffffff      ; たぶんボリュームシリアル番号
    DB      "HELLO-OS   "   ; ディスクの名前(11バイト)
    DB      "FAT12   "      ; フォーマットの名前(8バイト)
    TIMES   18 DB 0         ; とりあえず18バイトあけておく(本とは異なる命令を用いている)

; プログラム本体

    DB      0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
    DB      0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
    DB      0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
    DB      0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
    DB      0xee, 0xf4, 0xeb, 0xfd

; メッセージ表示部分

    DB      0x0a, 0x0a      ;改行x2
    DB      "hello, work."  ;12byte
    DB      0x0a            ;改行x1
    DB      0

    TIMES   0x1fe-($-$$) DB 0   ;0x1feまで0で繰り返し埋める(本とは異なる)

    DB      0x55, 0xaa

; ブートセクタ以外の記述らしい(よくわからない)
    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    TIMES   4600 DB 0
    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    TIMES   1469432 DB 0

書中のnaskのアセンブラと異なる点は、RESB命令ではなくTIMES命令を用いていることと、0x1feまでの0埋めに\$ではなく(\$-\$\$)を用いていることです。

RESB命令ではなくTIMES命令を用いたのは、NASMではRESB命令が推奨されないらしく、アセンブル時にwarningが出されたためです。TIMES命令は並べて書いた命令を指定回数繰り返すため、DB 0を指定回ということになり、結果としてRESB命令と同一の結果が得られます。

0埋めに\$ではなく(\$-\$\$)を用いたのは、\$単体ではNASMのアセンブルを通過しなかったためです。どうやらnaskでは簡単化のために\$単体でコード位置を示せるらしいのですが、NASMではそうは行かず、プログラムの最初の命令の位置を示す\$\$を併用するようです。確かにそう考えると計算も合致しますね。具体的に\$\$の中の値は、MBR(マスターブートレコード、電源投入時にドライブ中で一番最初に読まれる領域)の読み込み先の位置が入るようです。特に指定がなければx86では0x7c00が入るみたい?まだ知識が足りません。


修正:11/7
この考えでChapter2の書き換えを行っていたところ詰んだため間違いに気づきました。以下NASM公式ドキュメントからの引用です。

NASM supports two special tokens in expressions, allowing calculations to involve the current assembly position: the \$ and \$\$ tokens. \$ evaluates to the assembly position at the beginning of the line containing the expression; so you can code an infinite loop using JMP \$. \$\$ evaluates to the beginning of the current section; so you can tell how far into the section you are by using (\$-\$\$).

訳:NASMでは、2つの特殊トークンをサポートしているため、アセンブリの現在位置を計算することができます。\$と\$\$の2つがこれにあたります。\$は式を含むアセンブリの先頭位置を評価します。JMP \$とすればコードを無限ループさせることができます。\$\$は先頭位置から現在のセクションまでの長さを評価します。(\$-\$\$)とすれば先頭からセクションまでの距離を知ることができます。

はい。真逆ですね。(\$-\$\$)を使うというのは間違ってなかったのですが、理解が間違っていたようです。


それでは、作成したhelloos.asmをアセンブルしてみます。

$ nasm ./helloos.asm -o ./helloos.img

うまく行けば、バイナリファイルhelloos.imgが生成されます。先ほどと同じようにVirtualBoxで起動可能なVDIに変換した後、作成した仮想環境の仮想ドライブに指定して起動してみましょう。上で利用した環境のドライブを差し替えればいいと思います。
DeepinScreenshot_VirtualBox Machine_20181107142209.png

(上のコードをそのまま使っていれば)hello, work.と表示されました!やった!働きたくない!今日、学校になぜかハローワークの人が来て話を聞く儀式があったのです。

それはさておき、無事アセンブラから起動可能なイメージをアセンブルすることができて嬉しいです。書中でもChapter1はここまで、続きは2日目へ。→書きました