0

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
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Sawyer Herbst
  • 152
  • 3
  • 12
  • 1
    Best guess: likely not setting up segment registers properly. You should update your question and post all of your code and the commands you use to assemble/compile/link and create your disk image.Other than that I'd recommend using BOCHs (and its debugger) to step through the realmode code to identify where it fails (and why). – Michael Petch Feb 19 '19 at 05:00
  • @MichaelPetch Sorry, I assumed it was ok because I linked some other code that I tried. I have now added the code. Thanks! – Sawyer Herbst Feb 19 '19 at 14:53
  • That's the code, but what commands did you use to built it into an image, and what command did you use to run QEMU on it? You said you had the same problem with an example from a tutorial, so you're probably building it wrong. Thus those details are a key part of a [mcve]. – Peter Cordes Feb 19 '19 at 15:27
  • @PeterCordes Done, I have added the commands. – Sawyer Herbst Feb 19 '19 at 15:55
  • I assume you've ruled out `boot_sector.bin` being larger than 512 bytes? That probably wouldn't boot at all, because the signature would be in the wrong place. IDK, looks pretty simple. What happens when you single-step your code with a debugger? (qemu GDB remote, or BOCHS's built-in debugger) That's the obvious next step, otherwise you're just flying blind. – Peter Cordes Feb 19 '19 at 16:07

3 Answers3

5

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:

enter image description here

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:

enter image description here

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
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • I have done this, with still no change. I will post the commands I used to build. – Sawyer Herbst Feb 19 '19 at 15:47
  • Top left only says SeaBIOS, so that's a no. I would have to agree with PeterCordes comment that I built it wrong, but I'm no expert. – Sawyer Herbst Feb 19 '19 at 15:54
  • @SawyerHerbst : The way you built it is fine, although I'm unsure why you use `--nographic` to run it.I would verify that you modified the right descriptor entry. I have a feeling you are either using the wrong code, or you didn't make the suggested change in the right place. – Michael Petch Feb 19 '19 at 15:56
1

Ok, I figured it out. When simply running qemu-system-x86_64 boot_sector.bin on macOs, it doesn't display anything, not even in 16-bit real mode. I found somewhere online that adding -nographic will work, and that worked for 16-bit real mode. In 32 bit PM remove the -nographic tag and add -curses. This worked perfectly. Thanks to Michael Petch also for showing me my bad GDT entry.

Sawyer Herbst
  • 152
  • 3
  • 12
0

Put these things at the top of your switch_to_pm.asm like this:

;switch_to_pm.asm

[bits 16]

; Switch to protected mode

switch_to_pm:

mov ax, 0x2401
int 0x15 ; enable A20 bit
mov ax, 0x3
int 0x10 ; set vga text mode 3
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
Doj
  • 1,244
  • 6
  • 13
  • 19