DOS from Scratch: Debugging Functions
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.
print_hex
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"
print_mem
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