通过将EFLAGS.VM设置为1,从32位保护模式切换到v8086模式时出现问题 [英] Problem switching to v8086 mode from 32-bit protected mode by setting EFLAGS.VM to 1

查看:162
本文介绍了通过将EFLAGS.VM设置为1,从32位保护模式切换到v8086模式时出现问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我处于以当前特权级别(CPL = 0)运行的32位保护模式.我试图通过将EFLAGS.VM(位17)标志设置为1(并将IOPL设置为0)并将FAR JMP设置为我的16位实模式代码来进入v8086模式.我使用 PUSHF 得到当前标志.将EFLAGS.VM(位17)设置为1;将EFLAGS.IOPL(第22位和第23位)设置为0;使用 POPF 设置新的EFLAGS.的代码如下:

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:

  • 始终以CPL = 0中断.
  • 以v8086模式运行时中断.
  • 我没有IDT.
  • 我没有TSS,因为我没有通过中断,门和异常在特权级别之间进行转换.

要测试是否已进入v8086模式,请执行 HLT 指令.由于我没有适当的中断机制,因此我希望会发生双重错误. hlt似乎可以正确执行,并且系统位于该位置.在到达hlt的BOCH中,我注意到这些标志是:

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标志被标记为关闭(0),因为它被列为vm而不是VM.这不是我所期望的.

EFLAGS.VM flag is marked as off (0) since it is listed as vm and not VM. This isn't what I expected.

  • 我的代码有什么问题,如何纠正,以便进入v8086模式并出现hlt双重故障?
  • 在64位模式或32位兼容模式(长模式的子模式)下是否可以进入v8086模式?
  • 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

它可以在QEMU中通过以下方式运行:

It can be run in QEMU with:

qemu-system-i386 -fda v86.bin

推荐答案

TL; DR :

问题1 :

POPF 实际上不允许您更改VM每个指令集体系结构参考标记:

POPF doesn't actually allow you to change the VM flag per the Instruction Set Architecture reference:

特权级别0 下以受保护的,兼容性或64位模式运行(或在实地址模式下,等效于特权级别0),所有可以修改EFLAGS寄存器中的非保留标志,但RF1,VIP,VIF和VM除外. VIP,VIF和虚拟机不受影响.

When operating in protected, compatibility, or 64-bit mode at privilege level 0 (or in real-address mode, the equivalent to privilege level 0), all non-reserved flags in the EFLAGS register except RF1, VIP, VIF, and VM may be modified. VIP, VIF and VM remain unaffected.

可以使用两种通用机制来设置EFLAGS.VM和

There are two general mechanisms that can be used to set EFLAGS.VM and enter v8086 mode:

  • 到80386任务的任务切换从新的TSS加载EFLAGS的图像.新任务的TSS必须是80386 TSS,而不是80286 TSS,因为80286 TSS不会存储包含VM标志的EFLAGS的高位字.新EFLAGS的VM位中的值为1表示新任务正在执行8086指令;因此,当从TSS加载段寄存器时,-处理器将像8086那样形成基址.

  • A task switch to an 80386 task loads the image of EFLAGS from the new TSS. The TSS of the new task must be an 80386 TSS, not an 80286 TSS, because the 80286 TSS does not store the high-order word of EFLAGS, which contains the VM flag. A value of one in the VM bit of the new EFLAGS indicates that the new task is executing 8086 instructions; therefore, while loading the segment registers from the TSS, - the processor forms base addresses as the 8086 would.

80386任务过程中的IRET从堆栈中加载EFLAGS的图像.在这种情况下,VM中的值为1表示将控制返回到的过程是8086过程.执行IRET时的CPL必须为零,否则处理器不会更改VM.

An IRET from a procedure of an 80386 task loads the image of EFLAGS from the stack. A value of one in VM in this case indicates that the procedure to which control is being returned is an 8086 procedure. The CPL at the time the IRET is executed must be zero, else the processor does not change VM.

问题2 :

v8086模式仅在32位保护模式(旧模式)下的x86-64处理器上可用.您不能在64位模式或32位(或16位)兼容模式下使用它.您将不得不将处理器从长模式中切换出来,并进入以CPL = 0运行的32位保护模式(传统模式),并执行上述两种方法之一.这是一项昂贵的工作(明智的做法),并且充满了问题.完成后,您将不得不切换回长模式.

v8086 mode is only available on an x86-64 processor in 32-bit protected mode (legacy mode). You can not use it in 64-bit mode or 32-bit (or 16-bit) compatibility modes. You would have to switch the processor out of long mode and enter 32-bit protected mode (legacy mode) running at CPL=0 and perform one of the two methods noted above. This is an expensive (performance wise) undertaking and is fraught with problems. You would then have to switch back to long mode when finished.

如果有一些用例,并且您所在的系统上有多个内核-您可以在Bootstrap Processor(BSP)的情况下以32位保护模式启动其中一个内核.在长模式下运行.

If there is some use case for doing this and you are on a system with multiple cores - You can bring up one of the cores in 32-bit protected mode while the Bootstrap Processor (BSP) runs in long mode.

这是最简单的解决方案.如果您从32位保护模式(在CPL = 0的情况下)执行 IRET ),并设置了堆栈上的EFLAGS.VM寄存器,CPU将尝试返回v8086模式,并假定堆栈帧包含进行该转换所需的信息:

This is the easiest solution. If you do an IRET from 32-bit protected mode (in CPL=0) and the EFLAGS.VM register on the stack is set, the CPU will attempt to return to v8086 mode and assumes the stack frame contains the required information to make that transition:

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模式.

