30日OS本 (レジスタまとめ)

21日あたりでアセンブリプログラムが分からなくなってきたので,レジスタをまとめてみる


一般レジスタの一覧と名称

汎用レジスタ,32ビット (下位16ビット, 下位16~8ビット,下位8ビット)
EAX (AX, AH, AL) ... アキュムレータ
EBX (BX, BH, BL) ... ベース
ECX (CX, CH, CL) ... カウンタ
EDX (DX, DH, DL) ... データ
ESI (SI) ... ソースインデックス (下位16ビット)
EDI (DI) ... デスティネーションインデックス (下位16ビット)

ベースポインタ,32ビット (下位16ビット)
EBP (BP)

スタックポインタ,32ビット (下位16ビット)
ESP (SP)

インストラクションポインタ,32ビット
EIP

フラグレジスタ,32ビット
EFLAGS

セグメントレジスタ,16ビット
CS ... コードセグメント
DS ... データセグメント
ES ... エクストラセグメント
SS ... スタックセグメント
FS ... 本名なし
GS ... 本名なし


一般レジスタの役割

EAXレジスタの役割 ... c言語ではc言語ではRETしたときEAXレジスタに入っていた値が返り値となる


ESPレジスタの役割 ... スタックのトップを指し示すこと
EIPレジスタの役割 ... 次に実行する命令のアドレスを保持すること
タスクが切り替わったときも,次にEIP番地の命令が読まれる


CSレジスタの役割 ... マシン語命令を読み込むセグメントを指し示すこと
マシン語命令実行は
①メモリからマシン語の命令を読み込む
②命令の解釈
③命令の実行
という手順で実行される.このとき,① でのメモリアクセスはCSレジスタの指すセグメントに対して行われる
DSレジスタの役割 ... 命令実行の際,メモリアクセスをするセグメントを指し示すこと
前述の③でのメモリアクセスはDSレジスタの指すセグメントに対して行われる
SSレジスタの役割 ... スタック領域として使うセグメントを指し示すこと
前述の③でメモリのスタック領域にアクセスする際,SSレジスタの指すセグメントに対して行われる


システムレジスタの一覧と名称

システムアドレスレジスタ
GDTR ... global descriptor table register,48ビット(p.113)
IDTR ... interrupt descriptor table register,48ビット
LDTR ... 16ビット
TR ... task register 16ビット(p.295)

コントロールレジスタ(32ビット)
CR0 ...
CR1 ...
CR2 ...
CR3 ...


システムアドレスレジスタの役割

TRレジスタの役割 ... 現在どのタスクを実行しているのかを記憶する
タスクスイッチすると自動的にTRレジスタの値も変わる

30日OS本 (アセンブリ言語命令まとめ)

21日あたりでアセンブリプログラムが分からなくなってきたので,アセンブリ言語(nask)の命令をまとめてみる



・DB命令 (data byte) ... 1バイトのデータとしてメモリに格納する. ORGと併用することでメモリ番地を指定できる(p.25)

DB 0xeb ; メモリに0xebを書き込む

・DW命令 (data byte) ... 2バイトのデータとしてメモリに格納する(p.27)
・DD命令 (data byte) ... 4バイトのデータとしてメモリに格納する(p.27)

・RESB命令 (reserve byte) ... 指定したバイト数分のメモリに0x00を格納(p.25)

RESB 16 ; 16バイト分のメモリに0x00を格納

・ORG命令 ... org命令以下の命令を格納するアドレスを、指定したメモリ番地以降にする(p.31)

ORG 0x7c00 ; 
DB  0xeb ; 0x7c00番地に0xebを格納



・JMP命令 ... 目的のメモリの番地にジャンプする(p.31)(p.295)

; nearモード,EIPだけを切り替える
JMP 0x7c50 ; 0x7c50番地にジャンプ(次回0x7c50番地に格納された命令を読み込む)
; farモード,EIP,CSを同時に切り替える
JMP   4*8:0 ; 4*8に登録されたセグメントを指定

・JE命令 ... 比較結果が等しければジャンプする(p.39)

CMP AL, 0 ; ALと0が等しいか調べる
JE fin ; 等しければfin番地にジャンプ

・JC命令 ... キャリーフラグが1ならジャンプする(p.49)

JC error ; キャリーフラグが1ならerror番地にジャンプ

・JNC命令 ... キャリーフラグが0ならジャンプする(p.54)

JNC fin ; キャリーフラグが0ならfin番地にジャンプ

・JBE命令 ... 小さいか等しければジャンプする(p.55)

CMP CL, 18 ; CLと18を比較
JBE readloop ; CL≦18ならreadloop番地にジャンプ

・JB命令 ... 小さければジャンプする(p.57)

CMP DH, 2 ; DHと2を比較
JB readloop ; DH<2ならreadloop番地にジャンプ

・CALL命令 ... 関数を呼び出す(p.135)

