(自作CUIOS)VGA　VRAM直書き出力のデバッグ

## 「手探りでCUI OS作成に挑む」連載

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

## やりたいこと
VGAへの出力に`int 0x10`を使ってましたが、全てVRAMへ直書きするように処理を変えました。
すると、以下のような予期しない挙動が発生しました：

    1回目のエンターで、7〜8行もの空白行が一気に出力される
    2回目以降のエンターでは、本来1行で済むはずの改行で、１行分空白が出る

そこで新しく追加した関数を紙に書いて整理し、開発中のソースからから切り離して動かしながら原因を探すことにしました。

![图片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/2c904758-9817-4976-b50a-0947a2c74cda.png)
(名前を候OSに決定し、起動画面を少し変えてみました。)

恐らく以下のソースに不具合があると考えられます。
<details>
<summary>kernel.asm</summary>

```kernel.asm
[BITS 16]
[ORG 0x8000]

start:
    ; レジスタ初期化
    xor ax, ax
    mov ds, ax
    mov es, ax
    
    ; スタック設定
    mov ss, ax
    mov sp, 0x7C00
    
    mov si, message
    call print_string
    
command_loop:
    
    ; c:\>を表示
    mov si, prompt
    call print_string
    
    ;キーボードから入力されるのを待つ(入力が終わるまで返ってこない) int 0x16未使用
    call read_input
    
    ; 入力された命令によって処理をする
    mov si, input_buffer
    call parse_command
    
    ; command_loopへ戻りc:\>を表示して次の命令を待つ
    jmp command_loop

; 起動画面に表示する文字列
message db "[Kernel] Execution started at 0x0000:0x8000", 0x0D, 0x0A,
        db "", 0x0D, 0x0A,
        db "******************************", 0x0D, 0x0A,
        db "*   SOUROU OS Version 1.0    *", 0x0D, 0x0A,
        db "******************************", 0x0D, 0x0A,
        db "", 0x0D, 0x0A,
        db " Operating System is up and running. ", 0x0D, 0x0A,
        db "", 0x0D, 0x0A, 0

prompt db  'C:\>', 0

; 他のソースをこの位置へ展開
%include "driver/keyboard.asm"
%include "driver/vga.asm"
%include "command.asm"
%include "strings.asm"

; ここで64KBまで0埋めする
times 65536-($-$$) db 0
```
</details>

<details>
<summary>vga.asm</summary>

