从 DOS 程序进入保护模式

Entering protected mode from a DOS program

我想我已经阅读了十几个基本上与这个问题重复的问题,但我仍然没有找到解决方案。

期望的结果是进入保护模式并无故障停止。我遇到的问题是在使用 6 字节立即值执行段间 jmp 后出现三重故障。

这是我在 DOSBox 和 Pentium II PC 运行 MS-DOS 7 上产生错误的代码。 汇编程序是 MASM 5.10

single segment stack                                                      
assume cs:single,ds:single 

gdt        dq 0

c_limit_lo dw 0ffffh     
c_base_lo  dw 0
c_base_mid db 0
c_priv     db 10011110b ;present set, highest priv, type set, conforming, read      
c_limit_hi db 11001111b ;granularity set, operand size 32  
c_base_hi  db 0

d_limit_lo dw 0ffffh
d_base_lo  dw 0
d_base_mid db 0
d_priv     db 10010010b ;present set, highest priv, type clr, expand dn, write  
d_limit_hi db 11001111b ;granularity set, big set 
d_base_hi  db 0
gdt_end:

gdt_limit  dw gdt_end-offset gdt-1
gdt_addr   dd ?

start:
     mov ax, cs
     mov ds, ax
                                   ;calc phys address of current code segment and
                                   ;insert it into code and data descriptors 
     .386p                         
     xor eax, eax
     mov ax, cs                    
     mov cl, 4              
     shl eax, cl                   ;multiply cs by 16 to get phys address of seg
     mov edx, eax
     mov c_base_lo, ax
     mov d_base_lo, ax             ;low word
     mov cl, 16
     shr eax, cl
     mov c_base_mid, al
     mov d_base_mid, al            ;middle byte
     mov c_base_hi, ah
     mov d_base_hi, ah             ;high byte
     add edx, offset gdt           ;add offset of gdt 
     mov gdt_addr, edx             ;gdt address set 

                                   ;attempt to enter protected mode
     cli                           ;disable interrupts
     in al, 70h
     or al, 80h
     out 70h, al                   ;turn off nonmasked interrupts
     in al, 92h
     or al, 2
     out 92h, al                   ;enable A20 line
     lgdt [gdt_limit]
     mov eax, cr0         
     or eax, 1
     mov cr0, eax                  ;enter protected mode
     db 66h                        ;specify 32-bit operand
     jmp_op  db 0eah               ;manually encoded "jmp 8h:enter_32" TRIPLE FAULT
     jmp_loc_lo dw offset enter_32
     jmp_loc_hi dw 0
     jmp_sel dw 8
enter_32:  
     mov eax, 0ffffffffh           ;sometimes doesn't triple fault on infinite jump or hlt instruction
back:jmp back                      ;but always triple faults on mov



the_stack db 64 dup (0ffh)          ;64 byte stack

single ends                                                                  
end start       

三重故障似乎在某种程度上是基于"luck"。远跳后的 0x67 前缀和 nops 的某些配置导致 cpu 的行为就好像它已停止一样。不是很懂

我想我生成了错误的跳跃目标。

更新:单字节指令(与cpu模式无关的单一编码指令)没有错误。我想我会尝试跳入 USE32 定义的段。

这段代码没有错误:

     jmp_op  db 0eah               
     jmp_loc_lo dw offset enter_32
     jmp_loc_hi dw 0
     jmp_sel dw 8
enter_32:  
     aaa
     daa
     cmc
     cld
     cli
     stc
     nop
     aaa
     daa
     cmc
     cld
     cli
     stc
     nop
     hlt

由 fuz 回答,我只需要跳转到为 32 位模式汇编的代码。您通过使用 USE32 关键字定义段来告诉汇编程序生成 32 位代码。

带有 VGA 演示和模式切换回实模式的完整保护模式程序:

single segment stack                                                      
assume cs:single,ds:single 

gdt        dq 0                             ;global descriptor table
p_code     dq 00cf9e000000ffffh             ;protected mode code descriptor
p_data     dq 00cf92000000ffffh             ;protected mode data descriptor
r_code     dq 008f9a000000ffffh             ;real mode code descriptor
r_data     dq 008f92000000ffffh             ;real mode data descriptor
v_buff     dq 00cf920a0000ffffh             ;vga buffer descriptor

gdt_limit  dw offset gdt_limit-offset gdt-1 ;gdt_limit <- gdt byte size -1
gdt_addr   dd offset gdt                    ;gdt_addr <- offset of gdt, phys address of
                                            ;code segment will be added
