Stage 2 Bootloader Does Not Jump Into the Kernel Entry Correctly

I am developing a custom bootloader for my 64-bit OS in x86_64 NASM on macOS environment using QEMU. The bootloader is a 2-stage, and is designed to load operating systems from disks. I am experiencing issues where the bootloader fails to properly jump to the kernel entry point after loading it into memory.

Current Behavior

  1. The bootloader successfully reads and loads Stage 2 into memory (“Stage 2 loaded successfully!”).
  2. It then attempts to jump to the kernel located at the higher-half address 0xFFFFFFFF80000000
  3. The bootloader just freezes when it tries to load the kernel

Relevant Code:

first_stage.asm:

; First stage bootloader
[BITS 16]
[ORG 0x7C00]

start:
    ; Set up segments and stack
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00      ; Set stack just below the bootloader

    ; Store boot drive number
    mov [bootDrive], dl

    ; Load Stage 2 (3 sectors, starting from sector 2)
    mov ah, 0x02        ; BIOS read sector function
    mov al, 3           ; Number of sectors to read
    mov ch, 0           ; Cylinder number
    mov cl, 2           ; Start from sector 2
    mov dh, 0           ; Head number
    mov bx, 0x7E00      ; Load Stage 2 right after the bootloader

    int 0x13            ; BIOS interrupt to read disk
    jc disk_error       ; Jump if error

    ; Print success message
    mov si, successMsg
    call print_string

    ; Pass boot drive number to second stage and jump
    mov dl, [bootDrive] ; Load boot drive number
    jmp 0x0000:0x7E00

disk_error:
    mov si, diskErrorMsg
    call print_string
    mov ah, 0x0E        ; Print the error code in AH
    int 0x10
    cli
    hlt

print_string:
    lodsb
    or al, al
    jz done
    mov ah, 0x0E
    int 0x10
    jmp print_string
done:
    ret

diskErrorMsg db 'Disk Error!', 13, 10, 0
successMsg db 'Stage 2 loaded successfully!', 13, 10, 0
bootDrive db 0

times 510 - ($ - $$) db 0
dw 0xAA55   ; Boot signature

second_stage.asm:

; Second stage bootloader
; Second stage bootloader
[BITS 16]
[ORG 0x7E00]

start:
    ; Set up segments
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ax, 0x9000      ; Use segment 0x9000 (0x90000 physical address)
    mov ss, ax
    mov sp, 0xFFFF      ; Set stack pointer to the top of the segment

    ; Print initial success message
    mov si, stage2Msg
    call print_string_16

    ; Enable A20 line for mem access beyond 1MB
    call enable_A20

    ; Load the kernel
    call load_kernel

    ; Load GDT and switch to protected mode
    call enable_protected_mode
enable_A20:
    in al, 0x92
    or al, 2
    out 0x92, al
    ret

load_kernel:
    mov ah, 0x02        ; BIOS read sector function
    mov al, 9           ; Number of sectors to read (adjust as needed)
    mov ch, 0           ; Cylinder number
    mov cl, 5           ; Start from sector 5
    mov dh, 0           ; Head number
    ; DL already contains the boot drive number from the first stage
    mov bx, 0x8000      ; Load starting at 0x8000

    int 0x13            ; BIOS interrupt to read disk
    jc disk_error       ; Jump if error
    ret

enable_protected_mode:
    cli                 ; Disable interrupts
    lgdt [gdt_descriptor]

    ; Enable protected mode
    mov eax, cr0
    or eax, 1           ; Set PE (Protection Enable) bit
    mov cr0, eax

    ; Enable PAE
    mov eax, cr4
    or eax, 1 << 5      ; Set PAE bit
    mov cr4, eax

    ; Enter protected mode
    jmp 0x08:protected_mode_entry

[BITS 32]
protected_mode_entry:
    mov ax, 0x10        ; Data segment selector
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Set up stack for protected mode
    mov esp, 0x90000

    ; Debug print
    mov esi, protected_mode_msg
    call print_string_pm

    ; Set up paging for long mode
    call setup_paging

    ; Enable long mode
    call enable_long_mode

    ; Load 64-bit GDT
    lgdt [gdt64_descriptor]

    ; Jump to long mode entry
    jmp 0x08:long_mode_entry

