I'm in 32-bit protected mode running at current privilege level (CPL=0). I'm trying to enter v8086 mode by setting EFLAGS.VM (Bit 17) flag to 1 (and IOPL to 0) and doing a FAR JMP to my 16-bit real mode code. I get the current flags using PUSHF
; set EFLAGS.VM (bit 17) to 1; set EFLAGS.IOPL (bit 22 and bit 23) to 0; set the new EFLAGS with POPF
. The code for this looks like:
bits 32
cli
[snip]
pushf ; Get current EFLAGS
pop eax
or eax, 1<<EFLAGS_VM_BIT ; Set VM flag to enter v8086 mode
and eax, ~(3<<EFLAGS_IOPL_BITS)
; Set IOPL to 0
; IF flag already 0 because of earlier CLI
push eax
popf ; Reload new flags
jmp CODE32_SEL:v86_mode_entry
; Far JMP to v8086 entry point
; v8086 code entry point
bits 16
v86_mode_entry:
hlt ; Halt should double fault
[snip]
For these tests I'm deliberately running with:
- Interrupts off at CPL=0 all the time.
- Interrupts off when running in v8086 mode.
- I don't have an IDT.
- I don't have a TSS as I'm not transitioning between privilege levels through interrupts, gates,and exceptions.
To test whether I have entered v8086 mode I do a HLT
instruction. Since I have no interrupt mechanisms in place I expect a double fault to occur. The hlt
seems to execute correctly and the system sits there. In BOCHs when I reach the hlt
I noticed the flags are:
eflags 0x00000046: id vip vif ac vm rf nt IOPL=0 of df if tf sf ZF af PF cf
EFLAGS.VM flag is marked as off (0) since it is listed as vm
and not VM
. This isn't what I expected.
Questions:
- What is wrong with my code, and how can it be corrected so v8086 mode is entered and the
hlt
double faults? - Is it possible to enter v8086 mode while in 64-bit mode or 32-bit compatibility mode (submode of long mode)?
A minimal complete verifiable example of this code is a bootloader that enters protected mode, and performs the tasks outlined above:
VIDEO_TEXT_ADDR EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_BWHITE_ON_GREEN EQU 0x2f ; Bright white on green attribute
ATTR_BWHITE_ON_MAGENTA EQU 0x5f ; Bright White on magenta attribute
PM_MODE_STACK EQU 0x80000 ; Protected mode stack below EBDA
EFLAGS_VM_BIT EQU 17 ; EFLAGS VM bit
EFLAGS_IOPL_BITS EQU 12 ; EFLAGS IOPL bits (bit 12 and bit 13)
; Macro to build a GDT descriptor entry
%define MAKE_GDT_DESC(base, limit, access, flags)
(((base & 0x00FFFFFF) << 16) |
((base & 0xFF000000) << 32) |
(limit & 0x0000FFFF) |
((limit & 0x000F0000) << 32) |
((access & 0xFF) << 40) |
((flags & 0x0F) << 52))
bits 16
ORG 0x7c00
; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
; %include "bpb.inc"
boot_start:
xor ax, ax ; DS=SS=ES=0
mov ds, ax
mov ss, ax ; Stack at 0x0000:0x7c00
mov sp, 0x7c00
cld ; Set string instructions to use forward movement
; Fast method of enabling A20 may not work on all x86 BIOSes
; It is good enough for emulators and most modern BIOSes
; See: https://wiki.osdev.org/A20_Line
cli ; Disable interrupts for rest of code as we don't
; want A20 code to be interrupted. In protected mode
; we have no IDT so any interrupt that does occur will
; double fault and reboot.
in al, 0x92
or al, 2
out 0x92, al ; Enable A20 using Fast Method
lgdt [gdtr] ; Load our GDT
mov eax, cr0
or eax, 1
mov cr0, eax ; Set protected mode flag
jmp CODE32_SEL:start32 ; FAR JMP to set CS
; v8086 code entry point
v86_mode_entry:
hlt ; Halt
; 32-bit protected mode entry point
bits 32
start32:
mov ax, DATA32_SEL ; Setup the segment registers with data selector
mov ds, ax
mov es, ax
mov ss, ax
mov esp, PM_MODE_STACK ; Set protected mode stack pointer
mov fs, ax ; Not currently using FS and GS
mov gs, ax
mov ah, ATTR_BWHITE_ON_GREEN; Attribute to print with
mov al, ah ; Attribute to clear last line when scrolling
mov esi, in_pm_msg ; Print message that we are in protected mode
call print_string_pm
pushf ; Get current EFLAGS
pop eax
or eax, 1<<EFLAGS_VM_BIT ; Set VM flag to enter v8086 mode
and eax, ~(3<<EFLAGS_IOPL_BITS)
; Set IOPL to 0
; IF flag already 0 because of earlier CLI
push eax
popf ; Reload new flags
jmp CODE32_SEL:v86_mode_entry
; Far JMP to v8086 entry point
; Function: print_string_pm
; Display a string to the console on display page 0 in protected mode.
; Very basic. Doesn't update hardware cursor, doesn't handle scrolling,
; LF, CR, TAB.
;
; Inputs: ESI = Offset of address to print
; AH = Attribute of string to print
; Clobbers: None
; Returns: None
print_string_pm:
push edi
push esi
push eax
mov edi, [vidmem_ptr] ; Start from video address stored at vidmem_ptr
jmp .getchar
.outchar:
stosw ; Output character to video display
.getchar:
lodsb ; Load next character from string
test al, al ; Is character NUL?
jne .outchar ; If not, go back and output character
mov [vidmem_ptr], edi ; Update global video pointer
pop eax
pop esi
pop edi
ret
align 4
vidmem_ptr: dd VIDEO_TEXT_ADDR ; Start console output in upper left of display
in_pm_msg:
db "In 32-bit protected mode!", 0
align 4
gdt_start:
dq MAKE_GDT_DESC(0, 0, 0, 0) ; null descriptor
gdt32_code:
dq MAKE_GDT_DESC(0, 0x000fffff, 10011010b, 1100b)
; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_data:
dq MAKE_GDT_DESC(0, 0x000fffff, 10010010b, 1100b)
; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
; Pad boot sector to 510 bytes and add 2 byte boot signature
TIMES 510-($-$$) db 0
dw 0xaa55
The bootloader can be generated with:
nasm -f bin v86.asm -o v86.bin
It can be run in QEMU with:
qemu-system-i386 -fda v86.bin
See Question&Answers more detail:os