Two stage bootloader: GDT setup halts execution after ‘GDT loading…’ message

I’m working on a two-stage bootloader written in NASM, and I’m using QEMU as my emulator. Stage 1 successfully loads Stage 2, and the A20 line setup works fine with all expected messages being printed.

However, once the GDT loading… message is printed in Stage 2, the system halts unexpectedly, and no further messages appear on the terminal, particularly the success or failure message related to the GDT setup. I suspect something is going wrong either during or after the GDT setup.

Terminal Output:

Booting from Hard Disk...
A20 loading...
A20 line enable -> successfully
GDT loading...

I expected to see either the message:

GDT load -> successfully

or

GDT load -> failed.

Project Overview:

  • Assembler: NASM
  • Emulator: QEMU
  • Bootloader: Two-stage bootloader (Stage 1 loads Stage 2).
  • Makefile: I’m using a makefile to assemble the bootloader stages and create the bootloader image.

Here’s the relevant code structure:

Makefile:

PROJECT_ROOT := $(CURDIR)

STAGE1_SRC := $(PROJECT_ROOT)/Boot/Stage1/stage1.asm
STAGE2_SRC := $(PROJECT_ROOT)/Boot/Stage2/stage2.asm

STAGE1_BIN := $(PROJECT_ROOT)/Boot/Stage1/stage1.bin
STAGE2_BIN := $(PROJECT_ROOT)/Boot/Stage2/stage2.bin

IMG := $(PROJECT_ROOT)/bootloader.img

QEMU_IMG := qemu-img
DD := dd

output: clean $(STAGE1_BIN) $(STAGE2_BIN) $(IMG)

$(STAGE1_BIN): $(STAGE1_SRC)
    @echo "Assembling Stage 1..."
    nasm -f bin "$(STAGE1_SRC)" -o "$(STAGE1_BIN)"

$(STAGE2_BIN): $(STAGE2_SRC)
    @echo "Assembling Stage 2..."
    nasm -f bin "$(STAGE2_SRC)" -o "$(STAGE2_BIN)"

$(IMG): $(STAGE1_BIN) $(STAGE2_BIN)
    @echo "Creating bootloader.img..."
    $(QEMU_IMG) create -f raw "$(IMG)" 10M
    $(DD) if=$(STAGE1_BIN) of=$(IMG) bs=512 count=1 conv=notrunc status=progress
    $(DD) if=$(STAGE2_BIN) of=$(IMG) bs=512 seek=1 conv=notrunc status=progress

clean:
    @echo "Cleaning up..."
    rm -f "$(STAGE1_BIN)" "$(STAGE2_BIN)" "$(IMG)"

stage1.asm

stage1.asm:

[BITS 16]               ; We are working in 16-bit Real Mode
[org 0x7c00]            ; The origin (starting address) of the bootloader in memory, which is 0x7C00 as loaded by the BIOS.
                        ; This is the physical address where the bootloader is loaded into memory.

start:                  ; Start of execution, this label marks the entry point of the code.
    jmp main            ; Jump to the 'main' label to start execution.

main:                   ; Main routine of the bootloader begins here.

; -------------------------
; Setup segment registers
; -------------------------
    cli                 ; Clear interrupts to ensure no interrupts occur while setting up segments.
    xor ax, ax          ; Set AX to 0x0 (which is 0x0 >> 4).
                        ; Explanation: We are using segment:offset addressing in real mode.
                        ; Physical address = Segment * 16 + Offset
                        ; So, the segment 0x0 * 16 = 0x0 (physical address).
    mov ds, ax          ; Set Data Segment (DS) to 0x0. DS points to the bootloader code/data in memory.
    mov es, ax          ; Set Extra Segment (ES) to 0x0. ES is also set to point to our code/data.

; -------------------------
; Setup stack
; -------------------------
    mov ss, ax          ; Set Stack Segment (SS) to 0 (base of memory).
    mov sp, 0xFFFE      ; Set the Stack Pointer (SP) to the highest address within the current 64KB segment (0x0000:0xFFFE).
                        ; In real mode, the stack grows downward from 0xFFFE and 0xFFFE should be set to an even offset like 0xFFFE, not an odd one.

    sti                 ; Re-enable interrupts after segment and stack setup is complete.

