読者です 読者をやめる 読者になる 読者になる

yukke_longriders

ぼちぼちがんばる

LinuxでOS自作入門 2日目

今回からレジスタなどが出てきて、アセンブラらしくなります。

そして、NASM に関することコード等はこれからは省きたいと思います。

アセンブラの基本

基本文法とレジスタGAS_基本文法 CapmNetworkx86-レジスタ CapmNetworkを参考にしました。
軽くまとめていきます。

GASの命令はニーモニックといい、マシン語命令のオペコードとオペランドで扱うデータサイズを指定するオペレージョンサフィックスで構成されています。
オペレーションサフィックス

b バイト(8ビット)
s ショート (16ビット整数)またはsingle(32ビット浮動小数点数)
w ワード(16ビット)
l ロング(32ビット整数または64ビット浮動小数点数)
q クワッド(64ビット)
となります。(表は引用です。)

オペランドは引数のことです。

ニーモニック(オペコード + サフィックス) オペランド

という形式になります。
例)

movw $0, %ax
addw $1, %si

また、これを見れば何となく気づくかもしれませんが AT&T構文であるGASはソースオペランド、デスティネーションオペランドという順番になり演算結果は右側のレジスタに格納されます。
つまりプログラミング言語言語などの代入とは違い

x = 0

と演算結果が左側に格納されるというものと逆になるわけです。

レジスタですが、細かい説明は上の記事がわかりやすいのでそちらにお願いします。
GAS で使われているAT&T構文はレジスタの前には % をつけ、数字、ラベル名の前には $ をつけます。



書き換え

本で出てきたものを順番に、GAS の形式として中心としてまとめます。
コメントもつけました。
hellos2.s

	.code16
	jmp entry
	.byte 0x90
	.ascii "HELLOIPL" # ブートセクタの名前(8byte)
	.word 512 # 1セクタの大きさ(512固定) 
	.byte 1 # クラスタのセクタ数(1固定)
	.word 1 # FAT開始のセクタ
	.byte 2 # FATの個数(2固定)
	.word 224 # ルートディレクトリ領域のエントリ数 (224推奨)
	.word 2880 # ドライブのセクタ数(2880固定)
	.byte 0xf0 # メディアのタイプ(0xf0固定)
	.word 9 # FAT領域のセクタ数(9固定)
	.word 18 # 1トラックのセクタ数(18固定)
	.word 2 # ヘッドの数(2固定)
	.int 0 # パーティション数(未使用0)
	.int 2880 # ドライブのセクタ数
	.byte 0, 0, 0x29 # ?
	.int 0xffffffff # ボリュームシリアル番号
	.ascii "HELLO-OS   " # ディスクの名前(11byte)
	.ascii "FAT12   " # フォーマットの名前(8byte)
	.skip 18, 0x00 # 18byte空ける

# プログラム本体
entry:
	movw	$0, %ax # ax = 0 レジスタの初期化
	movw	%ax, %ss # ss = a = 0
	movw	$0x7c00, %sp # sp = 0x7c00
	movw	%ax, %ds # ds = ax = 0
	movw	%ax, %es # es = ax = 0

	movw	$msg, %si # si = &mag
putloop:
	movb	(%si), %al # al = &si
	addw	$1, %si # si += 1
	cmpb	$0, %al # if (al == 0)
	je      fin # jump to fin
	movb	$0x0e, %ah # ah = 0x0e  一文字表示ファンクション
	movw	$15, %bx # bx = 15    カラーコード
	int     $0x10 # ビデオBIOS呼び出し
	jmp	putloop
fin:
	hlt # 何かあるまでcpuを停止させる
	jmp	fin # 無限ループ

# メッセージ部分
msg:
	.byte	0x0a, 0x0a # 改行 改行(ascii)
	.ascii	"Hello, GAS" # 出力
	.byte	0x0a # 改行
	.byte	0

	.org	0x1fe #0x1fe分0x00で埋める命令

	.byte	0x55, 0xaa

# 以下はブートセクタ以外の部分の記述
	.byte	0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
	.org	.+4600
	.byte	0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
	.org	.+1469432

1行目
まず、このコードでは16bitのレジスタが使われています。なのでそれを指定する必要があります。

命令

