Следующий минимальный полный пример демонстрирует эту концепцию. В этом примере IOPB настроен так, чтобы разрешить порт доступу к первым портам 0x400 и запретить его для остальных:
ожусь в 32-битном защищенном режиме, работающем с текущим уровнем привилегий (CPL = 0). Я пытаюсь войти в режим v8086, устанавливая флаг EFLAGS.VM (бит 17) в 1 (и IOPL в 0) и выполняю FAR JMP для моего 16-битного кода реального режима. Я получаю текущие флаги используяPUSHF
; установите EFLAGS.VM (бит 17) в 1; установите EFLAGS.IOPL (бит 22 и бит 23) в 0; установить новые EFLAGS сPOPF
, Код для этого выглядит так:
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]
Для этих тестов я намеренно работаю с:
Прерывает при CPL = 0 все время.Прерывается при работе в режиме v8086.У меня нет IDT.У меня нет TSS, поскольку я не перехожу между уровнями привилегий через прерывания, ворота и исключения.Чтобы проверить, вошел ли я в режим v8086, я делаюHLT
инструкция. Поскольку у меня нет механизмов прерывания, я ожидаю двойной ошибки.hlt
кажется, работает правильно, и система сидит там. В BOCHs, когда я достигаюhlt
Я заметил флаги:
eflags 0x00000046: id vip vif ac vm rf nt IOPL=0 of df if tf sf ZF af PF cf
Флаг EFLAGS.VM помечен как выключенный (0), так как он указан какvm
и нетVM
, Это не то, что я ожидал.
hlt
двойные ошибки?Можно ли войти в режим v8086 в 64-битном длинном режиме или в 32-битной совместимости (длинном режиме)?Минимальным полным проверяемым примером этого кода является загрузчик, который входит в защищенный режим и выполняет задачи, описанные выше:
VIDEO_TEXT_ADDR EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_BWHITE_ON_GREEN EQU 0x2f ; Bright white on black 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 & 0x00FF0000) << 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, 0x00ffffff, 10011010b, 11001111b)
; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_data:
dq MAKE_GDT_DESC(0, 0x00ffffff, 10010010b, 11001111b)
; 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
Загрузчик может быть сгенерирован с помощью:
nasm -f bin v86.asm -o v86.bin
Его можно запустить в QEMU с помощью:
qemu-system-i386 -fda v86.bin