OS Coding 3 - BIOS based hex print function
Writing a simple print function
OS Coding
This is a third entry in the series of OS coding. If you want to see more you can see all the entries for OS coding or just see the previous entry.
Purpose of it all
The main purpose of this series of entries is to document my journey through coding a very simple operating system from scratch. Some educational value might be there… somewhere… if you look deep enough.
I take no responsibility for any mental issues that might arise from reading this post!
General references for later use
- The current code on Github; might have more than shown here; the github code shows the last possible iteration of this “OS coding” blog entries
- OSDev.org - an excelent source for OS coding freaks
- Writing a Simple Operating System — from Scratch by Nick Blundell - a really nice, down to earth starting point that explains a lot of the things needed for starting. This entry is heavily influenced by this
This document references
Requirements
All the same requirements as in the first entry on the OS coding are necessary.
The task
In the previous entry a successful string-based print function was created. If we want, in the near future, to be able to read some data from the disk or just reference some data from memory, it’d be nice to have an option to print out hex values. So that we know, if what we’ve read is ok or not. Since not all data is easily presentable in a string format - HEX it is.
source_folder:
|
| - bochs.conf
| - boot_sect.asm
| - print_string.asm
| - print_hex.asm
We’ll be using our, previously created, string-print function.
Printing HEX values
Ideally we’d like to print values like 0xFFFF
in a form of a string. Not including 0x
we’ll be printing out 4 characters.
The full code is as follows:
; use DX as string parameter
print_hex:
pusha ; save all registers
mov cx, 4 ; counter; we'll be printing 4 characters
; 4 bits per char, so 16 in total
print_hex_loop:
dec cx ; decrement the coutner
mov ax, dx ; copy DX into AX so that we can mask the last bytes
shr dx, 4 ; shift dx 4 bytes to the right
and ax, 0xf ; mask ax to get the last 4 bits
mov bx, HEX_OUT ; set bx to the address of our string
add bx, 2 ; skip '0x'
add bx, cx ; add the current counter to the address
cmp ax, 0xa ; check if it's a letter or number
jl print_hex_set_letter ; if it's a number go set a value
add al, 0x27 ; If it's a letter, add 0x27, and plus 0x30 down below
; ASCII letters start 0x61 for "a" characters after
; decimal numbers. We need to cover that distance.
jl print_hex_set_letter
print_hex_set_letter:
add al, 0x30 ; for and ASCII number, add 0x30
mov byte [bx], al ; Add the value of the byte to the char at bx
cmp cx, 0 ; check the counter and compare with 0
je print_hex_done ; finish is 0
jmp print_hex_loop ; otherwise loop again
print_hex_done:
mov si, HEX_OUT ; print out the string pointed by si
call print_string
popa
ret
; global variables
HEX_OUT: db '0x0000',0 ; null-terminated string to temporarily store hex str
A little description
Bellow you can find a short description (from top to bottom) of the print_hex function.
; use DX as string parameter
print_hex:
pusha ; save all registers
mov cx, 4 ; counter; we'll be printing 4 characters
; 4 bits per char, so 16 in total
- we’ll be processing 4 characters independently hence we’re using a counter
- our value is stored in DX
- the counter will be decremented each time we “loop” through the values
print_hex_loop:
dec cx ; decrement the coutner
- each time we “loop” we decrement the counter; once the counter reaches 0 we can assume we’re done
mov ax, dx ; copy DX into AX so that we can mask the last bytes
shr dx, 4 ; shift dx 4 bytes to the right
and ax, 0xf ; mask ax to get the last 4 bits
- each time we lop we’re copying the remaining, and possibly already modified value, at DX, to AX
- we’re shifting the DX 4 bytes to the right; next time the DX will be different so that the loop actually does something else each time
mov bx, HEX_OUT ; set bx to the address of our string
add bx, 2 ; skip '0x'
add bx, cx ; add the current counter to the address
- we’re using the previously-prepared string at HEX_OUT
- and we’re copying it to BX
- we’re skipping two first characters
- and we’re adding the counter to BX so that each loop the “pointer position” we’re… point at, is at a different position; this way each time we loop we’re basically putting the value in a different place
cmp ax, 0xa ; check if it's a letter or number
jl print_hex_set_letter ; if it's a number go set a value
add al, 0x27 ; If it's a letter, add 0x27, and plus 0x30 down below
; ASCII letters start 0x61 for "a" characters after
; decimal numbers. We need to cover that distance.
- when the actual inserting is happening we want to know if we’re dealing with a number of a letter
- in each case the value has to be converted into someting from the ASCII table that can be printed out as a string
print_hex_set_letter:
add al, 0x30 ; for and ASCII number, add 0x30
mov byte [bx], al ; Add the value of the byte to the char at bx
cmp cx, 0 ; check the counter and compare with 0
je print_hex_done ; finish is 0
jmp print_hex_loop ; otherwise loop again
- each value has to be converted into ASCII and put into place in the memory pointed out by HEX_OUT + counter (pointed out by CX)
- if we’ve reached the end of the string (null-terminated string) we can print out the full string
print_hex_done:
mov si, HEX_OUT ; print out the string pointed by si
call print_string
Testing it all
The final boot_sect.asm that is our main code is presented as follows:
;
; Boot sector program that prints a starting sting
;
[org 0x7c00] ; Tell where this code starts in memory
mov si, HELLO_MSG ; Print out the Hello Message by using
call print_string ; the print_string function which requires
; us to use SI as function parameter
mov dx, 0x1fb7 ; We want to print out the hex value
call print_hex ; by using the print_hex function
; that requies us to store the value in DX
jmp $
%include "print_string.asm"
%include "print_hex.asm"
; Data
HELLO_MSG:
db `Hello, World!`, 0 ; null-terminated string
; Padding and magic number
times 510-($-$$) db 0
dw 0xaa55
Like previously, if our bochs is properly set up, we can build the boot sector with nasm:
nasm boot_sect.asm -f bin -o boot_sect_loop.bin
and run bochs:
bochs -f bochs.conf
If no errors we found (👀) bochs will show us our lovely hello string: