Bootloader not jumping to stage2

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
4 posts • Page 1 of 1
trilobite
Posts: 4
Joined: Mon Nov 11, 2024 10:06 pm

Bootloader not jumping to stage2

Post by trilobite »

Hi all -
I have a small would-be OS - a two-stage bootloader and a very simple kernel.
I've got a problem in that the OS isn't jumping from stage1 to stage2.

This is what I get when running Qemu -

Booting from Floppy......
STAGE 1 OK

That's all.

A really big apology up-front - the code is AI-generated (but at least it is well-commented).
As penance for using AI code, I'm going through Dr Paul Carter's "Intro to assembly language" which is excellent!
Anyway, here is the code -

Code: Select all

 
 
; boot.asm — Stage 1 BIOS bootloader (must fit in 512 bytes)
; Loads a second-stage bootloader (sector 2) to memory at 0x0000:0x8000 and jumps to it
; This code is released to the public domain. 
; "Share and enjoy....." :)
BITS 16 ; We are in 16-bit real mode
ORG 0x7C00 ; BIOS loads the boot sector here (segment:offset = 0x0000:0x7C00)
start:
 ;-------------------------------------------
 ; Print "STAGE 1 OK" using BIOS teletype (INT 10h, AH=0x0E)
 ; This helps confirm that Stage 1 actually runs.
 ;-------------------------------------------
 mov si, msg ; Load address of message string into SI (source index)
.print:
 lodsb ; Load byte at [SI] into AL, and increment SI
 or al, al ; Set flags based on AL — if AL == 0 (null terminator), ZF is set
 jz .done ; Jump if ZF is set (end of string)
 mov ah, 0x0E ; BIOS function: teletype output
 mov bh, 0x00 ; Page number (always 0 in text mode)
 mov bl, 0x07 ; Text attribute: light gray on black (standard BIOS default)
 int 0x10 ; BIOS video interrupt
 jmp .print ; Loop to print next character
.done:
 ;-------------------------------------------
 ; Set up segment registers for clean execution
 ; Why? BIOS leaves segments in unknown state
 ; AX=0 means segment base = 0x0000
 ; SP is set to 0x7C00 so we avoid stack overflows
 ;-------------------------------------------
 cli ; Disable interrupts (for safety during segment setup)
 xor ax, ax ; AX = 0
 mov ds, ax ; Set DS (Data Segment) to 0x0000
 mov es, ax ; Set ES (Extra Segment) to 0x0000
 mov ss, ax ; Set SS (Stack Segment) to 0x0000
 mov sp, 0x7C00 ; Set stack pointer to 0x7C00 (just below the bootloader code)
 sti ; Re-enable interrupts
 ;-------------------------------------------
 ; Load Stage 2 loader (sector 2) from floppy disk into memory
 ; BIOS INT 13h uses CHS (Cylinder, Head, Sector) addressing
 ; We use LBA = 2 = CH=0, H=0, S=2
 ; Loads 1 sector (512 bytes) into 0x0000:0x8000
 ;-------------------------------------------
 mov ah, 0x02 ; BIOS INT 13h: function 2 = read sectors
 mov al, 1 ; Read 1 sector
 mov ch, 0 ; Cylinder number = 0
 mov cl, 2 ; Sector number = 2 (starts at 1)
 mov dh, 0 ; Head = 0
 mov dl, 0x00 ; Drive = 0x00 = first floppy drive (what BIOS booted from)
 mov bx, 0x8000 ; Offset where data will be loaded
 mov es, ax ; ES = 0x0000 (same as before), so ES:BX = 0x0000:0x8000
 int 0x13 ; BIOS Disk I/O interrupt
 jc fail ; If carry flag is set, read failed → jump to fail
 ;-------------------------------------------
 ; Jump to Stage 2 code we just loaded at 0x0000:0x8000
 ; This transfers control to Stage 2
 ;-------------------------------------------
 jmp 0x0000:0x8000 ; Far jump — loads CS and IP together
fail:
 ;-------------------------------------------
 ; If disk read failed, hang the system
 ;-------------------------------------------
 cli ; Disable interrupts
 hlt ; Halt CPU
 jmp $ ; Infinite loop: jump to current address
;-------------------------------------------
; Null-terminated message string (for printing)
;-------------------------------------------
msg: db "STAGE 1 OK", 0
;-------------------------------------------
; Padding to 510 bytes, then BIOS signature
; BIOS requires 0x55 0xAA at bytes 511-512
;-------------------------------------------
times 510-($-$$) db 0 ; Pad the rest of the sector with zeroes
dw 0xAA55 ; Boot signature — must be exactly here

Code: Select all

 
 
