Writing My Own Boot-loader With a Very Primitive Calculator by@Hvidt

Writing My Own Boot-loader With a Very Primitive Calculator

I attempted to run a boot-loader that can use more than just the 512 boot sector. It also runs a very simple kernel that contains a very primitive calculator!
image
Hvidt HackerNoon profile picture

Hvidt

I just do random things, it could be a game in unity or it might be building a simple calculator in asm!

Hello everyone, thanks for clicking on my first-ever blog post! Today, I will be “attempting” to write my very own boot-loader with a very simple and primitive calculator. It might only do addition but it still works!


The first thing we need to do is have a file that we can run, and it will set the rest up! I called this file “boot.asm”. It starts by setting up the dx register for disk loading and then setting up the stack. We then need to set up the ES:BX memory address.


After this, we need to call disk_load, which sets up the kernel; we will discuss this later. At the end of this, we need to actually set up the 512 bytes the boot loader can access by just jumping to every byte and setting it to zero.


At the very last 2 bytes, we need to set the 511th digit to AA and the 512th digit to 55. This basically tells the bios that this can be loaded.

 bits 16

global _start

_start:
    mov dh, 2
    mov dl, 0

    mov bp, 0x9000
    mov sp, bp

    mov bx, 0x1000
    mov es, bx
    mov bx, 0

    call disk_load
    jmp 0x1000:0


%include "drivers/disk.asm"

times 510-($ - $$) db 0
dw 0xAA55


I should probably tell you what the “disk.asm” file does. It contains the disk_load function. It’s actually simpler than expected. All we need to do is run an interrupt and tell it what sectors to read and the drive number. This is where we need the ES:BX memory address to be set up.


Once we move all the right data into the right register, we can run interrupt 13 which reads the sectors into memory so we can actually use them in the kernel. In case we get an error when reading the disk, we can just throw an error that will just halt everything, hence the hlt command.


disk_load:
    push dx
    mov ah, 0x02
    mov al, dh
    mov ch, 0x00
    mov dh, 0x00
    mov cl, 0x02

    int 0x13

    jc disk_error

    pop dx
    cmp dh, al

    jne disk_error

    ret

disk_error:
    hlt


After all of this setup, we can actually run the kernel! We do need to set up a couple of drivers beforehand. We need a way to print a character to the screen, set the video mode, clear the screen, and last but not least, create a new line.


We can do all of this in less than 40 lines of code! Now, if I understood assembly better, I would make it so that I can print more than 1 char at a time. But I decided to put myself through less pain and just cause more work for myself later on.


Either way, we can do this all with some interrupts. We can set the video mode by calling the 0x10 or the 10th intercept; we do need to set up 2 prior registers first, ah and al. We can set ah to 0 or 0x00 and al to 1 or 0x01. We can then call interrupt 10 and it will set the video mode. We now need a way to clear the screen.


Once again, we can use the 10th interrupt. We can set ax to 0x0003 and then call intercept 10. We can clear the screen this way. Now, we once again need to use the 10th interrupt to print a character to the screen. We set ah to 0x0e and then call interrupt 10.


Now, the last thing we need for this driver is to be able to create a new line. Surprisingly, we do not use the 10th interrupt, we instead use the print char function from earlier to do it. We set the ax register to 0x0a and then call print_char. After that, we need to set al to 0x0d and then call print_char again, and this should be it for our video driver!

ret


set_video_mode:
  mov ah, 0x00
  mov al, 0x01
  int 0x10

clear_screen:
    push ax
    mov ax, 0x0003
    int 0x10
    pop ax
    ret

print_char:
    push ax
    push bx
    push cx
    push dx
    push si 
    push di
    mov ah, 0x0e
    int 0x10
    pop di
    pop si
    pop dx
    pop cx
    pop bx
    pop ax
    ret

new_line:
    push ax
    mov ax, 0x0a
    call print_char
    mov al, 0x0d
    call print_char
    pop ax
    ret


We need to create a driver to read the keyboard driver. We can do this with another interrupt, but instead of using the 10th interrupt, we use the 16th interrupt. We can do all of this by setting ah to 0, and then just call the 16 interrupt. This should be the end of our keyboard driver!

ret

read_keyboard:
    push bx
    push cx
    push dx
    push si 
    push di
    mov ah, 0x00
    int 0x16
    pop di
    pop si 
    pop dx
    pop cx
    pop bx
    ret


Now, we have all we need to run our kernel! A lot of the code in our kernel is just printing characters to write whole words. Since I did not know how to write a function to write an entire word at a time, I have to specify each individual character.


After I write “Please Input Any Digit"!” to the screen, we need to read the keyboard. We can do this with a loop, now we can only have 1 thing that can trigger the loop to end. We do this with a lot of fancy code I do not even understand very well, so I am not even going to attempt to explain.


We then do this all again. We can take the 2 digits and convert their uni code to just the numbers by subtracting 48 from each. We then add both together and add 48 back to it so we can print the uni code on the screen. This is all I added to the kernel!

bits 16

start:
  call set_video_mode
  call clear_screen
  mov al, 'P'
  call print_char
  mov al, 'l'
  call print_char
  mov al, 'e'
  call print_char
  mov al, 'a'
  call print_char
  mov al, 's'
  call print_char
  mov al, 'e'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'I'
  call print_char
  mov al, 'n'
  call print_char
  mov al, 'p'
  call print_char
  mov al, 'u'
  call print_char
  mov al, 't'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'A'
  call print_char
  mov al, 'n'
  call print_char
  mov al, 'y'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'D'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 'g'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 't'
  call print_char
  mov al, '!'
  call print_char