setup_paging:
    ; Set up 4-level paging
    mov edi, 0x1000
    mov cr3, edi
    xor eax, eax
    mov ecx, 4096
    rep stosd
    mov edi, cr3

    mov dword [edi], 0x2003      ; PML4[0] -> PDPT
    add edi, 0x1000
    mov dword [edi], 0x3003      ; PDPT[0] -> PD
    add edi, 0x1000
    mov dword [edi], 0x4003      ; PD[0] -> PT
    add edi, 0x1000

    mov ebx, 0x00000003          ; Entry flags
    mov ecx, 512                 ; Number of entries

    .set_entry:
        mov dword [edi], ebx     ; Set entry
        add ebx, 0x1000          ; Next page
        add edi, 8               ; Next entry
        loop .set_entry

    ret

enable_long_mode:
    ; Enable long mode in IA32_EFER MSR
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8      ; Set Long Mode Enable (LME) bit
    wrmsr
    ret

[BITS 64]
long_mode_entry:
    ; Set up segment registers for long mode
    mov ax, 0x10        ; Data segment selector
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Set up stack for long mode
    mov rsp, 0x90000

    ; Print long mode message
    mov rsi, long_mode_msg
    call print_string_lm

    mov rax, 0xFFFFFFFF80000000   ; Kernel address
    jmp rax

[BITS 16]
print_string_16:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E        ; BIOS teletype function
    int 0x10
    jmp print_string_16
.done:
    ret

[BITS 32]
print_string_pm:
    push eax
    push ebx
.loop:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0F        ; Text attribute for character
    mov ebx, 0xB8000    ; VGA video memory address
    mov [ebx], ax       ; Write character to VGA memory
    add ebx, 2          ; Move to next character
    jmp .loop
.done:
    pop ebx
    pop eax
    ret

[BITS 64]
print_string_lm:
    push rax
    push rbx
.loop:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0F        ; Text attribute for character
    mov rbx, 0xB8000    ; VGA video memory address
    mov [rbx], ax       ; Write character to VGA memory
    add rbx, 2          ; Move to next character
    jmp .loop
.done:
    pop rbx
    pop rax
    ret

[BITS 16]
disk_error:
    mov si, diskErrorMsg
    call print_string_16
    cli
    hlt

; Data
stage2Msg db 'Entered Stage 2', 13, 10, 0
protected_mode_msg db 'Entered Protected Mode', 0
long_mode_msg db 'Entered Long Mode', 0
diskErrorMsg db 'Disk Error While Reading Kernel!', 13, 10, 0

; GDT for Protected Mode
gdt_start:
    dq 0x0000000000000000 ; Null descriptor
    dq 0x00CF9A000000FFFF ; 32-bit code descriptor
    dq 0x00CF92000000FFFF ; 32-bit data descriptor
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

; GDT for Long Mode
gdt64_start:
    dq 0x0000000000000000 ; Null descriptor
    dq 0x00AF9A000000FFFF ; 64-bit code descriptor
    dq 0x00AF92000000FFFF ; 64-bit data descriptor
gdt64_end:

gdt64_descriptor:
    dw gdt64_end - gdt64_start - 1
    dq gdt64_start

times 1536 - ($ - $$) db 0 ; Pad to 3 sectors

linker.ld:

ENTRY(kernel_start)

KERNEL_VMA = 0xFFFFFFFF80000000;
KERNEL_LMA = 0x8000;

SECTIONS
{
    . = KERNEL_VMA;

    .text ALIGN(4K) : AT(ADDR(.text) - KERNEL_VMA + KERNEL_LMA)
    {
        *(.text*)
    }

    .rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_VMA + KERNEL_LMA)
    {
        *(.rodata*)
    }

    .data ALIGN(4K) : AT(ADDR(.data) - KERNEL_VMA + KERNEL_LMA)
    {
        *(.data*)
    }

    .bss ALIGN(4K) : AT(ADDR(.bss) - KERNEL_VMA + KERNEL_LMA)
    {
        *(COMMON)
        *(.bss*)
    }

    /DISCARD/ :
    {
        *(.eh_frame)
        *(.comment)
    }
}

Makefile:

ARCH ?= x86_64