start:
    mov ax, cs 
    mov ds, ax                   ;ds = cs, single segment
    mov ax, 13h
    int 10h                      ;enter vga 320x200x256

    .386p                        ;enable 32-bit extensions
    xor eax, eax                 ;clear high word of eax
    mov ax, cs                   ;eax <- cs 
    shl eax, 4                   ;eax <- physical address of cs 
    add [gdt_addr], eax          ;gdt_addr <- physical address of gdt
    mov word ptr [r_code+2], ax  
    mov word ptr [r_data+2], ax  ;insert low word of cs phys address
    shr eax, 16
    mov byte ptr [r_code+4], al
    mov byte ptr [r_data+4], al  ;insert middle byte of cs address
    mov byte ptr [r_code+7], ah
    mov byte ptr [r_data+7], ah  ;insert high byte of cs address

    xor eax, eax                 ;clear high word of eax
    mov ax, seg32                ;eax <- seg32 segment address
    shl eax, 4                   ;eax <- physical address of seg32 
    mov word ptr [p_code+2], ax   
    mov word ptr [p_data+2], ax  ;insert low word of seg32 phys address
    shr eax, 16
    mov byte ptr [p_code+4], al
    mov byte ptr [p_data+4], al  ;insert middle byte of seg32 address
    mov byte ptr [p_code+7], ah
    mov byte ptr [p_data+7], ah  ;insert high byte of seg32 address

    cli                          ;disable interrupts
    in al, 70h                   ;al <- cmos ram index register port
    or al, 80h                   ;set bit 7 to disable nmi 
    out 70h, al                  ;nmi disabled
    in al, 92h                   ;al <- ps/2 system control port
    or al, 2                     ;set bit 1 to enable a20
    out 92h, al                  ;a20 enabled

    lgdt [gdt_limit]             ;load gdt 
    mov eax, cr0                  
    or eax, 1                    ;set pe bit
    mov cr0, eax                 ;enter protected mode
    db 66h                       ;specify 32-bit operand
    db 0eah                      ;manually encoded jmp 8h:0, jump to offset 0 of seg32
    dd offset enter_32                  
    dw 8 

ret_real:
    mov eax, cr0
    and al, 11111110b            ;clear pe bit
    mov cr0, eax                 ;real mode enabled     
    db 0eah                      ;jmp single:real_cs to load cs:ip
    dw offset real_cs
    dw seg single  

real_cs:
    mov ax, cs                      
    mov ds, ax                   ;ds = cs
    mov ss, ax                   ;ss = cs
    mov sp, offset s16_end       ;top of stack is end of stack
    in al, 70h                   ;al <- cmos ram index register port
    and al, 01111111b            ;clear bit 7 to enable nmi 
    out 70h, al                  ;nmi enabled
    sti                          ;enable interrupts

    mov ax, 40h
    mov es, ax                   ;access kbd data area via segment 40h
    mov word ptr es:[1ah], 1eh   ;set the kbd buff head to start of buff
    mov word ptr es:[1ch], 1eh   ;set kbd buff tail to same as buff head
                                 ;now the keyboard buffer is cleared.
    xor ah, ah                   ;select video mode function
    mov al, 3                    ;select 80x25 16 colors
    int 10h                      ;restore vga compatible text mode
    mov ax, 4c00h                ;Terminate process function selected
    int 21h                      ;return to ms-dos

s16 db 256 dup (0ffh)            ;needed 256 bytes to call int 10h on fx5200 vga bios
s16_end: 
single ends   


seg32 segment use32
assume cs:seg32,ds:seg32

enter_32:
    mov ax, 10h                  ;protected mode data segment selector
    mov ds, ax                   ;ds references main data segment
    mov ss, ax                   ;stack is in main data segment 
    mov esp, offset s32_end      ;initial top of stack is end of stack
    mov ax, 28h                  ;vga buffer selector
    mov es, ax                   ;es references vga buffer  
    mov eax, 0ffffffffh          ;initialize eax
write_scr:
    inc al
    inc ah
    rol eax, 16
    inc al
    inc ah                       ;increment each byte of eax
    xor edi, edi                 ;init index
    mov ecx, 320*200/4           ;vga buffer length in bytes

    push eax
    mov dx, 3dah                 ;dx <- vga status register
vrb_set:
    in al, dx                    ;al <- status byte
    test al, 8                   ;is bit vertical retrace bit set
    jnz vrb_set                  ;if so, wait for it to clear
vrb_clr:                         ;when clear, wait for it to be set
    in al, dx
    test al, 8
    jz vrb_clr                   ;loop back until vertical retrace bit has been set
    pop eax
    rep stosd                    ;fill vga buffer

    push eax
    in al, 60h                   ;al <- keyboard data port
    mov ebx, eax                 
    pop eax                     
    cmp bl, 1                    ;escape key scancode? 
    jne write_scr                ;if not, update screen
    mov ax, 20h                  ;real mode data selector
    mov ds, ax                   
    mov es, ax                   ;setup ds and es for real mode
    db 0eah                      ;jmp 18h:ret_real to load real mode code descriptor 
    dd offset ret_real
    dw 18h

s32 db 128 dup (0ffh)            ;128 byte stack
s32_end:
seg32 ends

end start

为 16 位实模式和 运行 在 32 位保护模式下汇编代码可能会产生意外行为和崩溃。这个 16 位代码:

    mov eax, 0ffffffffh
back:jmp back

编码为:

66B8FFFFFFFF      mov eax,0xffffffff
EBFE              jmp short 0x6

但是,如果将此字节序列解码为 32 位保护模式指令,它们将被解释为:

66B8FFFF          mov ax,0xffff
FF                db 0xff
FF                db 0xff
EBFE              jmp short 0x6

将 0xffff 移动到寄存器 AX 后,处理器会在发现无效指令(字节 0xff)时引发一般保护错误 (#GP)。在没有适当的中断描述符 Table (IDT) 和#GP 的异常处理程序的情况下,会发生三重错误。