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

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

概要

30日でOS自作入門挑戦記(Linux) Day1の続きです。
GitHubリポジトリ

環境

  • ArchLinux
  • Vim
  • zsh

2日目(11/7)

Chapter2 『アセンブラ学習とMakefile入門』

いよいよ、とりあえず書いていたアセンブラの学習が始まるようです。
まずはテキストエディタが紹介されています。筆者様のおすすめのテキストエディタはTerapadだそうですが、私はVimを使っていきます。
image.png
(正しいVimのロゴ)

前回のアセンブラではバイナリの内容をアセンブラ内にベタ書きしていた部分を、今回のパートではアセンブラ命令に直していくようです。
以下に示すのが、書中のnaskアセンブラをNASM向けに修正したアセンブラです。

helloos.asm
; hello-os
    ORG     0x7c00          ; このプログラムがどこに読み込まれるのか

; 以下は標準的なFAT12フォーマットフロッピーディスクのための記述

    JMP     entry
    DB      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バイトあけておく

; プログラム本体

entry:
    MOV     AX,0            ; レジスタ初期化
    MOV     SS,AX
    MOV     SP,0x7c00
    MOV     DS,AX
    MOV     ES,AX

    MOV     SI,msg
putloop:
    MOV     AL,[SI]
    ADD     SI,1            ; SIに1を足す
    CMP     AL,0
    JE      fin
    MOV     AH,0x0e         ; 一文字表示ファンクション
    MOV     BX,15           ; カラーコード
    INT     0x10            ; ビデオBIOS呼び出し
    JMP     putloop
fin:
    HLT                     ; 何かあるまでCPUを停止させる
    JMP     fin             ; 無限ループ

msg:
    DB      0x0a, 0x0a      ; 改行を2つ
    DB      "helloos, day2."
    DB      0x0a            ; 改行
    DB      0

    TIMES   0x7dfe-($-$$)-0x7c00 DB 0        ; 0x7dfeまでを0x00で埋める命令

    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

1日目同様、RESB命令はTIMES命令とDB命令のコンボに置き換えています。
0x7dfeまで0で埋める命令を記述した箇所で、0x7c00による減算を行っていますが、これはORG(疑似)命令でメモリ上のどこにこのアセンブラの吐いた機械語が展開されるかを指定したことによるものです。じゃあ指定しなければいいじゃないかという話に思えますが、ORG命令による指定が行われていなければ動作しない命令が存在するため必要な作業となっているようです。

上のアセンブラから生成したイメージがちゃんとブートできるか試してみます。
DeepinScreenshot_select-area_20181108001625.png
OKですね。

この後、アセンブラに関する説明が書かれています。ここは特にLinux特有な箇所はないと思われます。

続いて、開発を便利にするために、ディスクイメージ全体をNASMで作成するのではなく、512byteのブートセクタのみを開発して空のディスクイメージに結合して利用する方式に切り替えるようです。書中では作者様が作成したディスクイメージ管理ツールを用いていますが、要は512byteのバイナリデータと数Mbyteの変更の必要がないディスクイメージテンプレートを結合するだけなので、以下のシェルスクリプトでいけます。shellはつよいので、これに権限を与えて

mkimg.sh
$ cat ./$1 > $2
$ cat ./disktmp.bin >> $2

↓これをアセンブルしたdisktmp.binを同一ディレクトリに配置し、

disktmp.asm
;disk tmplate asm
    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

スクリプトを実行すれば使えます。catの可能性は無限大(リダイレクトが強い)。

$ ./mkimg.sh [ブートセクタバイナリファイル] [出力イメージ名]

そして、これらの機能を総合したシェルスクリプトを作成しました。テンプレートのディスクイメージをいちいち用意するのも面倒なので、イメージをtarで圧縮して、そのバイナリデータをシェルスクリプトに埋め込むことでスクリプト単体で動作可能にしました。埋め込みの都合によりここに記載できなくなってしまったため、ファイルのリンクを載せておきます。
mkimg.sh
一度テキストエディタ等で開くと起動できなくなることがあるので注意です。(というかアセンブラのが分量少ないので、自動でアセンブルして結合したほうが賢かったかも?)

また、書中で触れられているipl.lstにあたる、アセンブラ上のどの命令がどのような機械語に変換されたかのリストファイルもNASMでは以下のコマンドで出力できるようです。

