Проблема при переключении в режим v8086 из 32-битного защищенного режима путем установки EFLAGS.VM в 1 - PullRequest
0 голосов
/ 23 февраля 2019

Я нахожусь в 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, кажется, работает правильно, и система сидит там.В BOCH, когда я достигаю 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.Это не то, что я ожидал.

Вопросы:

  • Что не так с моим кодом, и как его можно исправить, чтобы войти в режим v8086 и двойные ошибки 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 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

Загрузчик может быть сгенерирован с помощью:

nasm -f bin v86.asm -o v86.bin

Он может быть запущен в QEMU с помощью:

qemu-system-i386 -fda v86.bin

Ответы [ 2 ]

0 голосов
/ 26 февраля 2019

Этот ответ пришлось отделить от первого, поскольку превышен лимит на публикацию.


Метод 3: Использование IRET и структуры TSS

Этот метод на самом деле тот жекак Метод # 1 .Используйте IRET для входа в режим v8086, но мы создаем структуру TSS и 32-битную запись TSS в GDT, как Method # 2 .Создание TSS при отсутствии аппаратного переключения задач позволяет нам указать битовую карту портов ввода-вывода при запуске непривилегированного (CPL = 1,2,3) кода, где IOPL

ЦП будет использовать поля .esp0 и .ss0 в качестве стека ядра, когда шлюз прерываний / вызовов / прерываний передает управление CPL =0 из CPL = 1,2,3.Вы не можете обрабатывать прерывания при запуске кода с CPL> 0 без TSS.Инструкция LTR используется для указания исходного TSS без фактического переключения задач.TSS помечен как занятый LTR .

Следующий минимальный полный пример демонстрирует эту концепцию.В этом примере IOPB настроен так, чтобы разрешить доступ к портам для первых портов 0x400 и запретить его для остальных:

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 0x400/8 ; IO Bitmap for 0x400 IO ports
                                   ; 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

    ; 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

    mov eax, TSS32_SEL
    ltr ax                      ; Load default TSS (used for exceptions, interrupts, etc)

    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
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
0 голосов
/ 23 февраля 2019

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, обычно определяемая как:

enter image description here

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, обычно не используют переключение аппаратных задач процессора.
...