I have written simple first stage bootloader which displays "Hello world" using interrupt to bios. Now as a next obvious step to write a second stage, but where code for that should exist and how to load it from first stage ?
Here is a program for first stage
[BITS 16] ;Tells the assembler that its a 16 bit code
[ORG 0x7C00] ;Origin, tell the assembler that where the code will
;be in memory after it is been loaded
MOV SI, HelloString ;Store string pointer to SI
CALL PrintString ;Call print string procedure
JMP $ ;Infinite loop, hang it here.
PrintCharacter: ;Procedure to print character on screen
;Assume that ASCII value is in register AL
MOV AH, 0x0E ;Tell BIOS that we need to print one charater on screen.
MOV BH, 0x00 ;Page no.
MOV BL, 0x07 ;Text attribute 0x07 is lightgrey font on black background
INT 0x10 ;Call video interrupt
RET ;Return to calling procedure
PrintString: ;Procedure to print string on screen
;Assume that string starting pointer is in register SI
next_character: ;Lable to fetch next character from string
MOV AL, [SI] ;Get a byte from string and store in AL register
INC SI ;Increment SI pointer
OR AL, AL ;Check if value in AL is zero (end of string)
JZ exit_function ;If end then return
CALL PrintCharacter ;Else print the character which is in AL register
JMP next_character ;Fetch next character from string
exit_function: ;End label
RET ;Return from procedure
;Data
HelloString db 'Hello World', 0 ;HelloWorld string ending with 0
TIMES 510 - ($ - $$) db 0 ;Fill the rest of sector with 0
DW 0xAA55 ;Add boot signature at the end of bootloader
On x86 you would do the following (simplified):
- Have the bootloader load the n-th sector of the disk/floppy (wherever you're booting from) into memory and execute it (i.e. load segment/offset and do
retf
). A better alternative is to search the filesystem for a certain filename (e.g. KERNEL.BIN) -- but you'd need to know the file system type (e.g. FAT12 if you're testing from a floppy image).
- The kernel would then start in real mode. It sets up code descriptors, GDT, and so on, activates 32-bit addressing (you should have heard of "A20") and finally enters protected mode. Then you need a far jump to a 32-bit code segment (kernel file must be linked together in a way that the 32-bit code is at an absolute position, e.g. at offset 512, right after the 16-bit real mode stuff).
- The 32-bit kernel assembly, then, just defines
EXTERN _mykernel
(for example) and calls that symbol.
- Then you can begin writing your kernel as C function
mykernel
.
Okay that was a short overview of what I did a few years ago (with lots of copy&paste from the Internet ;). If that isn't helpful, here are some good web resources on OS development:
- http://www.brokenthorn.com/Resources/OSDevIndex.html
- http://wiki.osdev.org/Main_Page
- http://lowlevel.brainsware.org/wiki/index.php/Hauptseite (wiki with many hobbyist OS developers, German only...)
Hope that helps ^^
Minimal runnable NASM BIOS example that loads stage 2 and jumps to it
use16
org 0x7C00
; You should do further initializations here
; like setup the stack and segment registers.
; Load stage 2 to memory.
mov ah, 0x02
; Number of sectors to read.
mov al, 1
; This may not be necessary as many BIOS set it up as an initial state.
mov dl, 0x80
; Cylinder number.
mov ch, 0
; Head number.
mov dh, 0
; Starting sector number. 2 because 1 was already loaded.
mov cl, 2
; Where to load to.
mov bx, stage2
int 0x13
jmp stage2
; Magic bytes.
times ((0x200 - 2) - ($ - $$)) db 0x00
dw 0xAA55
stage2:
; Print 'a'.
mov ax, 0x0E61
int 0x10
cli
hlt
; Pad image to multiple of 512 bytes.
times ((0x400) - ($ - $$)) db 0x00
Compile and run:
nasm -f bin -o main.img main.asm
qemu-system-i386 main.img
Expected outcome: a
gets printed to the screen, and then the program halts.
Tested on Ubuntu 14.04.
Saner GAS example using a linker script and more correct initialization (segment registers, stack) on my GitHub.
Look at the GRUB implementation here (stage 1):
http://src.illumos.org/source/xref/illumos-gate/usr/src/grub/grub-0.97/stage1/stage1.S
First noticed the starting point at 0x7c00 and the end signature of 0xaa55 for this first sector. From within the disassembly, u can see this:
349 copy_buffer:
350 movw ABS(stage2_segment), %es
351
352 /*
353 * We need to save %cx and %si because the startup code in
354 * stage2 uses them without initializing them.
355 */
356 pusha
357 pushw %ds
358
359 movw $0x100, %cx
360 movw %bx, %ds
361 xorw %si, %si
362 xorw %di, %di
363
364 cld
365
366 rep
367 movsw
368
369 popw %ds
370 popa
371
372 /* boot stage2 */
373 jmp *(stage2_address)
374
375 /* END OF MAIN LOOP */
376
Essentially the logic is to copy the stage 2 code into another part of memory, and after that jump directly there, and that is "boot stage2". In other words, "boot stage1" is effectively triggered from BIOS after it has loaded the sector into memory, whereas stage2 is where you jump there - it can be anywhere.