$ nasm [filename.asm] -l [filename.lst]
boot.lst
     1                                  ; hello-os
     2                                  
     3                                      ORG     0x7c00          ; このプログラムがどこに読み込まれるのか
     4                                  
     5                                  ; 以下は標準的なFAT12フォーマットフロッピーディスクのための記述
     6                                  
     7 00000000 EB4E                        JMP     entry
     8 00000002 90                          DB      0x90
     9 00000003 48454C4C4F49504C            DB      "HELLOIPL"      ; ブートセクタの名前を自由に書いてよい(8バイト)
    10 0000000B 0002                        DW      512             ; 1セクタの大きさ(512にしなければいけない)
    11 0000000D 01                          DB      1               ; クラスタの大きさ(1セクタにしなければいけない)
    12 0000000E 0100                        DW      1               ; FATがどこから始まるか(普通は1セクタ目からにする)
    13 00000010 02                          DB      2               ; FATの個数(2にしなければいけない)
    14 00000011 E000                        DW      224             ; ルートディレクトリ領域の大きさ(普通は224エントリにする)
    15 00000013 400B                        DW      2880            ; このドライブの大きさ(2880セクタにしなければいけない)
    16 00000015 F0                          DB      0xf0            ; メディアのタイプ(0xf0にしなければいけない)
    17 00000016 0900                        DW      9               ; FAT領域の長さ(9セクタにしなければいけない)
    18 00000018 1200                        DW      18              ; 1トラックにいくつのセクタがあるか(18にしなければいけない)
    19 0000001A 0200                        DW      2               ; ヘッドの数(2にしなければいけない)
    20 0000001C 00000000                    DD      0               ; パーティションを使ってないのでここは必ず0
    21 00000020 400B0000                    DD      2880            ; このドライブ大きさをもう一度書く
    22 00000024 000029                      DB      0,0,0x29        ; よくわからないけどこの値にしておくといいらしい
    23 00000027 FFFFFFFF                    DD      0xffffffff      ; たぶんボリュームシリアル番号
    24 0000002B 48454C4C4F2D4F5320-         DB      "HELLO-OS   "   ; ディスクの名前(11バイト)
    24 00000034 2020               
    25 00000036 4641543132202020            DB      "FAT12   "      ; フォーマットの名前(8バイト)
    26 0000003E 00<rept>                    TIMES   18 DB 0         ; とりあえず18バイトあけておく
    27                                  
    28                                  ; プログラム本体
    29                                  
    30                                  entry:
    31 00000050 B80000                      MOV     AX,0            ; レジスタ初期化
    32 00000053 8ED0                        MOV     SS,AX
    33 00000055 BC007C                      MOV     SP,0x7c00
    34 00000058 8ED8                        MOV     DS,AX
    35 0000005A 8EC0                        MOV     ES,AX
    36                                  
    37 0000005C BE[7400]                    MOV     SI,msg
    38                                  putloop:
    39 0000005F 8A04                        MOV     AL,[SI]
    40 00000061 83C601                      ADD     SI,1            ; SIに1を足す
    41 00000064 3C00                        CMP     AL,0
    42 00000066 7409                        JE      fin
    43 00000068 B40E                        MOV     AH,0x0e         ; 一文字表示ファンクション
    44 0000006A BB0F00                      MOV     BX,15           ; カラーコード
    45 0000006D CD10                        INT     0x10            ; ビデオBIOS呼び出し
    46 0000006F EBEE                        JMP     putloop
    47                                  fin:
    48 00000071 F4                          HLT                     ; 何かあるまでCPUを停止させる
    49 00000072 EBFD                        JMP     fin             ; 無限ループ
    50                                  
    51                                  msg:
    52 00000074 0A0A                        DB      0x0a, 0x0a      ; 改行を2つ
    53 00000076 68656C6C6F6F732C20-         DB      "helloos, day2."
    53 0000007F 646179322E         
    54 00000084 0A                          DB      0x0a            ; 改行
    55 00000085 00                          DB      0
    56                                  
    57 00000086 00<rept>                    TIMES   0x7dfe-($-$$)-0x7c00 DB 0        ; 0x7dfeまでを0x00で埋める命令
    58                                  
    59 000001FE 55AA                        DB      0x55, 0xaa

続いて、Makefileの作成です。Makefileは環境のセットアップやソフトウェアのインストールなどをmakeコマンドで簡単に行えるように設定ができるファイルです。今回は本の字面だけ読んで、私の環境に合わせて同じ動作をするであろうものをオリジナルで作成しました。作成したMakefileは以下のものです。

Makefile
boot.bin : boot.asm Makefile
    nasm boot.asm -o boot.bin -l boot.lst

halloos.img : boot.bin Makefile
    sh ../../my_tools/mkimg.sh boot.bin halloos.img

img :
    make -r halloos.img

asm :
    make -r boot.bin

vdi : halloos.img
    rm ./halloos.vdi -f
    sh ../../my_tools/convert.sh halloos.img halloos.vdi

clean :
    ls | grep -v -E 'Makefile|boot.asm|halloos.img|halloos.vdi' | xargs rm -f

src_clean :
    ls | grep -v -E 'Makefile|boot.asm' | xargs rm -f

ディレクトリ構造などは私の開発環境(GitHubリポジトリで確認できる)に対応していますので、変な場所でmakeしようとしても相対パス等を利用している箇所でエラーになると思います。
また、私は検証にVirtualBoxを利用しているので、make vdiで簡単にVirtualBoxで起動可能な仮想ドライブイメージを作成できるようにしています。

DeepinScreenshot_select-area_20181108030208.png

ちゃんと利用できました。
今日はここまで、Chapter2も終了です。
明日はChapter3/3日目です。