Grumpy Developer's Prompt

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

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
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. 
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

Image alt

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:

Image alt

#operating systems #os coding #x86