TL; DR :
Вопрос № 1 :
POPF
фактически не позволяет вамчтобы изменить флаг виртуальной машины в соответствии со ссылкой на архитектуру набора команд:
При работе в защищенном , совместимости или 64-битном режиме при уровне привилегий 0 (или в режиме реального адреса, эквивалентного уровню привилегий 0), все незарезервированные флаги в регистре EFLAGS, кроме RF1, VIP, VIF и VM, могут быть изменены.VIP, VIF и VM остаются неизменными .
Существует два основных механизма, которые можно использовать для установки EFLAGS.VM и входа в режим v8086 :
Переключатель задачи на задачу 80386 загружает образ EFLAGS из нового TSS.TSS новой задачи должен быть TSS 80386, а не TSS 80286, потому что TSS 80286 не хранит старшее слово EFLAGS, которое содержит флаг VM.Значение 1 в бите VM новой EFLAGS указывает, что новая задача выполняет 8086 инструкций;следовательно, при загрузке регистров сегмента из TSS, процессор формирует базовые адреса, как 8086.
IRET из процедуры задачи 80386 загружает образ EFLAGS изстек.В этом случае значение единицы в VM указывает, что процедура, которой возвращается элемент управления, является процедурой 8086.CPL во время выполнения IRET должен быть нулевым, иначе процессор не изменяет ВМ.
Вопрос № 2 :
*Режим 1043 * v8086 доступен только на процессоре x86-64 в 32-разрядном защищенном режиме (устаревший режим).Вы не можете использовать его в 64-битном длинном режиме или в 32-битном (или 16-битном) режиме совместимости.Вам нужно было бы вывести процессор из длинного режима и войти в 32-битный защищенный режим (традиционный режим), работающий с CPL = 0, и выполнить один из двух методов, отмеченных выше.Это дорогое (с точки зрения производительности) и сопряжено с проблемами.После этого вам придется переключиться обратно в длинный режим.
Если есть какой-то вариант использования для этого, и вы работаете в системе с несколькими ядрами - вы можете вызвать одно изядра в 32-разрядном защищенном режиме, а процессор начальной загрузки (BSP) работает в длинном режиме.
Метод 1: используйте IRET для входа в режим v8086
Это самое простое решение.Если вы делаете IRET
из 32-битного защищенного режима (в CPL = 0) и регистр EFLAGS.VM в стеке установлен, ЦП попытается вернуться в режим v8086 и примет стекфрейм содержит необходимую информацию для этого перехода:
PROTECTED-MODE:
[snip]
EIP ← Pop();
CS ← Pop(); (* 32-bit pop, high-order 16 bits discarded *)
tempEFLAGS ← Pop();
[snip]
RETURN-TO-VIRTUAL-8086-MODE:
(* Interrupted procedure was in virtual-8086 mode: PE = 1, CPL=0, VM = 1 in flag image *)
IF EIP not within CS limit
THEN #GP(0); FI;
EFLAGS ← tempEFLAGS;
ESP ← Pop();
SS ← Pop(); (* Pop 2 words; throw away high-order word *)
ES ← Pop(); (* Pop 2 words; throw away high-order word *)
DS ← Pop(); (* Pop 2 words; throw away high-order word *)
FS ← Pop(); (* Pop 2 words; throw away high-order word *)
GS ← Pop(); (* Pop 2 words; throw away high-order word *)
CPL ← 3;
(* Resume execution in Virtual-8086 mode *)
END;
Если вы поместите эти элементы в стек в обратном порядке и выполните команду iret
, вы сможете войти в режим v8086..
V86_STACK_SEG EQU 0x0000 ; v8086 stack SS
V86_STACK_OFS EQU 0x0000 ; v8086 stack SP
V86_CS_SEG EQU 0x0000 ; v8086 code segment CS
EFLAGS_VM_BIT EQU 17 ; EFLAGS VM bit
EFLAGS_BIT1 EQU 1 ; EFLAGS bit 1 (reserved , always 1)
[snip]
xor ebx, ebx ; EBX=0
push ebx ; Real mode GS=0
push ebx ; Real mode FS=0
push ebx ; Real mode DS=0
push ebx ; Real mode ES=0
push V86_STACK_SEG
push V86_STACK_OFS ; v8086 stack SS:SP (grows down from SS:SP)
push dword 1<<EFLAGS_VM_BIT | 1<<EFLAGS_BIT1
; Set VM Bit, IF bit is off, DF=0(forward direction),
; IOPL=0, Reserved bit (bit 1) always 1. Everything
; else 0. These flags will be loaded in the v8086 mode
; during the IRET. We don't want interrupts enabled
; because we have no v86 monitor via protected mode
; GPF handler
push V86_CS_SEG ; Real Mode CS (segment)
push v86_mode_entry ; Entry point (offset)
iret ; Transfer control to v8086 mode and our real mode code
Я установил ES = DS = CS = FS = GS = 0 и стек реального режима в V86_STACK_SEG: V86_STACK_OFS (определите их по своему усмотрению).IP устанавливается на смещение метки v86_mode_entry
.В приведенном выше фрагменте кода я установил только 2 бита в 1 (бит 1 и ВМ).Бит 1 является зарезервированным битом в EFLAGS , который всегда предполагается равным 1. Все остальные флаги в EFLAGS равны 0, поэтому IOPL = 0.
Все остальные регистры будут содержать те же значения, которые были у них до перехода в режим v8086.Вы можете обнулить их, чтобы избежать утечки информации в задачу v8086 из 32-разрядного защищенного режима (т. Е. Ядра).
Минимальный полный проверяемый пример использования этого кода:
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
V86_STACK_SEG EQU 0x0000 ; v8086 stack SS
V86_STACK_OFS EQU 0x0000 ; v8086 stack SP
V86_CS_SEG EQU 0x0000 ; v8086 code segment CS
EFLAGS_VM_BIT EQU 17 ; EFLAGS VM bit
EFLAGS_BIT1 EQU 1 ; EFLAGS bit 1 (reserved, always 1)
EFLAGS_IF_BIT EQU 9 ; EFLAGS IF bit
; 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:
sub dword [vidmem_ptr], VIDEO_TEXT_ADDR
; Adjust video pointer to be relative to beginning of
; segment 0xb800
mov si, in_v86_msg ; Print in v86 message
mov ah, ATTR_BWHITE_ON_MAGENTA
; Attribute to print with
call print_string_rm_nobios
.endloop:
jmp $ ; Infinite loop since we did code a solution to exit VM
; Function: print_string_rm_nobios
; Display a string to the console on display page 0 in real/v8086 mode
; without using the BIOS. We don't have a proper v8086 monitor so can't
; use BIOS to display.
;
; Very basic. Doesn't update hardware cursor, doesn't handle scrolling,
; LF, CR, TAB.
;
; Inputs: SI = Offset of address to print
; AH = Attribute of string to print
; Clobbers: None
; Returns: None
print_string_rm_nobios:
push di
push si
push ax
push es
mov di, VIDEO_TEXT_ADDR>>4 ; ES=0xb800 (text video mode segment)
mov es, di
mov di, [vidmem_ptr] ; Start from video address stored at vidmem_ptr
jmp .getchar
.outchar:
stosw ; Output character to display
.getchar:
lodsb ; Load next character from string
test al, al ; Is character NUL?
jne .outchar ; If not, go output character
mov [vidmem_ptr], di ; Update global video pointer
pop es
pop ax
pop si
pop di
ret
; 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
xor ebx, ebx ; EBX=0
push ebx ; Real mode GS=0
push ebx ; Real mode FS=0
push ebx ; Real mode DS=0
push ebx ; Real mode ES=0
push V86_STACK_SEG
push V86_STACK_OFS ; v8086 stack SS:SP (grows down from SS:SP)
push dword 1<<EFLAGS_VM_BIT | 1<<EFLAGS_BIT1
; Set VM Bit, IF bit is off, DF=0(forward direction),
; IOPL=0, Reserved bit (bit 1) always 1. Everything
; else 0. These flags will be loaded in the v8086 mode
; during the IRET. We don't want interrupts enabled
; because we have no v86 monitor via protected mode
; GPF handler
push V86_CS_SEG ; Real Mode CS (segment)
push v86_mode_entry ; Entry point (offset)
iret ; Transfer control to v8086 mode and our real mode code
; 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
in_v86_msg:
db "In v8086 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
Этот пример кода может быть изменен, чтобы сделать hlt
, и это удвоит ошибку.Он правильно входит в режим v8086.Я печатаю строку, когда она находится в 32-разрядном защищенном режиме, и строку, после которой она переходит в режим v8086.Поскольку IOPL = 0, код реального режима не использует никаких привилегированных инструкций и не использует никаких инструкций, чувствительных к флагу прерывания (IF), а также не использует порт ввода-вывода.Без монитора виртуальной машины (обработчик GPF с поддержкой режима v8086) вы ограничены непривилегированными инструкциями и инструкциями, не зависящими от флага прерывания.Поскольку инструкция INT чувствительна к IF, BIOS использовать нельзя.Я записываю символы непосредственно на дисплей.
Метод 2: Используйте аппаратный переключатель задач для входа в режим v8086
Если вы не используете аппаратное переключение задач в своей ОС, яне рекомендую использовать этот механизм.Если вы сделали выбор использовать аппаратное переключение задач, то использование этого метода имеет смысл. 1
При использовании аппаратного переключения задач для входа в режим v8086 структура TSS и запись TSS в GDTнеобходимы.Запись TSS в GDT должна указывать базу и пределы сегмента, содержащего TSS.Запись GDT, обычно определяемая как:
32-битный дескриптор TSS , который первоначально помечен как доступный, имеет тип 0x09;бит S
(системный сегмент) установлен в 0;P
бит 1;бит G
установлен в 0 (гранулярность байта);и оставшиеся биты флага установлены в 0. Для задачи v8086 мы хотим, чтобы уровень привилегий дескриптора (DPL) равнялся 0. Это приводит к байту доступа 0x89 и байту флага 0x00.
Сама структура TSS можетследуйте типу структуры, которая предлагается в этом связанном ответе Stackoverflow .В приведенном ниже примере мы не будем использовать битовую карту портов ввода-вывода, поэтому я установил TSS_IO_BITMAP_SIZE
в 0.
После создания соответствующих структур TSS можно заполнить необходимым состоянием регистров.по задаче v8086.Это будет включать CS: IP , с которого начнется выполнение задачи v8086.Чтобы войти в задачу v8086, все, что нужно, это FAR JMP через селектор TSS:
jmp TSS32_SEL:0 ; Transfer control to v8086 mode and our real mode code
Смещение игнорируется при переходе через селектор TSS.Я использую значение 0 для смещения, но оно может быть установлено на любое значение.Этот FAR JMP загрузит регистр задач с помощью селектора TSS и пометит задачу как занят ;установить состояние процессора в соответствии со структурой TSS;передать управление задаче.Минимальный полный пример выглядит следующим образом:
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
V86_STACK_SEG EQU 0x0000 ; v8086 stack SS
V86_STACK_OFS EQU 0x0000 ; v8086 stack SP
V86_CS_SEG EQU 0x0000 ; v8086 code segment CS
EFLAGS_VM_BIT EQU 17 ; EFLAGS VM bit
EFLAGS_BIT1 EQU 1 ; EFLAGS bit 1 (reserved, always 1)
EFLAGS_IF_BIT EQU 9 ; EFLAGS IF bit
TSS_IO_BITMAP_SIZE EQU 0 ; Size 0 disables IO port bitmap (no permission)
; 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:
sub dword [vidmem_ptr], VIDEO_TEXT_ADDR
; Adjust video pointer to be relative to beginning of
; segment 0xb800
mov si, in_v86_msg ; Print in v86 message
mov ah, ATTR_BWHITE_ON_MAGENTA
; Attribute to print with
call print_string_rm_nobios
.endloop:
jmp $ ; Infinite loop since we did code a solution to exit VM
; Function: print_string_rm_nobios
; Display a string to the console on display page 0 in real/v8086 mode
; without using the BIOS. We don't have a proper v8086 monitor so can't
; use BIOS to display.
;
; Very basic. Doesn't update hardware cursor, doesn't handle scrolling,
; LF, CR, TAB.
;
; Inputs: SI = Offset of address to print
; AH = Attribute of string to print
; Clobbers: None
; Returns: None
print_string_rm_nobios:
push di
push si
push ax
push es
mov di, VIDEO_TEXT_ADDR>>4 ; ES=0xb800 (text video mode segment)
mov es, di
mov di, [vidmem_ptr] ; Start from video address stored at vidmem_ptr
jmp .getchar
.outchar:
stosw ; Output character to display
.getchar:
lodsb ; Load next character from string
test al, al ; Is character NUL?
jne .outchar ; If not, go output character
mov [vidmem_ptr], di ; Update global video pointer
pop es
pop ax
pop si
pop di
ret
; 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
mov ecx, TSS_SIZE ; Zero out entire TSS structure
mov edi, tss_entry
xor eax, eax
rep stosb
; v8086 stack SS:SP (grows down from SS:SP)
mov dword [tss_entry.ss], V86_STACK_SEG
mov dword [tss_entry.esp], V86_STACK_OFS
mov dword [tss_entry.eflags], 1<<EFLAGS_VM_BIT | 1<<EFLAGS_BIT1
; Set VM Bit, IF bit is off, DF=0(forward direction),
; IOPL=0, Reserved bit (bit 1) always 1. Everything
; else 0. We don't want interrupts enabled upon entry to
; v8086 because we have no v8086 monitor (a protected mode
; GPF handler)
; Set Real Mode CS:EIP to start execution at
mov dword [tss_entry.cs], V86_CS_SEG
mov dword [tss_entry.eip], v86_mode_entry
; Set iomap_base in tss with the offset of the iomap relative to beginning of the tss
mov word [tss_entry.iomap_base], tss_entry.iomap-tss_entry
%if TSS_IO_BITMAP_SIZE > 0
; If using an IO Bitmap then a padding byte has to be set to 0xff at end of bitmap
mov byte [tss_entry.iomap_pad], 0xff
%endif
jmp TSS32_SEL:0 ; Transfer control to v8086 mode and our real mode code
; 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
in_v86_msg:
db "In v8086 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
gdt32_tss:
dq MAKE_GDT_DESC(tss_entry, TSS_SIZE-1, 10001001b, 0000b)
; 32-bit TSS, 1b gran, available, IOPL=0
end_of_gdt:
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
TSS32_SEL equ gdt32_tss - gdt_start
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
; Pad boot sector to 510 bytes and add 2 byte boot signature
TIMES 510-($-$$) db 0
dw 0xaa55
; Data section above bootloader @ 0x7c00. Acts like a BSS section
ABSOLUTE 0x7e00
; Store the TSS just beyond the boot signature read into memory
; at 0x0000:0x7e00
tss_entry:
.back_link: resd 1
.esp0: resd 1 ; Kernel stack pointer used on ring transitions
.ss0: resd 1 ; Kernel stack segment used on ring transitions
.esp1: resd 1
.ss1: resd 1
.esp2: resd 1
.ss2: resd 1
.cr3: resd 1
.eip: resd 1
.eflags: resd 1
.eax: resd 1
.ecx: resd 1
.edx: resd 1
.ebx: resd 1
.esp: resd 1
.ebp: resd 1
.esi: resd 1
.edi: resd 1
.es: resd 1
.cs: resd 1
.ss: resd 1
.ds: resd 1
.fs: resd 1
.gs: resd 1
.ldt: resd 1
.trap: resw 1
.iomap_base:resw 1 ; IOPB offset
;.cetssp: resd 1 ; Need this if CET is enabled
; Insert any kernel defined task instance data here
; ...
; If using VME (Virtual Mode extensions) there need to bean additional 32 bytes
; available immediately preceding iomap. If using VME uncomment next 2 lines
;.vmeintmap: ; If VME enabled uncomment this line and the next
; resb 32 ; 32*8 bits = 256 bits (one bit for each interrupt)
.iomap: resb TSS_IO_BITMAP_SIZE ; IO bitmap (IOPB) size 8192 (8*8192=65536) representing
; all ports. An IO bitmap size of 0 would fault all IO
; port access if IOPL < CPL (CPL=3 with v8086)
%if TSS_IO_BITMAP_SIZE > 0
.iomap_pad: resb 1 ; Padding byte that has to be filled with 0xff
; To deal with issues on some CPUs when using an IOPB
%endif
TSS_SIZE EQU $-tss_entry
Примечания
- 1 Сложно полагаться на переключение задач оборудованияпортировать на другие процессоры;процессоры x86 не оптимизированы для аппаратных переключателей задач;Состояние FPU и SIMD не сохраняется;производительность может быть ниже, чем при написании задачи переключения с помощью программного обеспечения.Длинный режим на процессорах x86-64 даже не поддерживает переключение аппаратных задач.Современные ОС, работающие на x86, обычно не используют переключение аппаратных задач процессора.