ASM=nasm
CC=$(ARCH)-elf-gcc
LD=$(ARCH)-elf-ld
OBJCOPY=x86_64-elf-objcopy
STRIP=x86_64-elf-strip
NASMFLAGS=-f bin
NASMFLAGS_ELF64=-f elf64
CFLAGS=-ffreestanding -nostdlib -mno-red-zone -Wall -Wextra -g -O2 -mcmodel=kernel
LDFLAGS=-T linker.ld

BUILD_DIR=bin
BOOT_DIR=boot/$(ARCH)
KERNEL_DIR=kernel
ARCH_DIR=kernel/arch/

KERNEL_BIN=$(BUILD_DIR)/kernel-$(ARCH).bin
KERNEL_ELF=$(BUILD_DIR)/kernel-$(ARCH).elf
BOOTLOADER_IMG=$(BUILD_DIR)/bootloader-$(ARCH).img

KERNEL_C=$(KERNEL_DIR)/kernel.c
KERNEL_ENTRY=$(ARCH_DIR)/entry-$(ARCH).asm

FIRST_STAGE=$(BOOT_DIR)/first_stage.asm
SECOND_STAGE=$(BOOT_DIR)/second_stage.asm

$(BUILD_DIR):
    @mkdir -p $(BUILD_DIR)

$(BUILD_DIR)/first_stage-$(ARCH).bin: $(FIRST_STAGE) | $(BUILD_DIR)
    $(ASM) $(NASMFLAGS) $< -o $@

$(BUILD_DIR)/second_stage-$(ARCH).bin: $(SECOND_STAGE) | $(BUILD_DIR)
    $(ASM) $(NASMFLAGS) $< -o $@

$(BUILD_DIR)/entry-$(ARCH).o: $(KERNEL_ENTRY) | $(BUILD_DIR)
    $(ASM) $(NASMFLAGS_ELF64) $< -o $@

$(BUILD_DIR)/kernel.o: $(KERNEL_C) | $(BUILD_DIR)
    $(CC) $(CFLAGS) -c $< -o $@

$(KERNEL_ELF): $(BUILD_DIR)/entry-$(ARCH).o $(BUILD_DIR)/kernel.o | $(BUILD_DIR)
    $(LD) $(LDFLAGS) -o $@ $^

$(KERNEL_BIN): $(KERNEL_ELF) | $(BUILD_DIR)
    $(OBJCOPY) -O binary $< $@

$(BOOTLOADER_IMG): $(BUILD_DIR)/first_stage-$(ARCH).bin $(BUILD_DIR)/second_stage-$(ARCH).bin $(KERNEL_BIN)
    dd if=/dev/zero of=$(BOOTLOADER_IMG) bs=512 count=131072
    dd if=$(BUILD_DIR)/first_stage-$(ARCH).bin of=$(BOOTLOADER_IMG) bs=512 seek=0 conv=notrunc
    dd if=$(BUILD_DIR)/second_stage-$(ARCH).bin of=$(BOOTLOADER_IMG) bs=512 seek=2 conv=notrunc
    dd if=$(KERNEL_BIN) of=$(BOOTLOADER_IMG) bs=512 seek=34 conv=notrunc

run: $(BOOTLOADER_IMG)
    qemu-system-x86_64 -drive file=bin/bootloader-$(ARCH).img,format=raw -monitor stdio

clean:
    rm -rf $(BUILD_DIR)/*.o $(BUILD_DIR)/*.bin $(BUILD_DIR)/*.img $(KERNEL_ELF)

all: $(BOOTLOADER_IMG)

rerun: clean all run

Question: What did I do wrong?

PS: The kernel is small enough to fit within the allocated sectors (190 bytes), and it is allocated correctly in the expected address. The first stage boot is at 0x7C00.

I tried debugging using print statements in VGA however there was nothing on the emulator console. There were also warnings during assemble as well:

boot/x86_64/second_stage.asm:172: warning: signed dword immediate exceeds bounds [-w+number-overflow]
boot/x86_64/second_stage.asm:172: warning: dword data exceeds bounds [-w+number-overflow]

Edit 1: It seems that load_kernel does not return after being called and BIOS interrupt caused issue(?).

Edit 2: The registers appeared to be right and correctly jumped into 64-bit. However, the kernel is not loaded correctly in the memory but no error was thrown (memory inaccessible at 0xFFFFFFFF80000000.

New contributor

typeAl_ is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

20

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật