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
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
User avatar
iansjack
Member
Member
Posts: 4908
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