l1:
  call new_line
  mov al, '>'
  call print_char
  mov al, ' '
  call print_char
  call read_keyboard
  call print_char
  cmp al, '1'
  mov cl, al
  cmp al, '2'
  mov cl, al
  cmp al, '3'
  mov cl, al
  cmp al, '4'
  mov cl, al
  cmp al, '5'
  mov cl, al
  cmp al, '6'
  mov cl, al
  cmp al, '7'
  mov cl, al
  cmp al, '8'
  mov cl, al
  cmp al, '9'
  mov cl, al
  cmp al, '0'
  mov cl, al
  mov al, '1'
  cmp al, '1'
  jne l1
  call new_line
  mov al, '1'
  call print_char
  mov al, 's'
  call print_char
  mov al, 't'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'D'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 'g'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 't'
  call print_char
  mov al, ':'
  call print_char
  mov al, ' '
  call print_char
  mov al, cl
  call print_char
  call new_line
  call new_line
  mov al, 'P'
  call print_char
  mov al, 'l'
  call print_char
  mov al, 'e'
  call print_char
  mov al, 'a'
  call print_char
  mov al, 's'
  call print_char
  mov al, 'e'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'I'
  call print_char
  mov al, 'n'
  call print_char
  mov al, 'p'
  call print_char
  mov al, 'u'
  call print_char
  mov al, 't'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'A'
  call print_char
  mov al, 'n'
  call print_char
  mov al, 'y'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'D'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 'g'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 't'
  call print_char
  mov al, '!'
  call print_char
l2:
  call new_line
  mov al, '>'
  call print_char
  mov al, ' '
  call print_char
  call read_keyboard
  call print_char
  cmp al, '1'
  mov dl, al
  cmp al, '2'
  mov dl, al
  cmp al, '3'
  mov dl, al
  cmp al, '4'
  mov dl, al
  cmp al, '5'
  mov dl, al
  cmp al, '6'
  mov dl, al
  cmp al, '7'
  mov dl, al
  cmp al, '8'
  mov dl, al
  cmp al, '9'
  mov dl, al
  cmp al, '0'
  mov dl, al
  mov al, '1'
  cmp al, '1'
  jne l2
  call new_line
  mov al, '2'
  call print_char
  mov al, 'n'
  call print_char
  mov al, 'd'
  call print_char
  mov al, ' '
  call print_char
  mov al, 'D'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 'g'
  call print_char
  mov al, 'i'
  call print_char
  mov al, 't'
  call print_char
  mov al, ':'
  call print_char
  mov al, ' '
  call print_char
  mov al, dl
  call print_char
  call new_line
end:
  call new_line
  mov al, cl
  call print_char
  mov al, ' '
  call print_char
  mov al, '+'
  call print_char
  mov al, ' '
  call print_char
  mov al, dl
  call print_char
  mov al, ' '
  call print_char
  mov al, '='
  call print_char
  mov al, ' '
  call print_char
  sub cl, 48
  sub dl, 48
  add cl, dl
  add cl, 48
  mov al, cl
  call print_char

jmp $
%include "drivers/vga_driver.asm"
%include "drivers/keyboard_driver.asm"

times 1024-($-$$) db 0


In the very end, we just need to combine everything. I do this with a very simple make file. It’s not much but it works!

all: clean run

boot.o: boot.asm
	nasm $< -f elf -o [email protected]

drivers/keyboard_driver.o: drivers/keyboard_driver.asm
	nasm $< -f elf -o [email protected]

drivers/vga_driver.o: drivers/vga_driver.asm
	nasm $< -f elf -o [email protected]

drivers/disk.o: drivers/disk.asm
	nasm $< -f elf -o [email protected]

kernel/kernel.o: kernel/kernel.asm
	nasm $< -f elf -o [email protected]

boot.bin: boot.o kernel/kernel.o drivers/disk.o drivers/vga_driver.o drivers/keyboard_driver.o 
	ld -m elf_i386 -o [email protected] -Ttext 0x1000 $^ --oformat binary

run: boot.bin
	qemu-system-i386 -fda boot.bin

clean:
	$(RM) *.bin *.o
	$(RM) drivers/*.o


I am sorry about the amount of code, there is definitely a better way to do this. I am just too much of a noob to figure it out. I will also link my GitHub repo at the bottom of this post for anyone curious! If anyone has any suggestions please tell me in the comments! Thanks for reading my first blog post!


https://github.com/RoboTiberius/Primiative-Boot-Loader


I am also taking heavy inspiration from:

https://www.youtube.com/watch?v=5FnrtmJXsdM&list=PLT7NbkyNWaqajsw8Xh7SP9KJwjfpP8TNX&index=2

https://dev.to/frosnerd/writing-my-own-boot-loader-3mld

react to story with heart
react to story with light
react to story with boat
react to story with money
Hvidt HackerNoon profile picture
by Hvidt @Hvidt.I just do random things, it could be a game in unity or it might be building a simple calculator in asm!
Thanks For Reading
L O A D I N G
. . . comments & more!