Attempting to change into 32-bit protected mode causes triple fault

I have a simplistic bootloader comprised of two parts – the actual bootloader that loads the second ‘stage’ into memory, and executes it.

In that second stage, I’m trying to switch to 32-bit protected mode, but I end up with a triple-fault, with the following error in bochs:

Below is the code for both boot assembly files, as well as the Makefile that I am compiling them with, in case there’s some error there.

From what I can tell, there seems to be some issue with the descriptors in the GDT, but I’ve checked them multiple times and they seem to be correct. I wonder if it’s something to do with loading the code into memory at a different address than expected maybe? I’m unsure. Any advice would be greatly appreciated!

Makefile

BUILD_DIR?=build
FLOPPY_OUTPUT?=$(BUILD_DIR)/floppy.img

.PHONY: all bootloader kernel run-bochs run-qemu build-dir

all: $(FLOPPY_OUTPUT)

run-bochs: all
    bochs -f bochsrc.txt

run-qemu: all
    qemu-system-i386 -monitor stdio -drive file=$(FLOPPY_OUTPUT),format=raw

# TODO: Make the drive a hard drive with FAT32.
$(FLOPPY_OUTPUT): build-dir bootloader kernel
    dd if=/dev/zero of=$(FLOPPY_OUTPUT) bs=512 count=2880
    mkfs.fat -F12 -n "AURORAOS" $(FLOPPY_OUTPUT)
    dd if=$(BUILD_DIR)/boot.bin of=$(FLOPPY_OUTPUT) conv=notrunc
    mcopy -i $(FLOPPY_OUTPUT) build/lstage.bin "::lstage.bin"

bootloader: build-dir bootloader/boot.asm bootloader/late_stage/lstage.asm
    nasm -f bin bootloader/boot.asm -o $(BUILD_DIR)/boot.bin
    nasm -felf bootloader/late_stage/lstage.asm -o $(BUILD_DIR)/lstage.o
    ld -melf_i386 -T bootloader/late_stage/linker.ld $(BUILD_DIR)/lstage.o -o $(BUILD_DIR)/lstage.bin --oformat=binary

kernel:

build-dir:
    mkdir -p $(BUILD_DIR)

linker.ld

ENTRY(_start)

SECTIONS
{
    . = 0x0000;
    .text : {
       *(.text) 
    }

    .rodata : {
       *(.rodata) 
    }

    .data : {
       *(.data) 
    }

    .bss : {
       *(.bss) 
    }
}

lstage.asm – Where the triple fault occurs:

[bits 16]

global _start
_start:
    mov si, msg_hello
    call puts

    cli

    call enable_a20
    call load_gdt

    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp dword CODE_SEGMENT:pmode_start

puts:
.loop:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp .loop
.done:
    ret

enable_a20:
    ; DataPort = 60
    ; CommandPort = 64
    ; DisableKeyboard = AD
    ; EnableKeyboard = AE
    ; ReadCtrl = D0
    ; WriteCtrl = D1

    call a20_wait_input
    mov al, 0xAD ; Disable keyboard
    out 0x64, al

    call a20_wait_input
    mov al, 0xD0 ; Read control output port
    out 0x64, al

    call a20_wait_output
    in al, 0x60
    push eax

    call a20_wait_input
    mov al, 0xD1
    out 0x64, al

    call a20_wait_input
    pop eax
    or al, 2
    out 0x60, al

    call a20_wait_input
    mov al, 0xAE ; Enable keyboard
    out 0x64, al

    call a20_wait_input
    ret


a20_wait_input:
    push ax
    in al, 0x64
    test al, 2
    jnz a20_wait_input
    pop ax
    ret

a20_wait_output:
    push ax
    in al, 0x64
    test al, 1
    jz a20_wait_output
    pop ax
    ret

load_gdt:
    lgdt [gdt_descriptor]

    ret

gdt_start:
null_descriptor: dq 0
code_descriptor: dw 0xFFFF ; Limit (0-15)
                 dw 0x0000 ; Base (0-15)
                 db 0x0   ; Base (16-23)
                 db 10011010b ; Access
                 db 11001111b ; Flags + Limit (16-19)
                 db 0x0 ; Base (24-31)
