起動情報の受け取りに構造体を使う

1
2
3
4
5
6
7
; BOOT_INFO関係
CYLS EQU 0x0ff0 ; ブートセクタが設定する
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 色数に関する情報。何ビットカラーか?
SCRNX EQU 0x0ff4 ; 解像度のX
SCRNY EQU 0x0ff6 ; 解像度のY
VRAM EQU 0x0ff8 ; グラフィックバッファの開始番地

それぞれこのアドレスに格納するので、

1
2
3
4
5
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};

先頭アドレスから構造体のポインタに型変換して受け取る。

1
2
struct BOOTINFO *binfo = (struct BOOTINFO*)0x0ff0;
xsize = binfo->scrnx;

文字表示

フォントデータ作成

著者作ツールのmakefont.exehankaku.txtからfont.binを作成する。さんざん著者作ツールは使わないと言ってきたが、バイナリにしただけなので容赦してほしい。

オブジェクトファイル作成

このバイナリはただのバイナリなので、オブジェクトファイルにする必要がある。

1
objcopy -I binary -O elf32-i386 -B i386 --redefine-sym _binary_font_bin_start=hankaku font.bin font.o

-Iで入力ファイル形式、-Oで出力ファイル形式、-Bでアーキテクチャを指定する。このあたりの情報はobjcopy --helpで出てくる。

--redifine-symは、このような構文を取る。

1
2
--redefine-sym old=new
Change the name of a symbol old, to new.

このオプションを指定しないと、バイナリの開始アドレスが_binary_font_bin_startになってしまう。今後の開発の互換性のために、本書に合わせてシンボル名をhankakuにしている。

objdumpで中身を確認すると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ objdump -x font.o

font.o: file format elf32-i386
font.o
architecture: i386, flags 0x00000010:
HAS_SYMS
start address 0x00000000

Sections:
Idx Name Size VMA LMA File off Algn
0 .data 00001000 00000000 00000000 00000034 2**0
CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
00000000 l d .data 00000000 .data
00000000 g .data 00000000 hankaku
00001000 g .data 00000000 _binary_font_bin_end
00001000 g *ABS* 00000000 _binary_font_bin_size

このバイナリの開始アドレスはhankakuで取得できることがわかる。

ややこしくなってきたので、Makefileを全部載せる。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
IPL_NAME=ipl10
IMG_NAME=os
SYS_NAME=haribote
ASM_HEAD=asmhead
BOOT_PACK=bootpack
OS_LINKERSCRIPT=os.ld
NASK_FUNC=naskfunc
FONT_NAME=font

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 $(FONT_NAME).o -o $(BOOT_PACK).bin
cat $(ASM_HEAD).bin $(BOOT_PACK).bin > $(SYS_NAME).sys

# フォントの生バイナリであるfont.binが既にあるものとする
$(FONT_NAME):
objcopy -I binary -O elf32-i386 -B i386 --redefine-sym _binary_font_bin_start=hankaku $(FONT_NAME).bin $(FONT_NAME).o

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

run:
qemu-system-x86_64 -fda .\$(IMG_NAME).img

pre:
nkf --overwrite *.nas *.c
sed -i s/0x7dfe-\\$$/0x01fe-\(\\$$\-\\$$\\$$\)/ ipl10.nas
sed -i s/_io_/io_/g naskfunc.nas
sed -i s/^\\[/\;\\[/ naskfunc.nas asmhead.nas

標準ライブラリ導入とsprintf

sprintfは文字列をフォーマットするだけなので、フォーマットだけsprintfにやらせてメモリに書き込んでもらい、そこにある文字列をHariboteOSの機能で出力する。

-nostdlibを外してmakeすると早速エラー。

1
2
# gcc -m32 -fno-pie -T os.ld bootpack.c naskfunc.o font.o -o bootpack.bin
/usr/lib/gcc/x86_64-linux-gnu/7/32/libgcc_s.so.1: error adding symbols: File in wrong format

シンボルとあるのでリンカでエラーがあると見て、コンパイルとリンクを分離。

1
2
3
# Makefileの一部
gcc -c -m32 -fno-pie $(BOOT_PACK).c -o $(BOOT_PACK).o
ld -m elf_i386 -T $(OS_LINKERSCRIPT) $(NASK_FUNC).o $(BOOT_PACK).o $(FONT_NAME).o -o $(BOOT_PACK).bin

makeすると、

1
2
bootpack.o: In function `HariMain':
bootpack.c:(.text+0xd3): undefined reference to `sprintf'

sprintfの定義がないらしい。試しにnaskfunc.nassprintfを定義してみると、

1
2
3
4
5
6
7
;12行目
;GLOBAL io_load_eflags, io_store_eflags
GLOBAL io_load_eflags, io_store_eflags, sprintf

;末尾
sprintf:
RET

一応ビルドは成功してイメージファイルが出来上がった。しかしsprintfRETするだけなので何も起こらない。

ただし上記のMakefileのldの部分で、naskfunc.obootpack.oよりも先に来ていることに注意。

これでsprintfの定義が無いだけであるとわかったので、どっかからゲッツしてくればいいのだが、色々探しても無いので自分で書くことにする。

1
2
3
4
// myclib.c
int sprintf(char* buf, const char* format, ...) {
// 後で書く
}

セグメンテーション

メモリをブロック化し、そのブロックの先頭アドレスを0として扱える機能。プログラムをORG 0として作ればよくなる。

32bitではMOV AL,[DS:EBX]は16bitのときと意味が異なり、EBXにセグメントの開始アドレスを足す。
セグメントはGDT(Global segment Descriptor Table)(構造体の配列)で管理する。セグメントレジスタは16bitだがCPUの仕様で下位3bitが使えないので、範囲は0~8191。

管理はC言語側で行う。

IDT(Interrupt Descriptor Table)

割り込み記述子表。割り込み番号0~256に対して、発生した番号に対応する関数を呼び出す。
仕組みはGDTと同じ。

おわりに

標準ライブラリ関連が面倒だったので自分で書いたが、本書の先の方を見てもsprintf strcmpくらいしか使ってないようなので必要になったときに実装していこうと思う。