ごちゃごちゃしたIT勉強記録

自分用メモ。主にセキュリティで、その他色々書きたい。

30日OS自作入門-4日目-

長くかかった3日目が終了し、これでやっとC言語を使った作業が行えます。
ということで早速はじめていきます。

今日のまとめ

4日目は画面表示がメインの項目になります。
画面表示をさせたい場合にはVRAMの領域に表示させたいものを記述させることで、OS起動時にVRAMを読み取って画面出力を行う、と言う流れです。
なので、今回やる内容としては

  • OS起動時に画面表示を行う関数を作成
  • 関数内で、「どういったものを画面表示させるか」を設定

ということになります。特にここではメモリのアドレスを意識してやっていく必要があります。

harib01a

3日目の最後に作成したharib00jのファイルに追記していく形になります。
まずは、nasmfunc.nasに画面表示用の関数と処理を定義します。nasmfunc.nasの中身は以下の通り

; File name : nasmfunc.nas
; TAB=4

[BITS 32]			; 32 bit mode machine code

; information for object file

	GLOBAL	io_hlt		; function name that contains this program
	GLOBAL	write_mem8


[SECTION .text]			; write this description at first
io_hlt:				; void io_hlt(void)
	HLT
	RET

write_mem8:			; void write_mem8(int addr, int data)
	MOV	ECX,[ESP+4]
	MOV	AL,[ESP+8]
	MOV	[ECX],AL
	RET			

write_mem8という関数を追加しています。
これ自体は、呼び出し元で設定された変数を読み出して各レジスタに格納している処理になります。

続いて、nasmfunc.nasで定義した関数を呼び出す側の方をみていきます。write_mem8を呼び出しているのはbookpack.cになります。

/* File name : bootpack.c*/
/* proto type declaration */
extern void io_hlt(void);
extern void write_mem8(int addr, int data);

void HariMain(void)
{
	int i;			/* 32bit integer */
	for(i = 0xa0000; i <= 0xaffff; i++){
		write_mem8(i,15);	/* MOV BYTE [i],15*/
	}

	for(;;){
		io_hlt();
	}
}

write_mem8で設定されている引数はarg1=i, arg2=15となります。
arg1で設定されているiは、繰り返しによって値が0xa0000から0xaffffまで変化します。
arg2では固定値として15が指定されています。

結論から言うと、動作させた結果は以下の通り真っ白な画面が出てきます。
f:id:motojiroxx:20180611023807p:plain
arg1の0xa0000から0xaffffの範囲は「画面表示させるものが記録されているメモリアドレス」でarg2の15が「実際に表示させるもの」になります。
で、15が画面の色で白を意味するので画面全体が真っ白になる、ということです。

ベースはわかったので、あとはちょいちょい値を変更していって画面の変化を観察していきます。

harib01b

今度はしましま模様を出します。変更する部分はbootpack.cのwrite_mem8に渡す引数部分です。
arg2で今度はiと0x0fのANDを取っています。

/* File name : bootpack.c
/* proto type declaration */
extern void io_hlt(void);
extern void write_mem8(int addr, int data);

void HariMain(void)
{
	int i;			/* 32bit integer */
	for(i = 0xa0000; i <= 0xaffff; i++){
		write_mem8(i,i & 0x0f);		/* MOV BYTE [i],15*/
	}

	for(;;){
		io_hlt();
	}
}

実行結果はこちら。
f:id:motojiroxx:20180611025504p:plain

んで、このあとのharib01cからharib01eまではポインタの話がつらつら書いているだけなので飛ばします。

harib01f

harib01bとはちょっと色違いのしましまを出します。その際、harib01bとは異なり色番号を自分で設定します。

harib01bでコードを追加したbootpack.cに、さらに結構コードを追加します。
追加するコードは、自分で設定する色番号とそれに関連する関数になります。

/* File name : bootpack.c */
/* proto type declaration */