; nearモード,同じセグメント内の番地を指定
CALL _inthandler21 ; _inthandler21番地の関数を呼ぶ
; farモード,呼び出す関数のセグメントと番地を指定
CALL 2*8;0xbe3 ; 2*8セグメントの0xbe3番地の関数を呼ぶ 


・RET命令 ... 呼び出し元に帰る
・RETF命令 ... 呼び出し元が違うセグメントの場合に使用(far-CALLした後とか)
・IRETD命令 ... 割込み終了時に使うRET(p.131)



・MOV命令 ... レジスタ,メモリに値を代入する (p.31)

MOV AL, 0 ; AXレジスタに0を代入
MOV AL, [SI] ; ALレジスタにSI番地の内容を代入
MOV [SI], AL ; SI番地にALレジスタの値を代入

・ADD命令 ... 加算する(p.39)

ADD SI, 1 ; SIレジスタに1を足す



・INT命令 ... ある割込み発生時にある関数を呼び出す(割込みとそれに対する関数をあらかじめIDTで設定しておく)(p.39)

INT 0x10 ; 割込み0x10を発生させ,それに対する関数を呼び出す

・HLT命令 ... CPUを待機状態にさせる,電力を節約できる(p.64)

HLT ;

・IN命令 ... 指定した装置(port)からデータを受け取る(p.88)

IN AL, DX ; DXの値のポートから8ビットのデータを受け取りALに格納

・OUT命令 ... 指定した装置(port)へデータを送る(p.88)

OUT DX, AL ; DXの値のポートへALに格納された8ビットのデータを送る




・LTR命令 ... TRレジスタの値を変更する(p.295)

LTR [ESP+4] ; スタックのESP+4の値をTRレジスタに格納

30日OS本 (21日目 5~7)

5. 例外をサポートしよう

導入

  • 異常終了をできるようにしたい
  • x86では,アプリがOSを壊そうとすると自動的にINT 0x0dの割込みが発生する

→割込み番号0x0dに関数を登録すればよい



_asm_inthandler0dを作る

_asm_inthandler0d:
 STI
 PUSH	ES
 PUSH	DS
 PUSHAD
 MOV	AX,SS
 CMP	AX,1*8 
 JNE	.from_app		; SSが1*8でなかったらfrom_appへ
;	OSが動いているときに割り込まれたのでほぼ今までどおり
 MOV	EAX,ESP
 PUSH	SS			; 割り込まれたときのSSを保存
 PUSH	EAX			; 割り込まれたときのESPを保存
 MOV	AX,SS		; DS, ES, SSが等しくなるよう調整(p.134)
 MOV	DS,AX		; DS, ES, SSが等しくなるよう調整(p.134)
 MOV	ES,AX		; DS, ES, SSが等しくなるよう調整(p.134)
 CALL	_inthandler0d
 ADD	ESP,8
 POPAD
 POP	DS
 POP	ES
 ADD	ESP,4			; INT 0x0d では、これが必要
 IRETD
.from_app:
;	アプリが動いているときに割り込まれた
 CLI
 MOV	EAX,1*8
 MOV	DS,AX			; とりあえずDSだけOS用にする
 MOV	ECX,[0xfe4]		; OSのESP
 ADD	ECX,-8
 MOV	[ECX+4],SS		; 割り込まれたときのSSを保存
 MOV	[ECX  ],ESP		; 割り込まれたときのESPを保存
 MOV	SS,AX
 MOV	ES,AX
 MOVESP,ECX
 STI
 CALL	_inthandler0d
 CLI
 CMP	EAX,0
 JNE	.kill
 POP	ECX
 POP	EAX
 MOV	SS,AX			; SSをアプリ用に戻す
 MOV	ESP,ECX			; ESPもアプリ用に戻す
 POPAD
 POP	DS
 POP	ES
 ADD	ESP,4			; INT 0x0d では、これが必要
 IRETD
 .kill:
;	アプリを異常終了させることにした
 MOV	EAX,1*8			; OS用のDS/SS
 MOV	ES,AX
 MOV	SS,AX
 MOV	DS,AX
 MOV	FS,AX
 MOV	GS,AX
 MOV	ESP,[0xfe4]		; start_appのときのESPに無理やり戻す
 STI		; 切り替え完了なので割り込み可能に戻す
 POPAD	; 保存しておいたレジスタを回復
 RET

細かいレジスタの退避はよくわからないので後で
_asm_inthandler0dをIDTに登録

void init_gdtidt(void)
{
 (中略)

 /* IDTの初期化 */
 for (i = 0; i <= LIMIT_IDT / 8; i++) {
  set_gatedesc(idt + i, 0, 0, 0);
 }
 load_idtr(LIMIT_IDT, ADR_IDT);

 /* IDTの設定 */
 set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32);
 /* 0x0dを登録 */
 set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x40, (int) asm_hrb_api,      2 * 8, AR_INTGATE32);

 return;
}

