gpt4 book ai didi

assembly - 通过将 EFLAGS.VM 设置为 1 从 32 位保护模式切换到 v8086 模式时出现问题

转载 作者:行者123 更新时间:2023-12-04 06:30:39 26 4
gpt4 key购买 nike

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

    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似乎正确执行,系统就在那里。在中银国际当我到达 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双重错误?
  • 是否可以在 64 位模式或 32 位兼容模式(长模式的子模式)下进入 v8086 模式?


  • 此代码的最小完整可验证示例是进入保护模式并执行上述任务的引导加载程序:
    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

    最佳答案

    TL;博士 :

    问题 #1 :

    POPF 实际上不允许您根据指令集架构引用更改 VM 标志:

    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 和 enter v8086 mode :

    • 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.

    • 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 位保护模式(传统模式)并执行上述两种方法之一。这是一项昂贵的(性能方面的)工作,并且充满了问题。完成后,您必须切换回长模式。

    如果有这样做的用例,并且您使用的是具有多个内核的系统 - 您可以在引导处理器 (BSP) 以长模式运行时以 32 位保护模式启动其中一个内核。

    方法一:使用IRET进入v8086模式

    这是最简单的解决方案。如果你做一个 IRET 从 32 位保护模式(在 CPL=0 中)并且设置了堆栈上的 EFLAGS.VM 寄存器,CPU 将尝试返回到 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

    我在 V86_STACK_SEG:V86_STACK_OFS 设置了 ES=DS=CS=FS=GS=0 和实模式堆栈(根据您的需要定义这些)。 IP 设置为 v86_mode_entry 的偏移量标签。在上面的代码片段中,我只将 2 位设置为 1(位 1 和 VM)。位 1 是 EFLAGS 中的保留位总是假设设置为 1。EFLAGS 中的所有其他标志都是 0,因此 IOPL=0。

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

    使用此代码的最小完整可验证示例是:
    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 监视器(支持 v8086 模式的 GPF 处理程序),您只能使用非特权和非中断标志敏感指令。由于 INT 指令对 IF 敏感,因此无法使用 BIOS。我将字符直接写入显示器。

    方法二:使用硬件任务开关进入v8086模式

    如果您没有在操作系统中使用硬件任务切换,我不建议使用这种机制。如果您选择使用硬件任务切换,那么使用此方法是有意义的。 1

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

    enter image description here

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

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

    一旦创建了适当的结构,就可以用 v8086 任务所需的寄存器状态填充 TSS。这将包括将在 v8086 任务中开始执行的 CS:IP。要进入 v8086 任务,只需要通过 TSS 选择器进行 FAR JMP:
    jmp TSS32_SEL:0             ; Transfer control to v8086 mode and our real mode code

    通过 TSS 选择器跳转时会忽略偏移量。我使用 0 值作为偏移量,但它可以设置为任何值。该 FAR JMP 将使用 TSS 选择器加载任务寄存器并将任务标记为忙碌;根据 TSS 结构设置 CPU 状态;将控制权转移给任务。一个最小的完整示例如下:
    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 的硬件任务切换。
  • 关于assembly - 通过将 EFLAGS.VM 设置为 1 从 32 位保护模式切换到 v8086 模式时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54845547/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com