```vga.asm
;------------------------------------------------------------
; VGAカーソル位置をI/Oポートから取得
; 結果は文字単位で cursor_pos に格納
; カーソル位置を取得しないとBIOS呼び出しを使って表示した文字が上にあった場合に
; 既に表示された文字の上から上書きしてしまいます
;------------------------------------------------------------
get_cursor_position:
    ; 下位バイト（カーソル位置のLSB）
    mov dx, 0x3D4
    mov al, 0x0F
    out dx, al
    inc dx        ; DX = 0x3D5
    in al, dx
    mov bl, al    ; BL = LSB

    ; 上位バイト（カーソル位置のMSB）
    mov dx, 0x3D4
    mov al, 0x0E
    out dx, al
    inc dx
    in al, dx
    mov bh, al    ; BH = MSB

    ; BX = カーソル位置（文字単位）
    mov [cursor_pos], bx
    ret
    
;------------------------------------------------------------
; ハードウェアカーソルを更新（VGAポートに書き込み）
; 入力：BX = 新しいカーソル位置（文字単位）
;------------------------------------------------------------
update_hardware_cursor:
    push ax
    push dx

    ; 下位バイト（0x0F）を設定
    mov dx, 0x3D4
    mov al, 0x0F
    out dx, al
    inc dx              ; DX = 0x3D5
    mov al, bl          ; 下位バイト（BXのLSB）
    out dx, al

    ; 上位バイト（0x0E）を設定
    dec dx              ; DX = 0x3D4
    mov al, 0x0E
    out dx, al
    inc dx              ; DX = 0x3D5
    mov al, bh          ; 上位バイト（BXのMSB）
    out dx, al

    pop dx
    pop ax
    ret

;------------------------------------------------------------
; VRAMへ文字列表示（改行対応、BIOSなし）
;------------------------------------------------------------
print_string:
    pusha
    push si
    
    ; カーソル位置を取得（BIOSなし）
    call get_cursor_position
    
    ; VRAMのセグメント B800h をESに設定
    mov ax, 0xB800
    mov es, ax

    ; カーソル位置を読み込んで、バイト単位に変換
    mov di, [cursor_pos]
    shl di, 1

.next_char:
    lodsb ; AL ← [DS:SI]
    or al, al ; 終端文字0なら終了
    jz .done

    cmp al, 0x0D        ; CR（行頭に戻る）
    je .carriage_return
    cmp al, 0x0A        ; LF（次の行）
    je .line_feed

    ; 通常文字出力
    mov [es:di], al
    mov byte [es:di+1], 0x0A  ; 緑文字
    add di, 2
    
    ; スクロール判定（80×25×2 = 4000バイト）
    cmp di, 4000
    jl .next_char
    call scroll_screen
    ; scroll後は最下行に戻す
    ; mov di, 80 * 24 * 2
    mov di, 80 * 24
    shl di, 1
    jmp .next_char

.carriage_return:
    mov ax, di
    shr ax, 1
    xor dx, dx
    mov bx, 80
    div bx        ; AX = 行番号, DX = 列番号
    mul bx        ; 行頭へ
    shl ax, 1
    mov di, ax
    jmp .next_char

.line_feed:
    add di, 160         ; 次の行へ（80文字×2バイト）
    
    ; スクロール判定
    cmp di, 4000
    jl .next_char
    call scroll_screen
    mov di, 80 * 24 * 2
    
    jmp .next_char

.done:
    shr di, 1
    mov [cursor_pos], di
    mov bx, di            ; BXにカーソル位置（文字単位）を設定
    call update_hardware_cursor  ; ハードウェアカーソル移動
    pop si
    popa
    ret

;------------------------------------------------------------
; putchar_direct: ALの文字をVRAMに直接書き込む
;------------------------------------------------------------
putchar_direct:
    pusha

    ; カーソル位置（文字単位）をDIに
    mov di, [cursor_pos]
    shl di, 1          ; DI *= 2（バイト単位に）

    ; セグメント ES に VRAM の 0xB800 を設定(ここでAXを退避せずAL破壊してどハマりした)
    push ax 
    mov ax, 0xB800
    mov es, ax
    pop ax

    ; 文字と属性を書き込む
    mov [es:di], al          ; 文字
    mov byte [es:di+1], 0x0A ; 属性（緑）

    ; カーソルを1文字進める(これがないと入力する度前の文字を上書きする)
    add di, 2
    
    shr di, 1
    mov [cursor_pos], di

    ; カーソル表示も更新
    mov bx, di
    call update_hardware_cursor

    popa
    ret
    
;------------------------------------------------------------
; newline: カーソル位置を改行(CR+LF)に更新する関数
; cursor_pos: 文字単位のカーソル位置（0～80*25-1）
; 画面幅80文字、画面高さ25行固定
;------------------------------------------------------------
newline:
    ; 現在位置から行頭計算
    mov ax, [cursor_pos]
    xor dx, dx
    mov bx, 80
    div bx          ; AX = 行番号, DX = 列番号
    
    ; 次の行へ
    inc ax
    cmp ax, 25
    jb .no_scroll
    
    ; スクロールが必要な場合
    call scroll_screen
    jmp .update_cursor
    
.no_scroll:
    ; 新しい位置計算 (行番号 * 80)
    mul bx
    mov [cursor_pos], ax
    
.update_cursor:
    mov bx, [cursor_pos]
    call update_hardware_cursor
    
;------------------------------------------------------------
; 画面を1行スクロール（上に1行詰めて最下行を空白に）
;------------------------------------------------------------
scroll_screen:
    pusha
    push es
    push ds

    ; ES = VRAM (0xB800)
    mov ax, 0xB800
    mov es, ax
    mov ds, ax          ; コピー元にも同じセグメント使う（安全のため）

    ; SI = 行1の先頭（2行目） = 1行160バイト(80文字×2byte　文字1byte 色1bite)
    mov si, 160

    ; DI = 行0の先頭（1行目）
    mov di, 0

    ; CX = 24行 × 80文字 = 1920文字（3840バイト）
    mov cx, 80 * 24 ; (ここは25にしてはいけない)

.copy_loop:
    mov ax, [ds:si]
    mov [es:di], ax
    add si, 2
    add di, 2
    loop .copy_loop

    ; 最下行（25行目 = 行24）の初期化
    ; DI は現在 3840バイト目（最下行の開始位置）
    mov cx, 80
    mov ax, 0x0720         ; 空白 + 属性（黒背景＋灰色文字）

.clear_last_line:
    mov [es:di], ax
    add di, 2
    loop .clear_last_line

    ; カーソル位置を1行上に（80文字 = 1行）
    ; sub word [cursor_pos], 80とすると画面の1行目に戻ってしまったので減算ではなくべた書き
    mov word [cursor_pos], 80 * 24  ; 行番号24× 80列 = 1920文字
    mov bx, 80 * 24
    call update_hardware_cursor

    pop ds
    pop es
    popa
    ret

cursor_pos: dw 0
```
</details>