; -------------------------
; Load Stage 2 bootloader from disk
; -------------------------
    mov ah, 02h         ; BIOS Interrupt 13h, Function 02h: Read sectors from the disk.
    mov al, 01h         ; Read 1 sector from the disk (this corresponds to the size of a sector, which is 512 bytes).

    mov ch, 00h         ; Set Cylinder number to 0 (since both Stage 1 and Stage 2 are on Cylinder 0).
    mov cl, 02h         ; Set Sector number to 2 (Stage 1 is in Sector 1, so Stage 2 starts at Sector 2).
    mov dh, 00h         ; Set Head number to 0 (assuming we are using Head 0 for now).
    mov dl, 80h         ; Use the first hard drive (usually 0x80 for the primary hard disk).

    mov bx, 0x8000      ; Set BX to 0x8000, the offset address where Stage 2 will be loaded.
                        ; Stage 2 will be loaded into memory using segment:offset addressing.
                        ; ES = 0x0, BX = 0x8000, so the physical address = ES * 16 + BX.
                        ; Formula: 0x0 * 16 + 0x8000 = 0x0 + 0x8000 = 0x8000 (the physical address where Stage 2 is loaded).

    int 13h             ; Call BIOS interrupt 13h to read the specified sectors into memory.

    jc disk_read_error  ; If carry flag is set (indicating an error), jump to the error handler.

pass:                   ; If the disk read was successful (carry flag is cleared), continue from here.
    jmp 0x0000:0x8000   ; Jump to the loaded Stage 2 at address 0x0000:0x8000 (this is where Stage 2 resides).
                        ; Here, 0x0000 is the segment, and 0x8000 is the offset.
                        ; Physical address = 0x0000 * 16 + 0x8000 = 0x8000, where Stage 2 is loaded.

disk_read_error:
    int 18h             ; If the disk read fails, call INT 18h to attempt a boot from a different device (like network boot).
                        ; This error message will occur --> IO write(0x01f0): current command is 20h displayed on bochs emulator.

TIMES 510-($-$$) DB 0   ; Pad the bootloader to ensure it is exactly 512 bytes, with zeros filling the remaining space.
DW 0xAA55               

stage2.asm

[BITS 16]
[org 0x8000]

jmp a20_enter
; -------------------------
; Messages
; -------------------------
section .data
msg_a20_loading db 'A20 loading... ', 0x0D, 0x0A, 0
msg_a20_error   db 'A20 line enable -> failed ', 0x0D, 0x0A, 0
msg_a20_enable  db 'A20 line enable -> successfully ', 0x0D, 0x0A, 0

msg_gdt_loading db 'GDT loading... ', 0x0D, 0x0A, 0
msg_gdt_success db 'GDT load -> successfully ', 0x0D, 0x0A, 0

msg_tss_loading db 'TSS loading... ', 0x0D, 0x0A, 0
msg_tss_success db 'TSS load -> successfully ', 0x0D, 0x0A, 0

; -------------------------
; BSS Section for TSS
; -------------------------
section .bss
tss resb 104    ; Reserve 104 bytes for the TSS (Task State Segment)

; -------------------------
; Initialize the TSS
; -------------------------
section .text

init_tss:
    ; Set ESP0 (stack pointer for privilege level 0)
    mov eax, 0x9FBFF        ; Example stack pointer for ring 0
    mov [tss + 4], eax      ; Set ESP0 in the TSS

    ; Set SS0 (stack segment for privilege level 0)
    mov ax, 0x10            ; Kernel data segment selector (0x10)
    mov [tss + 8], ax       ; Set SS0 in the TSS

    ret                     ; Return to the main flow

; -------------------------
; Print Message: A20 Loading
; -------------------------
a20_enter:
mov ax, 0x0000          ; Set data segment to 0 for memory access
mov ds, ax
mov ah, 0x0E            ; BIOS teletype function (interrupt 0x10, AH=0x0E) for printing characters
mov bh, 0x00            ; Display page number

mov si, msg_a20_loading ; Load pointer to "A20 loading..." message into SI

a20_print_loading:
    lodsb               ; Load next byte from [SI] into AL
    cmp al, 0           ; Check if null terminator (end of string)
    je a20_status_check ; Jump to A20 status check if end of message
    int 0x10            ; BIOS interrupt to print character in AL
    jmp a20_print_loading ; Repeat for next character

