「手探りでCUI OS作成に挑む」処理篇　x86スタックの初期化

## 「手探りでCUI OS作成に挑む」連載
この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は[「手探りでCUI OS作成に挑む」連載目次](https://qiita.com/earthen94/items/b5d77f98f0cf85a13346)を御覧下さい。

## 初期化処理
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
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/9ed604ca-1342-49fb-bf98-5bf40525955f.png)
POPの時 SP = SP - 2
PUSHの時 SP = SP + 2
が確認できる。

※追記
msiパソコン（bios起動）、8086実機でも試してみた
8086は何故か無限に同じものを表示し続けてしまう。
![IMG_1856.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/14f99552-67af-4ce5-945f-94ef9d365f60.jpeg)
![IMG_1851.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/ff5c4735-245a-45e7-b1fc-cb45bce4c140.jpeg)


## 追記
※callした段階で戻りアドレスが保存されるのでspが2減ります。
pushする前にspが既に0xFFFDとなっているのはその為です
慎重に検証するには関数呼び出しにjmpを使う必要がありました。
後日試します。