Bootstrapping a PC

Here I am going to go through bootstrapping a PC. I will walk through this using QEMU. Basically I just want to build and link a bootable disk image and get this up and running in QEMU.

The code I will run is a file called boot.s:

.globl enter
.code16 #we start in real mode
movl $222, %eax;
jmp enter;

This obviously does nothing. There is the label enter that we just put in an infinite loop. It also puts 0xDE into register eax that we can use to verify later that our code is actually running. Now I build this using the following command:

gcc -c -o bin/boot.o boot.s -nostdinc

Now we link it. The -e here tells the linker to use the label enter as the entry point. It also tells it to mark the code to be loaded at 0x7C00. This is because the BIOS loads our bootable disk into memory at physical address 0x7C00 and then sets the instruction pointer to this value which gives us control.

ld -e enter -Ttext 0x7C00 -o bin/boot bin/boot.o

We can now look at the output of this using objdump and readelf:

readelf -l bin/boot
Elf file type is EXEC (Executable file)
Entry point 0x7c00
There are 1 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00007000 0x00007000 0x00c09 0x00c09 R E 0x1000
Section to Segment mapping:
Segment Sections...
00 .text

~/src/steok$ objdump -d bin/boot
bin/boot: file format elf32-i386
Disassembly of section .text:
00007c00 :
7c00: 66 b8 de 00 mov $0xde,%ax
7c04: 00 00 add %al,(%eax)
7c06: e9 .byte 0xe9
7c07: f7 ff idiv %edi

Here you can see our code gets loaded at 0x7C00 as we expect and that the disassembly all looks good.

The next step is to create a bootable image of this.

objcopy -S -O binary bin/boot bin/bootdisk

We need to sign this image with a magic signature to mark it as bootable. Here is a perl file that does this that I got from here:

open(SIG, $ARGV[0]) || die "open $ARGV[0]: $!";
$n = sysread(SIG, $buf, 1000);
if($n > 510){
print STDERR "boot block too large: $n bytes (max 510)\n";
exit 1;
$buf .= "" x (510-$n);
$buf .= "\x55\xAA";
open(SIG, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
print SIG $buf;
close SIG;

perl bin/bootdisk

We can now load this into QEMU using:

qemu -hda bin/bootdisk

This will do nothing as we expect as it is just stuck in an infinite loop. So you will see
“Booting from hard disk….” and then no more. But how do we know that our code is actually running? We can debug the machine with gdb and check out that eax contains 0xDE. So start up qemu but this time use:

qemu -s -S -hda bin/bootdisk

This will start qemu with a gdb server listening on localhost:1234. Thats what the -s does. -S tells QEMU to freeze on the first instruction of the BIOS so we have time to connect. In another terminal, start gdb. Then issue gdb with the command

target remote localhost:1234

Then issue a continue and let the machine boot up as before. Wait a few seconds and then do a ctrl-C in gdb to halt execution. Then look at the registers using info reg.

eax 0xde 222
ecx 0x90000 589824
edx 0x80 128
ebx 0x0 0
esp 0xffd6 0xffd6
ebp 0x0 0x0
esi 0xe0000 917504
edi 0xfdba 64954
eip 0x7c00 0x7c00

The interesting values are the instruction pointer and eax. eax contains the value 0xDE that we put in and the IP is at 0x7C00. This could be any value in the range 0x7C00-0x7C07 depending on when we stop the machine. And thats it. Your PC is bootstrapped and you can start doing more stuff in boot.s. And thats it,

Good luck!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s