; -------------------------
; A20 Status Check
; -------------------------
a20_status_check:
    pushf               ; Save flags
    push ds             ; Save data segment
    push es             ; Save extra segment
    push di             ; Save destination index
    push si             ; Save source index

    cli                 ; Disable interrupts during A20 check
    xor ax, ax          ; Set AX to 0
    mov es, ax          ; Set extra segment (ES) to 0
    mov di, 0x0500      ; Set DI to test memory location 0x0500 in low memory

    not ax              ; Set AX to 0xFFFF (inverted 0)
    mov ds, ax          ; Set data segment (DS) to 0xFFFF
    mov si, 0x0510      ; Set SI to memory location 0x0510 (also in low memory)

    ; Save original values from memory locations
    mov al, byte [es:di] ; Load byte at ES:DI into AL
    push ax              ; Save value from ES:DI
    mov al, byte [ds:si] ; Load byte at DS:SI into AL
    push ax              ; Save value from DS:SI

    ; Modify memory locations for the test
    mov byte [es:di], 0x00 ; Set ES:DI to 0x00
    mov byte [ds:si], 0xFF ; Set DS:SI to 0xFF

    ; Check if memory write is visible at ES:DI
    cmp byte [es:di], 0xFF ; Compare ES:DI with 0xFF

    ; Restore original memory values
    pop ax               ; Restore value from stack to AX
    mov byte [ds:si], al  ; Restore original byte at DS:SI
    pop ax               ; Restore value from stack to AX
    mov byte [es:di], al  ; Restore original byte at ES:DI

    ; Determine A20 status based on comparison result
    mov ax, 0            ; Assume A20 is off
    je status_a20_off     ; Jump if A20 is off

    mov ax, 1            ; A20 is on
    jmp status_a20_on     ; Jump to A20 enabled handling

; -------------------------
; A20 Enable via BIOS Interrupt 15h
; -------------------------
status_a20_off:
    mov ax, 0x2401       ; Request to enable A20 via BIOS (INT 15h)
    int 0x15             ; BIOS interrupt call

    jc a20_error         ; If carry flag is set (error), jump to error handler
    jmp a20_status_check ; Otherwise, recheck A20 status after enabling

; -------------------------
; A20 Enable Failure Handling
; -------------------------
a20_error:
    mov ax, 0x0000       ; Reset segment register for message output
    mov ds, ax
    mov ah, 0x0E         ; Set up for character output
    mov bh, 0x00         ; Display page number

    mov si, msg_a20_error ; Load error message into SI

print_error_loop:
    lodsb                ; Load next byte from message
    cmp al, 0            ; Check for null terminator
    je end_error_msg_a20 ; If end of string, stop printing
    int 0x10             ; Print character in AL
    jmp print_error_loop ; Loop to print the next character

end_error_msg_a20:
    cli                  ; Disable interrupts
    hlt                  ; Halt the system

; -------------------------
; A20 Enable Success Handling
; -------------------------
status_a20_on:
    mov ax, 0x0000       ; Reset data segment for message output
    mov ds, ax
    mov ah, 0x0E         ; Set up for character output
    mov bh, 0x00         ; Display page number

    mov si, msg_a20_enable ; Load success message into SI

print_enable_loop:
    lodsb                ; Load next byte from message
    cmp al, 0            ; Check for null terminator
    je restore_registers_a20 ; If end of string, restore registers
    int 0x10             ; Print character in AL
    jmp print_enable_loop ; Loop to print the next character

; -------------------------
; Restore Registers and Continue
; -------------------------
restore_registers_a20:
    pop si               ; Restore source index
    pop di               ; Restore destination index
    pop es               ; Restore extra segment
    pop ds               ; Restore data segment
    popf                 ; Restore flags

    sti                  ; Re-enable interrupts

; -------------------------
; GDT And TSS Loading Message
; -------------------------
mov ax, 0x0000            ; Reset data segment for message output
mov ds, ax
mov ah, 0x0E              ; Set up for character output
mov bh, 0x00              ; Display page number

mov si, msg_gdt_loading   ; Load GDT loading message into SI

gdt_print_loading:
    lodsb                 ; Load next byte from message
    cmp al, 0             ; Check for null terminator
    je tss_loading_msg    ; If end of string, jump to GDT setup
    int 0x10              ; Print character in AL
    jmp gdt_print_loading ; Loop to print the next character

tss_loading_msg:
mov ax, 0x0000            ;
mov ds, ax
mov ah, 0x0E              ;
mov bh, 0x00              ;

mov si, msg_tss_loading   ; 

tss_print_loading:
    lodsb                 ;  
    cmp al, 0             ;
    je gdt_setup          ;
    int 0x10              ;
    jmp tss_print_loading ;