extern void io_hlt(void);
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);

void HariMain(void)
{
	int i;			/* 32bit integer */
	char *p;		/* pointer */

	init_palette();		/* setting palette */

	p = (char *)0xa0000;	/* set address of VRAM area */

	for(i = 0; i <= 0xffff; i++){
		p[i] = i & 0x0f;	/* MOV BYTE [i],15*/
	}

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)               /*パレットのアクセスの手順にしたがって処理を行う*/
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

色番号の設定は、以下の2つの関数で行なっています。

init_palette
set_palette

2つの関数の処理の流れとしては、

  1. init_palette関数を呼び出し、関数内部で色番号の配列を定義しset_palette関数を呼び出す
  2. set_palette関数内で、eflagの値を退避させたり割込み禁止にしたりしたあと、for文のなかで自分が設定しようとしている色番号を登録し、割込み禁止解除とeflagsの値を復元

というものになります。
特に、「set_palette関数って何やってるの?」というところについては、以下のサイトに詳しく書いてあります。
http://oswiki.osask.jp/?VGA
上記のリンクの「ビデオDAコンバータ」の項目の「パレットのアクセスの手順」を確認しておくといいかもです。
あとは、HariMainのルーチンに戻ったあと、登録した色番号を使用してVRAMのアドレスに色を書き込んでいきます。
これが今回書いたbootpack.cの処理フローです。

続いて。上記で追加した関数の処理についてはnasmfunc.nasにて定義します。なのでnasmfunc.nasもコードを追加します。

; nasmfunc
; TAB=4

; [FORMAT "WCOFF"]		; the mode creating object file
[BITS 32]			; 32 bit mode machine code

; information for object file

; [FILE "nasmfunc.nas"]		; source file information

	GLOBAL	io_hlt		; function name that contains this program
	GLOBAL	io_cli
	GLOBAL	io_sti
	GLOBAL	io_stihlt
	GLOBAL	io_in8
	GLOBAL	io_in16
	GLOBAL	io_in32
	GLOBAL	io_out8
	GLOBAL	io_out16
	GLOBAL	io_out32
	GLOBAL	io_load_eflags
	GLOBAL	io_store_eflags

section .text			; write this description at first

io_hlt:				; void io_hlt(void)
	HLT
	RET

io_cli:				; void io_cli(void)
	CLI
	RET

io_sti:				; void io_sti(void)
	STI
	RET

io_stihlt:			; void io_stihlt(void)
	STI
	HLT
	RET

io_in8:				; int io_in8(int port)
	MOV	EDX,[ESP+4]	; [ESP+4] is arg1
	MOV	EAX,0
	IN	AL,DX
	RET


io_in16:			; int io_in16(int port)
	MOV	EDX,[ESP+4]
	MOV	EAX,0
	IN	AX,DX
	RET

io_in32:			; int io_in32(int port)
	MOV	EDX,[ESP+4]
	IN	EAX,DX
	RET

io_out8:			; void io_out8(int port, int data)
	MOV	EDX,[ESP+4]
	MOV	AL,[ESP+8]
	OUT	DX,AL
	RET

io_out16:			; void io_out16(int port, int data)
	MOV	EDX,[ESP+4]
	MOV	EAX,[ESP+8]
	OUT	DX,AX
	RET

io_out32:			; void io_out32(int port, int data)
	MOV	EDX,[ESP+4]
	MOV	EAX,[ESP+8]
	OUT	DX,EAX
	RET

io_load_eflags:			; int io_load_eflags(void);
	PUSHFD			; it is equals to PUSH EFLAGS
	POP	EAX
	RET

io_store_eflags:		; void io_store_eflags(int eflags);
	MOV	EAX,[ESP+4]
	PUSH	EAX
	POPFD			; it is equals to POP EFLAGS
	RET

一応、いくつか出てくるアセンブリ命令についてまとめておきます。

