IPL(Initial Program Loader)がプログラムをロードするようにする(03_day/harib00a)

新出アセンブリ

命令
JC jump if carry。キャリーフラグが1だったらジャンプする
JNC jump if not carry
JBE jump if below or equal。<=
JB jump if below。<
<identifier> EQU <value> const <identifier> = <value>;

[ES:BX]

1
MOV AL,[ES:BX]

ESはエクストラセグメント。BXESともに16bit。アドレス指定に16bitしか使えないと困るから、[ES:BX]で、ES*16+BXのアドレス指定ができるようにしたらしい。なぜ*16なのかは不明。最大で0xFFFF0+0xFFFF=1114095のアドレスが指定できる。

セグメントレジスタはアドレス指定に必須

省略可能だが、[123][DS:123]と解釈されていたらしい。よって、DSを0にしないとアドレスが狂う。

起動

makefile

1
2
3
4
5
6
ipl: ipl.nas
nasm ipl.nas -o ipl.bin

img: ipl
mformat -f 1440 -C -B ipl.bin -i os.img ::
mcopy ipl.bin -i os.img ::

今まで通り起動するとロードに失敗した。

https://qemu.weilnetz.de/doc/qemu-doc.html に書いてあるとおり、フロッピーディスクとしてイメージファイルを読むには-fdaオプションを使用する。

1
PS> qemu-system-x86_64 -fda .\os.img

何も表示されず、起動に成功した。

なお、途中の節はフロッピーの構造に合わせて読み込むためのアセンブリを書いているだけなのでパス。

OS本体を作る(03_day/harib00f)

haribote.sysを別に作り、ipl.binからロードする。

OS本体をイメージファイルにコピーするには、mcopyを使えば良い。

4.8 Mcopy
The mcopy command is used to copy MS-DOS files to and from Unix. It uses the following syntax:

mcopy [-tnvm] MSDOSsourcefile

https://www.gnu.org/software/mtools/manual/mtools.html

haribote.sysがMSDOSsourcefileに当たるので、

1
mcopy haribote.sys -i os.img ::

-iオプションは前回参照。

1
2
3
4
5
6
7
8
9
default: ipl haribote
mformat -f 1440 -C -B ipl.bin -i os.img ::
mcopy haribote.sys -i os.img ::

ipl:
nasm ipl.nas -o ipl.bin

haribote:
nasm haribote.nas -o haribote.sys

画面モードをグラフィックスに切替(03_day/harib00g)

ビデオモード設定

詳細
AH 0x00
AL 0x03=16色テキスト 80*25
0x13=VGAグラフィックス、320*200*8bitカラー
戻り値 なし

ファイル構成が複雑になってきたのでmakefileで変数を使うようにした。

1
2
3
4
5
6
7
8
9
10
11
12
13
IPL_NAME=ipl10
IMG_NAME=os
SYS_NAME=haribote

default: $(IPL_NAME) $(SYS_NAME)
mformat -f 1440 -C -B $(IPL_NAME).bin -i $(IMG_NAME).img ::
mcopy $(SYS_NAME).sys -i $(IMG_NAME).img ::

$(IPL_NAME):
nasm $(IPL_NAME).nas -o $(IPL_NAME).bin

$(SYS_NAME):
nasm $(SYS_NAME).nas -o $(SYS_NAME).sys

32bitにするための準備(03_day/harib00h)

32bitのCコンパイラを使いたいが、BIOSは16bit用のアセンブリで書かれているため、互換性がない。そこでBIOSから得る情報は先に読み込み、メモリに格納しておく。

C言語導入(03_day/harib00i)

著者作のツールを使わない縛りなので、ここが一番たいへんだった。Cで書かれたプログラムをコンパイルした後、リンカスクリプトを使ってリンクし、asmhead.binと合わせて、haribote.sysを作る必要がある。つまり、このようになる。

1
2
3
4
5
6
7
8
9
asmhead.nas--(nasm)-> asmhead.bin --\
\
bootpack.c --(gcc)-> bootpack.bin --(cat)->(*)
/
os.ld --/

(*)--> haribote.sys --(mtools)--> haribote.img
/
ipl10.nas --(nasm)-> ipl10.bin --/

まず、asmhead.nas、そのままではアセンブルに失敗した。

1
2
$ nasm asmhead.nas -o asmhead.bin
asmhead.nas:58: error: unrecognised directive [INSTRSET]

どうやら[]のつくディレクティブは著者作のnask固有のようなので、この行を削除する。

次に問題となるのがリンカスクリプト、HariboteOSに合わせてバイナリを作らなければいけないので、P.460にある構造を作る必要がある。

私はリンカスクリプトなど全く知らないので、 https://vanya.jp.net/os/haribote.html こちらからお借りした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* os.ld */
OUTPUT_FORMAT("binary");

SECTIONS
{
.head 0x0 : {
LONG(64 * 1024) /* 0 : stack+.data+heap の大きさ(4KBの倍数) */
LONG(0x69726148) /* 4 : シグネチャ "Hari" */
LONG(0) /* 8 : mmarea の大きさ(4KBの倍数) */
LONG(0x310000) /* 12 : スタック初期値&.data転送先 */
LONG(SIZEOF(.data)) /* 16 : .dataサイズ */
LONG(LOADADDR(.data)) /* 20 : .dataの初期値列のファイル位置 */
LONG(0xE9000000) /* 24 : 0xE9000000 */
LONG(HariMain - 0x20) /* 28 : エントリアドレス - 0x20 */
LONG(0) /* 32 : heap領域(malloc領域)開始アドレス */
}

.text : { *(.text) }

.data 0x310000 : AT ( ADDR(.text) + SIZEOF(.text) ) {
*(.data)
*(.rodata*)
*(.bss)
}

/DISCARD/ : { *(.eh_frame) }

}