; ============================================================================
; stage2.asm — Stage 2 BIOS Bootloader (FAT12)
; ============================================================================
; Description:
; - This runs at 0x0000:0x8000, loaded by Stage 1 (boot.asm)
; - It prints a short message to show it is working
; - Then loads `KERNEL.BIN` from the FAT12 root directory (assumes contiguous)
; - Finally, jumps to the loaded 32-bit kernel (at 0x0000:0x1000)
;
; Limitations:
; - Only loads root directory files
; - Assumes no fragmentation (clusters are contiguous)
; - Hardcoded FAT12 geometry for 1.44MB floppy
; 
; This code is released to the public domain. 
; "Share and enjoy....." :)
; 
; ============================================================================
BITS 16
ORG 0x8000 ; Stage 1 loads us here
start:
 ; ---------------------------------------------
 ; Print "STAGE2 OK" to screen using BIOS INT 10h
 ; ---------------------------------------------
 mov si, msg ; Load message address
.print:
 lodsb ; Load next byte into AL from [SI], advance SI
 or al, al ; Check for null terminator
 jz .done ; If zero, message done
 mov ah, 0x0E ; BIOS teletype print function
 mov bh, 0x00 ; Page number
 mov bl, 0x02 ; Attribute: green on black
 int 0x10 ; BIOS video interrupt
 jmp .print
.done:
 ; ---------------------------------------------
 ; Set up DS/ES to 0x0000 for memory access
 ; ---------------------------------------------
 xor ax, ax
 mov ds, ax
 mov es, ax
 ; ---------------------------------------------
 ; Load KERNEL.BIN from FAT12 into 0x0000:0x1000
 ; ---------------------------------------------
 mov si, kernel_filename ; File name to find
 mov bx, 0x1000 ; Destination offset for kernel load
 call load_file
 ; ---------------------------------------------
 ; Transfer control to kernel at 0x1000:0x0000
 ; ---------------------------------------------
 jmp 0x1000:0x0000 ; Segment:Offset = 0x10000
