OSという言葉は知っているだろうか。そのOSを今回は作ってみようという企画である。
OSを作るには、まず以下のようなものが必要となる。
ブートローダー
カーネル
ドライバ
これらが必要となる。
まずは第一回ということで、ブートローダーを作ってみた。
(数ヶ月かかった。)
default rel
section .data
ImageHandle dq 0 ;住所の形式は64bitなのでdqを使い、UTF-16の文字の場合は一文字が16bitなのでdw
SystemTable dq 0
ConOut dq 0
OutputString dq 0
ConIn dq 0
ReadKey dq 0
RuntimeServices dq 0
shutdown dq 0
BootServices dq 0
uefiexit dq 0
getmemorymap dq 0
HandleProtocol dq 0
AllocatePool dq 0
FreePool dq 0
;getmemorymapの返り値
align 8 ;8バイト境界に整列。
mapsize dq 4096
memorymap times 4096 db 0 ;UEFIの仕様上、getmemorymapはこのようにしてバッファを用意しておく必要がある。
mapkey dq 0
DescriptorSize dq 0
DescriptorVersion dd 0
inputkey dq 0 ;文字データ自体はdwでいいのだが、readkeyは他のデータも2byte分書き込むのでオーバーフロー対策でddに、そして保険としての意味も込めてdqに。
tempkey dw 0
;文字列の後ろには0を絶対つける(db限定)。改行は13,10で終了は0。
msg dw __utf16__('Hello!'), 13,10, 0
bootmsg dw __utf16__('Do you want to boot?'), 13, 10, __utf16__('y/n'), 13, 10, 0
Fatal_error_msg dw __utf16__('Fatal_error_detected:...Restart...'), 13, 10, 0
Reboot_Failure dw __utf16__('Restart_failed:...Force_stop...'), 13, 10, 0
Debug_msg dw __utf16__('Debug'), 13, 10, 0
register dw 0
section .text
global _start
_start:
;最初はUEFIがいくつかのレジスタにUEFI命令のアドレスが入っている(rcx=ImageHandle(自分のプログラムの識別子)rdx=SystemTable(サービスの参照が入っている))
sub rsp, 40 ;スタックを調整
xor r15, r15
;レジスタからメモリへ保存
mov [ImageHandle], rcx
mov [SystemTable], rdx
mov rcx, [rdx + 64] ;SystemTableの64byte先には画面出力のConOutの住所が入っている
mov [ConOut], rcx
mov rax, [rcx + 8] ;ConOutの8byte先に文字列のOutputStringが入っている
mov [OutputString], rax
lea rdx, [msg] ;rdxに文字列のデータを入れる
call rax ;今回の引数:rcx SystemTableの住所(固定) rdx 文字列データ
;ここでブートするかを聞く文を表示
global judge_msg
judge_msg:
lea rdx, [bootmsg]
mov rcx, [ConOut] ;rcxはUEFIが書き換えることができる揮発性レジスタ故保険でもう一度mov。
mov rax, [OutputString]
call rax
cmp r15, 0 ;r15は0のままなのでこれで初回かどうかを判定できる。初回なら0、二回目以降なら0以外。
jnz setupskip
;他のツールの住所を指定しておく
mov r8, [SystemTable]
mov rcx, [r8 + 48]
mov [ConIn], rcx
mov rax, [rcx + 8]
mov [ReadKey], rax
mov rcx, [r8 + 88]
mov [RuntimeServices], rcx
mov rax, [rcx + 104]
mov [shutdown], rax
mov rcx, [r8 + 96]
mov [BootServices], rcx
mov rax, [rcx + 232]
mov [uefiexit], rax
mov rcx, [r8 + 96]
mov rax, [rcx + 32]
mov [getmemorymap], rax
setupskip:
jmp _bootjudge ;ブートしたいか判定するプロセスへ
ret
global _bootjudge
_bootjudge:
key:
mov rcx, [ConIn]
xor edx, edx
mov [inputkey], edx
lea rdx, [inputkey]
mov rax, [ReadKey]
call rax
test rax, rax ;ここでのraxは成功したかどうかがわかる。testはそれを確認できる。
jz _success
mov r10, 0x8000000000000006 ;二度手間の理由は64bit数値とレジスタを直接cmpできないx86_64規則のため。
cmp rax, r10 ;何も入力されていない場合は0x8000000000000006となるのでこれだった場合はkeyに。
je key
jmp Fatal_error_handler ;これ以外ということは致命的なエラーなので再起動、失敗したらシャットダウン、それでもダメならhlt(CPU強制停止)。
_success:
movzx ecx, word [inputkey + 2] ;inputkeyに返り値が入っている。
or ecx, 0x20 ;これで大文字を小文字に強制的に変換(右から6番目のビットを強制的にオンにする→2^6=32=16 x '2' +1 x '0' =0x20)
cmp ecx, 'y'
je _exituefi
cmp ecx, 'n'
je _shutdown
jmp judge_msg ;cmpでyおよびnでもなかった場合はy,n以外だったということなのでjudge_msg:に戻る。
add rsp, 40
ret
global _shutdown
_shutdown:
;(ここに来る際にはrspはすでに40なのでスタックの確保は省いてよし。)
mov rcx, 2 ;ここでどの操作をするかを選択
xor rdx, rdx ;基本的にはほぼ必要ないが追加データを入れることもできる(rdx r8 r9に入れる)
xor r8, r8
xor r9, r9
mov rax, [shutdown]
call rax
add rsp, 40 ;失敗した際の後片付け
ret
global _exituefi
_exituefi:
sub rsp, 16 ;第五引数(実はrspの空き容量は1バイト分だけ少なくなってしまう!)も使うため48以上は必要だが8+48では16の倍数にならない。そしてこのプロシージャに来る時はrspは40なので差分の16を引けばOK。
lea rcx, [rel mapsize]
lea rdx, [rel memorymap]
lea r8, [rel mapkey]
lea r9, [rel DescriptorSize]
lea rax, [rel DescriptorVersion]
mov [rsp + 32], rax ;第五引数はスタックに入れる
mov rax, [getmemorymap]
call rax
mov rcx, 0x8000000000000002
cmp rax, rcx
je debug
jmp skip
debug:
call _debug
cli
halt:
hlt
jmp halt
skip:
mov rcx, [ImageHandle]
mov rdx, [mapkey]
mov rax, [uefiexit]
call rax
add rsp, 56
ret
;ここからは通常は使われないプロシージャ
global Fatal_error_handler ;致命的なエラーが発生した際のハンドラー
Fatal_error_handler:
mov rcx, [ConOut]
mov rax, [OutputString]
lea rdx, [Fatal_error_msg]
call rax
mov rcx, 5000000
mov r10, [BootServices]
mov rax, [r10 + 168]
call rax
mov rcx, 0 ;これによって再起動がされる。
mov rdx, 0x8000000000000007 ;エラーコードはわからんがとりあえずDEVICES ERRORで。このプログラム自体が簡素な分起きるエラーも少ない。
xor r8, r8
xor r9, r9
mov rax, [shutdown]
call rax
;再起動すら失敗したガチでやばいケースの場合
mov rcx, [ConOut]
mov rax, [OutputString]
lea rdx, [Reboot_Failure]
call rax
mov rcx, 5000000
mov r10, [BootServices]
mov rax, [r10 + 168]
call rax
add rsp, 40
cli
halt2:
hlt
jmp halt2
global _debug
_debug:
push rax
mov rcx, [ConOut]
mov rax, [OutputString]
lea rdx, [Debug_msg]
call rax
pop rax
ret
こんなところだろう。
まあまだ荒削りの部分もあると思うが、これからも頑張っていきたい。