data_descriptor: dw 0xFFFF ; Limit (0-15)
                 dw 0x0000 ; Base (0-15)
                 db 0x0   ; Base (16-23)
                 db 10010010b ; Access
                 db 11001111b ; Flags + Limit (16-19)
                 db 0x0 ; Base (24-31)
gdt_end:

gdt_descriptor: dw gdt_descriptor - gdt_start - 1
                dd gdt_start

CODE_SEGMENT equ code_descriptor - gdt_start
DATA_SEGMENT equ data_descriptor - gdt_start

msg_hello: db "Hello from late-stage boot code!", 0x0D, 0x0A, 0x00

[bits 32]
pmode_start:
    cli
    hlt

boot.asm – The actual bootloader that loads lstage.bin into memory.

[org 0x7C00]
[bits 16]

jmp strict short _start
nop

oem_identifier: db "MSWIN4.1"
bytes_per_sector: dw 0x0200 ; 512
sectors_per_cluster: db 0x01
reserved_sectors: dw 0x0001 
fat_count: db 0x02
root_dir_entries: dw 0x00E0
total_sectors: dw 0x0B40 ; 2880 * 512 = 1.44MB
media_descriptor_type: db 0xF0 ; 3.5" floppy
sectors_per_fat: dw 0x0009
sectors_per_track: dw 0x0012 ; 18
head_count: dw 0x0002 
hidden_sectors: dd 0x0000 0000
large_sector_count: dd 0x0000 0000

; Extended Boot Partition
drive_number: db 0x00 ; 0x00 = floppy, 0x80 = hard disk
_reserved: db 0x00
signature: db 0x29
serial_number: dd 0x12345678 ; Unused
volume_label: db "AURORAOS   "
system_ident_string: db "FAT12   "

_start:
    mov ax, 0
    mov ds, ax
    mov es, ax

    mov ss, ax
    mov sp, 0x7C00

    mov [drive_number], dl 

    ; 07C0:0000 -> 0000:07C0
    push es
    push word .after
    retf
.after:

    mov si, msg_hello
    call puts

    ; Read drive params rather than relying on hard-coded values
    ; INT 13/AH=08h -> http://www.ctyme.com/intr/rb-0621.htm
    push es
    mov ah, 0x08
    int 0x13
    jc floppy_error
    pop es

    ; CH = low eight bits of max cylinder
    ; CL [bits 7-6] = high two bits of max cylinder
    ; CL [bits 5-0] = max sector number 
    ; DH = max head number
    ; DL = number of drives

    and cl, 0x3F
    xor ch, ch
    ; Now, cx is just sector count, as we removed bits [7-0]
    mov [sectors_per_track], cx

    ; Heads are indexed from 0, so max head number
    ; is one less than the count of heads.
    inc dh
    mov [head_count], dh

    ; Compute LBA of root dir = reserved + fats * sectors per fat
    mov ax, [sectors_per_fat]
    mov bl, [fat_count]
    xor bh, bh
    mul bx
    add ax, [reserved_sectors]
    push ax ; LBA

    ; Compute sector size of root dir = (32 * number of entries) / bytes_per_sector
    mov ax, [root_dir_entries]
    shl ax, 5
    xor dx, dx
    div word [bytes_per_sector]

    test dx, dx
    jz .root_dir_size_no_inc
    inc ax ; % bytes_per_sector > 0 => we need to round up
.root_dir_size_no_inc:

    mov cl, al ; cl = # of sectors to read
    pop ax ; LBA
    mov dl, [drive_number]
    mov bx, buffer
    call disk_read

    ; Search for LSTAGE BIN
    xor bx, bx
    mov di, buffer

.search_lstage:
    mov si, file_lstage_bin
    mov cx, 11 ; 11 bytes
    push di
    repe cmpsb
    pop di
    je .found_lstage

    add di, 32
    inc bx
    cmp bx, [root_dir_entries]
    jl .search_lstage
    jmp lstage_not_found_error