<details>
<summary>keyboad.asm</summary>

```keyboad.asm
%define MAX_INPUT 64     ; 命令の最大入力文字数
input_buffer times MAX_INPUT+1 db 0

; キーボード入力関数
; 入力された結果はinput_bufferへ格納
read_input:
    pusha
    ;BIOS依存(INT10)では動いていたがES=DSと明記と失敗してハマった
    ;(stosb は 必ず ES:DI を使う 命令)
    push ds
    pop es
    mov di, input_buffer ;入力バッファ
    mov cx, 0            ; 文字数を数える
    
.read_char:
    ; 一文字読み込み（結果はASCIIコードでALへ格納される）
    ; mov ah, 0x00
    ; int 0x16
    call get_key
    
    ; 無効キーは無視
    cmp al, 0
    je .read_char         ; 無効キーは無視
    
    ; エンターが押された場合の処理
    cmp al, 0x0D
    je .done_input
    
    ; 長さが超えていないか確認
    cmp cx, MAX_INPUT ; 入力文字数が上限に達していないか確認
    jae .read_char    ; 上限超えたら無視して次のキー入力へ
    
    ; 入力に問題が無い場合は入力バッファへ格納
    stosb    ; AL の文字を [ES:DI] に格納（バッファに保存）、DI++（次の位置へ）
    inc cx   ; 入力文字数＋１
    
    ; 入力された文字を画面へ表示
    ; mov ah, 0x0E
    ; int 0x10
    call putchar_direct ; 表示する文字はALに入っている
    jmp .read_char
    
.done_input:
    ; 文字列を終了（終端文字を追加）
    mov al, 0
    stosb
    
    ; 改行
    ; mov ah, 0x0E
    ; mov al, 0x0D
    ; int 0x10
    ; mov al, 0x0A
    ; int 0x10
    call newline
    
    popa
    ret
    
; キーボードからの入力を受け取り対応するASCIIコードをAL経由で返す
get_key:
    in al, 0x64       ; 0x64ポート経由で入力されているかを確認
    test al, 1        ; 0ビット目　1：有り　0：無し
    jz get_key        ; 押されていなければget_keyへ戻って初めから
    
    in al, 0x60       ; 押されたキーの番号を取得

    ; キーの番号最高位が　０：押された　１：離された
    test al, 0x80
    jnz get_key       ; 離された場合は入力と見做さない
    
    ; キーの番号からASCIIコードへ変換していく
    cmp al, 0x1E      ; 'a'
    je .a
    cmp al, 0x30      ; 'b'
    je .b
    
    ;...省略...

    cmp al, 0x2C      ; 'z'
    je .z
    cmp al, 0x1C      ; Enter
    je .enter
    
    xor al, al        ; 上記以外のキーは無効（0を返す）
    ret
    
.a:
    mov al, 'a'
    ret
.b:
    mov al, 'b'
    ret

    ;...省略...

.z:
    mov al, 'z'
    ret
.enter:
    mov al, 0x0D      ; EnterのASCIIコード（CR）
    ret
```
</details>


<details>
<summary>command.asm</summary>