6. OSを守ろう(3)

  • アセンブラを使ってDSにOS用のセグメントを代入すればOSを壊せてしまう


[INSTRSET "i486p"]
[BITS 32]
MOV		EAX,1*8			; OS用のセグメント番号
MOV		DS,AX			; これをDSにいれちゃう
MOV		BYTE [0x102600],0
RETF

7. OSを守ろう(4)

  • アプリがOS用のセグメントを使えないようにする機能がx86にはある
  • セグメント定義のときに,アクセス権に0x60を足すとアプリ用のセグメントという設定になる

→CSに入っているセグメントがアプリ用のセグメントの場合,OS用のセグメントを代入すると例外を起こすようになる

  • この方法の場合,TSSにOS用のセグメントとESPを登録しなければならない
  • この方法の場合,OSがアプリ用のセグメントへfar-CALL, JMPしてはいけない


int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
 (中略)
 char name[18], *p, *q;
 struct TASK *task = task_now();
 (中略)

 (中略)
 if (finfo != 0) {
  /* ファイルが見つかった場合 */
  (中略)
  set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
  /* アプリ用のコードセグメント,アクセス権に0x60を足す */
  set_segmdesc(gdt + 1004, 64 * 1024 - 1,   (int) q, AR_DATA32_RW + 0x60);
  /* アプリ用のデータセグメント,アクセス権に0x60を足す */
  (中略)
  start_app(0, 1003 * 8, 64 * 1024, 1004 * 8, &(task->tss.esp0));
  /* アプリ開始 */
  (中略)
 }
 /* ファイルが見つからなかった場合 */
 return 0;
}
_start_app:		; void start_app(int eip, int cs, int esp, int ds, int *tss_esp0);
 PUSHAD		; 32ビットレジスタを全部保存しておく
 MOV		EAX,[ESP+36]	; アプリ用のEIP
 MOV		ECX,[ESP+40]	; アプリ用のCS
 MOV		EDX,[ESP+44]	; アプリ用のESP
 MOV		EBX,[ESP+48]	; アプリ用のDS/SS
 MOV		EBP,[ESP+52]	; tss.esp0の番地
 MOV		[EBP  ],ESP		; OS用のESPを保存
 MOV		[EBP+4],SS		; OS用のSSを保存
 MOV		ES,BX
 MOV		DS,BX
 MOV		FS,BX
 MOV		GS,BX
; 	以下はRETFでアプリに行かせるためのスタック調整
 OR		ECX,3			; アプリ用のセグメント番号に3をORする
 OR		EBX,3			; アプリ用のセグメント番号に3をORする
 PUSH	EBX				; アプリのSS
 PUSH	EDX				; アプリのESP
 PUSH	ECX				; アプリのCS
 PUSH	EAX				; アプリのEIP
 RETF  ; OSからアプリへ
;	アプリが終了してもここには来ない

_start_appから_asm_hrb_api

_asm_hrb_api:
		STI
		PUSH	DS
		PUSH	ES
		PUSHAD		; 保存のためのPUSH
		PUSHAD		; hrb_apiにわたすためのPUSH
		MOV		AX,SS
		MOV		DS,AX		; OS用のセグメントをDSとESにも入れる
		MOV		ES,AX
		CALL	_hrb_api
		CMP		EAX,0		; EAXが0でなければアプリ終了処理
		JNE		end_app
		ADD		ESP,32
		POPAD
		POP		ES
		POP		DS
		IRETD
end_app:
;	EAXはtss.esp0の番地
		MOV		ESP,[EAX]
		POPAD
		RET					; cmd_appへ帰る

asm_hrb_apiから_hrb_api

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	int cs_base = *((int *) 0xfe8);
	struct TASK *task = task_now();
	struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
	if (edx == 1) {
		cons_putchar(cons, eax & 0xff, 1);
	} else if (edx == 2) {
		cons_putstr0(cons, (char *) ebx + cs_base);
	} else if (edx == 3) {
		cons_putstr1(cons, (char *) ebx + cs_base, ecx);
	} else if (edx == 4) {
		return &(task->tss.esp0);
	}
	return 0;
}

30日OS本 (21日目 1~4)

1. 文字列表示APIを今度こそ

導入

  • 一文字表示のときはCS(コードセグメントレジスタ)を指定していたが,文字列表示ではセグメントを指定していない
  • 0xfe8番地にメモしておき,それをhrb_apiで加える


int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
 (中略)
 if (finfo != 0) {
  /* ファイルが見つかった場合 */
  p = (char *) memman_alloc_4k(memman, finfo->size);
  *((int *) 0xfe8) = (int) p;
  /* 0xfe8番地の中身に確保した先頭番地を格納 */
  file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
  set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
  farcall(0, 1003 * 8);
  memman_free_4k(memman, (int) p, finfo->size);
  cons_newline(cons);
  return 1;
 }
 /* ファイルが見つからなかった場合 */
 return 0;
}

