lesson 13, first kernel

pull/6/head
Carlos Fenollosa 10 years ago
parent 14f51e8246
commit 2f1378d7a3

1
.gitignore vendored

@ -1,3 +1,4 @@
/*/*.bin
/*/*.o
/*/*.swp
/*/*.dis

@ -51,7 +51,7 @@ read it.
is booting from the right drive and set the drive on `dl` accordingly**
The BIOS sets `dl` to the drive number before calling the bootloader. However,
I found some problems with qemu then booting from the hdd.
I found some problems with qemu when booting from the hdd.
There are two quick options:

@ -0,0 +1,32 @@
# $@ = target file
# $< = first dependency
# $^ = all dependencies
# First rule is the one executed when no paramaters are fed to the Makefile
all: run
# Notice how dependencies are built as needed
kernel.bin: kernel_entry.o kernel.o
i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
kernel_entry.o: kernel_entry.asm
nasm $< -f elf -o $@
kernel.o: kernel.c
i386-elf-gcc -ffreestanding -c $< -o $@
# Rule to disassemble the kernel - may be useful to debug
kernel.dis: kernel.bin
ndisasm -b 32 $< > $@
bootsect.bin: bootsect.asm
nasm $< -f bin -o $@
os-image.bin: bootsect.bin kernel.bin
cat $^ > os-image.bin
run: os-image.bin
qemu-system-i386 -fda $<
clean:
rm *.bin *.o *.dis

@ -0,0 +1,84 @@
*Concepts you may want to Google beforehand: kernel, ELF format, makefile*
**Goal: Create a simple kernel and a bootsector capable of booting it**
The kernel
----------
Our C kernel will just print an 'X' on the top left corner of the screen. Go ahead
and open `kernel.c`.
You will notice a dummy function that does nothing. That function will force us
to create a kernel entry routine which does not point to byte 0x0 in our kernel, but
to an actual label which we know that launches it. In our case, function `main()`.
`i386-elf-gcc -ffreestanding -c kernel.c -o kernel.o`
That routine is coded on `kernel_entry.asm`. Read it and you will learn how to
use `[extern]` declarations in assembly. To compile this file, instead of generating
a binary, we will generate an `elf` format file which will be linked with `kernel.o`
`nasm kernel_entry.asm -f elf -o kernel_entry.o`
The linker
----------
A linker is a very powerful tool and we only started to benefit from it.
To link both object files into a single binary kernel and resolve label references,
run:
`i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary`
Notice how our kernel will be placed not at `0x0` in memory, but at `0x1000`. The
bootsector will need to know this address too.
The bootsector
--------------
It is very similar to the one in lesson 10. Open `bootsect.asm` and examine the code.
Actually, if you remove all the lines used to print messages on the screen, it accounts
to a couple dozen lines.
Compile it with `nasm bootsect.asm -f bin -o bootsect.bin`
Putting it all together
-----------------------
Now what? We have two separate files for the bootsector and the kernel?
Can't we just "link" them together into a single file? Yes, we can, and it's easy,
just concatenate them:
`cat bootsect.bin kernel.bin > os-image.bin`
Run!
----
You can now run `os-image.bin` with qemu.
Remember that if you find disk load errors you may need to play with the disk numbers
or qemu parameters (floppy = `0x0`, hdd = `0x80`). I usually use
`qemu-system-i386 -fda os-image.bin`
You will see four messages:
- "Started in 16-bit Real Mode"
- "Loading kernel into memory"
- (Top left) "Landed in 32-bit Protected Mode"
- (Top left, overwriting previous message) "X"
Congratulations!
Makefile
--------
As a last step, we will tidy up the compilation process with a Makefile. Open the `Makefile`
script and examine its contents. If you don't know what a Makefile is, now is a good time
to Google and learn it, as this will save us a lot of time in the future.

@ -0,0 +1,50 @@
[org 0x7c00]
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
mov bp, 0x9000
mov sp, bp
mov bx, MSG_REAL_MODE
call print
call print_nl
call load_kernel ; read the kernel from disk
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
jmp $ ; Never executed
%include "../05-bootsector-functions-strings/boot_sect_print.asm"
%include "../05-bootsector-functions-strings/boot_sect_print_hex.asm"
%include "../07-bootsector-disk/boot_sect_disk.asm"
%include "../09-32bit-gdt/32bit-gdt.asm"
%include "../08-32bit-print/32bit-print.asm"
%include "../10-32bit-enter/32bit-switch.asm"
[bits 16]
load_kernel:
mov bx, MSG_LOAD_KERNEL
call print
call print_nl
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
mov dh, 2
mov dl, [BOOT_DRIVE]
call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
call print_string_pm
call KERNEL_OFFSET ; Give control to the kernel
jmp $ ; Stay here when the kernel returns control to us (if ever)
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
; padding
times 510 - ($-$$) db 0
dw 0xaa55

@ -0,0 +1,8 @@
/* This will force us to create a kernel entry function */
void dummy_test_entrypoint() {
}
void main() {
char* video_memory = (char*) 0xb8000;
*video_memory = 'X';
}

@ -0,0 +1,4 @@
[bits 32]
[extern main] ; Define calling point. Must haveSame name as kernel.c 'main' function
call main ; Calls the C function. The linker will know where it is placed in memory
jmp $
Loading…
Cancel
Save