```command.asm
; 命令解析関数
parse_command:
    pusha
    
    ; 小文字→大文字変換(大文字小文字による差異を無くして後ろの文字列比較処理を簡単にする)
    mov si, input_buffer
    ; call print_string  ;TEST
    call to_upper
    
    ; 空でないか
    cmp byte [si], 0
    je .empty
   
    ; 命令毎に処理(それぞれの命令と入力を比較して一致すれば実行)

    ; help実行
    mov di, cmd_help
    call strcmp
    je .do_help

    ; reboot実行
    mov di, cmd_reboot
    call strcmp
    je .do_reboot
    
    ; 未定義命令
    mov si, unknown_cmd
    call print_string
    jmp .done
    
.do_help:
    mov si, help_text
    call print_string
    jmp .done
    
.do_reboot:
    ;int 0x19
    ;VBRは上書きしていないので再実行するだけの簡単な処理にした
    ;後からもっと細かい処理を実装する予定
    jmp 0x0000:0x7C00
    
.empty:
.done:
    popa
    ret

; データ置き場
cmd_dir db 'DIR', 0
cmd_help db 'HELP', 0
cmd_reboot db 'REBOOT', 0
teststr db "test", 0x0D, 0x0A, 0
teststr2 db "test2", 0x0D, 0x0A, 0
    
unknown_cmd db 'Unknown command', 0x0D, 0x0A, 0

help_text db 'Available commands:', 0x0D, 0x0A
          db 'DIR    - Show files', 0x0D, 0x0A
          db 'HELP   - This help', 0x0D, 0x0A
          db 'REBOOT - Restart system', 0x0D, 0x0A, 0
```
</details>



※その他のソースはは文言の変更程度で以下の記事以降、処理に関する変更は行っていません。
https://qiita.com/earthen94/items/f0f56bd46babffce8eda

## 動作確認
```
clear
rm -f os.img

nasm boot.asm -o boot.bin          # コンパイル
nasm testcode.asm -o testcode.bin  # コンパイル
cat boot.bin testcode.bin > os.img   # 結合

qemu-system-i386 -hda os.img  -monitor stdio 

rm -f boot.bin
rm -f testcode.bin
```
文字列を25行を越えるように表示したところ無事スクロールされています。
print_stringの関数には不具合が無いように見えます。
![图片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/0e1860b7-9062-4124-b31b-0c32dcc41c64.png)

## 検証ソース

<details>
<summary>boot.asm</summary>

```boot.asm
; 第2セクタを読み込み、実行するブートローダー
[bits 16]
org 0x7C00

start:
    ; セグメント初期化
    xor ax, ax
    mov ds, ax
    mov es, ax

    ; スタック設定
    mov ss, ax
    mov sp, 0x7C00
    
    mov si, msg1
    call print_string

    ; ディスクから第2セクタを 0x7E00 に読み込む
    mov ah, 0x02        ; INT 13h サービス 2: 読み込み
    mov al, 10           ; 読み込むセクタ数 = 10
    mov ch, 0           ; シリンダ = 0
    mov cl, 2           ; セクタ番号 = 2（1始まり）
    mov dh, 0           ; ヘッド = 0
    mov dl, 0x80           ; ドライブ番号
    mov bx, 0x7E00      ; 読み込み先アドレス
    int 0x13
    jc disk_error       ; エラー時に無限ループ

    ; 読み込んだコードへ跳ぶ（第2セクタ）
    jmp 0x0000:0x7E00

disk_error:
    mov si, msg2
    call print_string
    jmp $
    
print_string:
    pusha
    mov ah, 0x0E     ;1文字出力
    mov bh, 0
.print_loop:
    lodsb            ;SIの示すアドレスから1バイトをALに読み込み、SIを+1（次の文字へ）
    test al, al      ;alが0かどうかを確認する
    jz .done         ;文字が 0 か（＝終端記号か）を確認。※'hello',0　文字列の最後に目印の0を入れている
    int 0x10         ;BIOS割り込みで AL の文字を表示
    jmp .print_loop
.done:
    popa
    ret

msg1 db 'boot loader', 0x0D, 0x0A,0
msg2 db 'disk_error', 0x0D, 0x0A,0

times 510-($-$$) db 0
dw 0xAA55


```
</details>

