Issue
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
Solution
You don't correctly set a real mode stack pointer and the segment registers. A real mode stack pointer consists of SS:SP. You don't know where in memory the stack is since you've only modified SP. The beginning of your bootloader should be something like:
xor ax, ax ; Set ES=DS=0 since an ORG of 0x7c00 is used
mov es, ax ; 0x0000<<4+0x7c00 = physical address 0x07c00
mov ds, ax
mov bp, 0x9000
mov ss, ax ; Set stack to 0x0000:0x9000
mov sp, bp
Your code doesn't rely on BP so it doesn't have to be set, although it doesn't hurt anything by doing so.
The main problem getting into protected mode is an error in your GDT. Each descriptor entry is 8 bytes and the layout of each descriptor is as follows:
In your code you seem to be missing a byte in the 32-bit code descriptor:
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)
This entry is only 7 bytes long. It appears you are missing the last byte which should be 0 to complete the 32-bit base address. It should read:
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)
db 0x0 ; Base (bits 24-31)
When run in QEMU with the command qemu-system-x86_64 boot_sector.bin
it should appear as:
I have highlighted the text printed in protected mode in red.
If you wish to run the code outside of a graphical display in console mode, tell QEMU to use curses.
-curses
Normally, if QEMU is compiled with graphical window support, it
displays output such as guest graphics, guest console, and the QEMU
monitor in a window. With this option, QEMU can display the VGA
output when in text mode using a curses/ncurses interface. Nothing
is displayed in graphical mode.
Use the command line:
qemu-system-x86_64 boot_sector.bin -curses
Answered By - Michael Petch
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.