IN	EAX,DX                     ; 装置から電気信号を受け取る命令
OUT	DX,EAX                     ; 装置へ電気信号を送る命令
※扱うサイズによってEAXの部分はALAXになる

それぞれ、DXには「装置番号」、EAXには「受け取るor送る値」が設定されます
で、2つの命令では指定するレジスタはDXとEAXで、INとOUTでsourceとdestinationが入れ替わります。
わりと覚えやすいかも。

CLI                    ;  割り込みフラグを0にする命令(CLear Interrupt flag)-> つまり割り込み禁止にする
STI                    ;  割り込みフラグを1にする命令(SeT Interrupt flag)-> つまり割り込みを有効にする

何か重要な処理をしたいので「割り込みは勘弁!」といった際にはCLIを使い、その処理が終了後にSTIで元に戻せばいいんでしょうか。
割り込みに関しては後々やるとのことなので、こんな推測に留めておきます。

PUSHFD                  ; eflags(フラグレジスタの値)をDWORDサイズでスタックに積む
POPFD                    ; eflags(フラグレジスタの値)をDWORDサイズでスタックから取り出す

フラグレジスタの操作に関連したpushとpop命令です。


んで、最終的に実行結果はこちら。harib01bとはちょっと色が違うことが確認できるかと思います。
f:id:motojiroxx:20180613020616p:plain

harib01g

ここでは3つほど色の違う四角形を表示させます。
先ほど作成したbootpack.cの内容を色々変更しますが、変更点については以下の通り

  • 先ほど配列で定義していた色番号をdefineで定義しなおす
  • 四角形を描画する関数boxfill8を定義して、指定した色で塗りつぶして表示する処理を記述

というわけで変更後のbootpack.cのコードは以下の通りです。

/* proto type declaration */ 
extern void io_hlt(void); 
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);

#define COL8_000000	0
#define COL8_FF0000	1
#define COL8_00FF00	2
#define COL8_FFFF00	3
#define COL8_0000FF	4
#define COL8_FF00FF	5
#define COL8_00FFFF	6
#define COL8_FFFFFF	7
#define COL8_C6C6C6	8
#define COL8_840000	9
#define COL8_008400	10
#define COL8_848400	11
#define COL8_000084	12
#define COL8_840084	13
#define COL8_008484	14
#define COL8_848484	15

void HariMain(void)
{
	int i;			/* 32bit integer */
	char *p;		/* pointer */

	init_palette();		/* setting palette */

	p = (char *)0xa0000;	/* set address of VRAM area */

/* 3つの四角形を、描画位置と色を指定して描画 */
	boxfill8(p, 320, COL8_FF0000, 20, 20, 120, 120);
	boxfill8(p, 320, COL8_00FF00, 70, 50, 170, 150);
	boxfill8(p, 320, COL8_0000FF, 120, 80, 220, 180);

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x,y;
	for(y = y0; y <=y1; y++){
		for(x = x0; x <= x1; x++){
			vram[y * xsize + x] = c;
		}
	}
	return;
}

表示される画面はこんな感じです。
f:id:motojiroxx:20180614025851p:plain

boxfill8で特定の座標に四角形を描画していますが、座標軸は通常数学でやるときの中心を原点とするものではないです。
PCの場合、画面左上を原点として座標の計算を行います(一般的な話かどうかは知りませんが)。
今回四角形の描画で使用しているboxfill8の引数と関連させてみると、以下の図のようになるかと。
f:id:motojiroxx:20180616142652p:plain

あとは、実際に描画させる際にはVRAMのアドレスを対応させればいいです。
なので、「画面上の」原点の位置は、「メモリ上では」0xa0000に対応するので、特定の座標の色に対応するメモリの位置は以下の計算式で求められます。

0xa0000 + x + y * 320

