I had a philosophical introduction previously about my idea of writing “My own OS”, now it’s time to do some real coding. In this part I’ll roll out a Master Boot Record (MBR), that will load a Kernel from disk (actually, I’m going to port an existing solution to Visual Studio environment). First i’ll start with some free fly philosophy, but there is a candy at the end of this post.
Now, when we are finally getting serious, we need to lay out a plan, because MBR needs to know where the bootloader is, and the bootloader will need to know where it’s loaded in memory. We need some ground rules:
- MBR is always stored as the first 512 bytes on the disk – that’s for granted;
- Bootloader can be anywhere on the disk, and if we write our own MBR, we can hardcode some values on where the bootloader will reside, so we can load it in RAM;
- Also there has to be some mutual agreement between MBR and bootloader on where the bootloader will reside in memory after it’s been loaded from disk and where is the bootloader’s entry point located.
Then we have some options to choose from:
- We can write standard MBR that looks up for an active partition (marked as bootable) and try to read bootloader from that;
- We can treat our disk as one big chunk of data which we can crunch how ever we want to;
- We can write single assembly for MBR and bootloader, just remember to mark the boundary with the magical 0xAA55 bootsector signature at from byte no. 510 to 511 (0 based) and afterwards continiue with our bootloader code. But don’t forget about the Memory mapping I told you on my previous post – there’s 500Kb space after MBR in memory where to store your bootloader and then there’s some 30Kb before MBR – yep it’s messy;
- We can (probably) incorporate some C code for ease of use – we just need some glue code (just like this one).
And finally we need to know weather MBR and bootloader should use some file system. For example, weather a bootloader is a file in some popular filesystem or not. If not we can still treat the disk as an array of bytes and just read up everything we can after 512 byte MBR and treat it as our bootloader code. Same goes for the Kernel – is it a file or just a chunk of data on disk.
As this is an experiment – I’ll go the easy way – I’ll avoid any hard core Assembly and just use disk as a single array of bytes.
My disk layout will look like this:
- 0x0000 – MBR byte 0 – 511
- 0x0200 – Bootloader code byte 512 – … we’ll see (depends on the assembly instruction count, etc.)
- 0x???? – My C program (kernel) – … we’ll see
We also need to think about how we’ll manage our memory. As in Real Mode we only have 512Kb of space (without enabling some A20 gates and going into Unreal Mode) – it should be plenty for bootloader, but it will definitely not be suitable for our “Kernel” as it can be as long as your software needs to be. So we’ll need to go into Unreal Mode eventually to copy Kernel code after 1Mb boundary.
- 0x00007C00 – MBR code (we won’t do any relocation, as it makes stuff complicated)
- 0x00007E00 – Bootloader code (it seems we’re stuck with 480Kb limit on our bootloader, but it might be enough)
- 0x01000000 – Kernel (we are not going to store our kernel at 0x00100000 as there might be a memory hole at 0x00F00000)
Simple eh! But this is just theory, and theory usually takes me back. I want to run some code first and then think about what’s exactly happening – not to loose the passion, so to speak.
MBR and Bootloader code (not mine, but still)
Now let’s forget the theory for a minute, and I promise, once I’ve made my self clear, I’ll re-post these series in a readable manner.
I found this beautiful Bare Bones Bootloader and thought “Let’s do it”. My plan is to create a Visual Studio project for this loader/kernel and automated build events.
What will you need:
- Build GCC Cross-compiler (the one that can build type A code on type B system)
- Visual C++ 2010 Express
- Bochs Emulator
- Download this Visual C++ Solution that I created
Download the setup.exe file from Cygwin.com and install. I’ts better that you install it into the default location “C:\cygwin\” as my Visual Studio solution assumes this location. You’ll have to select these packages (just type the name in the search box):
- bison (binary)
- flex (binary)
- gmp and libgmp-devel (binary)
- mpfr and libmpfr-devel (binary)
- mpclib and libmpc-devel (binary)
- gcc4 (binary and sources)
- gcc4-core (binary and sources)
- gcc4-g++ (binary and sources) – maybe you won’t need it, but it’s good to have one around
- libgcc1 (binary and sources)
- nasm (binary)
- binutils (binary and sources)
- make (binary)
You need sources, to create a cross-compiler, because binary version is intended only for Windows development (thus only supports PE files).
The article is here, but I’m going to write down step-by-step for dummies on how to build one in Cygwin. Here we go:
- Fire up the Cygwin Terminal after you’ve installed everything I listed before
- type cd /usr/src/ - this is the directory where Cygwin installed all the source packages;
- type ls -la – you should see the installed sources (every package in it’s own directory);
- Now we’ll need to prepare our environment for the build
- Create directory /usr/local/cross/ with mkdir /usr/local/cross/ – my Visual Studio solution assumes that you’ll have all the tools in “C:\cygwin\usr\local\cross\bin\” directory;
- export TARGET=i586-elf – this tells make script that our cross-compiler will generate 32bit ELF files (Yes we’re not ready for 64bit ELF files yet);
- export PREFIX=/usr/local/cross/$TARGET – this will tell the make script where to put compiled binaries;
- To build binutils we’ll create a temporary build directory with mkdir /usr/src/build-binutils/ and enter it with cd /usr/src/build-binutils/. We need this to keep the source directory clean in case we’ll need to create other cross-compilers for other platforms;
- Now the usual *nix style build begins:
- type ../binutils-x.xx/configure –target=$TARGET –prefix=$PREFIX –disable-nls – the x.xx is the version of your binutils installed by Cygwin (look it up on 3rd step);
- Once configure script has done it’s work, it’s time to compile and install. Simply type make all and see the binutils being compiled from the source;
- Once compiler has finished it’s work hit make install to finalize installation and your done with binutils;
- Now let’s move on to GCC. GCC comes in tar.bz2 archive, so we have to expand it first:
- Unzip with: bunzip2 gcc-x.x.x.tar.bz2 (same as before x.x.x is the version number, the Cygwin has installed for you);
- Extract with: tar xf gcc-x.x.x.tar (bunzip2 will remove .bz2 extension)
- Add one more environment variable with export PATH=$PATH:$PREFIX/bin, this will tell gcc where the new binutils reside (I think … bare with me);
- Create a build directory for gcc with mkdir /usr/src/build-gcc/, same as with binutils – we’d like to keep the source directory clean, and if anything fails, we can delete this temporary directory and start over;
- Now the build itself:
- Configure build: ../gcc-x.x.x/configure –target=$TARGET –prefix=$PREFIX –disable-nls –enable-languages=c,c++ –without-headers
- Make: make all-gcc
- Install: make install-gcc
- Voila! You’re ready.
The MBR and Kernel
By now you should’ve been downloaded the Bochs Emulator, Visual Studio 2010 Express
Well, installing Visual Studio should be a Next->Next->Finish task, so I leave it to you as is. Same goes with the Bochs. You just need one more thing now – download my VS Solution file here and open it up. Press build and go to Release directory – there you have it – small 1Mb disk image with a running MBR, bootloader and C code. Just hit the bochsrc.bxrc (if it’s mapped to be executed by Bochs) and see how the C code boots up. Also you can use my Diskutils to write this image to hard drive and test it on a live hardware.
I’m out, thank you!
My previous diskutils version was intended only for MBR, thus it was reading only 512bytes from the file. I uploaded a new one, with 3rd argument for length in bytes (default: 512)
Download diskutils here.