.found_lstage:

    ; di has address to the entry
    mov ax, [di + 26] ; First logical cluster field at offset 26
    mov [lstage_cluster], ax

    ; Load first FAT into memory
    mov ax, [reserved_sectors]
    mov bx, buffer
    mov cl, [sectors_per_fat]
    mov dl, [drive_number]
    call disk_read

    mov bx, LSTAGE_LOAD_SEGMENT
    mov es, bx
    mov bx, LSTAGE_LOAD_OFFSET

.load_lstage_loop:
    mov ax, [lstage_cluster]

    ; TODO: unhardcode this
    add ax, 31 ; ((lstage_cluster - 2) * sectors_per_cluster) + reserved + fats + root_dir_size

    mov cl, 1
    mov dl, [drive_number]
    call disk_read

    add bx, [bytes_per_sector]

    mov ax, [lstage_cluster]
    mov cx, 3
    mul cx
    mov cx, 2
    div cx ; ax = index of entry in FAT, dx = cluster % 2

    mov si, buffer
    add si, ax
    mov ax, [ds:si] ; read entry from FAT table at index ax

    or dx, dx
    jz .even

    ; Align and get 12 bits (1.5 bytes)
.odd:
    shr ax, 4
    jmp .next_cluster_after
.even:
    and ax, 0x0FFF

.next_cluster_after:
    cmp ax, 0x0FF8 ; end of chain
    jge .read_finish

    mov [lstage_cluster], ax
    jmp .load_lstage_loop

.read_finish:
    mov dl, [drive_number]

    mov ax, LSTAGE_LOAD_SEGMENT
    mov ds, ax
    mov es, ax

    jmp LSTAGE_LOAD_SEGMENT:LSTAGE_LOAD_OFFSET

    jmp halt

halt:
    cli
    hlt
    jmp halt

lstage_not_found_error:
    mov si, msg_lstage_not_found
    call puts
    jmp halt

floppy_error:
    mov si, msg_floppy_error
    call puts
    jmp halt

;
; Convert LBA to CHS
;
; Parameters:
;   - ax: LBA
; Returns:
;   - cx [bits 0-5]: sector number
;   - cx [bits 6-15]: cylinder
;   - dh: head
;
lba_to_chs:
    push ax
    push dx

    xor dx, dx                          ; dx = 0
    div word [sectors_per_track]        ; ax = LBA / sectors_per_track
                                        ; dx = LBA % sectors_per_track

    inc dx                              ; dx = sector
    mov cx, dx

    xor dx, dx
    div word [head_count]               ; ax = (LBA / sectors_per_track) / heads = cylinder
                                        ; dx = (LBA / sectors_per_track) % heads = head

    mov dh, dl                          ; dh = head
    mov ch, al                          ; cylinder [15-8]
    shl ah, 6
    or cl, ah                           ; last 2 bits

    pop ax
    mov dl, al
    pop ax
    ret

;
; Read sectors from disk
;
; Parameters:
;   - ax: LBA
;   - cl: number of sectors to read
;   - dl: drive number
;   - es:bx: buffer
;
disk_read:
    pusha
    
    push cx
    call lba_to_chs
    pop ax                  ; al = sectors to read

    mov ah, 0x02
    mov di, 0x05            ; retry count

.retry:
    pusha
    stc
    int 0x13
    jnc .done

    popa
    call disk_reset

    dec di
    test di, di
    jnz .retry
.fail:
    jmp floppy_error
.done:
    popa
    popa
    ret

;
; Reset disk controller
;
; Parameters:
;   - dl: drive number
;
disk_reset:
    pusha
    mov ah, 0x00
    stc
    int 0x03
    jc floppy_error
    popa
    ret

puts:
    pusha
.loop:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    int 0x10
    jmp .loop
.done:
    popa
    ret

msg_hello: db "Hello, world!", 0x0D, 0x0A, 0x00
msg_floppy_error: db "Read error.", 0x0D, 0x0A, 0x00
msg_lstage_not_found: db "LSTAGE.BIN not found.", 0x0D, 0x0A, 0x00

lstage_cluster: dw 0x0000
LSTAGE_LOAD_SEGMENT equ 0x2000
LSTAGE_LOAD_OFFSET equ 0x0000

file_lstage_bin: db "LSTAGE  BIN"

times 510-($-$$) db 0
dw 0AA55h

buffer:

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