void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
 int cs_base = *((int *) 0xfe8);
 struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
 if (edx == 1) {
  cons_putchar(cons, eax & 0xff, 1);
 } else if (edx == 2) {
  cons_putstr0(cons, (char *) ebx + cs_base);
  /* コードセグメントを足す */
 } else if (edx == 3) {
  cons_putstr1(cons, (char *) ebx + cs_base, ecx);
  /* コードセグメントを足す */
 }
 return;
}

HariMainを実行
hello2と入力
hello2.hrbファイルを探し,見つかればアプリ用コードセグメントにコピー(ディスクからメモリにコピー)
そのセグメントにジャンプしアプリを実行
EBX番地に文字列を格納後,割込み(0x40)を発生させる
割込みルーチンでhrb_apiをコール
CS_base(事前にメモ)+EBX番地以降に格納された文字列をコンソールに表示

2. アプリケーションをc言語で作ってみたい

よくわからないので後で

3. OSを守ろう(1)

  • x86にはOSを守るための機能がある
  • 逆にこの機能を利用しないと,OSに関するメモリの中身をアプリが書き換えることができ危険



例えば,以下のcrack1.cというアプリ

void HariMain(void)
{
 *((char *) 0x00102600) = 0
 /* 0x00102600番地の中身を0にする */
 return ;
}

dirが正常に作動しなくなる

4. OSを守ろう(2)

  • 対策として,アプリにはアプリ用のメモリを与える(アプリ用のデータセグメントを作る)



1004*8に64KBのセグメントを作る

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
 (中略)
 char name[18], *p, *q;
 (中略)
 if (finfo != 0) {
  /* ファイルが見つかった場合 */
  p = (char *) memman_alloc_4k(memman, finfo->size);
  q = (char *) memman_alloc_4k(memman, 64 * 1024);
  /* 64KBのメモリを確保し,その先頭番地をqに格納 */
  *((int *) 0xfe8) = (int) p;
  file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
  set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
  set_segmdesc(gdt + 1004, 64 * 1024 - 1,   (int) q, AR_DATA32_RW);
  /* 1004*8にセグメントを登録 */
  (中略)
  start_app(0, 1003 * 8, 64 * 1024, 1004 * 8);
  /* アプリを起動するための関数 */
  memman_free_4k(memman, (int) p, finfo->size);
  memman_free_4k(memman, (int) q, 64 * 1024);
  /* 64KBのメモリを解放 */
  cons_newline(cons);
  return 1;
 }
 /* ファイルが見つからなかった場合 */
 return 0;
}

start_appはESP(スタックポインタ),DS(データセグメント),SS(スタックセグメント)の設定がある

_start_app:		; void start_app(int eip, int cs, int esp, int ds);
 PUSHAD		; 32ビットレジスタを全部保存しておく
 MOV		EAX,[ESP+36]	; アプリ用のEIP
 MOV		ECX,[ESP+40]	; アプリ用のCS
 MOV		EDX,[ESP+44]	; アプリ用のESP
 MOV		EBX,[ESP+48]	; アプリ用のDS/SS
 MOV		[0xfe4],ESP		; OS用のESP
 CLI			; 切り替え中に割り込みが起きてほしくないので禁止
 MOV		ES,BX
 MOV		SS,BX
 MOV		DS,BX
 MOV		FS,BX
 MOV		GS,BX
 MOV		ESP,EDX
 STI			; 切り替え完了なので割り込み可能に戻す
 PUSH	ECX				; far-CALLのためにPUSH(cs)
 PUSH	EAX				; far-CALLのためにPUSH(eip)
 CALL	FAR [ESP]		; アプリを呼び出す

;	アプリが終了するとここに帰ってくる

 MOV		EAX,1*8			; OS用のDS/SS
 CLI			; また切り替えるので割り込み禁止
 MOV		ES,AX
 MOV		SS,AX
 MOV		DS,AX
 MOV		FS,AX
 MOV		GS,AX
 MOV		ESP,[0xfe4]
 STI			; 切り替え完了なので割り込み可能に戻す
 POPAD	; 保存しておいたレジスタを回復
 RET

よくわからない



_asm_hrb_apiも改造

_asm_hrb_api:
; 都合のいいことに最初から割り込み禁止になっている
 PUSH	DS
 PUSH	ES
 PUSHAD		; 保存のためのPUSH
 MOV		EAX,1*8
 MOV		DS,AX			; とりあえずDSだけOS用にする
 MOV		ECX,[0xfe4]		; OSのESP
 ADD		ECX,-40
 MOV		[ECX+32],ESP	; アプリのESPを保存
 MOV		[ECX+36],SS		; アプリのSSを保存

