Grumpy Developer's Prompt

OS Coding 2 - BIOS based print function in 16-bit REAL mode

Writing a simple print function


OS Coding

This is a second 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 we’ve seen that we can make BIOS load our boot sector but nothing really happened. All we did is stop the BIOS from shouting that we don’t really have a boot sector. We’ve added one with a very simple app that basically loops forever.

It would be a good idea to print something so that we know it’s actually working. A print function in assembly would be a good starting point. We can write a simple function in C but… let’s write one in assembly. It’s been ages and my code will be… fairly bad. But… let’s try to print out the word “Hello, World!” in assembly. We’ll be using a null-terminated string - a string that is terminated with a ‘0’ value (a common thing in computing) so that our print function knows where to start.

The general idea is to write a looping function that checks the current character, compares it to a zero, and then either proceeds to the next character or ends the execution. Some knowledge of pointers is necessary.

Print Function In C

Since we’re a bit rusty in ASM let’s see how that would look like in C, before we move to more obscure ways of doing this simple thing. So if we were to write it in C we would have something similar to:

void print_function(char *c)
{
    while (*c != '\0')
    {
        // print out the character located at (*c)
        c++;
    }
}

This function:

That function, in C, can be executed in a simple manner:

char *string = "Hello, World!\0";
print_function(string);

This code:

That code in C is noting special and it fairly simple. But we’re not writing in C yet. We need that code in assembly.

Print Function In Assembly

To print out the character we’ll be using the BIOS functions for now. The easiest way (probably) is to use the BIOS function teletype. Teletype prints out one character and moves the screen cursor forward.

To do that the teletype function uses a value in a specific register and is controlled by an interrupt. There’s a great number of possible interrupts but we’ll be using the int=10/ah=0x0e one.

To use the teletype print character function we need to:

In assembly that would be something like:

; assume we already have the character in AL

mov ah, 0x0e            ; int=10/ah=0x0e -> BIOS teletype
int 0x10                ; AL already contains the character; print it out

This code assumes we already have a character in that specific register (AL). In the case of the C-language code we just provided a pointer. What happens on the assembly language in C is that, when we provide a parameter, that parameter goes on the stack or some specific register (depending on the C implementation). So, before we execute our assembly function, we need to have a common understanding where that string/parameter will be hend.

In the code before I’ve used SI. The assumptions is that the SI contains the start of a null-terminated string.

; use SI as string parameter
print_string:
    pusha
    mov bx, 0007h            ; BH is DisplayPage, BL is GraphicsColor

start:
    mov al, [si]            ; <<<<<<<
    cmp al, 0               ; if 0 then end
    je done
    mov ah, 0x0e            ; int=10/ah=0x0e -> BIOS teletype
    int 0x10                ; AL already contains the character

    inc si                  ; >>>>>>>
    jmp start

done:
    popa
    ret

The above code (from top to bottom):

We can save that code to “print_string.asm” and use that in our assembly boot-sector:


; Boot sector program that prints a starting sting
;

[org 0x7c00]                        ; Tell where this code starts in memory

    mov si, HELLO_MSG               ; Use SI as function parameter
    call print_string

    jmp $

%include "print_string.asm"

; Data
HELLO_MSG:
    db `Hello, World!`, 0           ; null-terminated string

; Padding and magic number
    times 510-($-$$) db 0
    dw 0xaa55

There’s a couple of added things in relation to the first entry on the OS coding.

The rest remains the same. The endless looping, the padding (to fit 512 bytes), the magic numbers at the end.

If our folder structure should looks something like (I’ve renamed loop.asm to boot_sect.asm):

source_folder:
|
| - bochs.conf
| - boot_sect.asm
| - print_string.asm

we can test it out.

Testing it all

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

Final thoughts

So what was done here was fairly simple. We modified our boot sector, written before, so that a string can be printed out. Some assembly knowledge was required. We iterated through the memory-block each time checking if a particular character should be printed out or not. We’ve added a function to the assembly language and called it from our boot sector.

#operating systems #os coding #x86