If you push these items on the stack in reverse order and do the iret you should be able to enter v8086 mode.

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和VM).位1是 EFLAGS 中的保留位,该位始终应该设置为1.所有其他EFLAGS中的标志为0,因此IOPL = 0.

I have set ES=DS=CS=FS=GS=0 and a real mode stack at V86_STACK_SEG:V86_STACK_OFS (define these as you see fit). IP is set to the offset of the v86_mode_entry label. In the code snippet above I only set 2 bits to 1 (bit 1 and VM). Bit 1 is a reserved bit in EFLAGS that is always suppose to be set to 1. All other flags in EFLAGS are 0, thus IOPL=0.

所有其他寄存器将包含与进入v8086模式之前相同的值.您可能希望将它们归零,以避免信息从32位保护模式(即内核)泄漏到v8086任务中.

All other registers will contain the same values they had before entering v8086 mode. You may wish to zero them out to avoid leaking information into the v8086 task from 32-bit protected mode (ie: a kernel).

使用此代码的一个最小的完整可验证示例是:

A minimal complete verifiable example of using this code is:

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)敏感的指令,也不使用端口IO.如果没有VM Monitor(支持v8086模式的GPF处理程序),则只能使用非特权和非中断标志敏感的指令.由于 INT 指令对IF敏感,因此无法使用BIOS.我直接将字符写到显示器上.

This example code can be modified to do the hlt and it will double fault. It does properly enter v8086 mode. I print a string while it is in 32-bit protected mode and a string after it enters v8086 mode. Since IOPL=0 the real mode code doesn't use any privileged instructions nor does it use any instructions that are Interrupt Flag (IF) sensitive, nor does it do port IO. Without a VM Monitor (GPF handler that is v8086 mode aware) you are limited to non-privileged and non interrupt flag sensitive instructions. Since the INT instruction is IF sensitive, the BIOS can not be used. I write the characters directly to the display.

如果您不在操作系统中使用硬件任务切换,则不建议使用此机制.如果您已选择使用硬件任务切换,则使用此方法很有意义. 1

If you aren't using hardware task switching in your OS, I don't recommend using this mechanism. If you have made the choice to use hardware task switching then using this method makes sense.1

如果使用硬件任务切换进入v8086模式,则需要GDT中的TSS结构和TSS条目. GDT中的TSS条目用于指定包含TSS的分段的基础和限制. GDT条目通常定义为:

If using hardware task switching to enter v8086 mode a TSS structure and a TSS entry in the GDT are needed. The TSS entry in the GDT is to specify the base and limits of the segment containing the TSS. A GDT entry generally defined as:

32位 TSS描述符最初标记为可用的类型为0x09; S位(系统段)设置为0; P位为1; G位设置为0(字节粒度);并将其余标志位设置为0.对于v8086任务,我们希望描述符特权级别(DPL)为0.这将导致访问字节0x89和标志字节0x00.

A 32-bit TSS descriptor that is initially marked available has a type of 0x09; the S bit (system segment) set to 0; a P bit of 1; a G bit set to 0 (byte granularity); and remaining flag bits set to 0. For a v8086 task we want a Descriptor Privilege Level (DPL) of 0. This results in an access byte of 0x89 and a flags byte of 0x00.

TSS结构本身可以遵循此相关 Stackoverflow答案中建议的结构类型.对于下面的示例,我们将不使用IO端口位图,因此将TSS_IO_BITMAP_SIZE设置为0.

The TSS structure itself can follow the type of structure that is suggested in this related Stackoverflow answer. For the example below we will not be using an IO Port Bitmap so I've set the TSS_IO_BITMAP_SIZE to 0.

一旦创建了适当的结构,即可使用v8086任务所需的寄存器状态填充TSS.这将包括 CS:IP ,其中将从v8086任务开始执行.要输入v8086任务,只需通过TSS选择器输入FAR JMP:

Once the appropriate structures are created, the TSS can be filled in with the state of registers needed by the v8086 task. This will include the CS:IP where execution will start in the v8086 task. To enter the v8086 task all that is needed is a FAR JMP through the TSS selector:

jmp TSS32_SEL:0             ; Transfer control to v8086 mode and our real mode code

通过TSS选择器跳转时,偏移量将被忽略.我将偏移量的值设置为0,但可以将其设置为任何值.该FAR JMP将使用TSS选择器加载任务寄存器,并将任务标记为 busy ;根据TSS结构设置CPU状态;将控制权转移给任务.最小的完整示例如下:

The offset is ignored when jumping via a TSS selector. I use a value of 0 for the offset, but it can be set to any value. This FAR JMP will load the Task Register with the TSS selector and mark the task as busy; setup the CPU state per the TSS structure; transfer control to the task. A minimal complete example is as follows:

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 依靠硬件任务切换很难移植到其他CPU. x86 CPU并未针对硬件任务切换进行优化; FPU和SIMD状态不会保留;性能可能比通过软件编写任务切换要慢. x86-64处理器上的长模式甚至不支持硬件任务切换.在x86上运行的现代操作系统通常不使用CPU的硬件任务切换.
  • 1Relying on hardware task switching is hard to port to other CPUs; the x86 CPUs aren't optimized for hardware task switches; FPU and SIMD state aren't preserved; the performance can be slower than writing the task switching via software. Long mode on the x86-64 processors doesn't even support hardware task switching. Modern OSes running on x86 generally don't use the CPU's hardware task switching.

这篇关于通过将EFLAGS.VM设置为1,从32位保护模式切换到v8086模式时出现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