; PUSHADした値をシステムのスタックにコピーする
 MOV		EDX,[ESP   ]
 MOV		EBX,[ESP+ 4]
 MOV		[ECX   ],EDX	; hrb_apiに渡すためコピー
 MOV		[ECX+ 4],EBX	; hrb_apiに渡すためコピー
 MOV		EDX,[ESP+ 8]
 MOV		EBX,[ESP+12]
  MOV		[ECX+ 8],EDX	; hrb_apiに渡すためコピー
 MOV		[ECX+12],EBX	; hrb_apiに渡すためコピー
 MOV		EDX,[ESP+16]
 MOV		EBX,[ESP+20]
 MOV		[ECX+16],EDX	; hrb_apiに渡すためコピー
 MOV		[ECX+20],EBX	; hrb_apiに渡すためコピー
 MOV		EDX,[ESP+24]
 MOV		EBX,[ESP+28]
 MOV		[ECX+24],EDX	; hrb_apiに渡すためコピー
 MOV		[ECX+28],EBX	; hrb_apiに渡すためコピー

 MOV		ES,AX			; 残りのセグメントレジスタもOS用にする
 MOV		SS,AX
 MOV		ESP,ECX
 STI			; やっと割り込み許可

CALL	_hrb_api

 MOV		ECX,[ESP+32]	; アプリのESPを思い出す
 MOV		EAX,[ESP+36]	; アプリのSSを思い出す
 CLI
 MOV		SS,AX
 MOV		ESP,ECX
 POPAD
 POP		ES
 POP		DS
 IRETD		; この命令が自動でSTIしてくれる

よくわからない
_asm_inthandler20も改造

_asm_inthandler20:
 PUSH	ES
 PUSH	DS
 PUSHAD
 MOV		AX,SS
 CMP		AX,1*8
 JNE		.from_app
;	OSが動いているときに割り込まれたのでほぼ今までどおり
 MOV		EAX,ESP
 PUSH	SS				; 割り込まれたときのSSを保存
 PUSH	EAX				; 割り込まれたときのESPを保存
 MOV		AX,SS
 MOV		DS,AX
 MOV		ES,AX
 CALL	_inthandler20
 ADD		ESP,8
 POPAD
 POP		DS
 POP		ES
 IRETD
.from_app:
;	アプリが動いているときに割り込まれた
 MOV		EAX,1*8
 MOV		DS,AX			; とりあえずDSだけOS用にする
 MOV		ECX,[0xfe4]		; OSのESP
 ADD		ECX,-8
 MOV		[ECX+4],SS		; 割り込まれたときのSSを保存
 MOV		[ECX  ],ESP		; 割り込まれたときのESPを保存
 MOV		SS,AX
 MOV		ES,AX
 MOV		ESP,ECX
 CALL	_inthandler20
 POP		ECX
 POP		EAX
 MOV		SS,AX			; SSをアプリ用に戻す
 MOV		ESP,ECX			; ESPもアプリ用に戻す
 POPAD
 POP		DS
 POP		ES
 IRETD

よくわからない

30日OS本 (20日目 5~8)

5. OSのバージョンが変わっても変わらないAPI

導入

  • asm_cons_putcharの番地をbootpack.mapから調べていたが,この番地はOSを改造するたびに変わってしまう

→OSのバージョンが変わるたびに書き直すのは面倒

  • 解決策

IDTに関数を登録する(具体的には,0x40番にする)


void init_gdtidt(void)
{
 (中略)
 /* IDTの設定 */
 set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
 set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);
 /* 0x40に関数asm_cons_putcharを登録 */

 return;
}

これに応じて,アプリも改造
CALL 2*8:0xbd1の代わりにINT 0x40と書く
INT命令で呼び出された場合は割込み扱いになるのでRETFでは戻れない
→IRETD命令を使う

_asm_cons_putchar:
 STI                 ;割込み処理扱いにされるため割込み禁止に自動的になる.それを解除
 PUSH	1
 AND	EAX,0xff
 PUSH	EAX
 PUSH	DWORD [0x0fec]	
 CALL	_cons_putchar
 ADD	ESP,12		
 IRETD               ;IRETDに変更

HariMainを実行
ファイルを探し,見つかればアプリ用コードセグメントにコピー
そのセグメントにジャンプしアプリを実行
レジスタに値を格納
割込みルーチンでcons_putcharをコール
実行後IRETDでアプリ用コードセグメントに戻る

6. アプリケーション名を自由に

導入

  • アプリ名hlt以外のアプリも使えるようにしたい



cons_runcmdを改造