gccのオプションは、今回は32bitで作り、リンカスクリプトを指定するので、以下のオプションを使う。また、標準ライブラリは使わないので-nostdlibもつける。

1
2
3
4
5
6
7
8
9
10
11
https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
-m32
-m64
-mx32
-m16
-miamcu
Generate code for a 16-bit, 32-bit or 64-bit environment. The -m32 option sets int, long, and pointer types to 32 bits, and generates code that runs on any i386 system.

https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options
-T script
Use script as the linker script. This option is supported by most systems using the GNU linker. On some targets, such as bare-board targets without an operating system, the -T option may be required when linking to avoid references to undefined symbols.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
IPL_NAME=ipl10
IMG_NAME=os
SYS_NAME=haribote
ASM_HEAD=asmhead
BOOT_PACK=bootpack
OS_LINKERSCRIPT=os.ld

default: $(IPL_NAME) $(SYS_NAME)
mformat -f 1440 -C -B $(IPL_NAME).bin -i $(IMG_NAME).img ::
mcopy $(SYS_NAME).sys -i $(IMG_NAME).img ::

$(IPL_NAME):
nasm $(IPL_NAME).nas -o $(IPL_NAME).bin

$(ASM_HEAD):
nasm $(ASM_HEAD).nas -o $(ASM_HEAD).bin

$(SYS_NAME): $(ASM_HEAD)
gcc -m32 -nostdlib -T $(OS_LINKERSCRIPT) $(BOOT_PACK).c -o $(BOOT_PACK).bin
cat $(ASM_HEAD).bin $(BOOT_PACK).bin > $(SYS_NAME).sys

正常に起動したが、C言語側からHLTを呼び出していないので、このようにCPU使用率が100%になっている。

CからHLTを呼ぶ(03_day/harib00j)

1
2
3
4
5
6
7
8
9
10
11
                     asmhead.nas--(nasm)-> asmhead.bin --\
\
naskfunc.nas--(nasm)-> naskfunc.o --\ \
\ \
bootpack.c --(gcc)-> bootpack.bin --(cat)->(*)
/
os.ld --/

(*)--> haribote.sys --(mtools)--> haribote.img
/
ipl10.nas --(nasm)-> ipl10.bin --/

例によって、asmhead.nasと、naskfunc.nas[]の行を削除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
IPL_NAME=ipl10
IMG_NAME=os
SYS_NAME=haribote
ASM_HEAD=asmhead
BOOT_PACK=bootpack
OS_LINKERSCRIPT=os.ld
NASK_FUNC=naskfunc

default: $(IPL_NAME) $(SYS_NAME)
mformat -f 1440 -C -B $(IPL_NAME).bin -i $(IMG_NAME).img ::
mcopy $(SYS_NAME).sys -i $(IMG_NAME).img ::

$(IPL_NAME):
nasm $(IPL_NAME).nas -o $(IPL_NAME).bin

$(ASM_HEAD):
nasm $(ASM_HEAD).nas -o $(ASM_HEAD).bin

$(NASK_FUNC):
nasm -f elf $(NASK_FUNC).nas -o $(NASK_FUNC).o

$(SYS_NAME): $(ASM_HEAD) $(NASK_FUNC)
gcc -m32 -fno-pie -nostdlib -T $(OS_LINKERSCRIPT) $(BOOT_PACK).c $(NASK_FUNC).o -o $(BOOT_PACK).bin
cat $(ASM_HEAD).bin $(BOOT_PACK).bin > $(SYS_NAME).sys

clean:
rm *.bin *.sys *.img *.o

ここで、新たなオプション、nasmの-f elf
https://www.nasm.us/doc/nasmdoc2.html#section-2.1.2
gccに入力するオブジェクトファイルなので、ELF(Executable and Linking Format)にアセンブルする必要がある。

エラーが出る。

1
bootpack.c:(.text+0x7): undefined reference to `io_hlt'

試しにnaskfunc.nas_io_hltio_hltに置換したら、うまく行った。

1
2
3
4
5
6
7
; naskfunc
; TAB=4
GLOBAL io_hlt

io_hlt:
HLT
RET

これで良いかと思いきや、またエラーが出る。

1
bootpack.c: undefined reference to `_GLOBAL_OFFSET_TABLE_'

どうやら、GCCがセキュリティ確保のためにデフォルトで位置独立実行形式(PIE)となるようにしているらしい。今回のアセンブリでは位置が重要なので、-fno-pieオプションでこれを無効化する。

位置独立コード(いちどくりつコード、英: position-independent code、PIC)または位置独立実行形式(いちどくりつじっこうけいしき、英: position-independent executable、PIE)とは、主記憶装置内のどこに置かれても絶対アドレスに関わらず正しく実行できる機械語の列である。
https://ja.wikipedia.org/wiki/位置独立コード

そうしてやっと起動できた。

確かにCPU使用率100%は改善されている。

おわりに

今回はいままでで一番大変だった。特に、著者作ツールを使わないせいでかなり調べることとなった。

参考文献