ORG命令機械語が実行時にPCに読み込まれるメモリを指定します。
メモリマップにおける 0x00007c00 - 0x00007dff はブートプログラムなどが格納されているというブートセクタが読み込まれるアドレスなのでこの頭の数字になるみたいです。
どうもGASには対応するものがないようなので外部で指定します。
yukke42.hatenablog.comで作ったリンカスクリプト
lnk.ls 後半をに追加します。

OUTPUT_FORMAT("binary");

IPL_BASE = 0x7C00;

SECTIONS {
  . = IPL_BASE;
}

2行目
jmp はラベルのところまで飛びます。(jumpの略)

25行目
movw は代入です。ここでは16bit レジスタなのでサフィックスwです。
33行目
movb は同じ代入ですが、8bit レジスタを使用しているので サフィックスb です。

34行目
addw は文字通り足し算の命令です。 si ← si + 1

35行目
cmp
je
で1セットの比較の分岐命令となります。c言語でいうと

if (ax == 0) 
    goto fin;

となります。(goto自体は使ったことないけど…)
GAS_制御構造 CapmNetwork に他の条件で分岐する場合も載っています。
(このサイト利用しまくってます。)
39行目
int はソフトウェア割り込み命令です。(interruptの略)
詳細は後日。

40行目
hlt はCPUを停止させる命令です。(haltの略)
fin では無限ループになっているのですが、筆者はCPUをから回しすというのが嫌なのでマイナーな命令ですが追加しているそうです。
なので、なくても動きます。

汎用レジスタ

基本は ax の汎用レジスタを使います。機械語では他のを使うより少し簡単になります。
bx はメモリ番地計算の基点(ベース)となります。
cx はカウンタで、回数を数えるのに使います。

また、ax, bx, ... には上位、下位それぞれ8bitずつを指定できる ah, al, bh, bl, ... というレジスタがあります。

汎用レジスタのうちほかの4つ(di, sp, si, di) は上位、下位で分けられません。もし分ける必要があるならば、ah 等を経由して操作します。

セグメントレジスタ

本編では3日目での説明となります。

アドレス

31行目
$msg とラベルの前に$をつけることでそのアドレスを指定できます。

33行目
(%si) とすることで siのアドレスを指定できます。
このとき16bit(2byte) の値がメモリに格納されているのですが、値は8bit(1byte)毎に格納され上位がアドレスの大きい側となります。
これをリトルエンディアンといいます。

これらのようなアドレス指定に使えるレジスタbx, bp, si, di のみでほかは使えません。

とりあえず一通りの説明は終わりです。

make rungas

で実行すればOKです。
表示は一日目と変わらないので割愛。

最後に

読み進めながらやっているので、本を持っていない人にはかなりわかりづらいと思います。。そこは改善していこうと思います。
本編のコードに書いてあるコメントを追加しましたが、聞いたことだけあるくらいの用語が大量にあります。一応調べはしたのですが、まとめるのはまたそのうち…
とりあえず

  • リンカスクリプト
  • ソフトウェア割り込み
  • セクタ等の関係 これは3日目の(1)に説明あります。

は優先度高めです。

~追記(2016/12/15)~
今まではブートセクタ以降の記述も一つのファイルに書いていたのですが、これではファイルサイズが大きくなりブートセクタに入りきらなくなるのでここの記述を外部に任せます。そのためにMakefileの内容も書き換えます。
mformatコマンドはOSイメージを作るときに1,440KB ディスクを全て埋めます。これ最後にあったブートセクタ以降の記述が必要なくなります。
そうすることで、読み込みのみのファイルが今日作ることができます。
それに伴ってMakefileを変更し、の書き方も少し変えました。run から一本道でたどっていけるようにしました。(分割するのとどっちがいいかは好みでしょうか。)
Makefile

GAS = ipl3a.s
IMG = ipl.img
IPL = ipl.bin

ipl.bin: $(GAS)
	gcc $(GAS) -nostdlib -T lnk.ls -o $(IPL)

ipl.img: $(IPL)
	mformat -f 1440 -C -B $(IPL) -i $(IMG)
	rm $(IPL)

run: $(IMG)
	qemu-system-x86_64 -fda $(IMG)
	rm $(IMG)