で、「なぜyに320をかけるのか?」と言う疑問がありますが、これについては以下のサイトにあるsksatさんの解説が非常にわかりやすいです(ページ上で「320」と検索をかければ見つかります」
http://hrb.osask.jp/wiki/?q_and_a

加えて、こちらのサイトの内容も確認しておくとこの疑問については解消されるのではないかと思います。
OS自作入門 4日目 【Linux】| VRAMとカラーパレット | サラリーマンがハッカーを真剣に目指す
具体的に、0xa0000から開始されるvramにおいて、(X座標, Y座標)がどのアドレスに該当するのかをわかりやすく示してくれています。


ここまでくると「おぉ〜」とちょっと感動しますね。

harib01h

4日目の最後です。ここでは、昔のウィンドウズっぽい画面を描画します。
ただ、とくに新しいことはなく、boxfill8関数を使ってそれっぽいものをじゃんじゃん描画していくのみになります。
なので、変更するのはbootpack.cのみ。

/* proto type declaration */ 
extern void io_hlt(void); 
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);

#define COL8_000000	0
#define COL8_FF0000	1
#define COL8_00FF00	2
#define COL8_FFFF00	3
#define COL8_0000FF	4
#define COL8_FF00FF	5
#define COL8_00FFFF	6
#define COL8_FFFFFF	7
#define COL8_C6C6C6	8
#define COL8_840000	9
#define COL8_008400	10
#define COL8_848400	11
#define COL8_000084	12
#define COL8_840084	13
#define COL8_008484	14
#define COL8_848484	15

void HariMain(void)
{
	char *vram;
	int xsize, ysize;	

	init_palette();		/* setting palette */
	vram = (char *)0xa0000;	/* set address of VRAM area */
	xsize = 320;
	ysize = 200;

	boxfill8(vram, xsize, COL8_008484,  0,		0,		xsize - 1, 	ysize - 29);
	boxfill8(vram, xsize, COL8_C6C6C6,  0,		ysize - 28,	xsize - 1, 	ysize - 28);
	boxfill8(vram, xsize, COL8_FFFFFF,  0,		ysize - 27,	xsize - 1, 	ysize - 27);
	boxfill8(vram, xsize, COL8_C6C6C6,  0,		ysize - 26,	xsize - 1, 	ysize - 1);

	boxfill8(vram, xsize, COL8_FFFFFF,  3,		ysize - 24,	59,		ysize - 24);
	boxfill8(vram, xsize, COL8_FFFFFF,  2,		ysize - 24,	2, 		ysize - 4);
	boxfill8(vram, xsize, COL8_848484,  3,		ysize - 4,	59, 		ysize - 4);
	boxfill8(vram, xsize, COL8_848484,  59,		ysize - 4,	59, 		ysize - 5);
	boxfill8(vram, xsize, COL8_000000,  2,		ysize - 3,	59, 		ysize - 3);
	boxfill8(vram, xsize, COL8_000000,  60,		ysize - 24,	60, 		ysize - 3);

	boxfill8(vram, xsize, COL8_848484,  xsize - 47,	ysize - 24,	xsize - 4, 	ysize - 24);
	boxfill8(vram, xsize, COL8_848484,  xsize - 47,	ysize - 23,	xsize - 47, 	ysize - 4);
	boxfill8(vram, xsize, COL8_FFFFFF,  xsize - 47,	ysize - 3,	xsize - 4, 	ysize - 3);
	boxfill8(vram, xsize, COL8_FFFFFF,  xsize - 3,	ysize - 24,	xsize - 3, 	ysize - 3);

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x,y;
	for(y = y0; y <=y1; y++){
		for(x = x0; x <= x1; x++){
			vram[y * xsize + x] = c;
		}
	}
	return;
}

表示される画面はこんな感じ。
f:id:motojiroxx:20180616154402p:plain

なんかOSぽくなってきました!ちょっと今後が楽しみ

という感じで、今回は特に大きな失敗はありませんでした(逆に不安