void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{
 if (strcmp(cmdline, "mem") == 0) {
  cmd_mem(cons, memtotal);
 } else if (strcmp(cmdline, "cls") == 0) {
  cmd_cls(cons);
 } else if (strcmp(cmdline, "dir") == 0) {
  cmd_dir(cons);
 } else if (strncmp(cmdline, "type ", 5) == 0) {
  cmd_type(cons, fat, cmdline);
 } else if (cmdline[0] != 0) {
  if (cmd_app(cons, fat, cmdline) == 0) { /* 新しい関数,ファイルが見つかったら1を見つからなかったら0を返す */
   /* コマンドではなく、アプリでもなく、さらに空行でもない */
   putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);
   cons_newline(cons);
   cons_newline(cons);
  }
 }
 return;
}
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
 struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
 struct FILEINFO *finfo;
 struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
 char name[18], *p;
 int i;

 /* コマンドラインからファイル名を生成 */
 for (i = 0; i < 13; i++) {
  if (cmdline[i] <= ' ') {
   break;
  }
  name[i] = cmdline[i];
  /* 変数nameにコンソールで入力した文字列をコピー */
 }
 name[i] = 0; /* とりあえずファイル名の後ろを0にする */

 /* ファイルを探す */
 finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
 if (finfo == 0 && name[i - 1] != '.') {
  /* 見つからなかったので後ろに".HRB"をつけてもう一度探してみる */
  /* hltでもhlt.hrbでも「hlt.hrb」が起動するようになる */
  name[i    ] = '.';
  name[i + 1] = 'H';
  name[i + 2] = 'R';
  name[i + 3] = 'B';
  name[i + 4] = 0;
  finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
 }

 if (finfo != 0) {
  /* ファイルが見つかった場合,そのアプリを実行 */
  p = (char *) memman_alloc_4k(memman, finfo->size);
  file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
  set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
  farcall(0, 1003 * 8);
  memman_free_4k(memman, (int) p, finfo->size);
  cons_newline(cons);
  return 1;
}
 /* ファイルが見つからなかった場合 */
 return 0;
}

7. レジスタに気を付けよう

hello.hrbを改造

; 改造前
[BITS 32]
 MOV		AL,'h'
 INT		0x40 
 MOV		AL,'e'
 INT		0x40 
 MOV		AL,'l'
 INT		0x40 
 MOV		AL,'l'
 INT		0x40
 MOV		AL,'o'
 INT		0x40
 RETF
;改造後
[INSTRSET "i486p"]
[BITS 32]
 MOV		ECX,msg
putloop:
 MOV		AL,[CS:ECX]
 CMP		AL,0
 JE		fin
 INT		0x40
 ADD		ECX,1
 JMP		putloop
fin:
 RETF
msg:
 DB	       "hello",0 ; "hello",0を格納



_asm_cons_putcharも変更

_asm_cons_putchar:
 STI
 PUSHAD ; すべてのレジスタをPUSHする
 PUSH	1
 AND	EAX,0xff	
 PUSH	EAX
 PUSH	DWORD [0x0fec]
 CALL	_cons_putchar
 ADD	ESP,12	
 POPAD ; すべてのレジスタをPOPする
 IRETD

8. 文字列表示API

導入

  • 1文字だけでなく文字列を表示したい(2通りの方法で実装)

文字コード0がくるまで表示する
文字数を指定して表示する


void cons_putstr0(struct CONSOLE *cons, char *s)
{
 for (; *s != 0; s++) {
  cons_putchar(cons, *s, 1);
  /* 文字コード0がくるまで表示する */
 }
 return;
}
void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{
 int i;
 for (i = 0; i < l; i++) {
  cons_putchar(cons, s[i], 1);
  /* 文字数を指定して表示する */
 }
 return;
}

その他のコマンドでも文字列表示の際,これを使う



cons_putstr0とcons_putstr1をどうやってAPIにするのか?(IDTは有限なので使いたくない)
BIOSのように機能番号を使う(ただしAHではなくEDXに機能番号を入れる)
機能番号の割り振り
機能番号1...一文字表示(AL=文字コード
機能番号2...文字列表示0(EBX=文字列の番地)
機能番号3...文字列表示1(EBX=文字列の番地,ECX=文字数)
_asm_cons_putcharを改造

_asm_hrb_api:
 STI
 PUSHAD	; 保存のためのPUSH
 PUSHAD	; hrb_apiに渡すためのPUSH
 CALL	_hrb_api
 ADD		ESP,32
 POPAD
 IRETD

APIの処理をc言語で行う.その処理プログラムが以下の通り

void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) /* PUSHADにより受け取る */
{
 struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
 if (edx == 1) { /* 機能番号 */
  cons_putchar(cons, eax & 0xff, 1);
 } else if (edx == 2) { /* 機能番号 */
  cons_putstr0(cons, (char *) ebx);
 } else if (edx == 3) { /* 機能番号 */
  cons_putstr1(cons, (char *) ebx, ecx);
 }
 return;
}

IDTの設定も変える(0x40にasm_hrb_apiを登録)



ここでのまとめ
hello2と入力
cons_runcmd ... コマンド実行開始