丁度スクロールされる量の文字列を表示しています。
```testcode.asm
[bits 16]
[org 0x7E00]

start:
    ; セグメント初期化
    xor ax, ax
    mov ds, ax
    mov ss, ax
    mov sp, 0x7C00

    ; カーソル位置を取得（BIOSなし）
    ; call get_cursor_position
    
    mov si, message
    call print_string

    ; メッセージ表示
    mov si, test_msg
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string
    call print_string

    jmp $   ; 無限ループ（停止）


test_msg  db "TEST", 0x0D, 0x0A, 0
test_msg2 db "TEST3", 0x0D, 0x0A, 0

; 起動画面に表示する文字列
message db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,
        db "HEAD", 0x0D, 0x0A,0
        
%include "vga.asm"

times 5120-($-$$) db 0
dw 0xAA55
```

```vga.asm
;------------------------------------------------------------
; VGAカーソル位置をI/Oポートから取得
; 結果は文字単位で cursor_pos に格納
; カーソル位置を取得しないとBIOS呼び出しを使って表示した文字が上にあった場合に
; 既に表示された文字の上から上書きしてしまいます
;------------------------------------------------------------
get_cursor_position:
    ; 下位バイト（カーソル位置のLSB）
    mov dx, 0x3D4
    mov al, 0x0F
    out dx, al
    inc dx        ; DX = 0x3D5
    in al, dx
    mov bl, al    ; BL = LSB

    ; 上位バイト（カーソル位置のMSB）
    mov dx, 0x3D4
    mov al, 0x0E
    out dx, al
    inc dx
    in al, dx
    mov bh, al    ; BH = MSB

    ; BX = カーソル位置（文字単位）
    mov [cursor_pos], bx
    ret
    
;------------------------------------------------------------
; ハードウェアカーソルを更新（VGAポートに書き込み）
; 入力：BX = 新しいカーソル位置（文字単位）
;------------------------------------------------------------
update_hardware_cursor:
    push ax
    push dx

    ; 下位バイト（0x0F）を設定
    mov dx, 0x3D4
    mov al, 0x0F
    out dx, al
    inc dx              ; DX = 0x3D5
    mov al, bl          ; 下位バイト（BXのLSB）
    out dx, al

    ; 上位バイト（0x0E）を設定
    dec dx              ; DX = 0x3D4
    mov al, 0x0E
    out dx, al
    inc dx              ; DX = 0x3D5
    mov al, bh          ; 上位バイト（BXのMSB）
    out dx, al

    pop dx
    pop ax
    ret

;------------------------------------------------------------
; VRAMへ文字列表示（改行対応、BIOSなし）
; 
; push siでsiを復元しておけば以下のような呼び出し方をしても問題ない
    mov si, test_msg
    call print_string
    call print_string
    call print_string
;------------------------------------------------------------
print_string:
    pusha
    push si
    
    ; カーソル位置を取得（BIOSなし）
    call get_cursor_position
    
    ; VRAMのセグメント B800h をESに設定
    mov ax, 0xB800
    mov es, ax

    ; カーソル位置を読み込んで、バイト単位に変換
    mov di, [cursor_pos]
    shl di, 1

.next_char:
    lodsb ; AL ← [DS:SI]
    or al, al ; 終端文字0なら終了
    jz .done

    cmp al, 0x0D        ; CR（行頭に戻る）
    je .carriage_return
    cmp al, 0x0A        ; LF（次の行）
    je .line_feed

    ; 通常文字出力
    mov [es:di], al
    mov byte [es:di+1], 0x0A  ; 緑文字
    add di, 2
    
    ; スクロール判定（80×25×2 = 4000バイト）
    cmp di, 4000
    jl .next_char
    call scroll_screen
    ; scroll後は最下行に戻す
    ; mov di, 80 * 24 * 2
    mov di, 80 * 24
    shl di, 1
    jmp .next_char

.carriage_return:
    mov ax, di
    shr ax, 1
    xor dx, dx
    mov bx, 80
    div bx        ; AX = 行番号, DX = 列番号
    mul bx        ; 行頭へ
    shl ax, 1
    mov di, ax
    jmp .next_char

.line_feed:
    add di, 160         ; 次の行へ（80文字×2バイト）
    
    ; スクロール判定
    cmp di, 4000
    jl .next_char
    call scroll_screen
    mov di, 80 * 24 * 2
    
    jmp .next_char

.done:
    shr di, 1
    mov [cursor_pos], di
    mov bx, di            ; BXにカーソル位置（文字単位）を設定
    call update_hardware_cursor  ; ハードウェアカーソル移動
    pop si
    popa
    ret

;------------------------------------------------------------
; putchar_direct: ALの文字をVRAMに直接書き込む
;------------------------------------------------------------
putchar_direct:
    pusha

    ; カーソル位置（文字単位）をDIに
    mov di, [cursor_pos]
    shl di, 1          ; DI *= 2（バイト単位に）

    ; セグメント ES に VRAM の 0xB800 を設定(ここでAXを退避せずAL破壊してどハマりした)
    push ax 
    mov ax, 0xB800
    mov es, ax
    pop ax

    ; 文字と属性を書き込む
    mov [es:di], al          ; 文字
    mov byte [es:di+1], 0x0A ; 属性（緑）

    ; カーソルを1文字進める(これがないと入力する度前の文字を上書きする)
    add di, 2
    
    shr di, 1
    mov [cursor_pos], di

    ; カーソル表示も更新
    mov bx, di
    call update_hardware_cursor

    popa
    ret
    
;------------------------------------------------------------
; newline: カーソル位置を改行(CR+LF)に更新する関数
; cursor_pos: 文字単位のカーソル位置（0～80*25-1）
; 画面幅80文字、画面高さ25行固定
;------------------------------------------------------------
newline:
    ; 現在位置から行頭計算
    mov ax, [cursor_pos]
    xor dx, dx
    mov bx, 80
    div bx          ; AX = 行番号, DX = 列番号
    
    ; 次の行へ
    inc ax
    cmp ax, 25
    jb .no_scroll
    
    ; スクロールが必要な場合
    call scroll_screen
    jmp .update_cursor
    
.no_scroll:
    ; 新しい位置計算 (行番号 * 80)
    mul bx
    mov [cursor_pos], ax
    
.update_cursor:
    mov bx, [cursor_pos]
    call update_hardware_cursor
    
;------------------------------------------------------------
; 画面を1行スクロール（上に1行詰めて最下行を空白に）
;------------------------------------------------------------
scroll_screen:
    pusha
    push es
    push ds

    ; ES = VRAM (0xB800)
    mov ax, 0xB800
    mov es, ax
    mov ds, ax          ; コピー元にも同じセグメント使う（安全のため）

    ; SI = 行1の先頭（2行目） = 1行160バイト(80文字×2byte　文字1byte 色1bite)
    mov si, 160

    ; DI = 行0の先頭（1行目）
    mov di, 0

    ; CX = 24行 × 80文字 = 1920文字（3840バイト）
    mov cx, 80 * 24 ; (ここは25にしてはいけない)

.copy_loop:
    mov ax, [ds:si]
    mov [es:di], ax
    add si, 2
    add di, 2
    loop .copy_loop

    ; 最下行（25行目 = 行24）の初期化
    ; DI は現在 3840バイト目（最下行の開始位置）
    mov cx, 80
    mov ax, 0x0720         ; 空白 + 属性（黒背景＋灰色文字）

.clear_last_line:
    mov [es:di], ax
    add di, 2
    loop .clear_last_line

    ; カーソル位置を1行上に（80文字 = 1行）
    ; sub word [cursor_pos], 80とすると画面の1行目に戻ってしまったので減算ではなくべた書き
    mov word [cursor_pos], 80 * 24  ; 行番号24× 80列 = 1920文字
    mov bx, 80 * 24
    call update_hardware_cursor

    pop ds
    pop es
    popa
    ret

cursor_pos: dw 0
```
## 紙に書いて整理した分
![default(1).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/90304855-9af2-433b-a677-da134aaadbc5.jpeg)
![default(2).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/f74b29a1-3bd0-402f-88aa-4f899bc55436.jpeg)
![default(3).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/3873d078-3d26-4f3c-b812-7b317b86c9fb.jpeg)
![default(4).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/80c39a18-f520-45ea-826a-7438cccccbb3.jpeg)

## 次回
キーボード入力をした時に画面に1文字ずつ出力している部分の処理にバグが無いかどうかを探ります。
この部分でカーソルがずれていないか等を中心に調べます。
```
c:\>command
```

## 作業風景
![default.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/92c1a54d-172f-4b37-9979-f162cf82b403.jpeg)