DOS from Scratch: Debugging Functions

Previous: A Simple Toolchain

One final task I want to get out of the way before attempting to load anything from disk is creating a couple debugging functions. print_hex will print the contents of a register in hexadecimal format, and print_mem will print a specified amount of data from memory in hexadecimal format.

The print_hex function is relatively simple. It isolates 4 bits of the ax register at a time by bitshifting the value until the bits we want are in the lowest 4 positions, then masking that value with 0x000F. It then uses the resulting value as an index for the string 0123456789ABCDEF.

; Prints a register value in hexadecimal
; ax: the value to print
print_hex:
    pusha

    ; Overwrite the first character of HEX_VALUE with bits 15-12
    mov bx, ax
    shr bx, 12
    mov bx, [bx + HEX_TABLE]
    mov [HEX_VALUE + 0], bl

    ; Overwrite the second character of HEX_VALUE with bits 11-8
    mov bx, ax
    shr bx, 8
    and bx, 0x000f
    mov bx, [bx + HEX_TABLE]
    mov [HEX_VALUE + 1], bl

    ; Overwrite the third character of HEX_VALUE with bits 7-4
    mov bx, ax
    shr bx, 4
    and bx, 0x000f
    mov bx, [bx + HEX_TABLE]
    mov [HEX_VALUE + 2], bl

    ; Overwrite the fourth character of HEX_VALUE with bits 3-0
    mov bx, ax
    and bx, 0x000f
    mov bx, [bx + HEX_TABLE]
    mov [HEX_VALUE + 3], bl

    ; Print the now populated HEX_VALUE
    mov bx, HEX_VALUE
    call print

    popa
    ret

    HEX_VALUE: db "****$"
    HEX_TABLE: db "0123456789ABCDEF"

The print_mem function is mostly straightforward now that we have a print_hex function. It just runs a loop loading memory into ax then calling print_hex. The only caveat is it loads the values in big endian orientation so you can read bytes in order from left to right. If we were to load an entire word at once by doing mov ax, [si] it would load as little endian and you'd see the bytes in the order 0, 1, 3, 2, 5, 4...

; Prints words of memory in hexadecimal
; bx: address of memory to print
; cx: number of words to print
print_mem:
    pusha

    ; SI will point to the current byte of memory
    mov si, bx

    print_mem_loop:
        ; Print one word of memory
        ; This loads bytes in big endian order
        mov ah, [si]
        mov al, [si + 1]
        call print_hex

        mov bx, SPACE
        call print

        ; Advance the memory pointer by 1 word
        ; Decrement the loop counter by 1 and exit if it's 0
        add si, 2
        sub cx, 1
        cmp cx, 0
        jne print_mem_loop

    mov bx, LINE_BREAK
    call print

    popa
    ret

SPACE: db " $"
LINE_BREAK: db 0x0a, 0x0d, "$"

Inspecting the Bootloader

Now that we have these debugging functions, we can have the bootloader print itself as a sanity check. This should output the same data as hexdump -C build/boot.img.

; print bootloader contents
mov bx, 0x7c00
mov cx, 256
call print_mem

Next: Getting Disk Info