Learn to Code via Tutorials on Repl.it!

← Back to all posts
16-bit OSDev, part 1
h
MocaCDeveloper (713)

Hi!

Boy, am I excited about this tutorial!
I am going to be sharing my experience over my journey into OSDev. This isn't my first, nor second attempt at it. It's my third attempt at OSDev. But, it is much more of a success than the first few times.

There are a few things we will need to do before we can get into the code, so lets get it out of the way

Starting off

This tutorial will go over 16-bit OSDev, and how we can create an OS using 16-bit assembly along with C code.

Development will take place on Windows. If you're on a Linux machine, don't worry, I will post a tutorial focusing on Linux-based development.

There are a few tools we will need to do this:

  • Qemu
  • TCC (Tiny C Compiler)
  • nasm

Setting up the work space

First, before all, we need to have qemu installed on our Windows machine. You can find the latest downloads:

The installation is easy. Click on one of the downloads, and when it has installed, find it within the file explorer, double click on the file(since it's a .exe, it will thus install).

Now, it will install on your Windows machine, but you will not have access to using it via the terminal. So, lets open up Windows env. You can do this by typing 'env' at the bottom left of the screen:

This will open a small Window. On this Window, you are going to want to click on 'Environment Variables..':

Now, this will make another window popup. Here, you are going to want to click on 'path', then click 'edit':

This will, yet again, popup another window. Simply click 'edit', and insert the path of qemu:

When you are done, just click 'ok' until all of the screens are gone.

To test and see if you have successfully rooted the command qemu, lets open up the command prompt and type qemu-system-i386:

You should see the following output.

Great, now, lets install nasm. You can find the latest downloads:

Thusly, just follow the above steps for downloading/rooting the qemu command. Double click the .exe file, open Windows env, put the path of nasm into the 'path' variable, then click ok until all the windows close.

Now, that's all we need for this tutorial. But, I am going to go ahead and walk you through on how to install TCC. It's simple, and near the same as the steps to downloading qemu and nasm.
You can find the downloads here. But notice, these downloads are zip files. Go ahead and download the one you want, I downloaded tcc-0.9.27-win64-bin.zip. Open up the file explorer, click on the folder, and there should be an option to "Extract All":

Click on it, then click "extract". But wait, before you do so, we're going to want to know the path! So, lets copy the path we are "dumping" it into so we can put that into our Windows ENV.

I named the folder it's "dumping" into tcc. You can name it however you want. Now, the path you copied, paste that into the Windows env under 'path'. You will now have tcc installed, and available to use via your terminal.

Lets test and see if it works:

You should see the similar output.

Great! We have qemu, nasm AND tcc installed. We're set!

Lets start getting dirty!

Do you know what a bootloader is? Little alone what a boot sector is? No? Well, let me briefly explain:

A bootloader is a program that loads in the boot sector. This program does all the "setup". Sometimes, more than not, you will need a second bootloader. But why?
Well, the boot sector dwells within the first 512 bytes of the program, meaning, we ONLY have 512 bytes available to us for the initial boot loader.

Why does the boot sector only have 512 bytes?
The boot sector is located on the first sector of the floppy(or hard disk). Sectors have 512 bytes, total. Now, you might be asking, why is the boot sector limited to such...low amount of space? That's where the BIOS comes in. The BIOS expects the "magic number" to be at the end of the 512 bytes. This 2-byte number tells the BIOS that this is a boot sector.

What is the magic number? The magic number is 0xaa55. The binary output of using nasm is in big-endian, meaning 0xaa55 will thus be 0x55aa in the binary output.

The BIOS will load the boot sector at address 0x7C00. Using nasm(or any other assembler), we have access to this thing called org. What this does is tell the assembler the memory segmentation of this assembly code. So, if we say org 0x8000, the assembler assumes this assembly code is loaded at the address 0x8000. To keep things simple, we will tell the assembler that this code will be at the address 0x7C00, otherwise there is rather more startup than there is setup.

Lets take a look at a simple bootloader:

[org 0x7C00]
bits 16

jmp $

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

Simple. But, lets look over it:

  • The first line is telling the assembler this code is to be aligned at the address '0x7C00'
  • The second line, bits 16, tells the assembler this is 16 bit assembly
  • jmp $ tells the assembler to continuously jump to the current program
  • times 510 - ($ - $$) db 0 tells the assembler to pad the rest of the program with bytes of the value zero. ($ - $$) gets the current amount of bytes taken up by the program
  • dw 0xaa55 tells assembly to fill 2 bytes with the value aa55

Why don't we do 512 - ($ - $$) db 0? Well, the last two bytes of the boot sector are expected to be 55aa. So, we leave the last two bytes out of padding, and then assign those last two bytes with dw 0xaa55.

Lets talk BIOS

The BIOS has many sub-routines that enable us to do many things within our boot-sector, along with any other 16-bit assembly code we write for the OS.

The most common interrupt is 0x10. This interrupt is used anywhere from changing the graphics of the terminal, to printing things to the terminal.

Now, before we do anything else, we have to do some initializing. Firstly, we have to set all data registers to zero, then, we have to setup the stack.
While this might sound hard, it is quite easy.
There are a few data registers available in 16-bit assembly:

  • dx, ds, dl
  • cx, cs
  • ss

Lets go ahead and set these all to zero:

[org 0x7C00]
bits 16

xor ax, ax
mov dx, ax
mov cx, ax

jmp $

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

Why did I not set ss to zero? When initializing the stack, ss and sp have to be right next to each other. Why? Because it's "safe".

Now, lets setup the stack. This is simple, we just set the base pointer register(bp) to the programs origin(0x7C00), set ss to zero, then set the stack pointer(sp) to the base pointer(bp).

[org 0x7C00]
bits 16

xor ax, ax
mov dx, ax
mov cx, ax

cli
mov bp, 0x7C00
mov ss, ax
mov sp, bp
sti

jmp $

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

What are cli and sti? cli clears the interrupt flag. sti stores the value the immediate value source into the data register(ds).

Great, we now initialized the data registers to zero and have setup the stack.

Now, lets see if it works, which brings me back to the BIOS interrupt 0x10. There are many functions aligned with this interrupt call. We can print to the screen with this interrupt, change the graphics mode etc.

I am simple going to focus on printing a single character to the screen to make sure the stack is functioning as it should.

I think we should all be familiar with the push operand. push, in assembly, pushes a value onto the stack, pop enables us to retrieve that value and store it back.

So, lets push a value, oh...lets say 'A', onto the stack.

[org 0x7C00]
bits 16

xor ax, ax
mov dx, ax
mov cx, ax

cli
mov bp, 0x7C00
mov ss, ax
mov sp, bp
sti

push 'A'

jmp $

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

Great, now 'A' should be stored on the stack.
How do we know it's on the stack, though? Well, firstly, lets understand how the stack works. You probably think the stack grows up. Simply put it, you are wrong. The stack rather grows downwards from the base pointer.

So, 'A' is a 2-byte value, and so, we can safely assume that 'A' as stored at the base pointer - 2(bp - 2).

But, how would we print? The function code to print is 0x0e. We store this function call in the higher-bit of ax(ah). We then store the value to print in the lower-bit of ax(al). Then, we call the BIOS interrupt 0x10. Lets see!

[org 0x7C00]
bits 16

xor ax, ax
mov dx, ax
mov cx, ax

cli
mov bp, 0x7C00
mov ss, ax
mov sp, bp
sti

push 'A'
mov ah, 0x0e
mov al, [bp - 2]
int 0x10

jmp $

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

Safely assuming that our stack is functional, and that it does indeed grow downwards, we can also assume that bp-2 will store the value 'A' that we pushed onto the stack.

Compiling

To compile the assembly code to a 16-bit executable, run the following command:
nasm file.asm -f bin -o file.bin

Now, on my local Windows(and Linux) machine, I have to directly give the path of qemu when running the qemu command, otherwise it fails to work.

So, I run the program via:
qemu-system-i386 -L "C:\path\to\qemu" file.bin

-L, I am guessing, is a directive that tells qemu to look for that specific path. I do not know, however, what exactly it does. Most people do run just with:
qemu-system-i386 file.bin, and, when we eventually get into working with a floppy drive, they will run with other commands to specify to qemu how to run.

Assuming our code is correct, and assuming it compiled successfully, we should get the following output:

Great! We successfully created our first bootloader!
We also were capable of setting up the stack, and making sure it's fully functional, we also learned how to print!

Summary

Next time, we will look deeper into BIOS and it's interrupts, the cool things we can do with the BIOS interrupts, and how to read sectors from the disk using a BIOS interrupt!

If you wish, you can get a head start by visiting this website. This website has a table of interrupts the BIOS offers to us, some useful, some not so useful.

Go ahead and knock yourself out with testing the interrupts, along with the functionality, out! I will also direct you to this wiki page over the interrupt 0x10 and some things you can do with it(P.S, this page also tells you how to change the graphics).

Until next time, MocaCDeveloper...OUT!

Comments
hotnewtop
ch1ck3n (2388)

our computer machine

our boot-sector

our code

our first bootloader

MocaCDeveloper (713)

@ch1ck3n

Lmao. I said "our" allot in this tutorial in a term that I am trying to make it seem like "we" did it together.

ch1ck3n (2388)

@MocaCDeveloper i dunno man, seems kinda communist to me

MocaCDeveloper (713)

@ch1ck3n

Believe me, I am not communist lol.
I am very well American and opposed to the ideal of communism.

ch1ck3n (2388)

@MocaCDeveloper i dunno man seems kinds capitalist to me

fuzzyastrocat (1867)

@MocaCDeveloper

opposed to the ideal of communism

Comrade, you must abandon the corrupt "closed-source" and "pay-for-use-software" bourgeoisie! Join the proud open-source proletariat as we share our software for all to use! It is o u r software, comrade!

(Jokes aside, open-source software actually does have a lot of similarities to communism when you think about it...)

MocaCDeveloper (713)

@ch1ck3n
can't tell if that's a joke or not

ch1ck3n (2388)

@MocaCDeveloper everything thats directly related to me is a joke

including my life

DynamicSquid (5027)

@fuzzyastrocat I never thought of it like that lol. But I guess open source is communism, just, without the millions of people starving to death

fuzzyastrocat (1867)

@DynamicSquid

without the millions of people starving to death

casually ignores the millions of developers starving to death due to zero income from the MIT license

IMayBeMe (552)

@ch1ck3n I see you well versed in you knowledge of memes

elipie (355)

Finally, a tutorial for a OS off replit, This is a great tutorial, cannot wait for the other parts coming!

CSharpIsGud (1070)

Keep in mind you can install qemu on repl with NIX!

Also I recommend that you try to do as much in C as you can, rather than doing a lot in asm, since more people will understand C.

MocaCDeveloper (713)

@CSharpIsGud

I know! I have a tutorial I am uploading tonight over how to install everything on your local Linux machine AND Repl!

DynamicSquid (5027)

@CSharpIsGud Is there any actual advantage of using assembly over C (or vice versa) to code an OS?

CSharpIsGud (1070)

@DynamicSquid Maybe a slight boost in speed if you're really really good with it, otherwise it just makes everything complicated. The most you should have to use it for is if you're making the kernel higher half

MocaCDeveloper (713)

@DynamicSquid

There is a bit of an advantage where speed is important, however, if you are not looking forward to writing thousands upon thousands of lines of assembly, that's where C comes in.

MocaCDeveloper (713)

@DynamicSquid

Also, when it comes to 16-bit OSDev, writing the OS in pure assembly meanings writing everything from scratch, from the file system, to the compatibility of the memory model being used, to the memory allocation functions such as malloc, calloc etc.

MocaCDeveloper (713)

@CSharpIsGud

Normally the only assembly there is when it comes to an OS is the bootloader and the jumping from the bootloader to the memory segmentation of the kernel where, normally, is where the C code comes in play. Writing it from pure assembly tends to be for learning purposes as well as a meme.
There is, however, a slight boost of speed when it comes to doing it in pure assembly, however, you'd commit virtual suicide doing such a thing.

CSharpIsGud (1070)

@MocaCDeveloper Even then, you only get that slight boost when you right near perfect assembly.

Infiniti20 (29)

huh do you have access to my browsing history? I legit just started work on my own (small) os two days ago.

DillonB07 (29)

Development will take place on Windows. If you're on a Linux machine, don't worry, I will post a tutorial focusing on Linux-based development.

What about macOS? Can I work on that?

MocaCDeveloper (713)

@DillonB07

Yes, you can! However, I do not know how to specifically install each ideal used, such as the tcc compiler.
I believe the TCC compiler is just for Windows due to it being a native C compiler on an 8086. But perhaps you could figure out a way, and if not, I do believe macOS supports objdump with enables you to convert a C binary executable to a 16-bit executable.

fuzzyastrocat (1867)

our Windows machine

Why not just use replit's linux environment? If you're going to post a tutorial on repl talk, I would think that readers should be able to use repl to follow along... (Also not everyone has Windows)

But nice work!

MocaCDeveloper (713)

@fuzzyastrocat

I mentioned within the tutorial that I would post a tutorial over 16-bit OSDev on Linux machines(which will include repl)!

DynamicSquid (5027)

How do you not have content creator yet??

actually gimme a sec I'll see what I can do

Also what does Qemu do exactly? Does it just funnel your computer's resources into the OS you're making?

fuzzyastrocat (1867)

@DynamicSquid Probably because a tutorial like this (OS dev I mean) has already been seen before, so it gets glossed over even though I'm sure a lot of work went into making this.

Qemu is basically a program that simulates a computer. So that way, you can load your OS onto the emulated computer instead of having to boot your real computer from the OS (which is a pain since you have to keep switching OS'es if you want to go back to your regular os).

MocaCDeveloper (713)

@fuzzyastrocat

I myself have not seen a tutorial where you write your own bootloader. The only OSDev tutorials I have seen used something like Grub, where you don't need to create the boot loader from scratch.

I am planning on going from complete scratch. Writing your own bootloader, aligning the kernel in memory, writing the kernel(in C), and how to compile the C code to a 16-bit executable.

As I mentioned, I have only ever, from what I have seen, seen a OSDev tutorial that uses something like Grub, and gets straight into the loading of the kernel.

I think a OSDev tutorial over writing your own bootloader is a good thing to have, it exposes you to BIOS routines, how to interact with BIOS, how to do many cool things with BIOS, it also teaches you some how to be flexible with the programs you write.

There are many advantages of creating your own bootloader, although BIOS will be obsolete here within the next decade, or less. It's still worth learning and at least seeing what OSDev was like when you had to create your own bootloader.

fuzzyastrocat (1867)

@MocaCDeveloper Perhaps I did not express myself clearly.

I am not saying what you are doing here is copying someone else, or doing what someone else has already posted. I am fully aware that you are going deeper than others have.

However, my point is that because it falls under the category of "os dev", and because most people on repl don't understand the subtleties of bootloader-from-scratch and the like, people just see this as "oh it's another os dev thing" and gloss over it. This isn't right, because you've put lots of work into this and it's a cool achievement, but I think it's what happens.

MocaCDeveloper (713)

@fuzzyastrocat

Hmm perhaps I should title the tutorials differently.

MocaCDeveloper (713)

@DynamicSquid

How do you not have content creator yet??

Good question, I wondered the same thing!

Also, Qemu does as @fuzzyastrocat said, it acts like a computer. It's a simulation of a computer so you don't have to worry about needing to store your OS on actual hardware.

DynamicSquid (5027)

@MocaCDeveloper title it "Programming With Communism Part 1"

Oh okay, it makes more sense now!

DynamicSquid (5027)

@MocaCDeveloper You have content creator now :)

MocaCDeveloper (713)

@DynamicSquid

I DO???

Holy bucket of nuggets with a side of ranch, I Do.
I've wanted that for a long time now :D

Thanks!!

fuzzyastrocat (1867)

@MocaCDeveloper

Holy bucket of nuggets with a side of ranch

Where can I subscribe to the Church of the McNugget?

DynamicSquid (5027)

@MocaCDeveloper :)

and btw barbecue sauce is better than ranch for nuggets

MocaCDeveloper (713)

@fuzzyastrocat

Sorry, it's elite members only :'(

MocaCDeveloper (713)

@DynamicSquid

Agreed!

Holy bucket of nuggets with a side of bbq