cmd_app ... hello2と同名のファイルを探し,見つかればfarcall(0, 1003 * 8)でそのアプリへ

hello2.nas ... 機能番号,表示したい文字をセットしてから割込みをする

_asm_hrb_api ... レジスタの値をスタックへPUSHしてから_hrb_apiをCALL

hrb_api ... 文字列を表示

30日OS本 (20日目 2~4)

2. 一文字表示API(1)

導入

  • アプリからcons_putcharを呼べばいい

hlt.nasで_asm_cons_putcharをCALL
_asm_cons_putcharでcons_putcharをCALL

  • consの番地は,console.cでメモリの0x0fec番地にメモしておく


[BITS 32]
 MOV  AL, 'A' ; ALレジスタに'A'の文字コードを格納 
 CALL 0xbe3  ; 0xbe3番地にとぶ (この番地はbootpack.mapから得た)
fin:
 HLT   ; HLTし続ける
 JMP fin

次のプログラムにとぶ

_asm_cons_putchar:
 PUSH 1
 AND EAX, 0xff ; AHやEAXの上位を0にして,EAXに文字コードが入った状態にする
 PUSH EAX
 PUSH DWORD [0x0fec] ; メモリの内容を読み込んでその値をPUSHする
 CALL _cons_putchar
 ADD ESP, 12
 RET

void cons_putchar(struct CONSOLE *cons, int chr, char move)に対応して
(1, 'A', 0x0fec番地の中身)をPUSHしている(PUSHなので逆順になる)

3. 一文字表示API(2)

導入

  • hlt.nas内のコールが原因

→アプリはセグメント「1003*8」で動いていて,OSはセグメント「2*8」で動いているためfarなCALLを使う必要がある


[BITS 32]
 MOV  AL, 'A' ; ALレジスタに'A'の文字コードを格納 
 CALL 2*8:0xbe3  ; 0xbe3番地にとぶ (セグメントも指定)
fin:
 HLT   ; HLTし続ける
 JMP fin

またfar-CALLに対し呼び出し元に戻る命令はRETFにする必要がある
→_asm_cons_putcharを修正

HariMainを実行
ファイルを探し,見つかればアプリ用コードセグメントにコピー
そのセグメントにジャンプしアプリを実行
レジスタに値を格納
OS用コードセグメントに移りcons_putcharをコール
実行後RETFでアプリ用コードセグメントに戻る

4. アプリケーションの終了

導入

  • いまのアプリでは,終了した後何もできない

→HLTする代わりにRETする(また次のコマンド入力ができる)



far-CALLするための命令をc言語で作る

_farcall:  ;void farcall(int eip, int cs);
 CALL FAR [ESP+4] ;eip, cs
 RET
hltコマンドの処理でfarcallを使う
void cmd_hlt(struct CONSOLE *cons, int *fat)
{
 (中略)
 if (finfo != 0) {
  /* ファイルが見つかった場合 */
  (中略)
  farcall(0, 1003 * 8); /* OSからアプリをコール */
  (中略)
 } else {
 /* ファイルが見つからなかった場合 */
 (中略)
 }
(中略)
}

アプリのプログラムも変更(HLTをやめてRETFに)

[BITS 32]
 MOV  AL, 'A' 
 CALL 2*8:0xbe8 ; _asm_cons_putcharの番地が変更されたみたい  
 RETF ; 呼び出し元のOSに帰る

30日OS本 (19日目 3~5)

3. FATに対応

導入

  • いまのtypeコマンドでは512バイト以上のファイルをちゃんと表示できない(違うファイルの一部を表示してしまう)
  • なぜなら,Windowsでは大きいファイルを連続していないセクタに書き込むことがあるから
  • ディスクの中に,どこにファイルの続きがあるのかメモされているのでそれを読む必要がある
  • そのメモは0x000200~0x0013ffのFAT(file allocation table)にある
  • FATは2重に用意されており,バックアップは0x001400~0x0025ffにある


void console_task(struct SHEET *sheet, unsigned int memtotal)
{
 (中略)
 int *fat = (int *) memman_alloc_4k(memman, 4 * 2880);
 (中略)
 file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));
 /* FATの圧縮をとき,fatに格納 */
 (中略)
 for (;;) {
  io_cli();
  if (fifo32_status(&task->fifo) == 0) {
   (中略)
  } else {
   (中略)
   if (256 <= i && i <= 511) { /* キーボードデータ(タスクA経由) */
    (中略)
   } else if (i == 10 + 256) {
    (中略)
    if (strcmp(cmdline, "mem") == 0) {
     (中略)
    } else if (strcmp(cmdline, "cls") == 0) {
     (中略)
    } else if (strcmp(cmdline, "dir") == 0) {
     (中略)
    } else if (strncmp(cmdline, "type ", 5) == 0) {
     /* typeコマンド */
     (中略)
     if (x < 224 && finfo[x].name[0] != 0x00) {
      /* ファイルが見つかった場合 */
      p = (char *) memman_alloc_4k(memman, finfo[x].size);
      /* ファイルサイズ分メモリを確保 */
      file_loadfile(finfo[x].clustno, finfo[x].size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
      /* ファイルの内容を繋ぎ合わせ,確保したメモリに転送 */
      cursor_x = 8;
      for (y = 0; y < finfo[x].size; y++) { /*サイズ分繰り返す*/
       /* 1文字ずつ出力 */
       s[0] = p[y];
       s[1] = 0;
       (中略)
      }
      memman_free_4k(memman, (int) p, finfo[x].size);
      /* 確保した分メモリを解放 */
     } else {
      /* ファイルが見つからなかった場合 */
      (中略)
     }
     cursor_y = cons_newline(cursor_y, sheet);
    } else if (cmdline[0] != 0) {
     (中略)
    }
    (中略)
   } else {
    /* 一般文字 */
    (中略)
   }
  }
  (中略)
  }
 }
}