; ============================================================================
; load_file — Find and load a file from the FAT12 root directory
; ============================================================================
; Inputs:
; SI = pointer to 11-char filename (e.g., "KERNEL BIN")
; BX = offset to load file into (ES assumed 0x0000)
; ============================================================================
load_file:
 ; ----------------------------
 ; Load Root Directory (14 sectors starting at LBA 19)
 ; ----------------------------
 mov ax, 0x0200 ; Start with sector 0x0200 (arbitrary sector #)
 mov cx, 14 ; Root directory spans 14 sectors (224 entries)
find_entry:
 push cx ; Save loop counter
 ; Setup BIOS CHS parameters to read root sector
 mov dx, ax ; Save current sector index in DX
 mov ah, 0x02 ; BIOS read sector
 mov al, 1 ; Read 1 sector
 mov ch, dl ; Cylinder 0 (simple map)
 mov cl, 1 ; Sector 1
 mov dh, 0 ; Head 0
 mov dl, 0x00 ; Drive 0 = floppy
 mov bx, 0x9000 ; Temp buffer to store root entries
 mov es, ax ; ES = 0
 int 0x13
 jc fail ; Abort on failure
 ; Scan 16 directory entries per sector
 mov di, 0x9000 ; Start of loaded sector
 mov cx, 16 ; 16 entries per 512-byte sector
scan_sector:
 push cx
 mov cx, 11 ; Filename is 11 bytes (8+3)
 mov si, kernel_filename
 repe cmpsb ; Compare filename to directory entry
 je found ; If match, jump to found
 add di, 21 ; Move to next directory entry (skip name + attrs + ...)
 pop cx
 loop scan_sector ; Loop over entries
 inc ax ; Next sector
 pop cx
 loop find_entry ; Continue scanning root directory
 jmp fail ; If nothing found, fail
found:
 ; ---------------------------------------------
 ; Found file. Get first cluster and load sectors.
 ; ---------------------------------------------
 mov si, di ; DI points to matched entry
 add si, 26 ; Offset 26 = start cluster
 mov ax, [si] ; Load start cluster number
 ; ------------------------------
 ; Load 8 sectors from cluster (assumes contiguous)
 ; ------------------------------
 mov cx, 8 ; Load up to 8 sectors (max 4096 bytes)
load_clusters:
 push ax ; Save cluster number
 mov dx, ax
 add dx, 31 ; Convert cluster → LBA (cluster 2 = sector 33)
 mov ah, 0x02 ; BIOS read sector
 mov al, 1 ; Read 1 sector
 mov ch, dl ; Cylinder (simple map)
 mov cl, 1
 mov dh, 0
 mov dl, 0x00 ; Floppy
 mov es, bx ; Load into ES:BX (BX = segment offset)
 mov bx, 0 ; Offset within segment (start of buffer)
 int 0x13
 jc fail ; Abort on read failure
 add bx, 512 ; Advance destination pointer by one sector
 pop ax
 inc ax ; Next cluster (naive assumption of contiguity)
 loop load_clusters
 ret
fail:
 cli
 hlt
 jmp $
; ============================================================================
; Data Section
; ============================================================================
msg: db "STAGE2 OK", 0
; Must be exactly 11 characters (8.3 filename)
; Filename = "KERNEL.BIN" → "KERNEL BIN"
kernel_filename: db "KERNEL BIN"
; Pad to 510 bytes, add boot signature
times 510-($-$$) db 0
dw 0xAA55 ; BIOS magic number (must be last 2 bytes)

Code: Select all

 
 
// ============================================================================
// kernel.c — Minimal 32-bit Kernel (Protected Mode)
// ============================================================================
// - Executed from 0x00100000 (1MB), loaded by Stage 2
// - Writes a string directly to VGA text mode memory (0xB8000)
// - Enters infinite loop using HLT to idle the CPU
// - No external dependencies or libc required
//
// This code is released to the public domain.
// "Share and enjoy....." :)
// ============================================================================
#include <stdint.h> // For fixed-width integer types like uint16_t
// --------------------------------------------------------------------------
// print() — Write a null-terminated string to VGA text screen
// --------------------------------------------------------------------------
// VGA text mode is mapped to memory address 0xB8000
// Each character cell is 2 bytes: [ASCII character][attribute byte]
// - Attribute byte 0x07 = light gray text on black background
// --------------------------------------------------------------------------
void print(const char *s) {
 volatile uint16_t *vga = (uint16_t*)0xB8000; // VGA text buffer base
 uint16_t attr = 0x0700; // Attribute byte in high byte (0x07 = light gray on black)
 for (int i = 0; s[i] != 0; i++) {
 vga[i] = attr | s[i]; // Lower byte = character, upper byte = color
 // Example: 'A' (0x41) becomes 0x0741 in VGA memory
 }
}
// --------------------------------------------------------------------------
// kernel_main() — Kernel entry point called from stage2 loader
// --------------------------------------------------------------------------
// In protected mode with flat segments already set up by stage2
// Writes message and halts forever
// --------------------------------------------------------------------------
__attribute__((section(".text"))) __attribute__((used)) void kernel_main(void) {
 print("Hello from the public domain!"); // Print greeting to top of screen
 // Infinite loop — halt CPU until next interrupt to save power
 while (1) {
 __asm__ __volatile__("hlt"); // Inline assembly: HLT instruction
 }
}


I'm thinking that if it can be made to work, it might possibly be useful as code for an "Assembly Bare Bones" tutorial - I noticed there isn't one yet although there are tutorials on bootloaders.
Anyway, any help is very gratefully received. Please let me know if you'd like to see the makefile and linker script - I can post them too if asked.

Many thanks - bye for now -
- trilobite
iansjack
Member
Member
Posts: 4874
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Bootloader not jumping to stage2

Post by iansjack »

You want commentary on AI generated code that doesn’t work? Good luck with that.

You are correct that there is no assembly Bare Bones. There’s a very good reason for that. I think most people would advise against a beginner writing an OS in assembler (many would advise against anyone writing an OS in assembler). But, if you are a good enough programmer to do so, you don’t need a Bare Bones tutorial.

As for using AI to write an OS....
EmanueleDavalli
Posts: 7
Joined: Fri Sep 20, 2024 10:36 am

Re: Bootloader not jumping to stage2

Post by EmanueleDavalli »

The first thing I notice is the line

Code: Select all

 mov es, ax ; ES = 0x0000 (same as before), so ES:BX = 0x0000:0x8000
is just a classic ai lie, because just before

Code: Select all

 mov ah, 0x02 ; BIOS INT 13h: function 2 = read sectors
 mov al, 1 ; Read 1 sector
ax gets set to 0x0201, meaning the second stage bootloader gets actually loaded at 0x0201:0x8000, which is 0x0a010
trilobite
Posts: 4
Joined: Mon Nov 11, 2024 10:06 pm

Re: Bootloader not jumping to stage2

Post by trilobite »

Hi Iansjack and EmanueleDavalli - thanks for your comments!

Before I go any further, I'll build up knowledge about assembly language.
Getting a deep understanding of it.. What each statement does in each example, and why it does it.
That will be the best way of going forward.

Thanks again. Bye for now -
- trilobite
Post Reply

4 posts • Page 1 of 1

Return to "OS Development"