「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は「手探りでCUI OS作成に挑む」連載目次を御覧下さい。
初期化処理
ss:spを9000:FFFFで初期化するコード。
(必ずしもスタックをこの場所に設置しないといけない訳ではない。)
mov ax,0x9000
mov ss,ax
mov sp,0xFFFF
メモリ分布
ブートローダから0x7E00に第2セクタ以降のプログラムを読み込んだ場合のメモリ分布。
今回はブートローダのみで0x7E00にプログラムは読み込んでいない。
+-------------------+ 0x00000
| IVT | 割り込みベクタテーブル (1KB)
| (割り込みベクタ) | BIOSが使用
+-------------------+ 0x00400
| BIOSデータ領域 | BIOSデータ
+-------------------+ 0x00500
| |
| 利用可能メモリ領域| (約28KB)
| |
+-------------------+ 0x7C00
| [BIOSブートセクタ] | 512バイト
| (ブートローダ) |
+-------------------+ 0x7E00
|自分で書いたプログラム | ← org 0x7E00
| (startラベル開始) |
+-------------------+ 0x9000
| |
| スタック領域 | ← SS:SP = 0x9000:0xFFFF
| (下方向に成長) | 実際のスタックトップは 0x9000:0xFFFE
| | (push時にSPが2減るため)
+-------------------+
POPの時 SP = SP - 2
PUSHの時 SP = SP + 2
SPが0xFFFF→0x0000まで
(0xFFFF - 0x0000 + 1) / 2 = 32768回 POP可能
STACK操作の例とレジスタの変化
mov ax, 0x1234
mov bx, 0x5678
push ax
push bx
mov ax, 0xAAAA
mov bx, 0xBBBB
pop bx
pop ax
検証コード
stack.asm
BITS 16
org 0x7C00
start:
; スタックとレジスタの初期化
; SS:SP = 0x9000:0xFFFF (スタックは0x9FFFFから0x00000向きに伸びる)
mov ax,0x9000
mov ss,ax
mov sp,0xFFFF
; レジスタ状態表示
mov ax, 0x1234
call print_state
mov bx, 0x5678
call print_state
push ax
call print_state
push bx
call print_state
mov ax, 0xAAAA
call print_state
mov bx, 0xBBBB
call print_state
pop bx
call print_state
pop ax
call print_state
cli ; 割り込み禁止
hlt ; プロセッサを停止
; 現在のレジスタ状態を表示(AX/BX/SS/SPは変更しない)
print_state:
; 1. AXレジスタの値を表示
mov si, ax_str
call print_str
mov cx, ax ; AXの値をCXにコピー
call print_hex_word ; CXの値を16進数で表示
; 2. BXレジスタの値を表示
mov si, bx_str
call print_str
mov cx, bx ; BXの値をCXにコピー
call print_hex_word
; 3. SSレジスタ(スタックセグメント)の値を表示
mov si, ss_str
call print_str
mov cx, ss ; SSの値をCXにコピー
call print_hex_word
; 4. SPレジスタ(スタックポインタ)の値を表示
mov si, sp_str
call print_str
mov cx, sp ; SPの値をCXにコピー
call print_hex_word
; 改行
mov si, newline
call print_str
ret
; 補助関数:CXレジスタの16進数値を表示
print_hex_word:
push ax ; AXをスタックに保存
push bx ; BXをスタックに保存
mov bx, cx ; CXの値をBXにコピー
mov al, bh ; 上位バイトをALに移動
call print_hex_byte ; 上位バイトを表示
mov al, bl ; 下位バイトをALに移動
call print_hex_byte ; 下位バイトを表示
pop bx ; BXを復元
pop ax ; AXを復元
ret
; 1バイトの16進数表示 (ALレジスタの値)
print_hex_byte:
push ax
push cx
mov cl, 4
shr al, cl ; 上位4ビットを取得
call .nibble ; 上位4ビットを表示
pop cx
pop ax
push ax
push cx
and al, 0x0F ; 下位4ビットを取得
call .nibble ; 下位4ビットを表示
pop cx
pop ax
ret
.nibble:
add al, '0' ; ASCIIコードに変換
cmp al, '9' ; 数字か確認
jbe .digit ; 0-9ならそのまま表示
add al, 7 ; A-Fの場合の補正
.digit:
mov ah, 0x0E ; BIOSテレタイプ機能
int 0x10 ; 文字表示
ret
; 文字列表示関数 (DS:SI = 文字列ポインタ)
print_str:
pusha ; すべての汎用レジスタを保存
mov ah, 0x0E ; BIOSテレタイプ機能
mov bx, 0x0007 ; ページ番号0、表示属性7(灰色)
.loop:
lodsb ; [DS:SI]からALに読み込み、SIをインクリメント
test al, al ; 終端文字(0)かチェック
jz .done ; 終端なら終了
int 0x10 ; BIOS文字表示
jmp .loop
.done:
popa ; 汎用レジスタを復元
ret
; 表示用文字列定義
ax_str db " ax=0x", 0
bx_str db " bx=0x", 0
ss_str db " ss=0x", 0
sp_str db " sp=0x", 0
stack_header db " stack: 0x", 0
newline db 13, 10, 0 ; CR+LF
times 510-($-$$) db 0
dw 0xAA55
動作確認
nasm stack.asm -f bin -o stack.bin
qemu-system-i386 -fda stack.bin
POPの時 SP = SP - 2
PUSHの時 SP = SP + 2
が確認できる。
※追記
msiパソコン(bios起動)、8086実機でも試してみた
8086は何故か無限に同じものを表示し続けてしまう。
追記
※callした段階で戻りアドレスが保存されるのでspが2減ります。
pushする前にspが既に0xFFFDとなっているのはその為です
慎重に検証するには関数呼び出しにjmpを使う必要がありました。
後日試します。