I am writing a simple NASM assembly boot sector. The code should print text to the screen while in 16 bit real mode, then switches to 32 bit protected mode and prints text to the screen.
I use QEMU as my CPU emulator, and it is printing the text from the 16 bit mode as it should. However, the text that's supposed to be printed while in 32 bit mode doesn't print.
I would assume this is an issue with my code, but I have also ran this similar code, with the same problem of only working while in 16 bit mode.
Am I not using QEMU correctly, or am I messing something else up? My code is:
boot_sector.asm
; Boot sector that enters 32 bit protected mode
[org 0x7c00]
mov bp, 0x9000 ; Set stack
mov sp, bp
mov bx, MSG_REAL_MODE
call print_string
call switch_to_pm ; We will never return to here
jmp $
%include "print_string.asm"
%include "gdt.asm"
%include "print_string_pm.asm"
%include "switch_to_pm.asm"
[bits 32]
;Where we arrive after switching to PM
BEGIN_PM:
mov ebx, MSG_PROTECTED_MODE
call print_string_pm ; 32 bit routine to print string
jmp $ ; Hang
; Global variables
MSG_REAL_MODE: db "Started in 16-bit real mode.", 0
MSG_PROTECTED_MODE: db "Successfully landed in 32-bit protected mode.", 0
; Boot sector padding
times 510-($-$$) db 0
dw 0xaa55
switch_to_pm.asm
[bits 16]
; Switch to protected mode
switch_to_pm:
mov bx, MSG_SWITCHING ; Log
call print_string
cli ; Clear interrupts
lgdt [gdt_descriptor] ; Load GDT
mov eax, cr0 ; Set the first bit of cr0 to move to protected mode, cr0 can't be set directly
or eax, 0x1 ; Set first bit only
mov cr0, eax
jmp CODE_SEG:init_pm ; Make far jump to to 32 bit code. Forces CPU to clear cache
[bits 32]
; Initialize registers and the stack once in PM
init_pm:
mov ax, DATA_SEG ; Now in PM, our old segments are meaningless
mov ds, ax ; so we point our segment registers to the data selector defined GDT
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; Move stack
mov esp, ebp
call BEGIN_PM ; Call 32 bit PM code
; Global variables
MSG_SWITCHING: db "Switching to 32-bit protected mode...", 0
gdt.asm
gdt_start:
gdt_null: ; The mandatory null descriptor
dd 0x0 ; dd = define double word (4 bytes)
dd 0x0
gdt_code: ; Code segment descriptor
dw 0xffff ; Limit (bites 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 16-23)
db 10011010b ; 1st flags, type flags
db 11001111b ; 2nd flags, limit (bits 16-19)
gdt_data:
dw 0xffff ; Limit (bites 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 16-23)
db 10010010b ; 1st flags, type flags
db 11001111b ; 2nd flags, limit (bits 16-19)
db 0x0
gdt_end: ; necessary so assembler can calculate gdt size below
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; GDT size
dd gdt_start ; Start adress of GDT
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
print_string_pm.asm
[bits 32]
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
print_string_pm:
pusha
mov edx, VIDEO_MEMORY
print_str_pm_loop:
mov al, [ebx]
mov ah, WHITE_ON_BLACK
cmp al, 0
je print_str_pm_return
mov [edx], ax
add ebx, 1
add edx, 2
jmp print_str_pm_loop
print_str_pm_return:
popa
ret
print_string.asm
print_string:
pusha
mov ah, 0x0e
_print_str_loop:
mov al, [bx]
cmp al, 0
je _print_str_return
int 0x10
inc bx
jmp _print_str_loop
_print_str_return:
popa
ret
Commands: To build:
nasm -f bin boot_sector.asm -o boot_sector.bin
Qemu:
qemu-system-x86_64 boot_sector.bin --nographic