void file_readfat(int *fat, unsigned char *img)
/* ディスクイメージ内のFATの圧縮をとく */
{
 int i, j = 0;
 for (i = 0; i < 2880; i += 2) {
  fat[i + 0] = (img[j + 0]      | img[j + 1] << 8) & 0xfff;
  fat[i + 1] = (img[j + 1] >> 4 | img[j + 2] << 4) & 0xfff;
  j += 3;
 }
 return;
}

void file_loadfile(int clustno, int size, char *buf, int *fat, char *img)
{
int i;
for (;;) {
 if (size <= 512) {
  for (i = 0; i < size; i++) {
   buf[i] = img[clustno * 512 + i];
  }
  break;
 }
 for (i = 0; i < 512; i++) {
  buf[i] = img[clustno * 512 + i];
 }
 size -= 512;
 buf += 512;
 clustno = fat[clustno];
 /* 次のクラスタを調べて格納 */
 }
 return;
}

5. ついに初アプリ

導入

  • HLTするだけのアプリを作りたい
  • hlt.nasというファイルを作り,naskでアセンブルしてhlt.hrbを作る
  • ファイルの実行方法

typeコマンドのようにfile_loadfileを使ってファイルの内容をメモリにコピー
アプリケーションのためのセグメントを作る
そのセグメントの中のプログラムへgotoする


void console_task(struct SHEET *sheet, unsigned int memtotal)
{
 (中略)
 struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;

 (中略)
 for (;;) {
  io_cli();
  if (fifo32_status(&task->fifo) == 0) {
   (中略)
  } else {
   (中略)
   if (256 <= i && i <= 511) { /* キーボードデータ(タスクA経由) */
    if (i == 8 + 256) {
     (中略)
    } else if (i == 10 + 256) {
     (中略)
     if (strcmp(cmdline, "mem") == 0) {
      (中略)
     } else if (strcmp(cmdline, "cls") == 0) {
      (中略)
     } else if (strcmp(cmdline, "dir") == 0) {
      (中略)
     } else if (strncmp(cmdline, "type ", 5) == 0) {
      (中略)
     } else if (strcmp(cmdline, "hlt") == 0) {
      /* hlt.hrbアプリケーションを起動 */
      for (y = 0; y < 11; y++) {
       s[y] = ' ';
      }
      s[0] = 'H';
      s[1] = 'L';
      s[2] = 'T';
      s[8] = 'H';
      s[9] = 'R';
      s[10] = 'B';
      /* hlt.hrb */
      for (x = 0; x < 224; ) {
       if (finfo[x].name[0] == 0x00) {
        break;
       }
       if ((finfo[x].type & 0x18) == 0) {
        for (y = 0; y < 11; y++) {
         if (finfo[x].name[y] != s[y]) {
          goto hlt_next_file;
         }
        }
        break; /* ファイルが見つかった */
       }
 hlt_next_file:
       x++;
      }
      if (x < 224 && finfo[x].name[0] != 0x00) {
       /* ファイルが見つかった場合 */
       p = (char *) memman_alloc_4k(memman, finfo[x].size);
       file_loadfile(finfo[x].clustno, finfo[x].size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
       set_segmdesc(gdt + 1003, finfo[x].size - 1, (int) p, AR_CODE32_ER);
       /* アプリケーションのためのセグメントを用意(GDTの1003番) */
       farjmp(0, 1003 * 8);
       /* 用意したセグメントにジャンプ→アプリケーションが実行される */
       memman_free_4k(memman, (int) p, finfo[x].size);
      } else {
       /* ファイルが見つからなかった場合 */
       putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);
       cursor_y = cons_newline(cursor_y, sheet);
      }
      cursor_y = cons_newline(cursor_y, sheet);
     } else if (cmdline[0] != 0) {
      (中略)
     }
     (中略)
    } else {
     (中略)
    }
   }
   (中略)
  }
 }
}