; -------------------------
; GDT and TSS Setup and Loading
; -------------------------
gdt_setup:
    cli                   ; Disable interrupts during GDT setup
    lgdt [gdt_descriptor] ; Load GDT descriptor into GDTR

    push ds
    push es

    call init_tss         ; Initialize the TSS

    pop es 
    pop ds
    
    mov ax, 0x28          ;
    ltr ax                ; <--- Bug is here!!!!!

    ; Display success message after loading GDT
    mov ax, 0x0000       ; Reset data segment for message output
    mov ds, ax
    mov ah, 0x0E         ; Set up for character output
    mov bh, 0x00         ; Display page number

    mov si, msg_gdt_success ; Load GDT success message into SIs

gdt_print_success:
    lodsb                   ; Load next byte from message
    cmp al, 0               ; Check for null terminator
    je tss_succsess_msg     ; If end of string, proceed to protected mode
    int 0x10                ; Print character in AL
    jmp gdt_print_success   ; Loop to print the next character

tss_succsess_msg:           ;
    mov ax, 0x0000          ;
    mov ds, ax
    mov ah, 0x0E            ;
    mov bh, 0x00            ;

    mov si, msg_tss_success ; 

tss_print_success:
    lodsb                 ; 
    cmp al, 0             ;
    je enter_protected_mode ; 
    int 0x10              ; 
    jmp tss_print_success ;

; -------------------------
; Enter Protected Mode
; -------------------------
enter_protected_mode:
    cli                 ; Disable interrupts before entering protected mode
    mov eax, cr0
    or eax, 1           ; Set protected mode bit in CR0
    mov cr0, eax

    ; Far jump to update CS with GDT code segment (0x08)
    jmp 0x08:update_segments 

[BITS 32]
update_segments:
    ; Set up segment registers for protected mode
    mov ax, 0x10        ; Load GDT data segment selector (0x10)
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    jmp kernel          ; Jump to kernel code

; -------------------------
; Kernel Code (currently halting)
; -------------------------
kernel:
    hlt                 ; Halt the CPU (placeholder for actual kernel code)

; -------------------------
; GDT Descriptor and TSS
; -------------------------
section .data
gdt_start:

     ; Null Descriptor
    dd 0x0
    dd 0x0

    ; Kernel Code Segment (DPL = 0)
    dw 0xFFFF              ; Limit
    dw 0x0000              ; Base (lower 16 bits)
    db 0x00                ; Base (next 8 bits)
    db 10011010b           ; Access byte
    db 11001111b           ; Flags and limit (upper 4 bits)
    db 0x00                ; Base (upper 8 bits)

    ; Kernel Data Segment (DPL = 0)
    dw 0xFFFF              ; Limit
    dw 0x0000              ; Base (lower 16 bits)
    db 0x00                ; Base (next 8 bits)
    db 10010010b           ; Access byte
    db 11001111b           ; Flags and limit (upper 4 bits)
    db 0x00                ; Base (upper 8 bits)

    ; User Code Segment (DPL = 3)
    dw 0xFFFF              ; Limit
    dw 0x0000              ; Base (lower 16 bits)
    db 0x00                ; Base (next 8 bits)
    db 11111010b           ; Access byte (DPL = 3)
    db 11001111b           ; Flags and limit (upper 4 bits)
    db 0x00                ; Base (upper 8 bits)

    ; User Data Segment (DPL = 3)
    dw 0xFFFF              ; Limit
    dw 0x0000              ; Base (lower 16 bits)
    db 0x00                ; Base (next 8 bits)
    db 11110010b           ; Access byte (DPL = 3)
    db 11001111b           ; Flags and limit (upper 4 bits)
    db 0x00                ; Base (upper 8 bits)

    ; TSS Descriptor
    tss_base_addr: equ 0x10000  ; Base address for TSS
    dw 0x0067                   ; Limit
    dw tss_base_addr            ; Base (lower 16 bits)
    db (tss_base_addr >> 16)    ; Base (next 8 bits)
    db 10001001b                ; Access byte
    db 00000000b                ; Flags and limit (upper 4 bits)
    db (tss_base_addr >> 24)    ; Base (upper 8 bits)

gdt_end:

; GDT Descriptor (contains size and location of GDT)
gdt_descriptor:
    dw gdt_end - gdt_start - 1  ; Size of the GDT (in bytes, minus 1)
    dd gdt_start            

The A20-related messages print correctly, so the A20 setup seems to be working as expected.

The GDT loading… message is printed, which means the flow reaches the GDT setup.

However, after setting up the GDT (with lgdt and ltr instructions), no success or failure message prints, and the system seems to halt.

I expected to see either:

GDT load -> successfully or.
GDT load -> failed.

I’m unsure if the issue lies within the GDT setup itself or if something else is preventing the success/failure messages from being displayed after the GDT is loaded.

4

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