Search code examples
assemblyoperating-system16-bit

VGA output not showing if DI > 32767


I have a 16-bit assembly program (NASM) that changes to VGA graphics mode and tries to fill the entire screen with a solid color, but i can only fill up to 32767 (0x7fff) pixels (instead of the full 320x200 display which is 64000 (0xfa00) pixels), any more and the all the white pixels disappear for some reason. I'm assuming it's some sort of signed increment, but I'm not too sure since I'm still learning a bit. Otherwise, the pixels can show up and get drawn to the screen (i use QEMU)

init.asm:

org 0h
bits 16

%define ENDL 0dh, 0ah

jmp _entry

_entry:
    push msg_hello
    call puts

    jmp entry
entry:
    call toggle_vga
    
    push 0fh
    push 07fffh
    call fill_screen
    
    jmp endp

toggle_vga:
    push ax
    mov ah, 0fh
    mov al, 0h
    int 10h

    cmp al, 13h
    je .toggle_vga_off
    jne .toggle_vga_on
.toggle_vga_on:
    mov ah, 00h
    mov al, 13h
    int 10h
    jmp .toggle_vga_end
.toggle_vga_off:
    mov ah, 00h
    mov al, 03h
    int 10h
    jmp .toggle_vga_end
.toggle_vga_end:
    pop ax
    ret
    
fill_screen:
    push bp
    mov bp, sp
    
    push es
    push ax
    push dx
    push cx
    push di
    
    mov ax, 0a000h
    mov es, ax
    mov dl, [bp + 6]
    mov cx, [bp + 4]
    mov di, 0
.fill_screen_loop:
    cmp di, cx
    jge .fill_screen_done
    
    mov [es:di], dx
    inc di
    
    jmp .fill_screen_loop
.fill_screen_done:
    pop di
    pop cx
    pop dx
    pop ax
    pop es

    pop bp
    ret

puts:
    push bp
    mov bp, sp

    push bx
    push ax
    mov bx, [bp + 4]
.puts_loop:
    cmp byte [bx], 0
    je .puts_done

    mov ah, 0eh
    mov al, [bx]
    int 10h

    inc bx
    jmp .puts_loop
.puts_done:
    pop ax
    pop bx
    pop bp
    ret

endp:
    cli
    hlt

msg_hello: db 'init: loaded successfully.', ENDL, 0


Solution

  • jge .fill_screen_done jumps based on the inequality between signed values. For purposes of signed two's-complement arithmetic, 0xfa00 is the number -1536. The values 0 through 0x7fff are positive, and so while di is in this range, we do have di >= cx and so the loop continues. But then you increment to 0x8000, which in signed arithmetic is -32768, and the condition is no longer true.

    You want to use jae .fill_screen_done which jumps based on the inequality between unsigned values. (There's more info in this answer; I seem to recall an even better one somewhere but I can't find it now.) Or even just je .fill_screen_done; you increment di by one on each step, so there is no possibility of it becoming "above" before being equal.

    Another small bug is mov [es:di], dx; this stores a word (two bytes) since dx is a 16-bit register. But you only want to store one byte from dl (you have left dh uninitialized), so make it mov [es:di], dl.


    You could make this more efficient by duplicating the byte into both halves of dx (mov dh, dl), then storing a word at a time (mov [es:di], dx) and incrementing di by two (add di, 2, or inc di twice). Make sure that cx is always an even number.

    Better still would be to use rep stosw, which I'll let you research on your own.